Files
ProLibertateGames/docs/SAVED-GAMES.md
T
Claude e370aa0ceb
CI / Build and check (pull_request) Successful in 8m23s
CI / Deploy to Cloudflare Pages (pull_request) Skipped
Save state for every game between sessions
A game against the computer is kept as it is played (the host's true state plus its
table config, one file per game) and can be resumed from the main menu or the game's
setup screen. Leaving keeps the position; End game discards it; a finished game is
not kept; a networked table is not saved.

MatchController gains restore() and an onHostState listener; MatchSaver writes off
the main thread and flushes on leaving and ON_STOP. Tests cover the store, the saver,
resuming, and a save/resume round trip of every game in the catalogue at every move.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016eTor2tpxKb4dXbd8RqnvZ
2026-10-17 19:04:47 +00:00

44 lines
2.7 KiB
Markdown

# Saved games
A game played against the computer (or on one device) is kept as it is played, and can be
picked up again after the app has been closed.
## What the player sees
- **Main menu → Continue.** Each game left half-played is listed, most recent first
("Chess · saved 5 minutes ago"); tapping it resumes.
- **A game's setup screen** shows a *Game in progress* card with **Resume** and **Discard**.
Starting a new game from that screen replaces the saved one.
- **Leaving the table** (the back arrow, or the app being closed or sent to the background)
keeps the position. **End game → End the game** is the deliberate way to throw it away.
- A game that is **finished** is not kept.
- A **networked** table is not kept: the other player's seat cannot be held for them. Only a
table with no remote human at it (`PlayerKind.HUMAN_REMOTE`) is saved.
One slot per game: there is one saved Chess game, one saved Katan game, and so on.
## How it works
| Piece | Where | Does |
| --- | --- | --- |
| `SavedMatch`, `SavedGameStore` | `game/engine/SavedGame.kt` | one JSON file per game under `filesDir/saved-games/`, written atomically (temp file, then renamed); an unreadable file is deleted and read as absent |
| `MatchController.onHostState`, `restore` | `net/MatchController.kt` | the host reports every position it reaches; `restore(json)` starts the table from a saved position instead of dealing |
| `MatchSaver` | `net/MatchSaver.kt` | takes the newest position and writes it off the main thread; `flush()` writes synchronously on leaving or on `ON_STOP`; `discard()` forgets the game |
| `StartMatch` | `ui/PlayScreen.kt` | wires the two together for every game; consumes the resume state once, so a solitaire "new deal" is not put back |
| `LocalEndGameHandle` | `ui/EndGameHandle.kt` | how each *End game* dialog tells the table to forget its save |
What is saved is `rules.encodeState` of the **host's true state** (hidden cards included),
plus the `TableConfig` (seed, seats, options) and the local seat. Nothing in the store knows
which game it is holding, so a new game is saved with no work, provided its state encodes.
A save this build cannot decode (one written by an older version whose state has since
changed shape) is thrown away and a fresh game is dealt.
## Tests
- `SavedGameStoreTest` — the files: round trip, one slot, ordering, corruption, a cut-off write.
- `MatchSaverTest` — saving as a table is played, resuming from a save, finished games, flush, discard.
- `SaveRoundTripAllGamesTest` — **every game in the catalogue**: the whole state is encoded and
decoded at every move of a computer-played game, and play carries on from the decoded copy.
A new game should be added to it.