61 lines
3.3 KiB
Markdown
61 lines
3.3 KiB
Markdown
# Super Auto Pets: The Board Game
|
|
|
|
Go backend (pure rules engine in `internal/game`, WebSocket rooms in
|
|
`internal/server`, computer opponent in `internal/ai`) + React frontend in
|
|
`web/`. See README.md for the full layout and rules summary.
|
|
|
|
- `mise run test` — Go tests · `mise run check` — go vet + frontend tsc
|
|
|
|
## Seats, sides, and packs
|
|
|
|
Three distinctions are easy to conflate and worth keeping straight:
|
|
|
|
- **Seat vs. side.** A game seats 2, 4, or 6 players, and each round pairs
|
|
them off into simultaneous battles (`schedule.go`). Inside a
|
|
`BattleResult` everything is indexed by *side* — 0 or 1 within that one
|
|
battle — including `BattleEvent.Seat`/`Target`, `Lineups`, `Survivors`,
|
|
and `ManaAfter`. `Seats` maps side → seat, and `Side(seat)` maps back.
|
|
`WinnerSeat` is the deliberate exception: it's a real seat, because it's
|
|
the only field that means anything outside the battle. When adding a
|
|
per-side field to a battle, index it by side and say so.
|
|
- **One battle vs. the round.** `resolveBattles` loops the round's pairings;
|
|
`runBattle(first, second)` resolves one of them and mutates no persistent
|
|
state. Anything that should happen once per round rather than once per
|
|
battle (clearing per-round banks, say) belongs in `resolveBattles`, not
|
|
`finalizeBattle`.
|
|
- **Packs are plural.** `Game.Packs` is a list whose tier decks shuffle
|
|
together. Anything reading printed card data must go through
|
|
`TierContentsForPacks` / `CatalogForPacks` / `g.packList()`, never a
|
|
single pack — a game can hold Trumpets, Mana, and Ailments at once.
|
|
|
|
## Keep the AI in sync with game behavior
|
|
|
|
**Whenever game behavior changes — rules, cards, effects, phases, log
|
|
entries, or views — make sure the computer opponent accounts for it.** The
|
|
AI plays from the same information a human sees, so changes ripple into it
|
|
in specific ways:
|
|
|
|
- **Battle rules**: `internal/ai` evaluates moves by running the real
|
|
resolver (`game.SimulateBattle`), so battle changes are picked up
|
|
automatically — but re-check the hand-written heuristics that summarize
|
|
battle wisdom: `leadScore`, `keepValue`, `deckValue` (eval.go) and the
|
|
food-placement strategies / synergy pet list (arrange.go).
|
|
- **New or changed cards/effects**: shop-time deck effects are mirrored in
|
|
`applyTemplateShopEffects` (shop.go); a new shop-time trigger or action
|
|
must be added there or the bot will misvalue it.
|
|
- **New public actions or log changes**: the bot tracks the opponent via
|
|
structured tags on public log entries (`LogBuy`, `LogSell`, `LogTrade`,
|
|
`LogTradePick`, spawn counts — see log.go). New public actions need tags
|
|
plus handling in `Observe` (memory.go). Tags must only ever duplicate
|
|
facts the entry's text already states publicly.
|
|
- **View changes**: the AI decides from `game.View` only — never hand it
|
|
the `Game`. If a field is added to the view, confirm it doesn't leak
|
|
hidden information (deck order, trade options, shop decks), because the
|
|
AI (and any client) would legitimately see it.
|
|
- **Phase/flow changes**: the server's bot driver (`internal/server/bots.go`)
|
|
must know when a bot owes an action (`ai.Pending`) and have a legal
|
|
fallback (`botFallback`) for any new phase or forced decision.
|
|
|
|
After any such change, run `go test ./internal/ai/` — it plays complete
|
|
bot-vs-bot games and fails on any illegal or missing bot action.
|