fix: 修复牌型识别、获胜显示、出牌区布局、移动端重连

1. 牌型识别: 新增三带一对、飞机、飞机带单/带对、四带二、四带两对
2. 获胜显示: 先显示最后出牌1秒后再弹出获胜对话框
3. 出牌区布局: 横向排列并换行
4. 移动端重连: 切回前台时自动重连WebSocket
5. 更新文档: README.md和API.md补充新增牌型
This commit is contained in:
wtz
2026-02-22 10:00:13 +08:00
parent af3b805dbf
commit 97e03acbe2
7 changed files with 279 additions and 74 deletions

View File

@@ -75,59 +75,100 @@ func (cl *CardLogic) GetCardType(cards []models.Card) models.CardType {
cl.SortCards(sorted)
counts := cl.getValueCounts(sorted)
switch n {
case 1:
return models.CardTypeSingle
case 2:
if len(counts) == 1 {
return models.CardTypePair
// 统计各种点数的数量
var singles, pairs, triples, quads []int
for v, c := range counts {
switch c {
case 1:
singles = append(singles, v)
case 2:
pairs = append(pairs, v)
case 3:
triples = append(triples, v)
case 4:
quads = append(quads, v)
}
return models.CardTypeInvalid
case 3:
if len(counts) == 1 {
return models.CardTypeBomb
}
if cl.isStraight(sorted) {
return models.CardTypeStraight
}
return models.CardTypeInvalid
case 4:
if len(counts) == 1 {
return models.CardTypeBomb
}
if len(counts) == 2 {
for _, c := range counts {
if c == 3 {
return models.CardTypeTripleOne
}
}
}
if cl.isStraight(sorted) {
return models.CardTypeStraight
}
if cl.isDoubleStraight(sorted, counts) {
return models.CardTypeDoubleStraight
}
return models.CardTypeInvalid
default:
if cl.isStraight(sorted) {
return models.CardTypeStraight
}
if cl.isDoubleStraight(sorted, counts) {
return models.CardTypeDoubleStraight
}
hasTriple := false
for _, c := range counts {
if c >= 3 {
hasTriple = true
break
}
}
if hasTriple && (n == 4 || n == 5) {
return models.CardTypeTripleOne
}
return models.CardTypeInvalid
}
// 炸弹(三张或以上相同)
if len(counts) == 1 && n >= 3 {
return models.CardTypeBomb
}
// 四带二4张相同+2单张
if len(quads) == 1 && len(singles) == 2 && len(pairs) == 0 && len(triples) == 0 && n == 6 {
return models.CardTypeFourWithTwo
}
// 四带两对4张相同+2对
if len(quads) == 1 && len(pairs) == 2 && len(singles) == 0 && len(triples) == 0 && n == 8 {
return models.CardTypeFourWithTwoPairs
}
// 飞机(连续的三张)
if len(triples) >= 2 && cl.isConsecutive(triples) {
planeCount := len(triples)
// 纯飞机
if n == planeCount*3 {
return models.CardTypeAirplane
}
// 飞机带单张
if n == planeCount*3+planeCount && len(singles) == planeCount {
return models.CardTypeAirplaneSingle
}
// 飞机带对子
if n == planeCount*3+planeCount*2 && len(pairs) == planeCount {
return models.CardTypeAirplanePair
}
}
// 单张
if n == 1 {
return models.CardTypeSingle
}
// 对子
if n == 2 && len(counts) == 1 {
return models.CardTypePair
}
// 三带一3张相同+1单张
if n == 4 && len(triples) == 1 && len(singles) == 1 {
return models.CardTypeTripleOne
}
// 三带一对3张相同+1对
if n == 5 && len(triples) == 1 && len(pairs) == 1 {
return models.CardTypeTriplePair
}
// 顺子
if cl.isStraight(sorted) {
return models.CardTypeStraight
}
// 连对
if cl.isDoubleStraight(sorted, counts) {
return models.CardTypeDoubleStraight
}
return models.CardTypeInvalid
}
func (cl *CardLogic) isConsecutive(values []int) bool {
if len(values) < 2 {
return true
}
sort.Ints(values)
for i := 1; i < len(values); i++ {
if values[i] != values[i-1]+1 {
return false
}
if values[i] >= 15 { // 2和王不能参与
return false
}
}
return values[0] < 15
}
func (cl *CardLogic) isRocket(cards []models.Card) bool {
@@ -230,9 +271,36 @@ func (cl *CardLogic) CanPlay(cards []models.Card, lastPlay *models.PlayRecord) b
if cardType != lastPlay.CardType || len(cards) != len(lastPlay.Cards) {
return false
}
if cardType == models.CardTypeAirplane || cardType == models.CardTypeAirplaneSingle || cardType == models.CardTypeAirplanePair {
return cl.compareAirplanes(cards, lastPlay.Cards)
}
return cl.getMainValue(cards) > cl.getMainValue(lastPlay.Cards)
}
func (cl *CardLogic) compareAirplanes(cards, lastCards []models.Card) bool {
myTriples := cl.getTripleValues(cards)
lastTriples := cl.getTripleValues(lastCards)
if len(myTriples) != len(lastTriples) {
return false
}
sort.Ints(myTriples)
sort.Ints(lastTriples)
return myTriples[len(myTriples)-1] > lastTriples[len(lastTriples)-1]
}
func (cl *CardLogic) getTripleValues(cards []models.Card) []int {
counts := cl.getValueCounts(cards)
var triples []int
for v, c := range counts {
if c >= 3 {
triples = append(triples, v)
}
}
return triples
}
func (cl *CardLogic) getMainValue(cards []models.Card) int {
counts := cl.getValueCounts(cards)
maxValue, maxCount := 0, 0

View File

@@ -29,6 +29,11 @@ const (
CardTypeDoubleStraight
CardTypeBomb
CardTypeRocket
CardTypeAirplane
CardTypeAirplaneSingle
CardTypeAirplanePair
CardTypeFourWithTwo
CardTypeFourWithTwoPairs
)
type Player struct {

View File

@@ -185,6 +185,7 @@ func (c *Client) handleMessage(data []byte) {
}
room, _ := c.Hub.GameMgr.GetRoom(c.RoomID)
if room != nil && room.State == models.RoomStateFinished {
c.Hub.broadcastFinalState(c.RoomID, room)
c.Hub.broadcastToRoom(c.RoomID, models.Message{
Type: models.MsgTypeGameOver,
Data: map[string]string{"winnerId": room.LastWinner},
@@ -313,6 +314,34 @@ func (h *Hub) broadcastRoomState(roomID string) {
}
}
func (h *Hub) broadcastFinalState(roomID string, room *models.Room) {
h.mu.RLock()
clients := make([]*Client, 0)
for _, c := range h.Clients {
if c.RoomID == roomID {
clients = append(clients, c)
}
}
h.mu.RUnlock()
for _, c := range clients {
state := h.GameMgr.GetRoomState(roomID, c.ID)
if state != nil {
state.State = models.RoomStatePlaying
data, _ := json.Marshal(models.Message{
Type: models.MsgTypeState,
Data: state,
Timestamp: time.Now().Unix(),
})
select {
case c.Send <- data:
case <-c.done:
default:
}
}
}
}
func (h *Hub) BroadcastRoomLeft(roomID, playerID string) {
h.mu.RLock()
clients := make([]*Client, 0)