Files
Ferrous-Solitaire/solitaire_core
funman300 87275bf340 feat(core,engine): solver-driven hints with heuristic fallback
The H-key hint now asks the v0.15.0 Klondike solver for the actual
best first move from the current game state instead of the existing
heuristic. The heuristic stays as the fallback path so hints still
work when the solver bails Inconclusive on the player's budget.

solitaire_core::solver gains a path-recording variant. The internal
DFS already enumerated moves on each frame; recording the root_move
on the stack frame is +16 bytes and one unwrap_or per expansion —
the new-game retry loop sees no measurable slowdown.

New public API (additive — try_solve unchanged):

  pub struct SolverMove { source, dest, count }
  pub struct SolveOutcome { result: SolverResult, first_move: Option<SolverMove> }
  pub fn try_solve_with_first_move(seed, draw_mode, &cfg) -> SolveOutcome
  pub fn try_solve_from_state(&GameState, &cfg) -> SolveOutcome

The internal solver-move enum was renamed InternalMove so the public
SolverMove can use engine-friendly (source, dest, count) types
instead of the compact internal form.

Engine wiring: handle_keyboard_hint calls try_solve_from_state on
the live GameStateResource. On Winnable + first_move, the hint
surfaces that exact move (no cycling — a single, optimal hint).
Unwinnable or Inconclusive falls through to the existing all_hints
cycling heuristic so hints remain useful in deals the solver gives
up on.

A new HintSolverConfig resource lets tests inject tight budgets to
force the fallback path; production uses SolverConfig::default()
and median solve time stays at 2 ms per H press.

Six new tests pin the contract: 4 in solitaire_core (Winnable
returns first_move, Unwinnable returns None, deterministic, seed
and state forms agree); 2 in solitaire_engine (hint uses solver
when Winnable, falls back to heuristic when Inconclusive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:10:02 +00:00
..