refactor: delete solitaire_data::solver wrapper; solve via card_game directly
Remove the standalone solver wrapper module. Its thin shaping — build a solve-budgeted Session, run card_game::Session::solve(), extract the first useful move — moves onto the domain type in solitaire_core as GameState::solve_first_move() / GameState::solve_fresh_deal(), with the budget consts and the SolveOutcome alias re-exported from solitaire_core. Solving is deterministic, IO-free game logic, so core (which already owns GameState and exposes session().solve()) is its correct home; solitaire_data is the persistence/sync layer and never should have owned it. Consumers now call the core API directly: - engine: pending_hint (solve_first_move), game_plugin + play_by_seed_plugin (solve_fresh_deal), input_plugin (budget consts) - assetgen: gen_seeds + gen_difficulty_seeds (solve_fresh_deal) The solver tests move to solitaire_core. cargo test --workspace and clippy --workspace --all-targets -- -D warnings both green. Resolves the "delete the solver" directive — card_game provides the solver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,7 @@ use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||
use bevy::window::AppLifecycle;
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::{DrawMode, game_state::{GameMode, GameState}};
|
||||
use solitaire_data::solver::{
|
||||
DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET, try_solve,
|
||||
};
|
||||
use solitaire_core::{DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET};
|
||||
#[allow(deprecated)]
|
||||
use solitaire_data::latest_replay_path;
|
||||
use solitaire_data::{
|
||||
@@ -318,7 +316,7 @@ fn seed_from_system_time() -> u64 {
|
||||
}
|
||||
|
||||
/// Walks forward from `initial_seed` (incrementing by 1 with wrapping
|
||||
/// arithmetic) until the [`solitaire_data::solver`] returns a verdict
|
||||
/// arithmetic) until the [`GameState::solve_fresh_deal`] returns a verdict
|
||||
/// the engine accepts as winnable, or until [`SOLVER_DEAL_RETRY_CAP`]
|
||||
/// attempts have elapsed.
|
||||
///
|
||||
@@ -393,7 +391,7 @@ fn poll_pending_new_game_seed(
|
||||
pub(crate) fn choose_winnable_seed(initial_seed: u64, draw_mode: DrawMode) -> u64 {
|
||||
let mut seed = initial_seed;
|
||||
for _ in 0..SOLVER_DEAL_RETRY_CAP {
|
||||
match try_solve(
|
||||
match GameState::solve_fresh_deal(
|
||||
seed,
|
||||
draw_mode,
|
||||
DEFAULT_SOLVE_MOVES_BUDGET,
|
||||
|
||||
@@ -95,8 +95,8 @@ pub struct HintSolverConfig {
|
||||
impl Default for HintSolverConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
moves_budget: solitaire_data::solver::DEFAULT_SOLVE_MOVES_BUDGET,
|
||||
states_budget: solitaire_data::solver::DEFAULT_SOLVE_STATES_BUDGET,
|
||||
moves_budget: solitaire_core::DEFAULT_SOLVE_MOVES_BUDGET,
|
||||
states_budget: solitaire_core::DEFAULT_SOLVE_STATES_BUDGET,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ use bevy::prelude::*;
|
||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||
use solitaire_core::KlondikeInstruction;
|
||||
use solitaire_core::game_state::GameState;
|
||||
use solitaire_data::solver::try_solve_from_state;
|
||||
|
||||
use crate::card_plugin::CardEntity;
|
||||
use crate::events::{HintVisualEvent, InfoToastEvent, StateChangedEvent};
|
||||
@@ -66,7 +65,7 @@ impl PendingHintTask {
|
||||
// Winnable (`Ok(Some)`) carries the first move on a winning path;
|
||||
// unwinnable (`Ok(None)`) and inconclusive (`Err`) both fall back
|
||||
// to the live-state heuristic so H always produces feedback.
|
||||
match try_solve_from_state(&state, moves_budget, states_budget) {
|
||||
match state.solve_first_move(moves_budget, states_budget) {
|
||||
Ok(Some(first_move)) => HintTaskOutput::SolverMove(first_move),
|
||||
Ok(None) | Err(_) => HintTaskOutput::NeedsHeuristic,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! 3. `handle_text_input` appends decimal digits / handles Backspace while
|
||||
//! the modal is open, updating [`SeedInputBuffer`] each frame.
|
||||
//! 4. `tick_debounce_and_spawn_solver_task` waits for 12 frames (~200 ms at
|
||||
//! 60 Hz) of no input before spawning a [`try_solve`] task on
|
||||
//! 60 Hz) of no input before spawning a [`GameState::solve_fresh_deal`] task on
|
||||
//! [`AsyncComputeTaskPool`]. Any fresh keypress drops the in-flight task
|
||||
//! by resetting the resource.
|
||||
//! 5. `poll_solver_task` polls the in-flight task each frame and updates the
|
||||
@@ -24,9 +24,8 @@ use bevy::input::ButtonInput;
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||
use solitaire_core::DrawMode;
|
||||
use solitaire_data::solver::{
|
||||
DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET, SolveOutcome, try_solve,
|
||||
};
|
||||
use solitaire_core::game_state::GameState;
|
||||
use solitaire_core::{DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET, SolveOutcome};
|
||||
|
||||
use crate::events::{NewGameRequestEvent, StartPlayBySeedRequestEvent};
|
||||
use crate::font_plugin::FontResource;
|
||||
@@ -343,7 +342,7 @@ fn tick_debounce_and_spawn_solver_task(
|
||||
.as_ref()
|
||||
.map_or(DrawMode::DrawOne, |s| s.0.draw_mode);
|
||||
let task = AsyncComputeTaskPool::get().spawn(async move {
|
||||
try_solve(
|
||||
GameState::solve_fresh_deal(
|
||||
seed,
|
||||
draw_mode,
|
||||
DEFAULT_SOLVE_MOVES_BUDGET,
|
||||
|
||||
@@ -241,7 +241,7 @@ enum SettingsButton {
|
||||
ToggleTouchInputMode,
|
||||
/// Toggle the [`Settings::winnable_deals_only`] flag. When on, new
|
||||
/// random Classic-mode deals are filtered through
|
||||
/// [`solitaire_data::solver::try_solve`] until one is provably
|
||||
/// [`solitaire_core::game_state::GameState::solve_fresh_deal`] until one is provably
|
||||
/// winnable (or the retry cap is hit). Off by default.
|
||||
ToggleWinnableDealsOnly,
|
||||
/// Toggle the inverse of [`Settings::disable_smart_default_size`].
|
||||
|
||||
Reference in New Issue
Block a user