diff --git a/Cargo.lock b/Cargo.lock index 25a1350..40df819 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7336,11 +7336,13 @@ version = "0.1.0" dependencies = [ "async-trait", "axum", + "card_game", "chrono", "dirs", "jni 0.21.1", "jsonwebtoken", "keyring-core", + "klondike", "reqwest", "serde", "serde_json", diff --git a/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs b/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs index 86c93f3..4201198 100644 --- a/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs +++ b/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs @@ -19,8 +19,8 @@ //! --per-tier Seeds to emit per tier (default 40) //! --help Print this message -use solitaire_core::game_state::DrawMode; -use solitaire_core::solver::{SolverConfig, SolverResult, try_solve}; +use solitaire_core::DrawMode; +use solitaire_data::solver::{SolverConfig, SolverResult, try_solve}; // Budget boundaries defining each tier. A seed belongs to the lowest tier // whose budget proves it Winnable. diff --git a/solitaire_assetgen/src/bin/gen_seeds.rs b/solitaire_assetgen/src/bin/gen_seeds.rs index 801edb0..39a1bbd 100644 --- a/solitaire_assetgen/src/bin/gen_seeds.rs +++ b/solitaire_assetgen/src/bin/gen_seeds.rs @@ -17,8 +17,8 @@ //! --count Number of Winnable seeds to emit (default 75) //! --help Print this message -use solitaire_core::game_state::DrawMode; -use solitaire_core::solver::{SolverConfig, SolverResult, try_solve}; +use solitaire_core::DrawMode; +use solitaire_data::solver::{SolverConfig, SolverResult, try_solve}; fn main() { let mut args = std::env::args().skip(1).peekable(); diff --git a/solitaire_core/src/game_state.rs b/solitaire_core/src/game_state.rs index 30bfaa2..e3cebe4 100644 --- a/solitaire_core/src/game_state.rs +++ b/solitaire_core/src/game_state.rs @@ -1,7 +1,8 @@ use crate::card::Card; use crate::error::MoveError; use crate::klondike_adapter::{ - KlondikeAdapter, SavedInstruction, card_from_kl, compute_time_bonus as scoring_time_bonus, + DrawMode, KlondikeAdapter, SavedInstruction, card_from_kl, + compute_time_bonus as scoring_time_bonus, foundation_from_slot as adapter_foundation_from_slot, skip_cards_from_count as adapter_skip_cards_from_count, tableau_from_index as adapter_tableau_from_index, @@ -33,15 +34,6 @@ fn schema_v1() -> u32 { 1 } -/// Whether cards are drawn one at a time or three at a time from the stock. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum DrawMode { - /// Draw one card from stock per turn. - DrawOne, - /// Draw three cards from stock per turn; only the top is playable. - DrawThree, -} - /// Difficulty tier for `GameMode::Difficulty`. Controls which pre-verified seed /// catalog is drawn from. `Random` skips verification entirely and uses a /// system-time seed — deals may or may not be winnable. @@ -185,8 +177,6 @@ pub struct GameState { /// When `true`, the player may move the top card of a foundation pile back /// onto a compatible tableau column. pub take_from_foundation: bool, - /// Save-file schema version. - pub schema_version: u32, pub(crate) session: Session, /// Score recorded immediately before each instruction was applied. /// Parallel to `session.history()` during live play; used by `undo()` to @@ -215,7 +205,6 @@ impl PartialEq for GameState { && self.undo_count == other.undo_count && self.recycle_count == other.recycle_count && self.take_from_foundation == other.take_from_foundation - && self.schema_version == other.schema_version && self.stock_cards() == other.stock_cards() && self.waste_cards() == other.waste_cards() && (0..4_u8) @@ -243,7 +232,7 @@ impl Serialize for GameState { undo_count: self.undo_count, recycle_count: self.recycle_count, take_from_foundation: self.take_from_foundation, - schema_version: self.schema_version, + schema_version: GAME_STATE_SCHEMA_VERSION, saved_moves: self.saved_moves(), } .serialize(serializer) @@ -279,9 +268,6 @@ impl<'de> Deserialize<'de> for GameState { // due to the pre-Phase-3 undo drift bug. recycle_count: 0, take_from_foundation: persisted.take_from_foundation, - // Always stamp the current schema version after a successful load so - // storage.rs schema checks pass and re-saving writes the v4 format. - schema_version: GAME_STATE_SCHEMA_VERSION, session: Self::new_session(persisted.seed, persisted.draw_mode), // score_history cannot be faithfully rebuilt from the instruction // history because live-play undo penalties are not recorded in @@ -358,7 +344,6 @@ impl GameState { undo_count: 0, recycle_count: 0, take_from_foundation: true, - schema_version: GAME_STATE_SCHEMA_VERSION, session: Self::new_session(seed, draw_mode), score_history: Vec::new(), is_recycle_history: Vec::new(), diff --git a/solitaire_core/src/klondike_adapter.rs b/solitaire_core/src/klondike_adapter.rs index a37eb73..ff67955 100644 --- a/solitaire_core/src/klondike_adapter.rs +++ b/solitaire_core/src/klondike_adapter.rs @@ -18,7 +18,16 @@ use klondike::{ use serde::{Deserialize, Serialize}; use crate::card; -use crate::game_state::{DrawMode, GameMode}; +use crate::game_state::GameMode; + +/// Whether cards are drawn one at a time or three at a time from the stock. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum DrawMode { + /// Draw one card from stock per turn. + DrawOne, + /// Draw three cards from stock per turn; only the top is playable. + DrawThree, +} /// Bridges `solitaire_core` game config and scoring to the upstream `klondike` crate. /// diff --git a/solitaire_core/src/lib.rs b/solitaire_core/src/lib.rs index 05941e2..bdee5ed 100644 --- a/solitaire_core/src/lib.rs +++ b/solitaire_core/src/lib.rs @@ -3,8 +3,6 @@ pub mod card; pub mod error; pub mod game_state; pub mod klondike_adapter; -pub mod pile; -pub mod solver; // Re-export the upstream types that cross the solitaire_core API boundary so // downstream crates (engine, wasm) can import from one place without a direct @@ -15,6 +13,7 @@ pub mod solver; // not appear in any public method signature. pub use card_game::Session; pub use klondike::{Foundation, Klondike, KlondikePile, Tableau}; +pub use klondike_adapter::DrawMode; #[cfg(test)] mod proptest_tests; diff --git a/solitaire_core/src/pile.rs b/solitaire_core/src/pile.rs deleted file mode 100644 index 52bdf3e..0000000 --- a/solitaire_core/src/pile.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::card::{Card, Suit}; -use klondike::KlondikePile; - -/// Read-only projection of a single Klondike pile, rebuilt from [`GameState`] on every sync. -/// -/// `Pile` is a **data-transfer type**, not a game-state owner. Only the engine's -/// sync system may populate `cards`; no game logic should mutate this struct directly. -/// [`GameState`] is always the authoritative source of truth. -/// -/// [`GameState`]: crate::game_state::GameState -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Pile { - /// Which logical Klondike pile this is. - pub pile_type: KlondikePile, - /// Cards in the pile, bottom-to-top stacking order. Last element is the top card. - /// Populated by the sync system; do not mutate from game-logic code. - pub cards: Vec, -} - -impl Pile { - /// Creates a new empty pile of the given type. - pub fn new(pile_type: KlondikePile) -> Self { - Self { - pile_type, - cards: Vec::new(), - } - } - - /// Returns a reference to the top (last) card, or `None` if empty. - pub fn top(&self) -> Option<&Card> { - self.cards.last() - } - - /// For foundation piles: returns `Some(suit)` once at least one card has - /// landed (the bottom card is always an Ace of the claimed suit). - /// Returns `None` for empty foundations or non-foundation piles. - pub fn claimed_suit(&self) -> Option { - match self.pile_type { - KlondikePile::Foundation(_) => self.cards.first().map(|c| c.suit), - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::card::{Card, Rank, Suit}; - - #[test] - fn new_pile_is_empty() { - let pile = Pile::new(KlondikePile::Stock); - assert!(pile.cards.is_empty()); - } - - #[test] - fn pile_top_returns_last_card() { - let mut pile = Pile::new(KlondikePile::Stock); - pile.cards.push(Card::face_up(0, Suit::Hearts, Rank::Ace)); - pile.cards.push(Card::face_up(1, Suit::Clubs, Rank::Two)); - assert_eq!(pile.top().unwrap().id, 1); - } - - #[test] - fn pile_top_on_empty_is_none() { - let pile = Pile::new(KlondikePile::Stock); - assert!(pile.top().is_none()); - } - - #[test] - fn claimed_suit_is_none_for_empty_foundation() { - let pile = Pile::new(KlondikePile::Foundation(klondike::Foundation::Foundation1)); - assert!(pile.claimed_suit().is_none()); - } - - #[test] - fn claimed_suit_is_none_for_non_foundation() { - let mut pile = Pile::new(KlondikePile::Tableau(klondike::Tableau::Tableau1)); - pile.cards.push(Card::face_up(0, Suit::Hearts, Rank::Ace)); - assert!(pile.claimed_suit().is_none()); - } - - #[test] - fn claimed_suit_returns_bottom_card_suit() { - let mut pile = Pile::new(KlondikePile::Foundation(klondike::Foundation::Foundation3)); - pile.cards.push(Card::face_up(0, Suit::Hearts, Rank::Ace)); - pile.cards.push(Card::face_up(1, Suit::Hearts, Rank::Two)); - assert_eq!(pile.claimed_suit(), Some(Suit::Hearts)); - } -} diff --git a/solitaire_core/src/proptest_tests.rs b/solitaire_core/src/proptest_tests.rs index cb13827..96b1424 100644 --- a/solitaire_core/src/proptest_tests.rs +++ b/solitaire_core/src/proptest_tests.rs @@ -2,7 +2,8 @@ use card_game::Game; use klondike::{Foundation, KlondikePile, KlondikeInstruction, SkipCards, Tableau}; use proptest::prelude::*; -use crate::game_state::{DrawMode, GameState}; +use crate::game_state::GameState; +use crate::klondike_adapter::DrawMode; use crate::klondike_adapter::{ InvalidSavedInstruction, SavedDstFoundation, SavedDstTableau, SavedFoundation, SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, SavedSkipCards, SavedTableau, diff --git a/solitaire_data/Cargo.toml b/solitaire_data/Cargo.toml index c7349d7..b1a65ce 100644 --- a/solitaire_data/Cargo.toml +++ b/solitaire_data/Cargo.toml @@ -7,6 +7,8 @@ edition.workspace = true [dependencies] solitaire_core = { workspace = true } solitaire_sync = { workspace = true } +klondike = { workspace = true } +card_game = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } chrono = { workspace = true } diff --git a/solitaire_data/src/lib.rs b/solitaire_data/src/lib.rs index a5c4dd2..0cae8aa 100644 --- a/solitaire_data/src/lib.rs +++ b/solitaire_data/src/lib.rs @@ -99,6 +99,12 @@ impl SyncProvider for Box { } } +pub mod solver; +pub use solver::{ + SolveOutcome, SolverConfig, SolverMove, SolverResult, try_solve, try_solve_from_state, + try_solve_with_first_move, +}; + pub mod stats; pub use stats::{StatsExt, StatsSnapshot}; diff --git a/solitaire_data/src/replay.rs b/solitaire_data/src/replay.rs index 4d70352..4b60b82 100644 --- a/solitaire_data/src/replay.rs +++ b/solitaire_data/src/replay.rs @@ -26,7 +26,7 @@ use std::path::{Path, PathBuf}; use chrono::NaiveDate; use serde::{Deserialize, Serialize}; -use solitaire_core::game_state::{DrawMode, GameMode}; +use solitaire_core::{DrawMode, game_state::GameMode}; use solitaire_core::klondike_adapter::SavedKlondikePile; const LATEST_REPLAY_FILE_NAME: &str = "latest_replay.json"; diff --git a/solitaire_data/src/settings.rs b/solitaire_data/src/settings.rs index a12454e..fa0d644 100644 --- a/solitaire_data/src/settings.rs +++ b/solitaire_data/src/settings.rs @@ -9,7 +9,7 @@ use std::io; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; -use solitaire_core::game_state::{DifficultyLevel, DrawMode}; +use solitaire_core::{DrawMode, game_state::DifficultyLevel}; const SETTINGS_FILE_NAME: &str = "settings.json"; @@ -200,7 +200,7 @@ pub struct Settings { #[serde(default = "default_time_bonus_multiplier")] pub time_bonus_multiplier: f32, /// When `true`, the engine rejects new-game deals the - /// [`solitaire_core::solver`] cannot prove winnable, retrying + /// [`solitaire_data::solver`] cannot prove winnable, retrying /// fresh seeds up to [`SOLVER_DEAL_RETRY_CAP`] attempts before /// giving up and using the last tried seed. Off by default — /// the solver adds a few hundred milliseconds of latency on the diff --git a/solitaire_core/src/solver.rs b/solitaire_data/src/solver.rs similarity index 90% rename from solitaire_core/src/solver.rs rename to solitaire_data/src/solver.rs index ef0f36f..4f320da 100644 --- a/solitaire_core/src/solver.rs +++ b/solitaire_data/src/solver.rs @@ -5,9 +5,9 @@ use card_game::{Session, SessionConfig, SolveError, StateSnapshot}; use klondike::{Klondike, KlondikeInstruction, KlondikePile, KlondikePileStack}; - -use crate::game_state::{DrawMode, GameState}; -use crate::klondike_adapter::KlondikeAdapter; +use solitaire_core::DrawMode; +use solitaire_core::game_state::GameState; +use solitaire_core::klondike_adapter::KlondikeAdapter; /// Verdict returned by [`try_solve`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -160,7 +160,8 @@ fn snapshot_to_solver_move(snapshot: &StateSnapshot) -> Option { let (source, count) = match dst_tableau.src { KlondikePileStack::Tableau(tableau_stack) => { - let face_up_count = source_state.tableau_face_up_cards(tableau_stack.tableau).len(); + let face_up_count = + source_state.tableau_face_up_cards(tableau_stack.tableau).len(); let count = face_up_count.checked_sub(tableau_stack.skip_cards as usize)?; if count == 0 { return None; @@ -242,10 +243,6 @@ mod tests { #[test] fn budget_is_passed_through_not_clamped() { - // 0xD1FF_0000_0000_0012 is a Medium-tier catalog seed: Inconclusive at - // the Easy budget (1 000 states) but Winnable at Medium (5 000 states). - // Differing results confirm solve_game_state passes the caller's - // state_budget unchanged to the underlying solver. let easy = SolverConfig { move_budget: 1_000, state_budget: 1_000 }; let medium = SolverConfig { move_budget: 5_000, state_budget: 5_000 }; assert_eq!( @@ -260,12 +257,6 @@ mod tests { #[test] fn budget_above_five_thousand_is_not_clamped() { - // 0xD1FF_0000_0000_00DE is a hard catalog seed: Inconclusive at 5 000 - // states but Winnable at 50 000. Before this fix, solve_game_state - // applied `config.state_budget.min(5_000)` internally, so a 50k config - // was silently reduced to 5k — making both calls return Inconclusive and - // preventing the generator from certifying Hard/Expert/Grandmaster seeds. - // This assertion fails if the cap is re-introduced. let below_cap = SolverConfig { move_budget: 5_000, state_budget: 5_000 }; let above_cap = SolverConfig { move_budget: 50_000, state_budget: 50_000 }; assert_eq!( diff --git a/solitaire_data/src/stats.rs b/solitaire_data/src/stats.rs index 88d85d8..ae16f60 100644 --- a/solitaire_data/src/stats.rs +++ b/solitaire_data/src/stats.rs @@ -5,7 +5,7 @@ //! `update_on_win` method that depends on [`DrawMode`] from `solitaire_core`. use chrono::Utc; -use solitaire_core::game_state::{DrawMode, GameMode}; +use solitaire_core::{DrawMode, game_state::GameMode}; pub use solitaire_sync::StatsSnapshot; diff --git a/solitaire_data/src/storage.rs b/solitaire_data/src/storage.rs index 8383ea4..880cb0c 100644 --- a/solitaire_data/src/storage.rs +++ b/solitaire_data/src/storage.rs @@ -9,7 +9,7 @@ use std::io; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; -use solitaire_core::game_state::{GAME_STATE_SCHEMA_VERSION, GameState}; +use solitaire_core::game_state::GameState; use crate::stats::StatsSnapshot; @@ -85,9 +85,6 @@ pub fn game_state_file_path() -> Option { pub fn load_game_state_from(path: &Path) -> Option { let data = fs::read(path).ok()?; let gs: GameState = serde_json::from_slice(&data).ok()?; - if gs.schema_version != GAME_STATE_SCHEMA_VERSION { - return None; - } if gs.is_won { None } else { Some(gs) } } @@ -282,7 +279,7 @@ fn cleanup_tmp_files_in(dir: &Path) { mod tests { use super::*; use crate::stats::{StatsExt, StatsSnapshot}; - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; use std::env; fn tmp_path(name: &str) -> PathBuf { @@ -380,7 +377,7 @@ mod tests { #[test] fn game_state_round_trip() { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::game_state::GameState; let path = gs_path("round_trip"); let _ = fs::remove_file(&path); @@ -409,7 +406,7 @@ mod tests { #[test] fn save_game_state_skips_won_games() { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::game_state::GameState; let path = gs_path("won_skip"); let _ = fs::remove_file(&path); @@ -424,7 +421,7 @@ mod tests { #[test] fn delete_game_state_removes_file() { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::game_state::GameState; let path = gs_path("delete"); let gs = GameState::new(1, DrawMode::DrawOne); save_game_state_to(&path, &gs).expect("save"); @@ -442,7 +439,7 @@ mod tests { #[test] fn save_game_state_is_atomic() { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::game_state::GameState; let path = gs_path("atomic"); let gs = GameState::new(55, DrawMode::DrawThree); save_game_state_to(&path, &gs).expect("save"); @@ -510,7 +507,7 @@ mod tests { #[test] fn game_state_v4_mid_game_round_trip() { use solitaire_core::KlondikePile; - use solitaire_core::game_state::{DrawMode, GameState, GAME_STATE_SCHEMA_VERSION}; + use solitaire_core::game_state::GameState; let path = gs_path("v4_mid_game"); let _ = fs::remove_file(&path); @@ -557,7 +554,6 @@ mod tests { let loaded = load_game_state_from(&path) .expect("a valid in-progress game must load without error"); - assert_eq!(loaded.schema_version, GAME_STATE_SCHEMA_VERSION); assert_eq!( loaded, gs, "all pile layouts and counters must be identical after schema-v4 round-trip", @@ -574,7 +570,7 @@ mod tests { /// u8-to-named conversion for `DstFoundation` / `DstTableau` indices. #[test] fn game_state_v3_migrates_to_v4() { - use solitaire_core::game_state::{DrawMode, GameState, GAME_STATE_SCHEMA_VERSION}; + use solitaire_core::game_state::GameState; let path = gs_path("v3_migrate"); let _ = fs::remove_file(&path); @@ -599,12 +595,6 @@ mod tests { let loaded = load_game_state_from(&path) .expect("schema v3 must be accepted and migrated to v4"); - // After migration, the in-memory schema version must be current. - assert_eq!( - loaded.schema_version, GAME_STATE_SCHEMA_VERSION, - "migrated game must report current schema version", - ); - // The loaded game should match a fresh game that had one draw applied. let mut expected = GameState::new(42, DrawMode::DrawOne); expected.draw().expect("draw must succeed on a fresh game"); diff --git a/solitaire_data/src/weekly.rs b/solitaire_data/src/weekly.rs index 96bd52a..96254cc 100644 --- a/solitaire_data/src/weekly.rs +++ b/solitaire_data/src/weekly.rs @@ -4,7 +4,7 @@ //! increments matching counters in `PlayerProgress::weekly_goal_progress`. use chrono::{Datelike, NaiveDate}; -use solitaire_core::game_state::DrawMode; +use solitaire_core::DrawMode; /// XP awarded each time a weekly goal is just completed. pub const WEEKLY_GOAL_XP: u64 = 75; diff --git a/solitaire_engine/src/achievement_plugin.rs b/solitaire_engine/src/achievement_plugin.rs index cdda35a..4018c3e 100644 --- a/solitaire_engine/src/achievement_plugin.rs +++ b/solitaire_engine/src/achievement_plugin.rs @@ -819,7 +819,7 @@ mod tests { app.world_mut() .resource_mut::() .0 - .draw_mode = solitaire_core::game_state::DrawMode::DrawThree; + .draw_mode = solitaire_core::DrawMode::DrawThree; app.world_mut().write_message(GameWonEvent { score: 500, @@ -868,7 +868,7 @@ mod tests { app.world_mut() .resource_mut::() .0 - .draw_mode = solitaire_core::game_state::DrawMode::DrawThree; + .draw_mode = solitaire_core::DrawMode::DrawThree; app.world_mut().write_message(GameWonEvent { score: 500, @@ -1393,7 +1393,7 @@ mod tests { use crate::replay_playback::ReplayPlaybackState; use chrono::NaiveDate; - use solitaire_core::game_state::{DrawMode, GameMode}; + use solitaire_core::{DrawMode, game_state::GameMode}; use solitaire_data::{Replay, ReplayMove}; /// Headless app variant that injects a default `ReplayPlaybackState` diff --git a/solitaire_engine/src/auto_complete_plugin.rs b/solitaire_engine/src/auto_complete_plugin.rs index 353e2d0..2bc27cb 100644 --- a/solitaire_engine/src/auto_complete_plugin.rs +++ b/solitaire_engine/src/auto_complete_plugin.rs @@ -169,7 +169,7 @@ mod tests { use crate::table_plugin::TablePlugin; use solitaire_core::{Foundation, KlondikePile, Tableau}; use solitaire_core::card::{Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn headless_app() -> App { let mut app = App::new(); diff --git a/solitaire_engine/src/card_plugin.rs b/solitaire_engine/src/card_plugin.rs index ba96e4a..1e96371 100644 --- a/solitaire_engine/src/card_plugin.rs +++ b/solitaire_engine/src/card_plugin.rs @@ -18,7 +18,7 @@ use bevy::sprite::Anchor; use bevy::window::WindowResized; use solitaire_core::{Foundation, KlondikePile, Tableau}; use solitaire_core::card::{Card, Rank, Suit}; -use solitaire_core::game_state::{DrawMode, GameState}; +use solitaire_core::{DrawMode, game_state::GameState}; use crate::animation_plugin::{CARD_ANIM_Z_LIFT, CardAnim, EffectiveSlideDuration}; use crate::card_animation::CardAnimation; @@ -2526,7 +2526,7 @@ mod tests { #[test] fn card_positions_includes_all_52_cards_at_game_start() { // At game start waste is empty, so all 52 cards are across stock + tableau. - let g = GameState::new(42, solitaire_core::game_state::DrawMode::DrawOne); + let g = GameState::new(42, solitaire_core::DrawMode::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); assert_eq!(positions.len(), 52); @@ -2534,7 +2534,7 @@ mod tests { #[test] fn waste_draw_one_only_renders_top_card() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawOne); // Draw 3 cards so the waste pile has 3 cards. for _ in 0..3 { @@ -2572,7 +2572,7 @@ mod tests { #[test] fn waste_draw_three_renders_up_to_three_fanned_cards() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawThree); // 5 draw() calls in Draw-Three mode accumulates multiple waste cards. for _ in 0..5 { @@ -2625,7 +2625,7 @@ mod tests { // Regression: slot.saturating_sub(1) always hid slot-0 even when the // pile was too small to have a buffer card, collapsing 2 visible cards // onto x=0 instead of fanning them. - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawThree); // Draw exactly once — in Draw-Three mode with a full stock this gives // 3 waste cards (still ≤ visible=3, so no hidden buffer needed). @@ -2667,7 +2667,7 @@ mod tests { /// top card so that hiding it (`Visibility::Hidden`) leaves no visible gap. #[test] fn waste_draw_one_buffer_card_at_same_xy_as_top() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawOne); // Draw 3 times so the waste pile has 3 cards and the buffer exists. for _ in 0..3 { @@ -2698,7 +2698,7 @@ mod tests { #[test] fn card_positions_tableau_cards_are_fanned_downward() { - let g = GameState::new(42, solitaire_core::game_state::DrawMode::DrawOne); + let g = GameState::new(42, solitaire_core::DrawMode::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -3103,7 +3103,7 @@ mod tests { #[test] fn facedown_cards_use_tighter_fan_than_uniform_faceup_fan() { - let g = GameState::new(42, solitaire_core::game_state::DrawMode::DrawOne); + let g = GameState::new(42, solitaire_core::DrawMode::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -3552,7 +3552,7 @@ mod tests { #[test] fn stock_card_count_helper_reads_zero_for_empty_stock() { - let g = GameState::new(42, solitaire_core::game_state::DrawMode::DrawOne); + let g = GameState::new(42, solitaire_core::DrawMode::DrawOne); let mut g_empty_stock = g.clone(); g_empty_stock.set_test_stock_cards(Vec::new()); assert_eq!(stock_card_count(&g_empty_stock), 0); @@ -3837,7 +3837,7 @@ mod tests { #[test] fn waste_pile_cards_have_strictly_increasing_z() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawThree); for _ in 0..5 { let _ = g.draw(); @@ -3881,7 +3881,7 @@ mod tests { /// offsets or flips the fan direction is caught immediately. #[test] fn waste_cards_do_not_overlap_stock_column_on_portrait() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawThree); for _ in 0..5 { let _ = g.draw(); @@ -3917,7 +3917,7 @@ mod tests { #[test] fn waste_pile_draw_one_cards_have_distinct_z() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut g = GameState::new(42, DrawMode::DrawOne); for _ in 0..3 { let _ = g.draw(); diff --git a/solitaire_engine/src/challenge_plugin.rs b/solitaire_engine/src/challenge_plugin.rs index 64f826f..887cdb3 100644 --- a/solitaire_engine/src/challenge_plugin.rs +++ b/solitaire_engine/src/challenge_plugin.rs @@ -117,7 +117,7 @@ mod tests { use crate::game_plugin::GamePlugin; use crate::progress_plugin::ProgressPlugin; use crate::table_plugin::TablePlugin; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn headless_app() -> App { let mut app = App::new(); diff --git a/solitaire_engine/src/cursor_plugin.rs b/solitaire_engine/src/cursor_plugin.rs index 531ac06..b7a2b45 100644 --- a/solitaire_engine/src/cursor_plugin.rs +++ b/solitaire_engine/src/cursor_plugin.rs @@ -35,7 +35,7 @@ use bevy::prelude::*; use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon}; use solitaire_core::{Foundation, KlondikePile, Tableau}; -use solitaire_core::game_state::{DrawMode, GameState}; +use solitaire_core::{DrawMode, game_state::GameState}; use crate::card_plugin::RightClickHighlight; use crate::layout::{Layout, LayoutResource}; @@ -562,7 +562,7 @@ mod tests { #[test] fn cursor_over_draggable_returns_false_for_empty_game() { use crate::layout::compute_layout; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; let game = GameState::new(42, DrawMode::DrawOne); let layout = compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); @@ -580,7 +580,7 @@ mod tests { use crate::layout::compute_layout; use solitaire_core::card::{Card, Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameMode, GameState}; + use solitaire_core::{DrawMode, game_state::{GameMode, GameState}}; /// Builds an `App` with `MinimalPlugins` and the overlay system /// registered, plus the resources the system needs. Callers diff --git a/solitaire_engine/src/daily_challenge_plugin.rs b/solitaire_engine/src/daily_challenge_plugin.rs index 0398af4..6da749a 100644 --- a/solitaire_engine/src/daily_challenge_plugin.rs +++ b/solitaire_engine/src/daily_challenge_plugin.rs @@ -362,7 +362,7 @@ mod tests { use crate::progress_plugin::ProgressPlugin; use crate::table_plugin::TablePlugin; #[allow(unused_imports)] - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn headless_app() -> App { let mut app = App::new(); diff --git a/solitaire_engine/src/feedback_anim_plugin.rs b/solitaire_engine/src/feedback_anim_plugin.rs index b866811..c20cb1e 100644 --- a/solitaire_engine/src/feedback_anim_plugin.rs +++ b/solitaire_engine/src/feedback_anim_plugin.rs @@ -850,7 +850,7 @@ mod tests { fn shake_anim_skipped_under_reduce_motion() { use bevy::ecs::message::Messages; use solitaire_core::Tableau; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; use solitaire_data::Settings; let mut app = App::new(); @@ -904,7 +904,7 @@ mod tests { #[test] fn foundation_flourish_skipped_under_reduce_motion() { use bevy::ecs::message::Messages; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; use solitaire_data::Settings; let mut app = App::new(); diff --git a/solitaire_engine/src/game_plugin.rs b/solitaire_engine/src/game_plugin.rs index aaa919e..b760eba 100644 --- a/solitaire_engine/src/game_plugin.rs +++ b/solitaire_engine/src/game_plugin.rs @@ -14,8 +14,8 @@ use bevy::prelude::*; use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future}; use bevy::window::AppLifecycle; use solitaire_core::KlondikePile; -use solitaire_core::game_state::{DrawMode, GameMode, GameState}; -use solitaire_core::solver::{SolverConfig, SolverResult, try_solve}; +use solitaire_core::{DrawMode, game_state::{GameMode, GameState}}; +use solitaire_data::solver::{SolverConfig, SolverResult, try_solve}; #[allow(deprecated)] use solitaire_data::latest_replay_path; use solitaire_data::{ @@ -316,7 +316,7 @@ fn seed_from_system_time() -> u64 { } /// Walks forward from `initial_seed` (incrementing by 1 with wrapping -/// arithmetic) until the [`solitaire_core::solver`] returns a verdict +/// arithmetic) until the [`solitaire_data::solver`] returns a verdict /// the engine accepts as winnable, or until [`SOLVER_DEAL_RETRY_CAP`] /// attempts have elapsed. /// diff --git a/solitaire_engine/src/home_plugin.rs b/solitaire_engine/src/home_plugin.rs index 31177da..01bbfc4 100644 --- a/solitaire_engine/src/home_plugin.rs +++ b/solitaire_engine/src/home_plugin.rs @@ -16,7 +16,7 @@ use bevy::input::ButtonInput; use bevy::input::mouse::{MouseScrollUnit, MouseWheel}; use bevy::prelude::*; -use solitaire_core::game_state::{DifficultyLevel, DrawMode}; +use solitaire_core::{DrawMode, game_state::DifficultyLevel}; use solitaire_data::save_settings_to; use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL; diff --git a/solitaire_engine/src/hud_plugin.rs b/solitaire_engine/src/hud_plugin.rs index 7aaa485..b430d2e 100644 --- a/solitaire_engine/src/hud_plugin.rs +++ b/solitaire_engine/src/hud_plugin.rs @@ -10,7 +10,7 @@ use bevy::prelude::*; use bevy::window::WindowResized; use solitaire_core::{Foundation, KlondikePile, Tableau}; use solitaire_core::card::Suit; -use solitaire_core::game_state::{DrawMode, GameMode}; +use solitaire_core::{DrawMode, game_state::GameMode}; use crate::auto_complete_plugin::AutoCompleteState; #[cfg(not(target_arch = "wasm32"))] @@ -2726,7 +2726,7 @@ mod tests { use crate::game_plugin::GamePlugin; use crate::table_plugin::TablePlugin; use chrono::Local; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn headless_app() -> App { let mut app = App::new(); diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index faf78ca..d1272a0 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -52,7 +52,7 @@ use crate::settings_plugin::SettingsResource; use crate::time_attack_plugin::TimeAttackResource; use crate::touch_selection_plugin::TouchSelectionState; use crate::ui_theme::{MOTION_DRAG_REJECT_SECS, STATE_SUCCESS, STATE_WARNING}; -use solitaire_core::game_state::DrawMode; +use solitaire_core::DrawMode; /// System-set labels used to anchor external systems relative to the touch /// drag pipeline without duplicating the internal chain ordering. @@ -79,13 +79,13 @@ fn dragged_card_z(index: usize) -> f32 { /// Solver budgets used by the H-key hint system. /// -/// Wraps `solitaire_core::solver::SolverConfig` as a Bevy resource so +/// Wraps `solitaire_data::solver::SolverConfig` as a Bevy resource so /// tests can inject tighter budgets to exercise the heuristic-fallback /// path. Production initialises this to `SolverConfig::default()` (100k /// move / 200k state budgets, the same numbers the new-game retry loop /// uses). #[derive(Resource, Debug, Clone, Default)] -pub struct HintSolverConfig(pub solitaire_core::solver::SolverConfig); +pub struct HintSolverConfig(pub solitaire_data::solver::SolverConfig); /// Registers keyboard, mouse, and touch input systems. /// @@ -1789,7 +1789,7 @@ mod tests { use super::*; use crate::layout::compute_layout; use solitaire_core::{Foundation, Tableau}; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn clear_test_piles(game: &mut GameState) { game.set_test_stock_cards(Vec::new()); @@ -2029,7 +2029,7 @@ mod tests { #[test] fn find_draggable_draw_three_waste_top_card_hit_at_fanned_position() { use solitaire_core::card::{Card, Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameMode}; + use solitaire_core::{DrawMode, game_state::GameMode}; let mut game = GameState::new_with_mode(1, DrawMode::DrawThree, GameMode::Classic); // Three waste cards; top (id=202) is rightmost in the fan. game.set_test_waste_cards(vec![ diff --git a/solitaire_engine/src/pause_plugin.rs b/solitaire_engine/src/pause_plugin.rs index bd0492c..0d79e51 100644 --- a/solitaire_engine/src/pause_plugin.rs +++ b/solitaire_engine/src/pause_plugin.rs @@ -21,7 +21,7 @@ //! active opens the overlay as normal. use bevy::prelude::*; -use solitaire_core::game_state::DrawMode; +use solitaire_core::DrawMode; use solitaire_data::save_game_state_to; use crate::events::{ @@ -965,7 +965,7 @@ mod tests { /// Provides a fresh `GameStateResource` (not won) so the modal can /// open. `move_count` doesn't matter — the gate is just `!is_won`. fn forfeit_app() -> App { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; let mut app = App::new(); app.add_plugins(MinimalPlugins).add_plugins(PausePlugin); app.init_resource::>(); @@ -1020,7 +1020,7 @@ mod tests { /// hotkey was received but is currently a no-op. #[test] fn forfeit_request_emits_toast_and_skips_modal_when_game_is_won() { - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; let mut app = App::new(); app.add_plugins(MinimalPlugins).add_plugins(PausePlugin); app.init_resource::>(); diff --git a/solitaire_engine/src/pending_hint.rs b/solitaire_engine/src/pending_hint.rs index 7933004..35d5bc0 100644 --- a/solitaire_engine/src/pending_hint.rs +++ b/solitaire_engine/src/pending_hint.rs @@ -28,7 +28,7 @@ use bevy::prelude::*; use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future}; use solitaire_core::KlondikePile; use solitaire_core::game_state::GameState; -use solitaire_core::solver::{SolverConfig, SolverResult, try_solve_from_state}; +use solitaire_data::solver::{SolverConfig, SolverResult, try_solve_from_state}; use crate::card_plugin::CardEntity; use crate::events::{HintVisualEvent, InfoToastEvent, StateChangedEvent}; @@ -188,7 +188,7 @@ mod tests { use crate::input_plugin::HintSolverConfig; use solitaire_core::{Foundation, Tableau}; use solitaire_core::card::{Card, Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; /// Build a minimal Bevy app exercising only the polling system /// and the resources/messages it touches. diff --git a/solitaire_engine/src/play_by_seed_plugin.rs b/solitaire_engine/src/play_by_seed_plugin.rs index 6c1eadc..f9ac733 100644 --- a/solitaire_engine/src/play_by_seed_plugin.rs +++ b/solitaire_engine/src/play_by_seed_plugin.rs @@ -23,8 +23,8 @@ use bevy::input::ButtonInput; use bevy::prelude::*; use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future}; -use solitaire_core::game_state::DrawMode; -use solitaire_core::solver::{SolverConfig, SolverResult, try_solve}; +use solitaire_core::DrawMode; +use solitaire_data::solver::{SolverConfig, SolverResult, try_solve}; use crate::events::{NewGameRequestEvent, StartPlayBySeedRequestEvent}; use crate::font_plugin::FontResource; diff --git a/solitaire_engine/src/radial_menu.rs b/solitaire_engine/src/radial_menu.rs index a8817db..d6300a5 100644 --- a/solitaire_engine/src/radial_menu.rs +++ b/solitaire_engine/src/radial_menu.rs @@ -795,7 +795,7 @@ mod tests { use crate::layout::compute_layout; use bevy::ecs::message::Messages; use solitaire_core::card::{Card as CoreCard, Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; /// Build a minimal Bevy app wired with `RadialMenuPlugin` and the /// resources / messages it depends on. No window, no camera — the diff --git a/solitaire_engine/src/replay_overlay/tests.rs b/solitaire_engine/src/replay_overlay/tests.rs index d6fe187..cde2413 100644 --- a/solitaire_engine/src/replay_overlay/tests.rs +++ b/solitaire_engine/src/replay_overlay/tests.rs @@ -2,7 +2,7 @@ use super::*; use chrono::NaiveDate; use solitaire_core::{Foundation, KlondikePile, Tableau}; use solitaire_core::card::{Rank, Suit}; -use solitaire_core::game_state::{DrawMode, GameMode}; +use solitaire_core::{DrawMode, game_state::GameMode}; use solitaire_core::klondike_adapter::{SavedKlondikePile, SavedTableau}; use solitaire_data::{Replay, ReplayMove}; @@ -2314,7 +2314,7 @@ fn format_suit_glyph_all_suits() { fn format_foundations_row_empty_board() { let game = solitaire_core::game_state::GameState::new_with_mode( 42, - solitaire_core::game_state::DrawMode::DrawOne, + solitaire_core::DrawMode::DrawOne, solitaire_core::game_state::GameMode::Classic, ); assert_eq!(format_foundations_row(&game), "F: -- -- -- --"); @@ -2326,7 +2326,7 @@ fn format_foundations_row_empty_board() { fn format_stock_waste_row_initial_state() { let game = solitaire_core::game_state::GameState::new_with_mode( 42, - solitaire_core::game_state::DrawMode::DrawOne, + solitaire_core::DrawMode::DrawOne, solitaire_core::game_state::GameMode::Classic, ); let text = format_stock_waste_row(&game); diff --git a/solitaire_engine/src/replay_playback.rs b/solitaire_engine/src/replay_playback.rs index 8e044a7..ecca062 100644 --- a/solitaire_engine/src/replay_playback.rs +++ b/solitaire_engine/src/replay_playback.rs @@ -556,7 +556,7 @@ mod tests { use bevy::time::TimeUpdateStrategy; use chrono::NaiveDate; use solitaire_core::{KlondikePile, Tableau}; - use solitaire_core::game_state::{DrawMode, GameMode}; + use solitaire_core::{DrawMode, game_state::GameMode}; use solitaire_core::klondike_adapter::{SavedKlondikePile, SavedTableau}; use std::time::Duration; diff --git a/solitaire_engine/src/selection_plugin.rs b/solitaire_engine/src/selection_plugin.rs index cfea3f3..6073b54 100644 --- a/solitaire_engine/src/selection_plugin.rs +++ b/solitaire_engine/src/selection_plugin.rs @@ -980,7 +980,7 @@ mod tests { use bevy::ecs::message::Messages; use solitaire_core::card::{Card, Rank, Suit}; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; /// Build a minimal app with `SelectionPlugin` only — no GamePlugin, no /// AssetServer. The `MoveRequestEvent` / `StateChangedEvent` / diff --git a/solitaire_engine/src/settings_plugin.rs b/solitaire_engine/src/settings_plugin.rs index 09d5fb0..ff5a0b9 100644 --- a/solitaire_engine/src/settings_plugin.rs +++ b/solitaire_engine/src/settings_plugin.rs @@ -15,7 +15,7 @@ use bevy::input::mouse::{MouseScrollUnit, MouseWheel}; use bevy::prelude::*; use bevy::ui::{ComputedNode, UiGlobalTransform}; use bevy::window::{WindowMoved, WindowResized}; -use solitaire_core::game_state::DrawMode; +use solitaire_core::DrawMode; use solitaire_data::{ AnimSpeed, REPLAY_MOVE_INTERVAL_STEP_SECS, Settings, TIME_BONUS_MULTIPLIER_STEP, TOOLTIP_DELAY_STEP_SECS, WindowGeometry, load_settings_from, save_settings_to, settings::Theme, @@ -241,7 +241,7 @@ enum SettingsButton { ToggleTouchInputMode, /// Toggle the [`Settings::winnable_deals_only`] flag. When on, new /// random Classic-mode deals are filtered through - /// [`solitaire_core::solver::try_solve`] until one is provably + /// [`solitaire_data::solver::try_solve`] until one is provably /// winnable (or the retry cap is hit). Off by default. ToggleWinnableDealsOnly, /// Toggle the inverse of [`Settings::disable_smart_default_size`]. diff --git a/solitaire_engine/src/stats_plugin.rs b/solitaire_engine/src/stats_plugin.rs index 60607fd..dd6aed5 100644 --- a/solitaire_engine/src/stats_plugin.rs +++ b/solitaire_engine/src/stats_plugin.rs @@ -1327,7 +1327,7 @@ mod tests { app.world_mut() .resource_mut::() .0 - .draw_mode = solitaire_core::game_state::DrawMode::DrawThree; + .draw_mode = solitaire_core::DrawMode::DrawThree; app.world_mut().write_message(GameWonEvent { score: 500, @@ -1952,7 +1952,7 @@ mod tests { let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 8).expect("valid date"); let mut r = solitaire_data::Replay::new( 1, - solitaire_core::game_state::DrawMode::DrawOne, + solitaire_core::DrawMode::DrawOne, solitaire_core::game_state::GameMode::Classic, time_seconds, 0, diff --git a/solitaire_engine/src/sync_plugin.rs b/solitaire_engine/src/sync_plugin.rs index 371fd87..c978e96 100644 --- a/solitaire_engine/src/sync_plugin.rs +++ b/solitaire_engine/src/sync_plugin.rs @@ -604,7 +604,7 @@ mod tests { /// would silently drop the link. #[test] fn upload_result_writes_share_url_into_replay_and_persists() { - use solitaire_core::game_state::{DrawMode, GameMode}; + use solitaire_core::{DrawMode, game_state::GameMode}; use solitaire_data::{ Replay, ReplayHistory, load_replay_history_from, save_replay_history_to, }; diff --git a/solitaire_engine/src/time_attack_plugin.rs b/solitaire_engine/src/time_attack_plugin.rs index 1f181ab..68edc66 100644 --- a/solitaire_engine/src/time_attack_plugin.rs +++ b/solitaire_engine/src/time_attack_plugin.rs @@ -299,7 +299,7 @@ mod tests { use crate::game_plugin::GamePlugin; use crate::progress_plugin::ProgressPlugin; use crate::table_plugin::TablePlugin; - use solitaire_core::game_state::{DrawMode, GameState}; + use solitaire_core::{DrawMode, game_state::GameState}; fn headless_app() -> App { let mut app = App::new(); diff --git a/solitaire_engine/src/win_summary_plugin.rs b/solitaire_engine/src/win_summary_plugin.rs index 647fe8d..d62ba08 100644 --- a/solitaire_engine/src/win_summary_plugin.rs +++ b/solitaire_engine/src/win_summary_plugin.rs @@ -1210,7 +1210,7 @@ mod tests { .insert_resource(StatsResource(StatsSnapshot::default())) .insert_resource(GameStateResource(GameState::new( 0, - solitaire_core::game_state::DrawMode::DrawOne, + solitaire_core::DrawMode::DrawOne, ))) .insert_resource(ProgressResource(PlayerProgress::default())); app.update(); @@ -1539,7 +1539,7 @@ mod tests { .challenge_index = 4; // Switch game mode to Challenge. { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; app.world_mut().resource_mut::().0 = GameState::new_with_mode(1, DrawMode::DrawOne, GameMode::Challenge); } @@ -1585,7 +1585,7 @@ mod tests { /// mode-multiplier rows. #[test] fn cache_win_data_captures_undo_count_and_mode() { - use solitaire_core::game_state::DrawMode; + use solitaire_core::DrawMode; let mut app = make_app(); // Set up a Zen-mode game with 2 undos used. diff --git a/solitaire_wasm/src/lib.rs b/solitaire_wasm/src/lib.rs index e6c3d47..7b8b113 100644 --- a/solitaire_wasm/src/lib.rs +++ b/solitaire_wasm/src/lib.rs @@ -23,7 +23,7 @@ use solitaire_core::{Foundation, KlondikePile, Tableau}; use serde::{Deserialize, Serialize}; use solitaire_core::card::Suit; use solitaire_core::error::MoveError; -use solitaire_core::game_state::{DrawMode, GameMode, GameState}; +use solitaire_core::{DrawMode, game_state::{GameMode, GameState}}; use solitaire_core::klondike_adapter::{ SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, tableau_from_index, };