From 18d7937b51137d404f7201daf0dd1270a378cae0 Mon Sep 17 00:00:00 2001 From: funman300 Date: Sun, 17 May 2026 21:18:23 -0700 Subject: [PATCH] refactor(core): derive Copy for DrawMode; drop redundant .clone() calls (M-18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DrawMode is a fieldless two-variant enum — it is trivially bitwise- copyable. Adding Copy + updating choose_winnable_seed to take the value directly eliminates 13 superfluous .clone() calls across solitaire_core, solitaire_engine, solitaire_assetgen, and solitaire_wasm. Co-Authored-By: Claude Sonnet 4.6 --- solitaire_assetgen/src/bin/gen_difficulty_seeds.rs | 2 +- solitaire_assetgen/src/bin/gen_seeds.rs | 2 +- solitaire_core/src/game_state.rs | 2 +- solitaire_core/src/solver.rs | 2 +- solitaire_engine/src/game_plugin.rs | 13 ++++++------- solitaire_engine/src/home_plugin.rs | 2 +- solitaire_engine/src/pause_plugin.rs | 2 +- solitaire_engine/src/play_by_seed_plugin.rs | 2 +- solitaire_engine/src/replay_playback.rs | 2 +- solitaire_engine/src/sync_plugin.rs | 2 +- solitaire_engine/src/weekly_goals_plugin.rs | 2 +- solitaire_wasm/src/lib.rs | 2 +- 12 files changed, 17 insertions(+), 18 deletions(-) diff --git a/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs b/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs index c8dfd09..2fabc5b 100644 --- a/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs +++ b/solitaire_assetgen/src/bin/gen_difficulty_seeds.rs @@ -96,7 +96,7 @@ fn main() { continue; } let cfg = SolverConfig { move_budget, state_budget }; - match try_solve(seed, draw_mode.clone(), &cfg) { + match try_solve(seed, draw_mode, &cfg) { SolverResult::Winnable => { buckets[i].push(seed); eprintln!( diff --git a/solitaire_assetgen/src/bin/gen_seeds.rs b/solitaire_assetgen/src/bin/gen_seeds.rs index 0c0441f..111d0bf 100644 --- a/solitaire_assetgen/src/bin/gen_seeds.rs +++ b/solitaire_assetgen/src/bin/gen_seeds.rs @@ -73,7 +73,7 @@ fn main() { while found.len() < count { tried += 1; if matches!( - try_solve(seed, draw_mode.clone(), &cfg), + try_solve(seed, draw_mode, &cfg), SolverResult::Winnable ) { found.push(seed); diff --git a/solitaire_core/src/game_state.rs b/solitaire_core/src/game_state.rs index dfbce2a..ea6bd5e 100644 --- a/solitaire_core/src/game_state.rs +++ b/solitaire_core/src/game_state.rs @@ -43,7 +43,7 @@ mod pile_map_serde { } /// Whether cards are drawn one at a time or three at a time from the stock. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum DrawMode { /// Draw one card from stock per turn. DrawOne, diff --git a/solitaire_core/src/solver.rs b/solitaire_core/src/solver.rs index 5d6e41d..6ad3256 100644 --- a/solitaire_core/src/solver.rs +++ b/solitaire_core/src/solver.rs @@ -665,7 +665,7 @@ impl SolverState { foundation, stock, waste, - draw_mode: game.draw_mode.clone(), + draw_mode: game.draw_mode, just_drew: false, consecutive_draws: 0, } diff --git a/solitaire_engine/src/game_plugin.rs b/solitaire_engine/src/game_plugin.rs index 3da6afe..15d05bc 100644 --- a/solitaire_engine/src/game_plugin.rs +++ b/solitaire_engine/src/game_plugin.rs @@ -380,11 +380,11 @@ fn poll_pending_new_game_seed( /// Pure helper extracted for testability — `new_game_with_solver_*` /// engine tests in the same file exercise this path. -pub(crate) fn choose_winnable_seed(initial_seed: u64, draw_mode: &DrawMode) -> u64 { +pub(crate) fn choose_winnable_seed(initial_seed: u64, draw_mode: DrawMode) -> u64 { let cfg = SolverConfig::default(); let mut seed = initial_seed; for _ in 0..SOLVER_DEAL_RETRY_CAP { - match try_solve(seed, draw_mode.clone(), &cfg) { + match try_solve(seed, draw_mode, &cfg) { SolverResult::Winnable | SolverResult::Inconclusive => return seed, SolverResult::Unwinnable => { seed = seed.wrapping_add(1); @@ -451,7 +451,7 @@ fn handle_new_game( // where SettingsPlugin is not installed. let draw_mode = settings .as_ref() - .map_or_else(|| game.0.draw_mode.clone(), |s| s.0.draw_mode.clone()); + .map_or_else(|| game.0.draw_mode, |s| s.0.draw_mode); let mode = ev.mode.unwrap_or(game.0.mode); // Solver-backed retry: when the player has opted in to @@ -473,9 +473,8 @@ fn handle_new_game( .as_ref() .is_some_and(|s| s.0.winnable_deals_only); if winnable_only && mode == GameMode::Classic && ev.seed.is_none() { - let dm = draw_mode.clone(); let task = AsyncComputeTaskPool::get() - .spawn(async move { choose_winnable_seed(initial_seed, &dm) }); + .spawn(async move { choose_winnable_seed(initial_seed, draw_mode) }); pending_seed.inner = Some(PendingSeedTask { handle: task, mode: ev.mode, @@ -970,7 +969,7 @@ pub fn record_replay_on_win( let win_move_index = recording.moves.len().checked_sub(1); let replay = Replay::new( game.0.seed, - game.0.draw_mode.clone(), + game.0.draw_mode, game.0.mode, ev.time_seconds, ev.score, @@ -2647,7 +2646,7 @@ mod tests { // resolves as Inconclusive — the engine treats Inconclusive // as winnable (see `choose_winnable_seed` doc), so the // helper must return 395 when started at 394. - let chosen = choose_winnable_seed(394, &DrawMode::DrawOne); + let chosen = choose_winnable_seed(394, DrawMode::DrawOne); assert_eq!( chosen, 395, "seed 394 is Unwinnable; the next seed (395, Inconclusive) must be accepted" diff --git a/solitaire_engine/src/home_plugin.rs b/solitaire_engine/src/home_plugin.rs index 1c49dc6..912f1e8 100644 --- a/solitaire_engine/src/home_plugin.rs +++ b/solitaire_engine/src/home_plugin.rs @@ -430,7 +430,7 @@ fn build_home_context<'a>( challenge_best: stats.map_or(0, |s| s.0.challenge_best_score), daily_today, draw_mode: settings - .map(|s| s.0.draw_mode.clone()) + .map(|s| s.0.draw_mode) .unwrap_or(DrawMode::DrawOne), font_res, difficulty_expanded, diff --git a/solitaire_engine/src/pause_plugin.rs b/solitaire_engine/src/pause_plugin.rs index 3e67ba1..4606db1 100644 --- a/solitaire_engine/src/pause_plugin.rs +++ b/solitaire_engine/src/pause_plugin.rs @@ -235,7 +235,7 @@ fn toggle_pause( // Snapshot current level and streak at pause time. let level = progress.as_deref().map(|p| p.0.level); let streak = stats.as_deref().map(|s| s.0.win_streak_current); - let draw_mode = settings.as_deref().map(|s| s.0.draw_mode.clone()); + let draw_mode = settings.as_deref().map(|s| s.0.draw_mode); spawn_pause_screen( &mut commands, level, diff --git a/solitaire_engine/src/play_by_seed_plugin.rs b/solitaire_engine/src/play_by_seed_plugin.rs index 39b07f0..a34170a 100644 --- a/solitaire_engine/src/play_by_seed_plugin.rs +++ b/solitaire_engine/src/play_by_seed_plugin.rs @@ -338,7 +338,7 @@ fn tick_debounce_and_spawn_solver_task( let draw_mode = settings .as_ref() - .map_or(DrawMode::DrawOne, |s| s.0.draw_mode.clone()); + .map_or(DrawMode::DrawOne, |s| s.0.draw_mode); let cfg = SolverConfig::default(); let task = AsyncComputeTaskPool::get() .spawn(async move { try_solve(seed, draw_mode, &cfg) }); diff --git a/solitaire_engine/src/replay_playback.rs b/solitaire_engine/src/replay_playback.rs index 97924c5..b9219d6 100644 --- a/solitaire_engine/src/replay_playback.rs +++ b/solitaire_engine/src/replay_playback.rs @@ -190,7 +190,7 @@ pub fn start_replay_playback( ) { use solitaire_core::game_state::GameState; - let fresh = GameState::new_with_mode(replay.seed, replay.draw_mode.clone(), replay.mode); + let fresh = GameState::new_with_mode(replay.seed, replay.draw_mode, replay.mode); commands.insert_resource(GameStateResource(fresh)); // Initial `secs_to_next` uses the constant rather than reading diff --git a/solitaire_engine/src/sync_plugin.rs b/solitaire_engine/src/sync_plugin.rs index a8c3dfe..8cd8325 100644 --- a/solitaire_engine/src/sync_plugin.rs +++ b/solitaire_engine/src/sync_plugin.rs @@ -322,7 +322,7 @@ fn push_replay_on_win( } let replay = Replay::new( game.0.seed, - game.0.draw_mode.clone(), + game.0.draw_mode, game.0.mode, ev.time_seconds, ev.score, diff --git a/solitaire_engine/src/weekly_goals_plugin.rs b/solitaire_engine/src/weekly_goals_plugin.rs index b7dd006..5d9027c 100644 --- a/solitaire_engine/src/weekly_goals_plugin.rs +++ b/solitaire_engine/src/weekly_goals_plugin.rs @@ -82,7 +82,7 @@ fn evaluate_weekly_goals( let ctx = WeeklyGoalContext { time_seconds: ev.time_seconds, used_undo: game.0.undo_count > 0, - draw_mode: game.0.draw_mode.clone(), + draw_mode: game.0.draw_mode, }; for def in WEEKLY_GOALS { if !def.matches(&ctx) { diff --git a/solitaire_wasm/src/lib.rs b/solitaire_wasm/src/lib.rs index 673dc5b..586bbc3 100644 --- a/solitaire_wasm/src/lib.rs +++ b/solitaire_wasm/src/lib.rs @@ -119,7 +119,7 @@ impl ReplayPlayer { let replay: Replay = serde_json::from_str(replay_json).map_err(|e| format!("invalid replay JSON: {e}"))?; let game = - GameState::new_with_mode(replay.seed, replay.draw_mode.clone(), replay.mode); + GameState::new_with_mode(replay.seed, replay.draw_mode, replay.mode); Ok(Self { game, moves: replay.moves,