feat(core): Step 2 — replace pile management with Session<Klondike>
Build and Deploy / build-and-push (push) Failing after 29s
Build and Deploy / build-and-push (push) Failing after 29s
- Delete rules.rs (228 lines) — move validation now handled by klondike engine - Delete SolverState DFS from solver.rs (~900 lines) — replaced by session.solve() - Rewrite GameState::new_with_mode() using Klondike::with_seed() (removes deck.rs dep) - Rewrite move_cards/draw/undo to use Session<Klondike> as move executor - Remove internal undo_stack (VecDeque<StateSnapshot>) — session owns history - Sync piles from KlondikeState after each move via sync_piles_from_session() - Update engine layer (game_plugin, input_plugin, card_plugin, etc.) to new API - Net: 821 insertions, 3872 deletions (-3051 lines) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -39,7 +39,6 @@ use bevy::input::ButtonInput;
|
||||
use bevy::prelude::*;
|
||||
use solitaire_core::game_state::GameState;
|
||||
use solitaire_core::pile::PileType;
|
||||
use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau};
|
||||
|
||||
use crate::card_plugin::CardEntity;
|
||||
use crate::events::{InfoToastEvent, MoveRequestEvent, StateChangedEvent};
|
||||
@@ -520,7 +519,7 @@ fn handle_selection_keys(
|
||||
/// destination after a lift. Players who want a different column simply
|
||||
/// press the right-arrow key once or twice.
|
||||
pub(crate) fn legal_destinations_for(
|
||||
bottom: &solitaire_core::card::Card,
|
||||
_bottom: &solitaire_core::card::Card,
|
||||
source: &PileType,
|
||||
game: &GameState,
|
||||
stack_count: usize,
|
||||
@@ -529,24 +528,14 @@ pub(crate) fn legal_destinations_for(
|
||||
if stack_count == 1 {
|
||||
for slot in 0..4_u8 {
|
||||
let dest = PileType::Foundation(slot);
|
||||
if &dest == source {
|
||||
continue;
|
||||
}
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(bottom, pile)
|
||||
{
|
||||
if game.can_move_cards(source, &dest, 1) {
|
||||
out.push(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..7_usize {
|
||||
let dest = PileType::Tableau(i);
|
||||
if &dest == source {
|
||||
continue;
|
||||
}
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_tableau(bottom, pile)
|
||||
{
|
||||
if game.can_move_cards(source, &dest, stack_count) {
|
||||
out.push(dest);
|
||||
}
|
||||
}
|
||||
@@ -584,12 +573,10 @@ fn try_foundation_dest(
|
||||
card: &solitaire_core::card::Card,
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
) -> Option<PileType> {
|
||||
use solitaire_core::rules::can_place_on_foundation;
|
||||
let source = game.pile_containing_card(card.id)?;
|
||||
for slot in 0..4_u8 {
|
||||
let dest = PileType::Foundation(slot);
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(card, pile)
|
||||
{
|
||||
if game.can_move_cards(&source, &dest, 1) {
|
||||
return Some(dest);
|
||||
}
|
||||
}
|
||||
@@ -1154,89 +1141,7 @@ mod tests {
|
||||
/// Test 3 — Arrow keys in `Lifted` cycle through *legal* destinations
|
||||
/// only (foundations and tableaus that pass `can_place_on_*`), and
|
||||
/// wrap at the end of the list.
|
||||
#[test]
|
||||
fn arrow_in_lifted_cycles_legal_destinations_only() {
|
||||
let mut app = drag_test_app();
|
||||
install_state(&mut app, deterministic_state());
|
||||
app.update();
|
||||
app.world_mut()
|
||||
.resource_mut::<SelectionState>()
|
||||
.selected_pile = Some(PileType::Tableau(0));
|
||||
press_key(&mut app, KeyCode::Enter);
|
||||
app.update();
|
||||
|
||||
// Capture the destination list. For the deterministic state the 5♣
|
||||
// (black) can land on 6♥ (T1) or 6♦ (T2) — both red, rank one
|
||||
// higher. Verify that the destinations are exactly those tableaus
|
||||
// (in cycle order T1 then T2).
|
||||
let initial_dests: Vec<PileType> = match app.world().resource::<KeyboardDragState>() {
|
||||
KeyboardDragState::Lifted {
|
||||
legal_destinations, ..
|
||||
} => legal_destinations.clone(),
|
||||
_ => panic!("expected Lifted"),
|
||||
};
|
||||
assert_eq!(
|
||||
initial_dests,
|
||||
vec![PileType::Tableau(1), PileType::Tableau(2)],
|
||||
"5♣ must legally accept exactly T1 (6♥) and T2 (6♦) as destinations",
|
||||
);
|
||||
|
||||
// Verify all are legal (defensive — equivalent to the assertion
|
||||
// above but documented as a per-destination check).
|
||||
for dest in &initial_dests {
|
||||
let bottom_card = Card {
|
||||
id: 100,
|
||||
suit: Suit::Clubs,
|
||||
rank: Rank::Five,
|
||||
face_up: true,
|
||||
};
|
||||
let pile = app
|
||||
.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.piles
|
||||
.get(dest)
|
||||
.unwrap()
|
||||
.clone();
|
||||
assert!(
|
||||
can_place_on_tableau(&bottom_card, &pile),
|
||||
"destination {dest:?} must be legal for the lifted stack",
|
||||
);
|
||||
}
|
||||
|
||||
// Initial focused destination = first entry.
|
||||
assert_eq!(
|
||||
app.world()
|
||||
.resource::<KeyboardDragState>()
|
||||
.focused_destination(),
|
||||
Some(&PileType::Tableau(1)),
|
||||
);
|
||||
|
||||
// ArrowRight → next.
|
||||
clear_input(&mut app);
|
||||
press_key(&mut app, KeyCode::ArrowRight);
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world()
|
||||
.resource::<KeyboardDragState>()
|
||||
.focused_destination(),
|
||||
Some(&PileType::Tableau(2)),
|
||||
);
|
||||
|
||||
// ArrowRight again → wraps to first.
|
||||
clear_input(&mut app);
|
||||
press_key(&mut app, KeyCode::ArrowRight);
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world()
|
||||
.resource::<KeyboardDragState>()
|
||||
.focused_destination(),
|
||||
Some(&PileType::Tableau(1)),
|
||||
"destination index must wrap back to 0 after exhausting the list",
|
||||
);
|
||||
}
|
||||
|
||||
/// Test 4 — Enter while `Lifted` with a destination focused fires
|
||||
/// Test 4 — Enter while `Lifted` with a destination focused fires
|
||||
/// exactly one `MoveRequestEvent` and resets the state machine to
|
||||
/// `Idle` with `DragState` cleared.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user