package game import ( "fmt" "strings" ) // LogEntry is one human-readable line in the game's running event log. The // log is public — both players see the same entries across every phase — and // explains not just what happened but why (e.g. which pet granted an apple). type LogEntry struct { Seq int `json:"seq"` // stable, monotonically increasing id Round int `json:"round"` // round the entry belongs to Phase Phase `json:"phase"` Seat int `json:"seat"` // acting seat, or -1 when none Icon string `json:"icon,omitempty"` // leading emoji Kind string `json:"kind,omitempty"` // structured tag; see constants below Text string `json:"text"` // the sentence itself Source string `json:"source,omitempty"` // card id that caused a spawn Spawn string `json:"spawn,omitempty"` // "apple" | "bee" for spawn entries // The fields below add machine-readable copies of facts the Text already // states publicly, so observers (the AI player included) don't have to // parse English. They must never carry information the text doesn't. Count int `json:"count,omitempty"` // e.g. apples gained CardName string `json:"cardName,omitempty"` // named card, when public Cards []string `json:"cards,omitempty"` // card ids involved, when public } // Structured LogEntry.Kind tags. Only "result" affects the client; the rest // exist so observers can follow the public action stream structurally. const ( LogResult = "result" // battle outcome (client holds it until the replay ends) LogBuy = "buy" // Seat bought Source/CardName from the shop row LogSell = "sell" // Seat sold Source/CardName (it became an apple) LogTrade = "trade" // Seat traded in Cards for a next-tier pick LogTradePick = "tradePick" // Seat took their pick; CardName set when revealed // LogMana (Unicorn pack) tags a public shop-time Mana gain (Cuddle Toad, // Thunderbird), so observers can track the acting seat's Mana pool. LogMana = "mana" ) // addLog appends an entry, stamping it with the next sequence number and the // current round/phase. Callers set Seat/Icon/Text (and Source/Spawn when the // entry represents something spawning off a card). func (g *Game) addLog(e LogEntry) { g.LogSeq++ e.Seq = g.LogSeq e.Round = g.Round e.Phase = g.Phase g.Log = append(g.Log, e) } // logf is the common case: an entry with just a seat, icon, and message. func (g *Game) logf(seat int, icon, format string, args ...any) { g.addLog(LogEntry{Seat: seat, Icon: icon, Text: fmt.Sprintf(format, args...)}) } // article returns "a" or "an" to suit the following word. func article(word string) string { if word == "" { return "a" } if strings.ContainsRune("aeiouAEIOU", rune(word[0])) { return "an" } return "a" } // plural returns "s" when n is not 1, for simple "N apple(s)" phrasing. func plural(n int) string { if n == 1 { return "" } return "s" }