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 cycle
  • autoSave() – Checks preferences and triggers automatic saves
  • loadGame(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:

  1. Header initializationstate.initForSave(this, details) stores hash values and game identifiers
  2. Subclass data – Optional custom data via saveSubclassData(state)
  3. Players and observersstate.savePlayers(this) and state.saveObservers(this)
  4. Current phase – Added when SAVE_ALL is requested
  5. Territories – All territories cached via state.saveTerritories(territories)
  6. 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 territories
  • SAVE_DIRTY (2) – Writes only modified components
  • SAVE_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: Game orchestrates, GameState represents data, and SaveDetails controls granularity.
  • The saveWriteGame() method in Game.java coordinates the full persistence cycle from serialization to disk I/O.
  • Auto-save triggers based on EngineConstants.PREF_AUTOSAFE preferences and excludes online matches.
  • Files follow the pattern save.NNNNNN.dat in the configurable save directory.
  • SaveDetails allows 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →