fix(engine,wasm,web): detect no-legal-moves correctly and surface banner
Build and Deploy / build-and-push (push) Successful in 4m24s

Engine: replace broken has_legal_moves loop (which checked buried
mid-column cards without sequence validation) with a delegation to
possible_instructions(), mirroring the hint system's logic exactly.

WASM: add has_moves: bool to GameSnapshot, computed in snap() using the
same stock/waste/possible_instructions check so the web client gets the
flag in every state update at no extra round-trip cost.

Web: show a non-blocking no-moves banner (slide-up toast) with Undo and
New Game actions when has_moves is false and the game is not won. Banner
hides automatically once a move restores legal play (e.g. after undo).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-19 16:53:52 -07:00
parent a2dd8d220c
commit da601bebd6
5 changed files with 99 additions and 41 deletions
+8
View File
@@ -241,6 +241,8 @@ pub struct GameSnapshot {
pub move_count: u32,
pub is_won: bool,
pub is_auto_completable: bool,
/// `false` when stock, waste, and all pile-to-pile moves are exhausted.
pub has_moves: bool,
pub undo_count: u32,
/// Number of snapshots currently on the undo stack; 0 means undo is unavailable.
pub undo_stack_len: usize,
@@ -279,11 +281,17 @@ impl SolitaireGame {
.map(|p| p.cards.iter().map(CardSnapshot::from).collect())
.unwrap_or_default()
};
let has_moves = {
let stock_empty = self.game.piles.get(&PileType::Stock).is_none_or(|p| p.cards.is_empty());
let waste_empty = self.game.piles.get(&PileType::Waste).is_none_or(|p| p.cards.is_empty());
!stock_empty || !waste_empty || !self.game.possible_instructions().is_empty()
};
GameSnapshot {
score: self.game.score,
move_count: self.game.move_count,
is_won: self.game.is_won,
is_auto_completable: self.game.is_auto_completable,
has_moves,
undo_count: self.game.undo_count,
undo_stack_len: self.game.undo_stack_len(),
stock: cards(PileType::Stock),