refactor: slim solver to card_game-native types
Per Rhys: card_game's solver is the real engine, so drop the redundant
adapter types in solitaire_data::solver rather than maintain a parallel
verdict/config/move vocabulary.
- Delete SolverResult, SolverConfig, SolverMove, and snapshot_to_solver_move.
The verdict now reads straight off card_game's return:
Ok(Some(instr)) = winnable (first move on the path)
Ok(None) = provably unwinnable
Err(_) = inconclusive (budget exceeded)
- SolveOutcome is now Result<Option<KlondikeInstruction>, SolveError>.
- try_solve / try_solve_from_state take plain (moves_budget, states_budget)
u64s; add DEFAULT_SOLVE_{MOVES,STATES}_BUDGET consts.
- snapshot_to_solver_move duplicated core's GameState::instruction_to_move,
so make that pub and have the hint convert the first-move instruction to
highlighted (from, to) piles through it. Re-export KlondikeInstruction
from solitaire_core.
- HintSolverConfig now holds { moves_budget, states_budget } instead of
wrapping the deleted SolverConfig.
- Update consumers: pending_hint, play_by_seed (verdict badge), game_plugin
(choose_winnable_seed), input_plugin, hud_plugin, and the gen_seeds /
gen_difficulty_seeds asset tools.
solver.rs drops 274 -> 140 lines. cargo test --workspace and
cargo clippy --workspace --all-targets -- -D warnings pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
//! `HARD_SEEDS`, `EXPERT_SEEDS`, and `GRANDMASTER_SEEDS` in
|
||||
//! `solitaire_data/src/difficulty_seeds.rs`.
|
||||
//!
|
||||
//! A seed's tier is determined by the **smallest** `SolverConfig` budget that
|
||||
//! returns `SolverResult::Winnable`. Seeds that are `Unwinnable` at any budget
|
||||
//! are discarded; `Inconclusive` at all budgets are also discarded (we only emit
|
||||
//! provably-winnable seeds).
|
||||
//! A seed's tier is determined by the **smallest** solve budget at which it is
|
||||
//! proven winnable (`Ok(Some(_))`). Seeds proven dead (`Ok(None)`) at any budget
|
||||
//! are discarded; seeds inconclusive (`Err`) at all budgets are also discarded
|
||||
//! (we only emit provably-winnable seeds).
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
@@ -20,11 +20,11 @@
|
||||
//! --help Print this message
|
||||
|
||||
use solitaire_core::DrawMode;
|
||||
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||
use solitaire_data::solver::try_solve;
|
||||
|
||||
// Budget boundaries defining each tier. A seed belongs to the lowest tier
|
||||
// whose budget proves it Winnable.
|
||||
const BUDGETS: &[(&str, u64, usize)] = &[
|
||||
const BUDGETS: &[(&str, u64, u64)] = &[
|
||||
("Easy", 1_000, 1_000),
|
||||
("Medium", 5_000, 5_000),
|
||||
("Hard", 25_000, 25_000),
|
||||
@@ -99,12 +99,8 @@ fn main() {
|
||||
if buckets[i].len() >= per_tier {
|
||||
continue;
|
||||
}
|
||||
let cfg = SolverConfig {
|
||||
move_budget,
|
||||
state_budget,
|
||||
};
|
||||
match try_solve(seed, draw_mode, &cfg) {
|
||||
SolverResult::Winnable => {
|
||||
match try_solve(seed, draw_mode, move_budget, state_budget) {
|
||||
Ok(Some(_)) => {
|
||||
buckets[i].push(seed);
|
||||
eprintln!(
|
||||
" [{name} {:>3}/{}] 0x{seed:016X} (tried {tried})",
|
||||
@@ -113,13 +109,13 @@ fn main() {
|
||||
);
|
||||
break 'tier; // assign to the cheapest tier that proves it winnable
|
||||
}
|
||||
SolverResult::Unwinnable => {
|
||||
Ok(None) => {
|
||||
// Definitely unsolvable — skip all remaining tiers.
|
||||
break 'tier;
|
||||
}
|
||||
SolverResult::Inconclusive => {
|
||||
Err(_) => {
|
||||
// Budget exhausted without proof — try the next larger tier.
|
||||
// If this is the last tier, the seed is discarded (Inconclusive
|
||||
// If this is the last tier, the seed is discarded (inconclusive
|
||||
// at max budget means "probably but not provably winnable").
|
||||
if i == num_tiers - 1 {
|
||||
break 'tier;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Generate provably-winnable Klondike seeds for `CHALLENGE_SEEDS`.
|
||||
//!
|
||||
//! Walks seeds incrementally from `--start`, calls the solver on each, and
|
||||
//! collects only those that return `SolverResult::Winnable` (Inconclusive is
|
||||
//! collects only those proven winnable (`Ok(Some(_))`; inconclusive is
|
||||
//! rejected — the curated list wants proof). Prints Rust source suitable for
|
||||
//! pasting into `solitaire_data/src/challenge.rs`.
|
||||
//!
|
||||
@@ -18,7 +18,7 @@
|
||||
//! --help Print this message
|
||||
|
||||
use solitaire_core::DrawMode;
|
||||
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||
use solitaire_data::solver::{DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET, try_solve};
|
||||
|
||||
fn main() {
|
||||
let mut args = std::env::args().skip(1).peekable();
|
||||
@@ -67,7 +67,6 @@ fn main() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let cfg = SolverConfig::default();
|
||||
let draw_mode = DrawMode::DrawOne;
|
||||
let mut found: Vec<u64> = Vec::with_capacity(count);
|
||||
let mut tried: u64 = 0;
|
||||
@@ -77,7 +76,15 @@ fn main() {
|
||||
|
||||
while found.len() < count {
|
||||
tried += 1;
|
||||
if matches!(try_solve(seed, draw_mode, &cfg), SolverResult::Winnable) {
|
||||
if matches!(
|
||||
try_solve(
|
||||
seed,
|
||||
draw_mode,
|
||||
DEFAULT_SOLVE_MOVES_BUDGET,
|
||||
DEFAULT_SOLVE_STATES_BUDGET
|
||||
),
|
||||
Ok(Some(_))
|
||||
) {
|
||||
found.push(seed);
|
||||
eprintln!(
|
||||
" [{:>3}/{}] 0x{:016X} ({} tried so far)",
|
||||
|
||||
Reference in New Issue
Block a user