From 2cfcd0089fbb44394a2ee51282e56073baee2b8d Mon Sep 17 00:00:00 2001 From: minie4 Date: Fri, 28 Mar 2025 00:05:10 +0100 Subject: [PATCH] feat(deck-hexv1): implement experimental HexV1 card deck --- backend/decks/decks.go | 8 ++ backend/decks/hexv1.go | 205 +++++++++++++++++++++++++++++++++++++++++ backend/game/game.go | 2 +- backend/utils/utils.go | 3 + 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 backend/decks/hexv1.go diff --git a/backend/decks/decks.go b/backend/decks/decks.go index 8b61151..0cb27f8 100644 --- a/backend/decks/decks.go +++ b/backend/decks/decks.go @@ -13,6 +13,10 @@ func DeckFromInterface(cardDeckId int, cardDeck bson.D) types.CardDeck { deck := Classic{} bson.Unmarshal(bsonBytes, &deck) return &deck + case 1: + deck := HexV1{} + bson.Unmarshal(bsonBytes, &deck) + return &deck } return nil @@ -26,6 +30,10 @@ func CardFromInterface(cardDeckId int, card bson.D) types.Card { deck := ClassicCard{} bson.Unmarshal(bsonBytes, &deck) return &deck + case 1: + deck := HexV1Card{} + bson.Unmarshal(bsonBytes, &deck) + return &deck } return nil } diff --git a/backend/decks/hexv1.go b/backend/decks/hexv1.go new file mode 100644 index 0000000..fab58ac --- /dev/null +++ b/backend/decks/hexv1.go @@ -0,0 +1,205 @@ +package decks + +import ( + "fmt" + "math/rand/v2" + + "github.com/HexCardGames/HexDeck/types" + "github.com/HexCardGames/HexDeck/utils" +) + +type HexV1 struct { + room *types.Room + CardsPlayed []*HexV1Card + PlayerOrder []int + ActiveIndex int +} + +type HexV1Card struct { + Symbol string + Color string + NumericValue int +} + +var HexV1Colors = []string{"blue", "green", "yellow", "purple"} +var HexV1ActionCards = []string{"shuffle", "skip", "draw", "swap"} + +func (deck *HexV1) Init(room *types.Room) { + deck.room = room + deck.PlayerOrder = make([]int, len(room.Players)) + deck.ActiveIndex = 0 + + deck.room.PlayersMutex.Lock() + defer deck.room.PlayersMutex.Unlock() + for i, player := range deck.room.Players { + deck.PlayerOrder[i] = i + player.Mutex.Lock() + defer player.Mutex.Unlock() + deck.drawMany(player, 8) + } +} + +func (deck *HexV1) SetRoom(room *types.Room) { + deck.room = room +} + +func (deck *HexV1) IsEmpty() bool { + return false +} + +func (deck *HexV1) getTopCard() *HexV1Card { + if len(deck.CardsPlayed) == 0 { + return nil + } + return deck.CardsPlayed[len(deck.CardsPlayed)-1] +} + +func (deck *HexV1) GetTopCard() types.Card { + return deck.getTopCard() +} + +func (deck *HexV1) generateCard() *HexV1Card { + cardType := rand.IntN(16 + len(HexV1ActionCards)) + cardColor := HexV1Colors[rand.IntN(len(HexV1Colors))] + if cardType < 16 { + return &HexV1Card{ + Symbol: fmt.Sprintf("%x", cardType), + Color: cardColor, + NumericValue: cardType, + } + } + cardSymbol := HexV1ActionCards[cardType-16] + if rand.IntN(100) <= 10 { + cardColor = "rainbow" + } + return &HexV1Card{ + Symbol: "action:" + cardSymbol, + Color: cardColor, + NumericValue: 3, + } +} + +func (deck *HexV1) drawCard(player *types.Player) types.Card { + card := deck.generateCard() + player.Cards = append(player.Cards, card) + return card +} + +func (deck *HexV1) drawMany(player *types.Player, cards int) { + for i := 0; i < cards; i++ { + deck.drawCard(player) + } +} + +func (deck *HexV1) DrawCard() types.Card { + // Can't draw another card before wildcard color is selected + topCard := deck.getTopCard() + if topCard != nil && topCard.Color == "rainbow" { + return nil + } + + card := deck.drawCard(deck.getPlayer(deck.ActiveIndex)) + deck.nextPlayer() + return card +} + +func (deck *HexV1) getNextValidIndex(index int) int { + if len(deck.room.Players) == 0 || len(deck.PlayerOrder) == 0 { + return -1 + } + checkIndex := utils.Mod(index, len(deck.PlayerOrder)) + for deck.PlayerOrder[checkIndex] >= len(deck.room.Players) { + checkIndex = utils.Mod(checkIndex+1, len(deck.PlayerOrder)) + } + return checkIndex +} + +func (deck *HexV1) getPlayer(index int) *types.Player { + playerIndex := deck.getNextValidIndex(index) + if playerIndex == -1 { + return nil + } + return deck.room.Players[deck.PlayerOrder[playerIndex]] +} + +func (deck *HexV1) getNextPlayerIndex() int { + return deck.getNextValidIndex(deck.ActiveIndex + 1) +} + +func (deck *HexV1) nextPlayer() { + deck.ActiveIndex = deck.getNextPlayerIndex() +} + +func (deck *HexV1) IsPlayerActive(target *types.Player) bool { + return deck.getPlayer(deck.ActiveIndex) == target +} + +func (deck *HexV1) CanPlay(card types.Card) bool { + topCard := deck.getTopCard() + checkCard := card.(*HexV1Card) + if topCard == nil || checkCard == nil { + return topCard == nil + } + return topCard.Color != "rainbow" && (checkCard.Color == "rainbow" || checkCard.Color == topCard.Color || checkCard.Symbol == topCard.Symbol) +} + +func (deck *HexV1) PlayCard(card types.Card) bool { + if !deck.CanPlay(card) { + return false + } + deckCard := card.(*HexV1Card) + targetPlayer := deck.getPlayer(deck.ActiveIndex) + nextPlayer := deck.getPlayer(deck.getNextPlayerIndex()) + + if deckCard.Symbol == "action:skip" && deckCard.Color != "rainbow" { + deck.nextPlayer() + } else if deckCard.Symbol == "action:draw" { + amount := 3 + topCard := deck.getTopCard() + if topCard != nil { + amount = topCard.NumericValue + } + deck.drawMany(nextPlayer, amount) + } else if deckCard.Symbol == "action:shuffle" { + utils.ShuffleSlice(&deck.PlayerOrder) + } else if deckCard.Symbol == "action:swap" { + p1Cards := targetPlayer.Cards + p2Cards := nextPlayer.Cards + targetPlayer.Cards = p2Cards + nextPlayer.Cards = p1Cards + } + + if deckCard.Color != "rainbow" { + deck.nextPlayer() + } + + deck.CardsPlayed = append(deck.CardsPlayed, deckCard) + return true +} + +func (deck *HexV1) UpdatePlayedCard(cardData interface{}) types.Card { + topCard := deck.getTopCard() + if topCard.Color != "rainbow" { + return nil + } + updateData, ok := cardData.(map[string]interface{}) + if !ok { + return nil + } + newColor, ok := updateData["Color"].(string) + if !ok { + return nil + } + + for _, color := range HexV1Colors { + if newColor == color { + deck.nextPlayer() + if topCard.Symbol == "action:skip" { + deck.nextPlayer() + } + topCard.Color = color + return topCard + } + } + return nil +} diff --git a/backend/game/game.go b/backend/game/game.go index 2ee1613..f72ae18 100644 --- a/backend/game/game.go +++ b/backend/game/game.go @@ -39,7 +39,7 @@ func CreateRoom() *types.Room { GameState: types.StateLobby, Players: make([]*types.Player, 0), PlayersMutex: &sync.Mutex{}, - CardDeckId: 0, + CardDeckId: 1, } db.Conn.InsertRoom(newRoom) diff --git a/backend/utils/utils.go b/backend/utils/utils.go index 44a5044..8a2c6da 100644 --- a/backend/utils/utils.go +++ b/backend/utils/utils.go @@ -34,5 +34,8 @@ func ShuffleSlice[T any](slice *([]T)) { } func Mod(a, b int) int { + if b == 0 { + return 0 + } return (a%b + b) % b }