package game // PlayerView is what any player may know about a seat. Deck contents are // only included for the viewer's own seat; opponents see counts. type PlayerView struct { ID string `json:"id"` Name string `json:"name"` Seat int `json:"seat"` Coins int `json:"coins"` Trophies int `json:"trophies"` Ready bool `json:"ready"` Connected bool `json:"connected"` IsBot bool `json:"isBot,omitempty"` DeckSize int `json:"deckSize"` PetCount int `json:"petCount"` // Avocados is the player's set-aside Avocado token count (Golden pack). // Public: buying an Avocado is a public shop event. Avocados int `json:"avocados,omitempty"` // FirstBuyFree / BuysThisRound (Golden pack) are self-only: they'd reveal // hidden deck facts (a Manta Ray) to opponents. FirstBuyFree bool `json:"firstBuyFree,omitempty"` BuysThisRound int `json:"buysThisRound,omitempty"` // Trumpets is the count banked for the next battle (Bird of Paradise). // Self-only — shown under the player's own deck. Trumpets int `json:"trumpets,omitempty"` // Mana is the player's persistent Mana pool (Unicorn pack). Public: it's a // play-area counter derivable from public shop/battle events. Mana int `json:"mana,omitempty"` // ShopPeek is the card Bigfoot revealed off the top of the shop deck this // round (Unicorn pack). Self-only — it's private information. ShopPeek *Card `json:"shopPeek,omitempty"` Deck []Card `json:"deck,omitempty"` // self only } // View is the full game state as seen by one player. type View struct { GameID string `json:"gameId"` Code string `json:"code"` Phase Phase `json:"phase"` Round int `json:"round"` MaxRounds int `json:"maxRounds"` MaxPets int `json:"maxPets"` // Pack is the selected card pack; Packs is the catalog of choices for the // lobby. HostSeat is the seat that controls the lobby (always 0 for now); // MinPlayers is how many seats must be filled before the host can start. Pack string `json:"pack"` Packs []PackInfo `json:"packs"` HostSeat int `json:"hostSeat"` MinPlayers int `json:"minPlayers"` MaxPlayers int `json:"maxPlayers"` YouSeat int `json:"youSeat"` Turn int `json:"turn"` // PrioritySeat is the seat currently holding the priority token. PrioritySeat int `json:"prioritySeat"` ShopRow []Card `json:"shopRow"` DeckCounts []int `json:"deckCounts"` // remaining shop cards per tier Players []PlayerView `json:"players"` // Pending is included for everyone so opponents see a trade is in // progress, but the revealed options are only shown to the trader. Pending *PendingTrade `json:"pending,omitempty"` // PendingReveal (Golden pack: Cockatoo) is shown to everyone so opponents // see a reveal is in progress; the eligible options are only sent to the // buyer (they name the buyer's own hidden pets). PendingReveal *PendingReveal `json:"pendingReveal,omitempty"` // PendingSacrifice (Unicorn pack: Water of Youth) mirrors PendingReveal: the // options (the buyer's own pets) are only sent to the buyer. PendingSacrifice *PendingSacrifice `json:"pendingSacrifice,omitempty"` Battle *BattleResult `json:"battle,omitempty"` WinnerSeat int `json:"winnerSeat"` // Log is the shared, public event log shown across every phase. Log []LogEntry `json:"log,omitempty"` // Debug is set by the server when its DEBUG flag is on, unlocking the // client's "buy any card" panel. Not part of the pure game state. Debug bool `json:"debug,omitempty"` } // ViewFor builds the state visible to the given player. func (g *Game) ViewFor(playerID string) View { v := View{ GameID: g.ID, Code: g.Code, Phase: g.Phase, Round: g.Round, MaxRounds: MaxRounds, MaxPets: MaxPets, Pack: g.Pack, Packs: Packs, HostSeat: 0, MinPlayers: MinPlayers, MaxPlayers: MaxPlayers, YouSeat: -1, Turn: g.Turn, PrioritySeat: g.PrioritySeat, ShopRow: g.ShopRow, WinnerSeat: g.WinnerSeat, Log: g.Log, } for _, deck := range g.ShopDecks { v.DeckCounts = append(v.DeckCounts, len(deck)) } for _, p := range g.Players { pv := PlayerView{ ID: p.ID, Name: p.Name, Seat: p.Seat, Coins: p.Coins, Trophies: p.Trophies, Ready: p.Ready, Connected: p.Connected, IsBot: p.IsBot, DeckSize: len(p.Deck), PetCount: p.PetCount(), Avocados: p.Avocados, Mana: p.Mana, } if p.ID == playerID { v.YouSeat = p.Seat pv.Deck = p.Deck pv.FirstBuyFree = p.FirstBuyFree pv.BuysThisRound = p.BuysThisRound pv.Trumpets = p.PendingTrumpets pv.ShopPeek = p.ShopPeek } v.Players = append(v.Players, pv) } if g.Pending != nil { pending := *g.Pending if pending.PlayerID != playerID { pending.Options = [2]Card{} // hide the revealed cards } v.Pending = &pending } if g.PendingReveal != nil { reveal := *g.PendingReveal if reveal.PlayerID != playerID { reveal.Options = nil // hide which of the buyer's pets are eligible } v.PendingReveal = &reveal } if g.PendingSacrifice != nil { sac := *g.PendingSacrifice if sac.PlayerID != playerID { sac.Options = nil // hide which of the buyer's pets are eligible } v.PendingSacrifice = &sac } // Battle results (lineups, events) are public once resolved. Keep the // battle around during the following shop phase too, so late joiners / // reconnects can still see the last result. v.Battle = g.Battle return v }