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"` // RoundWins are the rounds this player won a battle in. Public — every // result is — and what the end-of-game countback tie-break runs on. RoundWins []int `json:"roundWins,omitempty"` 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"` // Packs are the selected card packs, whose tiers shuffle together; // PackCatalog is the list of choices for the lobby and PacksNeeded is how // many the current table size requires. HostSeat is the seat that controls // the lobby (always 0 for now); PlayerCounts lists the table sizes a game // can start at. Packs []string `json:"packs"` PackCatalog []PackInfo `json:"packCatalog"` PacksNeeded int `json:"packsNeeded"` HostSeat int `json:"hostSeat"` MinPlayers int `json:"minPlayers"` MaxPlayers int `json:"maxPlayers"` PlayerCounts []int `json:"playerCounts"` YouSeat int `json:"youSeat"` Turn int `json:"turn"` // PrioritySeat is the seat holding the first-shopper token: it shops first // this round (and, in a two-player game, also acts first in the battle). PrioritySeat int `json:"prioritySeat"` ShopRow []Card `json:"shopRow"` DeckCounts []int `json:"deckCounts"` // remaining shop cards per tier Players []PlayerView `json:"players"` // Matchups are this round's battle pairings — public, since the schedule is // printed in the rulebook. YourOpponent is the seat you face this round, or // -1 outside a running game. Matchups []Matchup `json:"matchups,omitempty"` YourOpponent int `json:"yourOpponent"` // 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 is the viewer's own battle this round; Battles holds every table's, // so a player can replay any of them. Both are public once resolved. Battle *BattleResult `json:"battle,omitempty"` Battles []*BattleResult `json:"battles,omitempty"` // WinnerSeat is the outright winner at gameover (-1 when shared); // WinnerSeats lists everyone holding the title. WinnerSeat int `json:"winnerSeat"` WinnerSeats []int `json:"winnerSeats,omitempty"` // 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, Packs: g.packList(), PackCatalog: Packs, PacksNeeded: PacksNeeded(len(g.Players)), HostSeat: 0, MinPlayers: MinPlayers, MaxPlayers: MaxPlayers, PlayerCounts: PlayerCounts, YouSeat: -1, YourOpponent: -1, Turn: g.Turn, PrioritySeat: g.PrioritySeat, ShopRow: g.ShopRow, WinnerSeat: g.WinnerSeat, WinnerSeats: g.WinnerSeats, Matchups: g.Pairings(), 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, RoundWins: p.RoundWins, 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 v.YourOpponent = OpponentOf(len(g.Players), g.Round, 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 — every table's, // not just your own, so players can watch how the rest of the field did. // They stay around during the following shop phase too, so late joiners / // reconnects can still see the last round. v.Battles = g.Battles if v.YouSeat >= 0 { v.Battle = g.BattleFor(v.YouSeat) } return v } // PlayerView returns the view of one seat within a View, or nil. func (v *View) PlayerView(seat int) *PlayerView { for i := range v.Players { if v.Players[i].Seat == seat { return &v.Players[i] } } return nil }