Files
Ferrous-Solitaire/docs/card-game-integration.md
T
funman300 ccccdd2b40 docs: add card-game integration gap analysis
Documents what Quaternions/card_game already provides, what
solitaire_core requires that is currently missing, and the
suggested step-by-step integration path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-28 15:28:44 -07:00

6.8 KiB
Raw Blame History

Integrating card_game / klondike as the Solitaire Core

Context: A collaborator (Quaternions) is building a pure-logic Klondike library in Rust. This document maps what that library currently provides against what Ferrous Solitaire's solitaire_core crate requires, and lists the concrete work needed before the two can be integrated.


What card_game + klondike Already Has

card_game crate (generic primitives)

Feature Notes
Card (Deck + Suit + Rank packed in 1 byte) NonZeroU8 layout — no heap allocation
Suit, Rank, Deck enums Full A→K, 4 suits, up to 4 deck IDs
Stack<CAP> Const-generic ArrayVec wrapper
Pile<DN, UP> Face-down + face-up stacks; flip_up, pop_flip_up
Game trait possible_instructions, is_instruction_valid, process_instruction, is_win
Session Wraps a Game; tracks history, replays for undo
SessionInstruction::Undo Undo via full-state replay from seed
no_std-compatible design Const generics on stack sizes

klondike crate (Klondike rules)

Feature Notes
7 tableau + 4 foundation + 1 stock Fully dealt from a seeded RNG
Draw-1 / Draw-3 config KlondikeConfig::draw_stock
Foundation placement (Ace start, suit-matched A→K)
Tableau placement (alternating colour, K on empty)
Multi-card stack moves (via SkipCards)
RotateStock (recycle waste → stock)
KlondikeStats (score, recycle_count, moves) Basic tracking
is_win_trivial (all face-down cards cleared) Auto-complete trigger
get_auto_move / get_sorted_moves Priority-ranked move suggestion
Benchmark suite (klondike-bench) 1 000-game throughput test
CLI display (klondike-cli) Terminal renderer

What Ferrous Solitaire's solitaire_core Needs (Gaps)

The items below are either missing from klondike today or behave differently from what the engine expects.

1. Scoring — missing sub-rules

Ferrous uses Windows XP Standard scoring. KlondikeStats tracks a score and a recycle count, but the following deltas are not yet applied:

Rule klondike solitaire_core
Card → foundation +10 +10
Waste → tableau +5 +5
Flip face-down card +5
Foundation → tableau 15
Undo 15
Recycle penalty (Draw-1) 100 after 1st free recycle
Recycle penalty (Draw-3) 20 after 3rd free recycle
Time bonus on win 700 000 / elapsed_seconds
Score floor of 0 score.max(0) enforced

2. Game Modes

Ferrous has three modes that alter rules and scoring. None exist in klondike today:

Mode Behaviour
Classic Standard scoring, undo allowed
Challenge Undo disabled (returns an error)
Zen All scoring disabled; score is always 0

3. Solvability Solver

The engine has a Settings toggle — "Winnable deals only" — backed by a DFS solver with canonical-state memoisation (solitaire_core::solver). The solver classifies each deal as Winnable, Unwinnable, or Inconclusive (budget exceeded). klondike has no equivalent.

Work needed: Port or reimplement the DFS solver against KlondikeState. The memoisation key must be a deterministic canonical hash of the full game state.

4. take_from_foundation House Rule

A configurable option allows moving the top foundation card back onto a compatible tableau column. klondike marks Foundation → Foundation as is_useless and does not expose foundation → tableau as a valid instruction.

Work needed: Add foundation_to_tableau as a valid KlondikeInstruction (guarded by a config flag).

5. JSON Serialisation / Persistence

solitaire_core::GameState serialises the full mid-game state to JSON via serde so the engine can save on exit and restore on launch. KlondikeState derives Clone + Eq + Hash but not Serialize / Deserialize.

Work needed: Derive (or implement) serde::Serialize / Deserialize on KlondikeState, KlondikeStats, KlondikeConfig, and SessionState. The schema needs a version field so save files can be migrated.

6. Typed Move Errors

solitaire_core::error::MoveError returns structured errors the engine uses to trigger UI feedback (wrong-destination toast, stock-empty chime, etc.):

GameAlreadyWon
UndoStackEmpty
StockEmpty
InvalidSource
InvalidDestination
RuleViolation(String)

klondike::is_instruction_valid returns bool. Work needed: Return Result<(), MoveError> (or an equivalent) so callers know why a move was rejected.

7. Waste Pile as Separate Concept

Ferrous tracks PileType::Waste as a distinct pile. klondike folds waste into Stock (the face-up half of the stock Pile). The engine's UI and scoring logic reference the waste pile directly; the mapping needs to be explicit.

8. Undo Stack Approach

solitaire_core keeps a snapshot stack (up to 64 snapshots) so undo is O(1) without replaying history. Session undoes by replaying all moves from the seed, which is O(n). For a game-in-progress with many moves this is fine for a solver but may be perceptible in the UI on slow hardware.

Options:

  • Accept the replay cost (simple, no changes needed)
  • Add a snapshot-based fast-undo path to Session

Integration Path (Suggested Steps)

Work items in rough dependency order:

  1. Add serde feature to card_game and klondike (gated, not mandatory) — unblocks persistence.
  2. Add flip-bonus + recycle-penalty scoring to KlondikeStats::process_instruction.
  3. Add GameMode config variant; gate undo and scoring accordingly.
  4. Add MoveError return type to process_instruction / is_instruction_valid.
  5. Add take_from_foundation instruction behind a config flag.
  6. Implement the DFS solvability solver (can be a separate crate, e.g. klondike-solver).
  7. Add save/load round-trip tests once serde is wired up.
  8. Adapter layer in solitaire_core (or replace it) that wraps klondike types in the engine's PileType/GameState API so the Bevy engine layer (solitaire_engine) does not need to change.

What Does NOT Need to Change

  • The solitaire_engine Bevy layer — it works against solitaire_core types; changes are isolated to solitaire_core.
  • The solitaire_sync merge logic — operates on a SyncPayload DTO, independent of core card types.
  • The solitaire_server — speaks only SyncPayload JSON, unaffected.

References

  • Friend's repo: https://git.aleshym.co/Quaternions/card_game
  • solitaire_core source: solitaire_core/src/
  • Scoring spec: solitaire_core/src/scoring.rs
  • Solver spec: solitaire_core/src/solver.rs
  • Architecture overview: ARCHITECTURE.md