refactor(core): derive draw_mode/is_won/move_count/is_auto_completable from session

Remove the draw_mode, move_count, is_won, and is_auto_completable fields
from GameState; they are now &self methods deriving from the underlying
card_game session (draw_mode from session config, move_count from history
length, is_won/is_auto_completable from check_win/check_auto_complete).

Tests previously fabricated these via direct field writes, which is no
longer possible. Add gated test-support overrides on TestPileState
(won/auto_completable/move_count) plus setters set_test_won,
set_test_auto_completable, set_test_move_count, and set_test_draw_mode
(re-deals the seed). All compiled out in production builds.

Fix the field->method ripple across solitaire_data, solitaire_wasm, and
solitaire_engine. Add a test-support dev-dependency to solitaire_data for
the won-game storage test.

cargo test --workspace and cargo clippy --workspace -- -D warnings pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-10 09:24:03 -07:00
parent 1438fd6265
commit 056459619b
20 changed files with 187 additions and 120 deletions
+10 -10
View File
@@ -194,8 +194,8 @@ impl ReplayPlayer {
step_idx: self.step_idx,
total_steps: self.moves.len(),
score: self.game.score,
move_count: self.game.move_count,
is_won: self.game.is_won,
move_count: self.game.move_count(),
is_won: self.game.is_won(),
stock: self
.game
.stock_cards()
@@ -359,7 +359,7 @@ fn pile_name(pile: KlondikePile) -> String {
}
fn can_stock_click(game: &GameState) -> bool {
!(game.is_won || game.stock_cards().is_empty() && game.waste_cards().is_empty())
!(game.is_won() || game.stock_cards().is_empty() && game.waste_cards().is_empty())
}
fn legal_moves_for_game(game: &GameState) -> Vec<DebugMove> {
@@ -450,7 +450,7 @@ fn invariant_report_for_game(game: &GameState, legal_moves: &[DebugMove]) -> Deb
false
});
let soft_lock = !game.is_won && stock.is_empty() && waste.is_empty() && legal_moves.is_empty();
let soft_lock = !game.is_won() && stock.is_empty() && waste.is_empty() && legal_moves.is_empty();
let state_ok = duplicate_card_ids.is_empty()
&& missing_card_ids.is_empty()
@@ -496,9 +496,9 @@ impl SolitaireGame {
};
GameSnapshot {
score: self.game.score,
move_count: self.game.move_count,
is_won: self.game.is_won,
is_auto_completable: self.game.is_auto_completable,
move_count: self.game.move_count(),
is_won: self.game.is_won(),
is_auto_completable: self.game.is_auto_completable(),
has_moves,
undo_count: self.game.undo_count,
undo_stack_len: self.game.undo_stack_len(),
@@ -582,7 +582,7 @@ impl SolitaireGame {
fn replay_moves_native(&self) -> Result<Vec<ReplayMove>, String> {
let mut replay_game =
GameState::new_with_mode(self.game.seed, self.game.draw_mode, self.game.mode);
GameState::new_with_mode(self.game.seed, self.game.draw_mode(), self.game.mode);
let mut replay_moves = Vec::new();
for instruction in self.game.instruction_history() {
@@ -668,7 +668,7 @@ impl SolitaireGame {
let state_json = serde_json::to_string(&self.game).unwrap_or_default();
DebugSnapshot {
seed: self.game.seed,
draw_mode: self.game.draw_mode,
draw_mode: self.game.draw_mode(),
mode: self.game.mode,
state: self.snap(),
legal_moves,
@@ -822,7 +822,7 @@ impl SolitaireGame {
/// waste by calling `draw()` so the next step can try again. Returns the
/// post-move snapshot, or `null` when no progress is possible.
pub fn auto_complete_step(&mut self) -> JsValue {
if !self.game.is_auto_completable {
if !self.game.is_auto_completable() {
return JsValue::NULL;
}
if let Some((from, to)) = self.game.next_auto_complete_move() {