refactor: replace local DrawMode with upstream klondike::DrawStockConfig (#82)
DrawMode was a 1:1 mirror of klondike::DrawStockConfig (DrawOne/DrawThree). Delete it and use the upstream type everywhere; re-export DrawStockConfig from solitaire_core. config_for assigns draw_stock directly and draw_mode() returns session.config().inner.draw_stock. Serde is unchanged — DrawStockConfig serialises to the same "DrawOne"/"DrawThree" named variants, so persisted game_state.json / replay JSON stay byte-compatible (no migration). Field/method/variable names containing draw_mode are unchanged. 35 files, mechanical type swap across all crates. Implemented via a multi-agent workflow (core → per-crate consumers → verify). cargo test --workspace and clippy --workspace --all-targets -- -D warnings green. Closes #82 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solitaire_core::{DrawMode, game_state::GameMode};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameMode};
|
||||
use solitaire_core::klondike_adapter::SavedKlondikePile;
|
||||
|
||||
const LATEST_REPLAY_FILE_NAME: &str = "latest_replay.json";
|
||||
@@ -124,7 +124,7 @@ pub struct Replay {
|
||||
/// `GameState::new_with_mode(seed, draw_mode, mode)`.
|
||||
pub seed: u64,
|
||||
/// Draw mode the recorded game was played in.
|
||||
pub draw_mode: DrawMode,
|
||||
pub draw_mode: DrawStockConfig,
|
||||
/// Game mode the recorded game was played in.
|
||||
pub mode: GameMode,
|
||||
/// Total wall-clock seconds the win took. Used for the Stats UI
|
||||
@@ -180,7 +180,7 @@ impl Replay {
|
||||
/// latter directly when the upload task resolves.
|
||||
pub fn new(
|
||||
seed: u64,
|
||||
draw_mode: DrawMode,
|
||||
draw_mode: DrawStockConfig,
|
||||
mode: GameMode,
|
||||
time_seconds: u64,
|
||||
final_score: i32,
|
||||
@@ -453,7 +453,7 @@ mod tests {
|
||||
let date = NaiveDate::from_ymd_opt(2026, 5, 2).expect("valid date");
|
||||
Replay::new(
|
||||
12345,
|
||||
DrawMode::DrawThree,
|
||||
DrawStockConfig::DrawThree,
|
||||
GameMode::Classic,
|
||||
134,
|
||||
5_120,
|
||||
@@ -596,7 +596,7 @@ mod tests {
|
||||
let date = NaiveDate::from_ymd_opt(2026, 5, 2).expect("valid date");
|
||||
Replay::new(
|
||||
id as u64,
|
||||
DrawMode::DrawOne,
|
||||
DrawStockConfig::DrawOne,
|
||||
GameMode::Classic,
|
||||
60,
|
||||
id,
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solitaire_core::{DrawMode, game_state::DifficultyLevel};
|
||||
use solitaire_core::{DrawStockConfig, game_state::DifficultyLevel};
|
||||
|
||||
const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||
|
||||
@@ -101,7 +101,7 @@ pub struct WindowGeometry {
|
||||
pub struct Settings {
|
||||
/// Draw mode selected for new games.
|
||||
#[serde(default = "default_draw_mode")]
|
||||
pub draw_mode: DrawMode,
|
||||
pub draw_mode: DrawStockConfig,
|
||||
/// Linear SFX volume in `[0.0, 1.0]`. Applied to kira's SFX channel gain.
|
||||
#[serde(default = "default_sfx_volume")]
|
||||
pub sfx_volume: f32,
|
||||
@@ -288,8 +288,8 @@ pub struct Settings {
|
||||
pub touch_input_mode: TouchInputMode,
|
||||
}
|
||||
|
||||
fn default_draw_mode() -> DrawMode {
|
||||
DrawMode::DrawOne
|
||||
fn default_draw_mode() -> DrawStockConfig {
|
||||
DrawStockConfig::DrawOne
|
||||
}
|
||||
|
||||
fn default_sfx_volume() -> f32 {
|
||||
@@ -392,7 +392,7 @@ pub const SOLVER_DEAL_RETRY_CAP: u32 = 50;
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
draw_mode: DrawMode::DrawOne,
|
||||
draw_mode: DrawStockConfig::DrawOne,
|
||||
sfx_volume: default_sfx_volume(),
|
||||
music_volume: default_music_volume(),
|
||||
animation_speed: AnimSpeed::Normal,
|
||||
|
||||
+26
-26
@@ -2,10 +2,10 @@
|
||||
//!
|
||||
//! [`StatsSnapshot`] is defined in `solitaire_sync` and re-exported here.
|
||||
//! This module adds the [`StatsExt`] extension trait, which supplies the
|
||||
//! `update_on_win` method that depends on [`DrawMode`] from `solitaire_core`.
|
||||
//! `update_on_win` method that depends on [`DrawStockConfig`] from `solitaire_core`.
|
||||
|
||||
use chrono::Utc;
|
||||
use solitaire_core::{DrawMode, game_state::GameMode};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameMode};
|
||||
|
||||
pub use solitaire_sync::StatsSnapshot;
|
||||
|
||||
@@ -18,9 +18,9 @@ pub trait StatsExt {
|
||||
///
|
||||
/// Tracks lifetime totals only — per-mode best scores and times are
|
||||
/// updated separately via [`StatsExt::update_per_mode_bests`] so the
|
||||
/// long-standing call sites that only know about [`DrawMode`] keep
|
||||
/// long-standing call sites that only know about [`DrawStockConfig`] keep
|
||||
/// compiling.
|
||||
fn update_on_win(&mut self, score: i32, time_seconds: u64, draw_mode: &DrawMode);
|
||||
fn update_on_win(&mut self, score: i32, time_seconds: u64, draw_mode: &DrawStockConfig);
|
||||
|
||||
/// Updates the per-mode best score and fastest-win-time fields for the
|
||||
/// given [`GameMode`]. Call alongside [`StatsExt::update_on_win`] from
|
||||
@@ -37,7 +37,7 @@ pub trait StatsExt {
|
||||
}
|
||||
|
||||
impl StatsExt for StatsSnapshot {
|
||||
fn update_on_win(&mut self, score: i32, time_seconds: u64, draw_mode: &DrawMode) {
|
||||
fn update_on_win(&mut self, score: i32, time_seconds: u64, draw_mode: &DrawStockConfig) {
|
||||
let prev_wins = self.games_won;
|
||||
self.games_played += 1;
|
||||
self.games_won += 1;
|
||||
@@ -64,8 +64,8 @@ impl StatsExt for StatsSnapshot {
|
||||
};
|
||||
|
||||
match draw_mode {
|
||||
DrawMode::DrawOne => self.draw_one_wins += 1,
|
||||
DrawMode::DrawThree => self.draw_three_wins += 1,
|
||||
DrawStockConfig::DrawOne => self.draw_one_wins += 1,
|
||||
DrawStockConfig::DrawThree => self.draw_three_wins += 1,
|
||||
}
|
||||
|
||||
self.last_modified = Utc::now();
|
||||
@@ -135,7 +135,7 @@ mod tests {
|
||||
#[test]
|
||||
fn first_win_sets_all_fields() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(1500, 120, &DrawMode::DrawOne);
|
||||
s.update_on_win(1500, 120, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.games_played, 1);
|
||||
assert_eq!(s.games_won, 1);
|
||||
assert_eq!(s.win_streak_current, 1);
|
||||
@@ -152,7 +152,7 @@ mod tests {
|
||||
fn streak_tracks_across_wins() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
for _ in 0..3 {
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
}
|
||||
assert_eq!(s.win_streak_current, 3);
|
||||
assert_eq!(s.win_streak_best, 3);
|
||||
@@ -161,8 +161,8 @@ mod tests {
|
||||
#[test]
|
||||
fn record_abandoned_resets_streak_and_increments_played() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.win_streak_current, 2);
|
||||
s.record_abandoned();
|
||||
assert_eq!(s.games_played, 3);
|
||||
@@ -174,35 +174,35 @@ mod tests {
|
||||
#[test]
|
||||
fn fastest_win_takes_minimum() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(100, 300, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 120, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 500, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 300, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 120, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 500, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.fastest_win_seconds, 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avg_time_is_correct_rolling_average() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(100, 100, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 200, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 300, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 100, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 200, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 300, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.avg_time_seconds, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn best_score_updates_only_on_higher_score() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(500, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(300, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(500, 60, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(300, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.best_single_score, 500);
|
||||
s.update_on_win(800, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(800, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.best_single_score, 800);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_score_treated_as_zero() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(-50, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(-50, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.best_single_score, 0);
|
||||
assert_eq!(s.lifetime_score, 0);
|
||||
}
|
||||
@@ -210,8 +210,8 @@ mod tests {
|
||||
#[test]
|
||||
fn draw_three_wins_tracked_separately() {
|
||||
let mut s = StatsSnapshot::default();
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawMode::DrawThree);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawThree);
|
||||
assert_eq!(s.draw_one_wins, 1);
|
||||
assert_eq!(s.draw_three_wins, 1);
|
||||
}
|
||||
@@ -221,7 +221,7 @@ mod tests {
|
||||
let mut s = StatsSnapshot::default();
|
||||
// Build a streak of 5.
|
||||
for _ in 0..5 {
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
}
|
||||
assert_eq!(s.win_streak_best, 5);
|
||||
// Lose (abandon), resetting current.
|
||||
@@ -229,7 +229,7 @@ mod tests {
|
||||
assert_eq!(s.win_streak_current, 0);
|
||||
assert_eq!(s.win_streak_best, 5, "best must survive the loss");
|
||||
// Win once — current becomes 1, best must remain 5.
|
||||
s.update_on_win(100, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(100, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(s.win_streak_current, 1);
|
||||
assert_eq!(
|
||||
s.win_streak_best, 5,
|
||||
@@ -243,7 +243,7 @@ mod tests {
|
||||
lifetime_score: u64::MAX - 100,
|
||||
..Default::default()
|
||||
};
|
||||
s.update_on_win(200, 60, &DrawMode::DrawOne);
|
||||
s.update_on_win(200, 60, &DrawStockConfig::DrawOne);
|
||||
assert_eq!(
|
||||
s.lifetime_score,
|
||||
u64::MAX,
|
||||
|
||||
@@ -279,7 +279,7 @@ fn cleanup_tmp_files_in(dir: &Path) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::stats::{StatsExt, StatsSnapshot};
|
||||
use solitaire_core::DrawMode;
|
||||
use solitaire_core::DrawStockConfig;
|
||||
use std::env;
|
||||
|
||||
fn tmp_path(name: &str) -> PathBuf {
|
||||
@@ -292,7 +292,7 @@ mod tests {
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let mut stats = StatsSnapshot::default();
|
||||
stats.update_on_win(1000, 180, &DrawMode::DrawOne);
|
||||
stats.update_on_win(1000, 180, &DrawStockConfig::DrawOne);
|
||||
save_stats_to(&path, &stats).expect("save");
|
||||
|
||||
let loaded = load_stats_from(&path);
|
||||
@@ -381,7 +381,7 @@ mod tests {
|
||||
let path = gs_path("round_trip");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let gs = GameState::new(12345, DrawMode::DrawOne);
|
||||
let gs = GameState::new(12345, DrawStockConfig::DrawOne);
|
||||
save_game_state_to(&path, &gs).expect("save");
|
||||
|
||||
let loaded = load_game_state_from(&path).expect("load");
|
||||
@@ -410,7 +410,7 @@ mod tests {
|
||||
let path = gs_path("won_skip");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let mut gs = GameState::new(99, DrawMode::DrawOne);
|
||||
let mut gs = GameState::new(99, DrawStockConfig::DrawOne);
|
||||
gs.set_test_won(true);
|
||||
save_game_state_to(&path, &gs).expect("save should be no-op, not error");
|
||||
assert!(
|
||||
@@ -423,7 +423,7 @@ mod tests {
|
||||
fn delete_game_state_removes_file() {
|
||||
use solitaire_core::game_state::GameState;
|
||||
let path = gs_path("delete");
|
||||
let gs = GameState::new(1, DrawMode::DrawOne);
|
||||
let gs = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
save_game_state_to(&path, &gs).expect("save");
|
||||
assert!(path.exists());
|
||||
delete_game_state_at(&path).expect("delete");
|
||||
@@ -441,7 +441,7 @@ mod tests {
|
||||
fn save_game_state_is_atomic() {
|
||||
use solitaire_core::game_state::GameState;
|
||||
let path = gs_path("atomic");
|
||||
let gs = GameState::new(55, DrawMode::DrawThree);
|
||||
let gs = GameState::new(55, DrawStockConfig::DrawThree);
|
||||
save_game_state_to(&path, &gs).expect("save");
|
||||
let tmp = path.with_extension("json.tmp");
|
||||
assert!(!tmp.exists(), ".tmp must be cleaned up after rename");
|
||||
@@ -512,7 +512,7 @@ mod tests {
|
||||
let path = gs_path("v4_mid_game");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let mut gs = GameState::new(42, DrawMode::DrawOne);
|
||||
let mut gs = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
|
||||
// Draw several times to populate the instruction history with
|
||||
// RotateStock entries and expose waste cards for further moves.
|
||||
@@ -619,7 +619,7 @@ mod tests {
|
||||
.expect("schema v3 must be accepted and migrated to v4");
|
||||
|
||||
// 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, DrawStockConfig::DrawOne);
|
||||
expected.draw().expect("draw must succeed on a fresh game");
|
||||
assert_eq!(loaded, expected, "migrated v3 game state must match equivalent v4 state");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! increments matching counters in `PlayerProgress::weekly_goal_progress`.
|
||||
|
||||
use chrono::{Datelike, NaiveDate};
|
||||
use solitaire_core::DrawMode;
|
||||
use solitaire_core::DrawStockConfig;
|
||||
|
||||
/// XP awarded each time a weekly goal is just completed.
|
||||
pub const WEEKLY_GOAL_XP: u64 = 75;
|
||||
@@ -36,7 +36,7 @@ pub struct WeeklyGoalDef {
|
||||
pub struct WeeklyGoalContext {
|
||||
pub time_seconds: u64,
|
||||
pub used_undo: bool,
|
||||
pub draw_mode: DrawMode,
|
||||
pub draw_mode: DrawStockConfig,
|
||||
}
|
||||
|
||||
impl WeeklyGoalDef {
|
||||
@@ -47,7 +47,7 @@ impl WeeklyGoalDef {
|
||||
WeeklyGoalKind::WinGame => true,
|
||||
WeeklyGoalKind::WinWithoutUndo => !ctx.used_undo,
|
||||
WeeklyGoalKind::WinUnder { seconds } => ctx.time_seconds < seconds,
|
||||
WeeklyGoalKind::WinDrawThree => ctx.draw_mode == DrawMode::DrawThree,
|
||||
WeeklyGoalKind::WinDrawThree => ctx.draw_mode == DrawStockConfig::DrawThree,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ mod tests {
|
||||
WeeklyGoalContext {
|
||||
time_seconds: time,
|
||||
used_undo: undo,
|
||||
draw_mode: DrawMode::DrawOne,
|
||||
draw_mode: DrawStockConfig::DrawOne,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ mod tests {
|
||||
WeeklyGoalContext {
|
||||
time_seconds: time,
|
||||
used_undo: false,
|
||||
draw_mode: DrawMode::DrawThree,
|
||||
draw_mode: DrawStockConfig::DrawThree,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user