How DD Poker Handles Game State Persistence and Saved Games
DD Poker persists game state by serializing a GameState object through a coordinated save/write cycle managed by the Game class, using SaveDetails to control precisely which components are written to disk.
The open-source DD Poker project (available at dougdonohoe/ddpoker) implements a robust persistence layer that allows players to save progress mid-hand and resume later. The architecture separates concerns between orchestration, data representation, and granular persistence controls, enabling both full snapshots and partial updates depending on the context.
Core Components of the Persistence Layer
The persistence system relies on three primary classes working in concert to manage saved games and game state persistence.
Game.java – The Orchestration Controller
Located at [code/gameengine/src/main/java/com/donohoedigital/games/engine/Game.java](https://github.com/dougdonohoe/ddpoker/blob/main/code/gameengine/src/main/java/com/donohoedigital/games/engine/Game.java), the Game class serves as the central coordinator. It determines when to trigger saves, manages the auto-save timer, and handles restoration of saved states.
Key methods include:
saveWriteGame()– Performs a synchronized full persistence cycleautoSave()– Checks preferences and triggers automatic savesloadGame(GameState, boolean)– Restores the engine from a saved snapshot
GameState.java – The Serialized Snapshot
The GameState class represents a single persisted snapshot containing player data, territories, phases, and custom module-specific payload. It handles low-level serialization logic and binary file I/O through BaseDataFile utilities.
SaveDetails.java – Fine-Grained Persistence Control
SaveDetails provides selective control over what gets persisted. Rather than always writing the entire game world, the engine uses this configuration object to skip expensive serialization steps when only specific data needs updating.
The Save Workflow
DD Poker implements a deterministic, multi-step workflow for game state persistence that triggers on user actions or timer events.
Triggering Saves
Saves initiate either manually through UI actions or automatically via the background timer. When a player finishes a hand, the UI invokes:
public synchronized void saveWriteGame() {
GameState state = getLastGameState();
ApplicationError.assertNotNull(state, "No last game state");
saveGame(state); // Serialize into GameState
writeGame(state); // Write to filesystem
}
For auto-save functionality, the engine periodically checks EngineConstants.PREF_AUTOSAVE. If enabled and the game is not an online match, Game.autoSave() invokes the same saveWriteGame() method automatically.
Serializing Game Data
The Game._saveGame(state, details) method executes a strict sequence to ensure consistent snapshots:
- Header initialization –
state.initForSave(this, details)stores hash values and game identifiers - Subclass data – Optional custom data via
saveSubclassData(state) - Players and observers –
state.savePlayers(this)andstate.saveObservers(this) - Current phase – Added when
SAVE_ALLis requested - Territories – All territories cached via
state.saveTerritories(territories) - Custom data – Module-specific payloads through
state.saveCustomData()
The SaveDetails parameter (typically new SaveDetails(SaveDetails.SAVE_ALL)) acts as a gate, determining which steps actually write data.
Writing to Disk
GameState.write() persists the binary blob using the naming convention <begin>.<nnnnnn>.<ext> (for example, save.000123.dat). These files reside in the user-configurable directory returned by GameConfigUtils.getSaveDir().
Loading Saved Games
Restoring a session reverses the persistence process. The application constructs a GameState from the file system, then delegates restoration to the Game instance:
File savedFile = new File(GameConfigUtils.getSaveDir(), "save.000045.dat");
// Read header and metadata
GameState savedState = new GameState(savedFile, true);
// Restore game state, processing the saved phase
Game currentGame = GameEngine.getGameEngine().getGame();
currentGame.loadGame(savedState, true);
The _loadGame(state, bProcessPhase) method restores players, territories, and phase data exactly as they existed when saved.
Fine-Grained Control with SaveDetails
The SaveDetails class enables optimization by skipping unnecessary serialization. Available constants include:
SAVE_ALL(1) – Persists everything including AI state, players, and territoriesSAVE_DIRTY(2) – Writes only modified componentsSAVE_NONE(3) – Skips persistence entirely
To exclude AI state from a save operation:
SaveDetails details = new SaveDetails(SaveDetails.SAVE_ALL);
details.setSaveAI(SaveDetails.SAVE_NONE);
game.saveGame(currentState, details);
Configuring Auto-Save Behavior
Auto-save functionality depends on the EngineConstants.PREF_AUTOSAVE preference stored in EnginePrefs. The engine checks this boolean before triggering automatic persistence cycles, ensuring saved games are created only when explicitly enabled by the user.
if (prefs.getBoolean(EngineConstants.PREF_AUTOSAVE, false) && getLastGameState() != null) {
saveWriteGame();
}
Summary
- DD Poker uses a three-tier architecture:
Gameorchestrates,GameStaterepresents data, andSaveDetailscontrols granularity. - The
saveWriteGame()method inGame.javacoordinates the full persistence cycle from serialization to disk I/O. - Auto-save triggers based on
EngineConstants.PREF_AUTOSAFEpreferences and excludes online matches. - Files follow the pattern
save.NNNNNN.datin the configurable save directory. SaveDetailsallows selective persistence, enabling optimizations by skipping AI state or unmodified territories when appropriate.
Frequently Asked Questions
How does DD Poker name its save files?
DD Poker generates save files using the pattern <prefix>.<sequence_number>.<extension>, such as save.000123.dat. The sequence number zero-pads to six digits, and the file location derives from GameConfigUtils.getSaveDir().
Can I disable auto-save in DD Poker?
Yes. Auto-save respects the EngineConstants.PREF_AUTOSAVE preference flag. When this boolean is false (the default), the autoSave() method in Game.java exits immediately without triggering saveWriteGame().
What data is included in a full game state save?
A complete save includes the game header with hash values, all player and observer states, the current phase (if SAVE_ALL is specified), territory data, and any custom module-specific data. The SaveDetails object passed to _saveGame() determines which of these components actually serialize.
How does the game restore from a saved file?
The restoration process instantiates a GameState object with the target file and bLoadHeader=true, then calls Game.loadGame(). This method invokes _loadGame(), which reconstructs players, territories, and phase data in the engine to match the persisted snapshot exactly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →