refactor(core): derive Copy for DrawMode; drop redundant .clone() calls (M-18)

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 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-17 21:18:23 -07:00
parent fa84152429
commit 18d7937b51
12 changed files with 17 additions and 18 deletions
@@ -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!(
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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,
}
+6 -7
View File
@@ -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"
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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) });
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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,