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:
@@ -39,6 +39,7 @@ keyring-core = { workspace = true }
|
||||
jni = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
solitaire_core = { workspace = true, features = ["test-support"] }
|
||||
solitaire_server = { path = "../solitaire_server" }
|
||||
solitaire_sync = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
|
||||
@@ -93,7 +93,7 @@ fn solve_game_state(initial: &GameState, config: &SolverConfig) -> SolveOutcome
|
||||
|
||||
// Preserve the historical payload contract: winnable verdicts always carry
|
||||
// a first move. An already-won state therefore returns no recommendation.
|
||||
if initial.is_won {
|
||||
if initial.is_won() {
|
||||
return SolveOutcome {
|
||||
result: SolverResult::Unwinnable,
|
||||
first_move: None,
|
||||
@@ -101,7 +101,7 @@ fn solve_game_state(initial: &GameState, config: &SolverConfig) -> SolveOutcome
|
||||
}
|
||||
|
||||
let solver_config = SessionConfig {
|
||||
inner: KlondikeAdapter::config_for(initial.draw_mode, initial.take_from_foundation),
|
||||
inner: KlondikeAdapter::config_for(initial.draw_mode(), initial.take_from_foundation),
|
||||
undo_penalty: 0,
|
||||
solve_moves_budget: config.move_budget,
|
||||
solve_states_budget: config.state_budget as u64,
|
||||
|
||||
@@ -85,13 +85,13 @@ pub fn game_state_file_path() -> Option<PathBuf> {
|
||||
pub fn load_game_state_from(path: &Path) -> Option<GameState> {
|
||||
let data = fs::read(path).ok()?;
|
||||
let gs: GameState = serde_json::from_slice(&data).ok()?;
|
||||
if gs.is_won { None } else { Some(gs) }
|
||||
if gs.is_won() { None } else { Some(gs) }
|
||||
}
|
||||
|
||||
/// Save an in-progress `GameState` atomically. Skips the write if `gs.is_won`
|
||||
/// because a completed game should not be resumed.
|
||||
pub fn save_game_state_to(path: &Path, gs: &GameState) -> io::Result<()> {
|
||||
if gs.is_won {
|
||||
if gs.is_won() {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(parent) = path.parent() {
|
||||
@@ -386,8 +386,8 @@ mod tests {
|
||||
|
||||
let loaded = load_game_state_from(&path).expect("load");
|
||||
assert_eq!(loaded.seed, gs.seed);
|
||||
assert_eq!(loaded.draw_mode, gs.draw_mode);
|
||||
assert!(!loaded.is_won);
|
||||
assert_eq!(loaded.draw_mode(), gs.draw_mode());
|
||||
assert!(!loaded.is_won());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -411,7 +411,7 @@ mod tests {
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let mut gs = GameState::new(99, DrawMode::DrawOne);
|
||||
gs.is_won = true;
|
||||
gs.set_test_won(true);
|
||||
save_game_state_to(&path, &gs).expect("save should be no-op, not error");
|
||||
assert!(
|
||||
!path.exists(),
|
||||
|
||||
Reference in New Issue
Block a user