Files
super-auto-pets-board-game/internal/game/view.go
T

193 lines
7.2 KiB
Go

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
}