package game import ( "doudizhu-server/internal/models" "math/rand" "sort" "sync" "time" ) type CardLogic struct{} func NewCardLogic() *CardLogic { return &CardLogic{} } func (cl *CardLogic) CreateDeck() []models.Card { deck := make([]models.Card, 0, 55) suits := []models.Suit{models.SuitHeart, models.SuitDiamond, models.SuitClub, models.SuitSpade} for _, suit := range suits { for value := 3; value <= 15; value++ { deck = append(deck, models.Card{Suit: suit, Value: value}) } } deck = append(deck, models.Card{Suit: models.SuitJoker, Value: 16}) deck = append(deck, models.Card{Suit: models.SuitJoker, Value: 17}) deck = append(deck, models.Card{Suit: models.SuitSuper, Value: 18}) return deck } func (cl *CardLogic) Shuffle(deck []models.Card) { r := rand.New(rand.NewSource(time.Now().UnixNano())) r.Shuffle(len(deck), func(i, j int) { deck[i], deck[j] = deck[j], deck[i] }) } func (cl *CardLogic) SortCards(cards []models.Card) { sort.Slice(cards, func(i, j int) bool { if cards[i].Value != cards[j].Value { return cards[i].Value > cards[j].Value } return cards[i].Suit < cards[j].Suit }) } func (cl *CardLogic) GetCardType(cards []models.Card) models.CardType { n := len(cards) if n == 0 { return models.CardTypeInvalid } if cl.isRocket(cards) { return models.CardTypeRocket } sorted := make([]models.Card, n) copy(sorted, cards) cl.SortCards(sorted) counts := cl.getValueCounts(sorted) switch n { case 1: return models.CardTypeSingle case 2: if len(counts) == 1 { return models.CardTypePair } 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 } } func (cl *CardLogic) isRocket(cards []models.Card) bool { if len(cards) != 2 { return false } hasSmall, hasBig := false, false for _, c := range cards { if c.Suit == models.SuitJoker { if c.Value == 16 { hasSmall = true } else if c.Value == 17 { hasBig = true } } } return hasSmall && hasBig } func (cl *CardLogic) isStraight(cards []models.Card) bool { if len(cards) < 3 { return false } for _, c := range cards { if c.Value >= 15 || c.Suit == models.SuitJoker || c.Suit == models.SuitSuper { return false } } values := make([]int, len(cards)) for i, c := range cards { values[i] = c.Value } sort.Ints(values) for i := 1; i < len(values); i++ { if values[i] != values[i-1]+1 { return false } } return true } func (cl *CardLogic) isDoubleStraight(cards []models.Card, counts map[int]int) bool { if len(cards) < 4 || len(cards)%2 != 0 { return false } pairs := make([]int, 0) for v, c := range counts { if c != 2 { return false } if v >= 15 { return false } pairs = append(pairs, v) } sort.Ints(pairs) for i := 1; i < len(pairs); i++ { if pairs[i] != pairs[i-1]+1 { return false } } return true } func (cl *CardLogic) getValueCounts(cards []models.Card) map[int]int { counts := make(map[int]int) for _, c := range cards { counts[c.Value]++ } return counts } func (cl *CardLogic) CanPlay(cards []models.Card, lastPlay *models.PlayRecord) bool { cardType := cl.GetCardType(cards) if cardType == models.CardTypeInvalid { return false } if lastPlay == nil { return true } if cardType == models.CardTypeRocket { return true } if cardType == models.CardTypeBomb { if lastPlay.CardType == models.CardTypeBomb { return cl.getMainValue(cards) > cl.getMainValue(lastPlay.Cards) } if lastPlay.CardType == models.CardTypeRocket { return false } return true } if lastPlay.CardType == models.CardTypeBomb || lastPlay.CardType == models.CardTypeRocket { return false } if cardType != lastPlay.CardType || len(cards) != len(lastPlay.Cards) { return false } return cl.getMainValue(cards) > cl.getMainValue(lastPlay.Cards) } func (cl *CardLogic) getMainValue(cards []models.Card) int { counts := cl.getValueCounts(cards) maxValue, maxCount := 0, 0 for v, c := range counts { if c > maxCount || (c == maxCount && v > maxValue) { maxCount, maxValue = c, v } } return maxValue } type GameManager struct { rooms map[string]*models.Room players map[string]*models.Player deck map[string][]models.Card discard map[string][]models.Card mu sync.RWMutex cl *CardLogic } func NewGameManager() *GameManager { return &GameManager{ rooms: make(map[string]*models.Room), players: make(map[string]*models.Player), deck: make(map[string][]models.Card), discard: make(map[string][]models.Card), cl: NewCardLogic(), } } func (gm *GameManager) CreateRoom(playerName string, maxPlayers int) (*models.Room, *models.Player) { gm.mu.Lock() defer gm.mu.Unlock() roomID := generateID() playerID := generateID() player := &models.Player{ID: playerID, Name: playerName, IsOnline: true} room := &models.Room{ ID: roomID, Players: []*models.Player{player}, MaxPlayers: maxPlayers, State: models.RoomStateWaiting, } deck := gm.cl.CreateDeck() gm.cl.Shuffle(deck) gm.rooms[roomID] = room gm.players[playerID] = player gm.deck[roomID] = deck gm.discard[roomID] = make([]models.Card, 0) return room, player } func (gm *GameManager) JoinRoom(roomID, playerName string) (*models.Room, *models.Player, error) { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return nil, nil, ErrRoomNotFound } if len(room.Players) >= room.MaxPlayers { return nil, nil, ErrRoomFull } if room.State == models.RoomStatePlaying { return nil, nil, ErrGameStarted } playerID := generateID() player := &models.Player{ID: playerID, Name: playerName, IsOnline: true} room.Players = append(room.Players, player) gm.players[playerID] = player return room, player, nil } func (gm *GameManager) LeaveRoom(roomID, playerID string) error { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return ErrRoomNotFound } for i, p := range room.Players { if p.ID == playerID { room.Players = append(room.Players[:i], room.Players[i+1:]...) delete(gm.players, playerID) if len(room.Players) == 0 { delete(gm.rooms, roomID) delete(gm.deck, roomID) delete(gm.discard, roomID) } return nil } } return ErrPlayerNotFound } func (gm *GameManager) SetReady(roomID, playerID string, ready bool) error { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return ErrRoomNotFound } if room.State == models.RoomStatePlaying { return ErrGameStarted } for _, p := range room.Players { if p.ID == playerID { p.IsReady = ready if room.State == models.RoomStateFinished { room.State = models.RoomStateWaiting } return nil } } return ErrPlayerNotFound } func (gm *GameManager) StartGame(roomID string) error { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return ErrRoomNotFound } if len(room.Players) < 2 { return ErrNotEnoughPlayers } if room.State == models.RoomStatePlaying { return ErrGameStarted } for _, p := range room.Players { p.Cards = make([]models.Card, 0) p.CardCount = 0 p.IsReady = false } deck := gm.cl.CreateDeck() gm.cl.Shuffle(deck) gm.deck[roomID] = deck gm.discard[roomID] = make([]models.Card, 0) room.State = models.RoomStatePlaying room.RoundCount = 1 room.CurrentTurn = 0 room.LastPlay = nil room.LastWinner = "" gm.dealCards(roomID, 5) return nil } func (gm *GameManager) dealCards(roomID string, count int) { room := gm.rooms[roomID] deck := gm.deck[roomID] for i := 0; i < count; i++ { for _, p := range room.Players { if len(deck) == 0 { deck = gm.discard[roomID] gm.cl.Shuffle(deck) gm.discard[roomID] = make([]models.Card, 0) } if len(deck) > 0 { p.Cards = append(p.Cards, deck[0]) deck = deck[1:] } } } gm.deck[roomID] = deck for _, p := range room.Players { gm.cl.SortCards(p.Cards) p.CardCount = len(p.Cards) } } func (gm *GameManager) PlayCards(roomID, playerID string, cards []models.Card) error { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return ErrRoomNotFound } if room.State != models.RoomStatePlaying { return ErrGameNotStarted } if room.Players[room.CurrentTurn].ID != playerID { return ErrNotYourTurn } player := room.Players[room.CurrentTurn] if !gm.hasCards(player, cards) { return ErrCardsNotInHand } cardType := gm.cl.GetCardType(cards) if cardType == models.CardTypeInvalid { return ErrInvalidCardType } if !gm.cl.CanPlay(cards, room.LastPlay) { return ErrCannotBeat } gm.removeCards(player, cards) player.CardCount = len(player.Cards) room.LastPlay = &models.PlayRecord{ PlayerID: playerID, Cards: cards, CardType: cardType, } gm.discard[roomID] = append(gm.discard[roomID], cards...) if len(player.Cards) == 0 { room.LastWinner = playerID room.State = models.RoomStateFinished return nil } gm.nextTurn(room) return nil } func (gm *GameManager) Pass(roomID, playerID string) error { gm.mu.Lock() defer gm.mu.Unlock() room, ok := gm.rooms[roomID] if !ok { return ErrRoomNotFound } if room.State != models.RoomStatePlaying { return ErrGameNotStarted } if room.Players[room.CurrentTurn].ID != playerID { return ErrNotYourTurn } if room.LastPlay == nil || room.LastPlay.PlayerID == playerID { return ErrCannotPass } gm.nextTurn(room) if room.Players[room.CurrentTurn].ID == room.LastPlay.PlayerID { room.LastPlay = nil gm.dealCards(roomID, 1) room.RoundCount++ } return nil } func (gm *GameManager) nextTurn(room *models.Room) { room.CurrentTurn = (room.CurrentTurn + 1) % len(room.Players) } func (gm *GameManager) hasCards(player *models.Player, cards []models.Card) bool { hand := make(map[string]int) for _, c := range player.Cards { hand[cardKey(c)]++ } for _, c := range cards { k := cardKey(c) if hand[k] == 0 { return false } hand[k]-- } return true } func (gm *GameManager) removeCards(player *models.Player, cards []models.Card) { toRemove := make(map[string]int) for _, c := range cards { toRemove[cardKey(c)]++ } newCards := make([]models.Card, 0) removed := make(map[string]int) for _, c := range player.Cards { k := cardKey(c) if removed[k] < toRemove[k] { removed[k]++ } else { newCards = append(newCards, c) } } player.Cards = newCards } func (gm *GameManager) GetRoom(roomID string) (*models.Room, error) { gm.mu.RLock() defer gm.mu.RUnlock() room, ok := gm.rooms[roomID] if !ok { return nil, ErrRoomNotFound } return room, nil } func (gm *GameManager) GetRoomState(roomID, playerID string) *models.Room { gm.mu.RLock() defer gm.mu.RUnlock() room, ok := gm.rooms[roomID] if !ok { return nil } players := make([]*models.Player, len(room.Players)) for i, p := range room.Players { pp := &models.Player{ ID: p.ID, Name: p.Name, IsReady: p.IsReady, IsOnline: p.IsOnline, CardCount: p.CardCount, } if p.ID == playerID { pp.Cards = p.Cards } players[i] = pp } return &models.Room{ ID: room.ID, Players: players, CurrentTurn: room.CurrentTurn, State: room.State, LastPlay: room.LastPlay, LastWinner: room.LastWinner, RoundCount: room.RoundCount, MaxPlayers: room.MaxPlayers, } }