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
2.7 KiB
2.7 KiB
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.