refactor(core): make KlondikeInstruction the move currency
Remove the (from, to, count) tuple as an internal move-passing wrapper. Game logic now stays in KlondikeInstruction space end to end: - Add GameState::apply_instruction, the native apply path. move_cards becomes a thin pile-coordinate adapter that converts to an instruction and delegates, so move bookkeeping (validation, score/recycle history, undo snapshot) lives in one place instead of being duplicated. - next_auto_complete_move matches DstFoundation directly instead of projecting every candidate to pile coordinates. - proptests and the storage round-trip test apply instructions directly rather than round-tripping instruction -> tuple -> move_cards. The single instruction -> pile decode is renamed instruction_to_highlight -> instruction_to_piles and kept in core: decoding a tableau run length needs upstream pile-stack types core does not re-export, so relocating it would duplicate the logic across engine and wasm. The two rendering edges (engine hint highlight, wasm debug move list) call this one decoder; the engine's hint_piles is a thin delegation to it. Also includes the CardEntityIndex render-side index and a SelectionPlugin init_resource fix so update_selection_highlight no longer panics in test harnesses that omit CardPlugin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ use solitaire_data::solver::try_solve_from_state;
|
||||
|
||||
use crate::card_plugin::CardEntity;
|
||||
use crate::events::{HintVisualEvent, InfoToastEvent, StateChangedEvent};
|
||||
use crate::input_plugin::{emit_hint_visuals, find_heuristic_hint};
|
||||
use crate::input_plugin::{emit_hint_visuals, find_heuristic_hint, hint_piles};
|
||||
use crate::resources::{GameStateResource, HintCycleIndex};
|
||||
|
||||
/// In-flight async work for the H-key hint.
|
||||
@@ -93,7 +93,7 @@ struct HintTask {
|
||||
enum HintTaskOutput {
|
||||
/// Solver verdict was winnable; here is the first move on the solution
|
||||
/// path. Converted to highlighted `(from, to)` piles by the poll system
|
||||
/// via [`GameState::instruction_to_move`].
|
||||
/// via [`crate::input_plugin::hint_piles`].
|
||||
SolverMove(KlondikeInstruction),
|
||||
/// Solver was `Unwinnable` or `Inconclusive`. The poll system
|
||||
/// runs the legacy heuristic against the live `GameState` so the
|
||||
@@ -153,10 +153,7 @@ pub fn poll_pending_hint_task(
|
||||
// Resolve the solver's first move to highlighted piles; fall back to the
|
||||
// live-state heuristic when there's no solver move or it maps to a no-op.
|
||||
let solver_pair = match output {
|
||||
HintTaskOutput::SolverMove(instruction) => g
|
||||
.0
|
||||
.instruction_to_move(instruction)
|
||||
.map(|(from, to, _count)| (from, to)),
|
||||
HintTaskOutput::SolverMove(instruction) => hint_piles(&g.0, instruction),
|
||||
HintTaskOutput::NeedsHeuristic => None,
|
||||
};
|
||||
let (from, to) = match solver_pair.or_else(|| find_heuristic_hint(&g.0, &mut hint_cycle)) {
|
||||
|
||||
Reference in New Issue
Block a user