refactor(core): move solver to solitaire_data, DrawMode to klondike_adapter, remove pile/solver/schema_version
- Delete solitaire_core::solver — moved wholesale to solitaire_data::solver (re-exported at crate root) - Delete solitaire_core::pile — no external users - Move DrawMode from game_state to klondike_adapter; re-export as solitaire_core::DrawMode - Remove schema_version field from GameState (redundant — deserializer stamps it from the constant) - Update all callers across solitaire_data, solitaire_engine, solitaire_assetgen, solitaire_wasm Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Generated
+2
@@ -7336,11 +7336,13 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
|
"card_game",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"keyring-core",
|
"keyring-core",
|
||||||
|
"klondike",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
//! --per-tier Seeds to emit per tier (default 40)
|
//! --per-tier Seeds to emit per tier (default 40)
|
||||||
//! --help Print this message
|
//! --help Print this message
|
||||||
|
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||||
|
|
||||||
// Budget boundaries defining each tier. A seed belongs to the lowest tier
|
// Budget boundaries defining each tier. A seed belongs to the lowest tier
|
||||||
// whose budget proves it Winnable.
|
// whose budget proves it Winnable.
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
//! --count Number of Winnable seeds to emit (default 75)
|
//! --count Number of Winnable seeds to emit (default 75)
|
||||||
//! --help Print this message
|
//! --help Print this message
|
||||||
|
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = std::env::args().skip(1).peekable();
|
let mut args = std::env::args().skip(1).peekable();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::card::Card;
|
use crate::card::Card;
|
||||||
use crate::error::MoveError;
|
use crate::error::MoveError;
|
||||||
use crate::klondike_adapter::{
|
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,
|
foundation_from_slot as adapter_foundation_from_slot,
|
||||||
skip_cards_from_count as adapter_skip_cards_from_count,
|
skip_cards_from_count as adapter_skip_cards_from_count,
|
||||||
tableau_from_index as adapter_tableau_from_index,
|
tableau_from_index as adapter_tableau_from_index,
|
||||||
@@ -33,15 +34,6 @@ fn schema_v1() -> u32 {
|
|||||||
1
|
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
|
/// Difficulty tier for `GameMode::Difficulty`. Controls which pre-verified seed
|
||||||
/// catalog is drawn from. `Random` skips verification entirely and uses a
|
/// catalog is drawn from. `Random` skips verification entirely and uses a
|
||||||
/// system-time seed — deals may or may not be winnable.
|
/// 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
|
/// When `true`, the player may move the top card of a foundation pile back
|
||||||
/// onto a compatible tableau column.
|
/// onto a compatible tableau column.
|
||||||
pub take_from_foundation: bool,
|
pub take_from_foundation: bool,
|
||||||
/// Save-file schema version.
|
|
||||||
pub schema_version: u32,
|
|
||||||
pub(crate) session: Session<Klondike>,
|
pub(crate) session: Session<Klondike>,
|
||||||
/// Score recorded immediately before each instruction was applied.
|
/// Score recorded immediately before each instruction was applied.
|
||||||
/// Parallel to `session.history()` during live play; used by `undo()` to
|
/// 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.undo_count == other.undo_count
|
||||||
&& self.recycle_count == other.recycle_count
|
&& self.recycle_count == other.recycle_count
|
||||||
&& self.take_from_foundation == other.take_from_foundation
|
&& self.take_from_foundation == other.take_from_foundation
|
||||||
&& self.schema_version == other.schema_version
|
|
||||||
&& self.stock_cards() == other.stock_cards()
|
&& self.stock_cards() == other.stock_cards()
|
||||||
&& self.waste_cards() == other.waste_cards()
|
&& self.waste_cards() == other.waste_cards()
|
||||||
&& (0..4_u8)
|
&& (0..4_u8)
|
||||||
@@ -243,7 +232,7 @@ impl Serialize for GameState {
|
|||||||
undo_count: self.undo_count,
|
undo_count: self.undo_count,
|
||||||
recycle_count: self.recycle_count,
|
recycle_count: self.recycle_count,
|
||||||
take_from_foundation: self.take_from_foundation,
|
take_from_foundation: self.take_from_foundation,
|
||||||
schema_version: self.schema_version,
|
schema_version: GAME_STATE_SCHEMA_VERSION,
|
||||||
saved_moves: self.saved_moves(),
|
saved_moves: self.saved_moves(),
|
||||||
}
|
}
|
||||||
.serialize(serializer)
|
.serialize(serializer)
|
||||||
@@ -279,9 +268,6 @@ impl<'de> Deserialize<'de> for GameState {
|
|||||||
// due to the pre-Phase-3 undo drift bug.
|
// due to the pre-Phase-3 undo drift bug.
|
||||||
recycle_count: 0,
|
recycle_count: 0,
|
||||||
take_from_foundation: persisted.take_from_foundation,
|
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),
|
session: Self::new_session(persisted.seed, persisted.draw_mode),
|
||||||
// score_history cannot be faithfully rebuilt from the instruction
|
// score_history cannot be faithfully rebuilt from the instruction
|
||||||
// history because live-play undo penalties are not recorded in
|
// history because live-play undo penalties are not recorded in
|
||||||
@@ -358,7 +344,6 @@ impl GameState {
|
|||||||
undo_count: 0,
|
undo_count: 0,
|
||||||
recycle_count: 0,
|
recycle_count: 0,
|
||||||
take_from_foundation: true,
|
take_from_foundation: true,
|
||||||
schema_version: GAME_STATE_SCHEMA_VERSION,
|
|
||||||
session: Self::new_session(seed, draw_mode),
|
session: Self::new_session(seed, draw_mode),
|
||||||
score_history: Vec::new(),
|
score_history: Vec::new(),
|
||||||
is_recycle_history: Vec::new(),
|
is_recycle_history: Vec::new(),
|
||||||
|
|||||||
@@ -18,7 +18,16 @@ use klondike::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::card;
|
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.
|
/// Bridges `solitaire_core` game config and scoring to the upstream `klondike` crate.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ pub mod card;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod game_state;
|
pub mod game_state;
|
||||||
pub mod klondike_adapter;
|
pub mod klondike_adapter;
|
||||||
pub mod pile;
|
|
||||||
pub mod solver;
|
|
||||||
|
|
||||||
// Re-export the upstream types that cross the solitaire_core API boundary so
|
// 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
|
// 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.
|
// not appear in any public method signature.
|
||||||
pub use card_game::Session;
|
pub use card_game::Session;
|
||||||
pub use klondike::{Foundation, Klondike, KlondikePile, Tableau};
|
pub use klondike::{Foundation, Klondike, KlondikePile, Tableau};
|
||||||
|
pub use klondike_adapter::DrawMode;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod proptest_tests;
|
mod proptest_tests;
|
||||||
|
|||||||
@@ -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<Card>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Suit> {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,8 @@ use card_game::Game;
|
|||||||
use klondike::{Foundation, KlondikePile, KlondikeInstruction, SkipCards, Tableau};
|
use klondike::{Foundation, KlondikePile, KlondikeInstruction, SkipCards, Tableau};
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
use crate::game_state::{DrawMode, GameState};
|
use crate::game_state::GameState;
|
||||||
|
use crate::klondike_adapter::DrawMode;
|
||||||
use crate::klondike_adapter::{
|
use crate::klondike_adapter::{
|
||||||
InvalidSavedInstruction, SavedDstFoundation, SavedDstTableau, SavedFoundation,
|
InvalidSavedInstruction, SavedDstFoundation, SavedDstTableau, SavedFoundation,
|
||||||
SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, SavedSkipCards, SavedTableau,
|
SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, SavedSkipCards, SavedTableau,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ edition.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
solitaire_core = { workspace = true }
|
solitaire_core = { workspace = true }
|
||||||
solitaire_sync = { workspace = true }
|
solitaire_sync = { workspace = true }
|
||||||
|
klondike = { workspace = true }
|
||||||
|
card_game = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ impl SyncProvider for Box<dyn SyncProvider + Send + Sync> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 mod stats;
|
||||||
pub use stats::{StatsExt, StatsSnapshot};
|
pub use stats::{StatsExt, StatsSnapshot};
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solitaire_core::game_state::{DrawMode, GameMode};
|
use solitaire_core::{DrawMode, game_state::GameMode};
|
||||||
use solitaire_core::klondike_adapter::SavedKlondikePile;
|
use solitaire_core::klondike_adapter::SavedKlondikePile;
|
||||||
|
|
||||||
const LATEST_REPLAY_FILE_NAME: &str = "latest_replay.json";
|
const LATEST_REPLAY_FILE_NAME: &str = "latest_replay.json";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::io;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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";
|
const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ pub struct Settings {
|
|||||||
#[serde(default = "default_time_bonus_multiplier")]
|
#[serde(default = "default_time_bonus_multiplier")]
|
||||||
pub time_bonus_multiplier: f32,
|
pub time_bonus_multiplier: f32,
|
||||||
/// When `true`, the engine rejects new-game deals the
|
/// 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
|
/// fresh seeds up to [`SOLVER_DEAL_RETRY_CAP`] attempts before
|
||||||
/// giving up and using the last tried seed. Off by default —
|
/// giving up and using the last tried seed. Off by default —
|
||||||
/// the solver adds a few hundred milliseconds of latency on the
|
/// the solver adds a few hundred milliseconds of latency on the
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
use card_game::{Session, SessionConfig, SolveError, StateSnapshot};
|
use card_game::{Session, SessionConfig, SolveError, StateSnapshot};
|
||||||
use klondike::{Klondike, KlondikeInstruction, KlondikePile, KlondikePileStack};
|
use klondike::{Klondike, KlondikeInstruction, KlondikePile, KlondikePileStack};
|
||||||
|
use solitaire_core::DrawMode;
|
||||||
use crate::game_state::{DrawMode, GameState};
|
use solitaire_core::game_state::GameState;
|
||||||
use crate::klondike_adapter::KlondikeAdapter;
|
use solitaire_core::klondike_adapter::KlondikeAdapter;
|
||||||
|
|
||||||
/// Verdict returned by [`try_solve`].
|
/// Verdict returned by [`try_solve`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -160,7 +160,8 @@ fn snapshot_to_solver_move(snapshot: &StateSnapshot<Klondike>) -> Option<SolverM
|
|||||||
KlondikeInstruction::DstTableau(dst_tableau) => {
|
KlondikeInstruction::DstTableau(dst_tableau) => {
|
||||||
let (source, count) = match dst_tableau.src {
|
let (source, count) = match dst_tableau.src {
|
||||||
KlondikePileStack::Tableau(tableau_stack) => {
|
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)?;
|
let count = face_up_count.checked_sub(tableau_stack.skip_cards as usize)?;
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -242,10 +243,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn budget_is_passed_through_not_clamped() {
|
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 easy = SolverConfig { move_budget: 1_000, state_budget: 1_000 };
|
||||||
let medium = SolverConfig { move_budget: 5_000, state_budget: 5_000 };
|
let medium = SolverConfig { move_budget: 5_000, state_budget: 5_000 };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -260,12 +257,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn budget_above_five_thousand_is_not_clamped() {
|
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 below_cap = SolverConfig { move_budget: 5_000, state_budget: 5_000 };
|
||||||
let above_cap = SolverConfig { move_budget: 50_000, state_budget: 50_000 };
|
let above_cap = SolverConfig { move_budget: 50_000, state_budget: 50_000 };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
//! `update_on_win` method that depends on [`DrawMode`] from `solitaire_core`.
|
//! `update_on_win` method that depends on [`DrawMode`] from `solitaire_core`.
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use solitaire_core::game_state::{DrawMode, GameMode};
|
use solitaire_core::{DrawMode, game_state::GameMode};
|
||||||
|
|
||||||
pub use solitaire_sync::StatsSnapshot;
|
pub use solitaire_sync::StatsSnapshot;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::io;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solitaire_core::game_state::{GAME_STATE_SCHEMA_VERSION, GameState};
|
use solitaire_core::game_state::GameState;
|
||||||
|
|
||||||
use crate::stats::StatsSnapshot;
|
use crate::stats::StatsSnapshot;
|
||||||
|
|
||||||
@@ -85,9 +85,6 @@ pub fn game_state_file_path() -> Option<PathBuf> {
|
|||||||
pub fn load_game_state_from(path: &Path) -> Option<GameState> {
|
pub fn load_game_state_from(path: &Path) -> Option<GameState> {
|
||||||
let data = fs::read(path).ok()?;
|
let data = fs::read(path).ok()?;
|
||||||
let gs: GameState = serde_json::from_slice(&data).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) }
|
if gs.is_won { None } else { Some(gs) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +279,7 @@ fn cleanup_tmp_files_in(dir: &Path) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::stats::{StatsExt, StatsSnapshot};
|
use crate::stats::{StatsExt, StatsSnapshot};
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
fn tmp_path(name: &str) -> PathBuf {
|
fn tmp_path(name: &str) -> PathBuf {
|
||||||
@@ -380,7 +377,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn game_state_round_trip() {
|
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 path = gs_path("round_trip");
|
||||||
let _ = fs::remove_file(&path);
|
let _ = fs::remove_file(&path);
|
||||||
|
|
||||||
@@ -409,7 +406,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save_game_state_skips_won_games() {
|
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 path = gs_path("won_skip");
|
||||||
let _ = fs::remove_file(&path);
|
let _ = fs::remove_file(&path);
|
||||||
|
|
||||||
@@ -424,7 +421,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_game_state_removes_file() {
|
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 path = gs_path("delete");
|
||||||
let gs = GameState::new(1, DrawMode::DrawOne);
|
let gs = GameState::new(1, DrawMode::DrawOne);
|
||||||
save_game_state_to(&path, &gs).expect("save");
|
save_game_state_to(&path, &gs).expect("save");
|
||||||
@@ -442,7 +439,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save_game_state_is_atomic() {
|
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 path = gs_path("atomic");
|
||||||
let gs = GameState::new(55, DrawMode::DrawThree);
|
let gs = GameState::new(55, DrawMode::DrawThree);
|
||||||
save_game_state_to(&path, &gs).expect("save");
|
save_game_state_to(&path, &gs).expect("save");
|
||||||
@@ -510,7 +507,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn game_state_v4_mid_game_round_trip() {
|
fn game_state_v4_mid_game_round_trip() {
|
||||||
use solitaire_core::KlondikePile;
|
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 path = gs_path("v4_mid_game");
|
||||||
let _ = fs::remove_file(&path);
|
let _ = fs::remove_file(&path);
|
||||||
@@ -557,7 +554,6 @@ mod tests {
|
|||||||
let loaded = load_game_state_from(&path)
|
let loaded = load_game_state_from(&path)
|
||||||
.expect("a valid in-progress game must load without error");
|
.expect("a valid in-progress game must load without error");
|
||||||
|
|
||||||
assert_eq!(loaded.schema_version, GAME_STATE_SCHEMA_VERSION);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded, gs,
|
loaded, gs,
|
||||||
"all pile layouts and counters must be identical after schema-v4 round-trip",
|
"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.
|
/// u8-to-named conversion for `DstFoundation` / `DstTableau` indices.
|
||||||
#[test]
|
#[test]
|
||||||
fn game_state_v3_migrates_to_v4() {
|
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 path = gs_path("v3_migrate");
|
||||||
let _ = fs::remove_file(&path);
|
let _ = fs::remove_file(&path);
|
||||||
@@ -599,12 +595,6 @@ mod tests {
|
|||||||
let loaded = load_game_state_from(&path)
|
let loaded = load_game_state_from(&path)
|
||||||
.expect("schema v3 must be accepted and migrated to v4");
|
.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.
|
// The loaded game should match a fresh game that had one draw applied.
|
||||||
let mut expected = GameState::new(42, DrawMode::DrawOne);
|
let mut expected = GameState::new(42, DrawMode::DrawOne);
|
||||||
expected.draw().expect("draw must succeed on a fresh game");
|
expected.draw().expect("draw must succeed on a fresh game");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! increments matching counters in `PlayerProgress::weekly_goal_progress`.
|
//! increments matching counters in `PlayerProgress::weekly_goal_progress`.
|
||||||
|
|
||||||
use chrono::{Datelike, NaiveDate};
|
use chrono::{Datelike, NaiveDate};
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
|
|
||||||
/// XP awarded each time a weekly goal is just completed.
|
/// XP awarded each time a weekly goal is just completed.
|
||||||
pub const WEEKLY_GOAL_XP: u64 = 75;
|
pub const WEEKLY_GOAL_XP: u64 = 75;
|
||||||
|
|||||||
@@ -819,7 +819,7 @@ mod tests {
|
|||||||
app.world_mut()
|
app.world_mut()
|
||||||
.resource_mut::<GameStateResource>()
|
.resource_mut::<GameStateResource>()
|
||||||
.0
|
.0
|
||||||
.draw_mode = solitaire_core::game_state::DrawMode::DrawThree;
|
.draw_mode = solitaire_core::DrawMode::DrawThree;
|
||||||
|
|
||||||
app.world_mut().write_message(GameWonEvent {
|
app.world_mut().write_message(GameWonEvent {
|
||||||
score: 500,
|
score: 500,
|
||||||
@@ -868,7 +868,7 @@ mod tests {
|
|||||||
app.world_mut()
|
app.world_mut()
|
||||||
.resource_mut::<GameStateResource>()
|
.resource_mut::<GameStateResource>()
|
||||||
.0
|
.0
|
||||||
.draw_mode = solitaire_core::game_state::DrawMode::DrawThree;
|
.draw_mode = solitaire_core::DrawMode::DrawThree;
|
||||||
|
|
||||||
app.world_mut().write_message(GameWonEvent {
|
app.world_mut().write_message(GameWonEvent {
|
||||||
score: 500,
|
score: 500,
|
||||||
@@ -1393,7 +1393,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::replay_playback::ReplayPlaybackState;
|
use crate::replay_playback::ReplayPlaybackState;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use solitaire_core::game_state::{DrawMode, GameMode};
|
use solitaire_core::{DrawMode, game_state::GameMode};
|
||||||
use solitaire_data::{Replay, ReplayMove};
|
use solitaire_data::{Replay, ReplayMove};
|
||||||
|
|
||||||
/// Headless app variant that injects a default `ReplayPlaybackState`
|
/// Headless app variant that injects a default `ReplayPlaybackState`
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ mod tests {
|
|||||||
use crate::table_plugin::TablePlugin;
|
use crate::table_plugin::TablePlugin;
|
||||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||||
use solitaire_core::card::{Rank, Suit};
|
use solitaire_core::card::{Rank, Suit};
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
|
|
||||||
fn headless_app() -> App {
|
fn headless_app() -> App {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use bevy::sprite::Anchor;
|
|||||||
use bevy::window::WindowResized;
|
use bevy::window::WindowResized;
|
||||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||||
use solitaire_core::card::{Card, Rank, Suit};
|
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::animation_plugin::{CARD_ANIM_Z_LIFT, CardAnim, EffectiveSlideDuration};
|
||||||
use crate::card_animation::CardAnimation;
|
use crate::card_animation::CardAnimation;
|
||||||
@@ -2526,7 +2526,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn card_positions_includes_all_52_cards_at_game_start() {
|
fn card_positions_includes_all_52_cards_at_game_start() {
|
||||||
// At game start waste is empty, so all 52 cards are across stock + tableau.
|
// 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 layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||||
let positions = card_positions(&g, &layout);
|
let positions = card_positions(&g, &layout);
|
||||||
assert_eq!(positions.len(), 52);
|
assert_eq!(positions.len(), 52);
|
||||||
@@ -2534,7 +2534,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_draw_one_only_renders_top_card() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawOne);
|
||||||
// Draw 3 cards so the waste pile has 3 cards.
|
// Draw 3 cards so the waste pile has 3 cards.
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
@@ -2572,7 +2572,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_draw_three_renders_up_to_three_fanned_cards() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawThree);
|
||||||
// 5 draw() calls in Draw-Three mode accumulates multiple waste cards.
|
// 5 draw() calls in Draw-Three mode accumulates multiple waste cards.
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
@@ -2625,7 +2625,7 @@ mod tests {
|
|||||||
// Regression: slot.saturating_sub(1) always hid slot-0 even when the
|
// 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
|
// pile was too small to have a buffer card, collapsing 2 visible cards
|
||||||
// onto x=0 instead of fanning them.
|
// 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);
|
let mut g = GameState::new(42, DrawMode::DrawThree);
|
||||||
// Draw exactly once — in Draw-Three mode with a full stock this gives
|
// Draw exactly once — in Draw-Three mode with a full stock this gives
|
||||||
// 3 waste cards (still ≤ visible=3, so no hidden buffer needed).
|
// 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.
|
/// top card so that hiding it (`Visibility::Hidden`) leaves no visible gap.
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_draw_one_buffer_card_at_same_xy_as_top() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawOne);
|
||||||
// Draw 3 times so the waste pile has 3 cards and the buffer exists.
|
// Draw 3 times so the waste pile has 3 cards and the buffer exists.
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
@@ -2698,7 +2698,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn card_positions_tableau_cards_are_fanned_downward() {
|
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 layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||||
let positions = card_positions(&g, &layout);
|
let positions = card_positions(&g, &layout);
|
||||||
|
|
||||||
@@ -3103,7 +3103,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn facedown_cards_use_tighter_fan_than_uniform_faceup_fan() {
|
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 layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||||
let positions = card_positions(&g, &layout);
|
let positions = card_positions(&g, &layout);
|
||||||
|
|
||||||
@@ -3552,7 +3552,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stock_card_count_helper_reads_zero_for_empty_stock() {
|
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();
|
let mut g_empty_stock = g.clone();
|
||||||
g_empty_stock.set_test_stock_cards(Vec::new());
|
g_empty_stock.set_test_stock_cards(Vec::new());
|
||||||
assert_eq!(stock_card_count(&g_empty_stock), 0);
|
assert_eq!(stock_card_count(&g_empty_stock), 0);
|
||||||
@@ -3837,7 +3837,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_pile_cards_have_strictly_increasing_z() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawThree);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let _ = g.draw();
|
let _ = g.draw();
|
||||||
@@ -3881,7 +3881,7 @@ mod tests {
|
|||||||
/// offsets or flips the fan direction is caught immediately.
|
/// offsets or flips the fan direction is caught immediately.
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_cards_do_not_overlap_stock_column_on_portrait() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawThree);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let _ = g.draw();
|
let _ = g.draw();
|
||||||
@@ -3917,7 +3917,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn waste_pile_draw_one_cards_have_distinct_z() {
|
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);
|
let mut g = GameState::new(42, DrawMode::DrawOne);
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
let _ = g.draw();
|
let _ = g.draw();
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ mod tests {
|
|||||||
use crate::game_plugin::GamePlugin;
|
use crate::game_plugin::GamePlugin;
|
||||||
use crate::progress_plugin::ProgressPlugin;
|
use crate::progress_plugin::ProgressPlugin;
|
||||||
use crate::table_plugin::TablePlugin;
|
use crate::table_plugin::TablePlugin;
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
|
|
||||||
fn headless_app() -> App {
|
fn headless_app() -> App {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon};
|
use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon};
|
||||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
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::card_plugin::RightClickHighlight;
|
||||||
use crate::layout::{Layout, LayoutResource};
|
use crate::layout::{Layout, LayoutResource};
|
||||||
@@ -562,7 +562,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cursor_over_draggable_returns_false_for_empty_game() {
|
fn cursor_over_draggable_returns_false_for_empty_game() {
|
||||||
use crate::layout::compute_layout;
|
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 game = GameState::new(42, DrawMode::DrawOne);
|
||||||
let layout = compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
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 crate::layout::compute_layout;
|
||||||
use solitaire_core::card::{Card, Rank, Suit};
|
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
|
/// Builds an `App` with `MinimalPlugins` and the overlay system
|
||||||
/// registered, plus the resources the system needs. Callers
|
/// registered, plus the resources the system needs. Callers
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ mod tests {
|
|||||||
use crate::progress_plugin::ProgressPlugin;
|
use crate::progress_plugin::ProgressPlugin;
|
||||||
use crate::table_plugin::TablePlugin;
|
use crate::table_plugin::TablePlugin;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
|
|
||||||
fn headless_app() -> App {
|
fn headless_app() -> App {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -850,7 +850,7 @@ mod tests {
|
|||||||
fn shake_anim_skipped_under_reduce_motion() {
|
fn shake_anim_skipped_under_reduce_motion() {
|
||||||
use bevy::ecs::message::Messages;
|
use bevy::ecs::message::Messages;
|
||||||
use solitaire_core::Tableau;
|
use solitaire_core::Tableau;
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
use solitaire_data::Settings;
|
use solitaire_data::Settings;
|
||||||
|
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
@@ -904,7 +904,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn foundation_flourish_skipped_under_reduce_motion() {
|
fn foundation_flourish_skipped_under_reduce_motion() {
|
||||||
use bevy::ecs::message::Messages;
|
use bevy::ecs::message::Messages;
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
use solitaire_data::Settings;
|
use solitaire_data::Settings;
|
||||||
|
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use bevy::prelude::*;
|
|||||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||||
use bevy::window::AppLifecycle;
|
use bevy::window::AppLifecycle;
|
||||||
use solitaire_core::KlondikePile;
|
use solitaire_core::KlondikePile;
|
||||||
use solitaire_core::game_state::{DrawMode, GameMode, GameState};
|
use solitaire_core::{DrawMode, game_state::{GameMode, GameState}};
|
||||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use solitaire_data::latest_replay_path;
|
use solitaire_data::latest_replay_path;
|
||||||
use solitaire_data::{
|
use solitaire_data::{
|
||||||
@@ -316,7 +316,7 @@ fn seed_from_system_time() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Walks forward from `initial_seed` (incrementing by 1 with wrapping
|
/// 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`]
|
/// the engine accepts as winnable, or until [`SOLVER_DEAL_RETRY_CAP`]
|
||||||
/// attempts have elapsed.
|
/// attempts have elapsed.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
use bevy::input::ButtonInput;
|
use bevy::input::ButtonInput;
|
||||||
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use solitaire_core::game_state::{DifficultyLevel, DrawMode};
|
use solitaire_core::{DrawMode, game_state::DifficultyLevel};
|
||||||
use solitaire_data::save_settings_to;
|
use solitaire_data::save_settings_to;
|
||||||
|
|
||||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use bevy::prelude::*;
|
|||||||
use bevy::window::WindowResized;
|
use bevy::window::WindowResized;
|
||||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||||
use solitaire_core::card::Suit;
|
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;
|
use crate::auto_complete_plugin::AutoCompleteState;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -2726,7 +2726,7 @@ mod tests {
|
|||||||
use crate::game_plugin::GamePlugin;
|
use crate::game_plugin::GamePlugin;
|
||||||
use crate::table_plugin::TablePlugin;
|
use crate::table_plugin::TablePlugin;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
|
|
||||||
fn headless_app() -> App {
|
fn headless_app() -> App {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ use crate::settings_plugin::SettingsResource;
|
|||||||
use crate::time_attack_plugin::TimeAttackResource;
|
use crate::time_attack_plugin::TimeAttackResource;
|
||||||
use crate::touch_selection_plugin::TouchSelectionState;
|
use crate::touch_selection_plugin::TouchSelectionState;
|
||||||
use crate::ui_theme::{MOTION_DRAG_REJECT_SECS, STATE_SUCCESS, STATE_WARNING};
|
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
|
/// System-set labels used to anchor external systems relative to the touch
|
||||||
/// drag pipeline without duplicating the internal chain ordering.
|
/// 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.
|
/// 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
|
/// tests can inject tighter budgets to exercise the heuristic-fallback
|
||||||
/// path. Production initialises this to `SolverConfig::default()` (100k
|
/// path. Production initialises this to `SolverConfig::default()` (100k
|
||||||
/// move / 200k state budgets, the same numbers the new-game retry loop
|
/// move / 200k state budgets, the same numbers the new-game retry loop
|
||||||
/// uses).
|
/// uses).
|
||||||
#[derive(Resource, Debug, Clone, Default)]
|
#[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.
|
/// Registers keyboard, mouse, and touch input systems.
|
||||||
///
|
///
|
||||||
@@ -1789,7 +1789,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::compute_layout;
|
use crate::layout::compute_layout;
|
||||||
use solitaire_core::{Foundation, Tableau};
|
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) {
|
fn clear_test_piles(game: &mut GameState) {
|
||||||
game.set_test_stock_cards(Vec::new());
|
game.set_test_stock_cards(Vec::new());
|
||||||
@@ -2029,7 +2029,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn find_draggable_draw_three_waste_top_card_hit_at_fanned_position() {
|
fn find_draggable_draw_three_waste_top_card_hit_at_fanned_position() {
|
||||||
use solitaire_core::card::{Card, Rank, Suit};
|
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);
|
let mut game = GameState::new_with_mode(1, DrawMode::DrawThree, GameMode::Classic);
|
||||||
// Three waste cards; top (id=202) is rightmost in the fan.
|
// Three waste cards; top (id=202) is rightmost in the fan.
|
||||||
game.set_test_waste_cards(vec![
|
game.set_test_waste_cards(vec![
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
//! active opens the overlay as normal.
|
//! active opens the overlay as normal.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use solitaire_data::save_game_state_to;
|
use solitaire_data::save_game_state_to;
|
||||||
|
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
@@ -965,7 +965,7 @@ mod tests {
|
|||||||
/// Provides a fresh `GameStateResource` (not won) so the modal can
|
/// Provides a fresh `GameStateResource` (not won) so the modal can
|
||||||
/// open. `move_count` doesn't matter — the gate is just `!is_won`.
|
/// open. `move_count` doesn't matter — the gate is just `!is_won`.
|
||||||
fn forfeit_app() -> App {
|
fn forfeit_app() -> App {
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
app.add_plugins(MinimalPlugins).add_plugins(PausePlugin);
|
app.add_plugins(MinimalPlugins).add_plugins(PausePlugin);
|
||||||
app.init_resource::<ButtonInput<KeyCode>>();
|
app.init_resource::<ButtonInput<KeyCode>>();
|
||||||
@@ -1020,7 +1020,7 @@ mod tests {
|
|||||||
/// hotkey was received but is currently a no-op.
|
/// hotkey was received but is currently a no-op.
|
||||||
#[test]
|
#[test]
|
||||||
fn forfeit_request_emits_toast_and_skips_modal_when_game_is_won() {
|
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();
|
let mut app = App::new();
|
||||||
app.add_plugins(MinimalPlugins).add_plugins(PausePlugin);
|
app.add_plugins(MinimalPlugins).add_plugins(PausePlugin);
|
||||||
app.init_resource::<ButtonInput<KeyCode>>();
|
app.init_resource::<ButtonInput<KeyCode>>();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use bevy::prelude::*;
|
|||||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||||
use solitaire_core::KlondikePile;
|
use solitaire_core::KlondikePile;
|
||||||
use solitaire_core::game_state::GameState;
|
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::card_plugin::CardEntity;
|
||||||
use crate::events::{HintVisualEvent, InfoToastEvent, StateChangedEvent};
|
use crate::events::{HintVisualEvent, InfoToastEvent, StateChangedEvent};
|
||||||
@@ -188,7 +188,7 @@ mod tests {
|
|||||||
use crate::input_plugin::HintSolverConfig;
|
use crate::input_plugin::HintSolverConfig;
|
||||||
use solitaire_core::{Foundation, Tableau};
|
use solitaire_core::{Foundation, Tableau};
|
||||||
use solitaire_core::card::{Card, Rank, Suit};
|
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
|
/// Build a minimal Bevy app exercising only the polling system
|
||||||
/// and the resources/messages it touches.
|
/// and the resources/messages it touches.
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
use bevy::input::ButtonInput;
|
use bevy::input::ButtonInput;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
use solitaire_data::solver::{SolverConfig, SolverResult, try_solve};
|
||||||
|
|
||||||
use crate::events::{NewGameRequestEvent, StartPlayBySeedRequestEvent};
|
use crate::events::{NewGameRequestEvent, StartPlayBySeedRequestEvent};
|
||||||
use crate::font_plugin::FontResource;
|
use crate::font_plugin::FontResource;
|
||||||
|
|||||||
@@ -795,7 +795,7 @@ mod tests {
|
|||||||
use crate::layout::compute_layout;
|
use crate::layout::compute_layout;
|
||||||
use bevy::ecs::message::Messages;
|
use bevy::ecs::message::Messages;
|
||||||
use solitaire_core::card::{Card as CoreCard, Rank, Suit};
|
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
|
/// Build a minimal Bevy app wired with `RadialMenuPlugin` and the
|
||||||
/// resources / messages it depends on. No window, no camera — the
|
/// resources / messages it depends on. No window, no camera — the
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::*;
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||||
use solitaire_core::card::{Rank, Suit};
|
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_core::klondike_adapter::{SavedKlondikePile, SavedTableau};
|
||||||
use solitaire_data::{Replay, ReplayMove};
|
use solitaire_data::{Replay, ReplayMove};
|
||||||
|
|
||||||
@@ -2314,7 +2314,7 @@ fn format_suit_glyph_all_suits() {
|
|||||||
fn format_foundations_row_empty_board() {
|
fn format_foundations_row_empty_board() {
|
||||||
let game = solitaire_core::game_state::GameState::new_with_mode(
|
let game = solitaire_core::game_state::GameState::new_with_mode(
|
||||||
42,
|
42,
|
||||||
solitaire_core::game_state::DrawMode::DrawOne,
|
solitaire_core::DrawMode::DrawOne,
|
||||||
solitaire_core::game_state::GameMode::Classic,
|
solitaire_core::game_state::GameMode::Classic,
|
||||||
);
|
);
|
||||||
assert_eq!(format_foundations_row(&game), "F: -- -- -- --");
|
assert_eq!(format_foundations_row(&game), "F: -- -- -- --");
|
||||||
@@ -2326,7 +2326,7 @@ fn format_foundations_row_empty_board() {
|
|||||||
fn format_stock_waste_row_initial_state() {
|
fn format_stock_waste_row_initial_state() {
|
||||||
let game = solitaire_core::game_state::GameState::new_with_mode(
|
let game = solitaire_core::game_state::GameState::new_with_mode(
|
||||||
42,
|
42,
|
||||||
solitaire_core::game_state::DrawMode::DrawOne,
|
solitaire_core::DrawMode::DrawOne,
|
||||||
solitaire_core::game_state::GameMode::Classic,
|
solitaire_core::game_state::GameMode::Classic,
|
||||||
);
|
);
|
||||||
let text = format_stock_waste_row(&game);
|
let text = format_stock_waste_row(&game);
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ mod tests {
|
|||||||
use bevy::time::TimeUpdateStrategy;
|
use bevy::time::TimeUpdateStrategy;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use solitaire_core::{KlondikePile, Tableau};
|
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 solitaire_core::klondike_adapter::{SavedKlondikePile, SavedTableau};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|||||||
@@ -980,7 +980,7 @@ mod tests {
|
|||||||
|
|
||||||
use bevy::ecs::message::Messages;
|
use bevy::ecs::message::Messages;
|
||||||
use solitaire_core::card::{Card, Rank, Suit};
|
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
|
/// Build a minimal app with `SelectionPlugin` only — no GamePlugin, no
|
||||||
/// AssetServer. The `MoveRequestEvent` / `StateChangedEvent` /
|
/// AssetServer. The `MoveRequestEvent` / `StateChangedEvent` /
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
use bevy::ui::{ComputedNode, UiGlobalTransform};
|
||||||
use bevy::window::{WindowMoved, WindowResized};
|
use bevy::window::{WindowMoved, WindowResized};
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
use solitaire_data::{
|
use solitaire_data::{
|
||||||
AnimSpeed, REPLAY_MOVE_INTERVAL_STEP_SECS, Settings, TIME_BONUS_MULTIPLIER_STEP,
|
AnimSpeed, REPLAY_MOVE_INTERVAL_STEP_SECS, Settings, TIME_BONUS_MULTIPLIER_STEP,
|
||||||
TOOLTIP_DELAY_STEP_SECS, WindowGeometry, load_settings_from, save_settings_to, settings::Theme,
|
TOOLTIP_DELAY_STEP_SECS, WindowGeometry, load_settings_from, save_settings_to, settings::Theme,
|
||||||
@@ -241,7 +241,7 @@ enum SettingsButton {
|
|||||||
ToggleTouchInputMode,
|
ToggleTouchInputMode,
|
||||||
/// Toggle the [`Settings::winnable_deals_only`] flag. When on, new
|
/// Toggle the [`Settings::winnable_deals_only`] flag. When on, new
|
||||||
/// random Classic-mode deals are filtered through
|
/// 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.
|
/// winnable (or the retry cap is hit). Off by default.
|
||||||
ToggleWinnableDealsOnly,
|
ToggleWinnableDealsOnly,
|
||||||
/// Toggle the inverse of [`Settings::disable_smart_default_size`].
|
/// Toggle the inverse of [`Settings::disable_smart_default_size`].
|
||||||
|
|||||||
@@ -1327,7 +1327,7 @@ mod tests {
|
|||||||
app.world_mut()
|
app.world_mut()
|
||||||
.resource_mut::<crate::resources::GameStateResource>()
|
.resource_mut::<crate::resources::GameStateResource>()
|
||||||
.0
|
.0
|
||||||
.draw_mode = solitaire_core::game_state::DrawMode::DrawThree;
|
.draw_mode = solitaire_core::DrawMode::DrawThree;
|
||||||
|
|
||||||
app.world_mut().write_message(GameWonEvent {
|
app.world_mut().write_message(GameWonEvent {
|
||||||
score: 500,
|
score: 500,
|
||||||
@@ -1952,7 +1952,7 @@ mod tests {
|
|||||||
let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 8).expect("valid date");
|
let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 8).expect("valid date");
|
||||||
let mut r = solitaire_data::Replay::new(
|
let mut r = solitaire_data::Replay::new(
|
||||||
1,
|
1,
|
||||||
solitaire_core::game_state::DrawMode::DrawOne,
|
solitaire_core::DrawMode::DrawOne,
|
||||||
solitaire_core::game_state::GameMode::Classic,
|
solitaire_core::game_state::GameMode::Classic,
|
||||||
time_seconds,
|
time_seconds,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ mod tests {
|
|||||||
/// would silently drop the link.
|
/// would silently drop the link.
|
||||||
#[test]
|
#[test]
|
||||||
fn upload_result_writes_share_url_into_replay_and_persists() {
|
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::{
|
use solitaire_data::{
|
||||||
Replay, ReplayHistory, load_replay_history_from, save_replay_history_to,
|
Replay, ReplayHistory, load_replay_history_from, save_replay_history_to,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ mod tests {
|
|||||||
use crate::game_plugin::GamePlugin;
|
use crate::game_plugin::GamePlugin;
|
||||||
use crate::progress_plugin::ProgressPlugin;
|
use crate::progress_plugin::ProgressPlugin;
|
||||||
use crate::table_plugin::TablePlugin;
|
use crate::table_plugin::TablePlugin;
|
||||||
use solitaire_core::game_state::{DrawMode, GameState};
|
use solitaire_core::{DrawMode, game_state::GameState};
|
||||||
|
|
||||||
fn headless_app() -> App {
|
fn headless_app() -> App {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
@@ -1210,7 +1210,7 @@ mod tests {
|
|||||||
.insert_resource(StatsResource(StatsSnapshot::default()))
|
.insert_resource(StatsResource(StatsSnapshot::default()))
|
||||||
.insert_resource(GameStateResource(GameState::new(
|
.insert_resource(GameStateResource(GameState::new(
|
||||||
0,
|
0,
|
||||||
solitaire_core::game_state::DrawMode::DrawOne,
|
solitaire_core::DrawMode::DrawOne,
|
||||||
)))
|
)))
|
||||||
.insert_resource(ProgressResource(PlayerProgress::default()));
|
.insert_resource(ProgressResource(PlayerProgress::default()));
|
||||||
app.update();
|
app.update();
|
||||||
@@ -1539,7 +1539,7 @@ mod tests {
|
|||||||
.challenge_index = 4;
|
.challenge_index = 4;
|
||||||
// Switch game mode to Challenge.
|
// Switch game mode to Challenge.
|
||||||
{
|
{
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
app.world_mut().resource_mut::<GameStateResource>().0 =
|
app.world_mut().resource_mut::<GameStateResource>().0 =
|
||||||
GameState::new_with_mode(1, DrawMode::DrawOne, GameMode::Challenge);
|
GameState::new_with_mode(1, DrawMode::DrawOne, GameMode::Challenge);
|
||||||
}
|
}
|
||||||
@@ -1585,7 +1585,7 @@ mod tests {
|
|||||||
/// mode-multiplier rows.
|
/// mode-multiplier rows.
|
||||||
#[test]
|
#[test]
|
||||||
fn cache_win_data_captures_undo_count_and_mode() {
|
fn cache_win_data_captures_undo_count_and_mode() {
|
||||||
use solitaire_core::game_state::DrawMode;
|
use solitaire_core::DrawMode;
|
||||||
|
|
||||||
let mut app = make_app();
|
let mut app = make_app();
|
||||||
// Set up a Zen-mode game with 2 undos used.
|
// Set up a Zen-mode game with 2 undos used.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use solitaire_core::{Foundation, KlondikePile, Tableau};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solitaire_core::card::Suit;
|
use solitaire_core::card::Suit;
|
||||||
use solitaire_core::error::MoveError;
|
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::{
|
use solitaire_core::klondike_adapter::{
|
||||||
SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, tableau_from_index,
|
SavedInstruction, SavedKlondikePile, SavedKlondikePileStack, tableau_from_index,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user