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>
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
# Integrating `card_game` / `klondike` as the Solitaire Core
|
||||||
|
|
||||||
|
**Context:** A collaborator ([Quaternions](https://git.aleshym.co/Quaternions/card_game)) 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`
|
||||||
Reference in New Issue
Block a user