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:
@@ -96,7 +96,7 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let cfg = SolverConfig { move_budget, state_budget };
|
let cfg = SolverConfig { move_budget, state_budget };
|
||||||
match try_solve(seed, draw_mode.clone(), &cfg) {
|
match try_solve(seed, draw_mode, &cfg) {
|
||||||
SolverResult::Winnable => {
|
SolverResult::Winnable => {
|
||||||
buckets[i].push(seed);
|
buckets[i].push(seed);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ fn main() {
|
|||||||
while found.len() < count {
|
while found.len() < count {
|
||||||
tried += 1;
|
tried += 1;
|
||||||
if matches!(
|
if matches!(
|
||||||
try_solve(seed, draw_mode.clone(), &cfg),
|
try_solve(seed, draw_mode, &cfg),
|
||||||
SolverResult::Winnable
|
SolverResult::Winnable
|
||||||
) {
|
) {
|
||||||
found.push(seed);
|
found.push(seed);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ mod pile_map_serde {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether cards are drawn one at a time or three at a time from the stock.
|
/// 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 {
|
pub enum DrawMode {
|
||||||
/// Draw one card from stock per turn.
|
/// Draw one card from stock per turn.
|
||||||
DrawOne,
|
DrawOne,
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ impl SolverState {
|
|||||||
foundation,
|
foundation,
|
||||||
stock,
|
stock,
|
||||||
waste,
|
waste,
|
||||||
draw_mode: game.draw_mode.clone(),
|
draw_mode: game.draw_mode,
|
||||||
just_drew: false,
|
just_drew: false,
|
||||||
consecutive_draws: 0,
|
consecutive_draws: 0,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,11 +380,11 @@ fn poll_pending_new_game_seed(
|
|||||||
|
|
||||||
/// Pure helper extracted for testability — `new_game_with_solver_*`
|
/// Pure helper extracted for testability — `new_game_with_solver_*`
|
||||||
/// engine tests in the same file exercise this path.
|
/// 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 cfg = SolverConfig::default();
|
||||||
let mut seed = initial_seed;
|
let mut seed = initial_seed;
|
||||||
for _ in 0..SOLVER_DEAL_RETRY_CAP {
|
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::Winnable | SolverResult::Inconclusive => return seed,
|
||||||
SolverResult::Unwinnable => {
|
SolverResult::Unwinnable => {
|
||||||
seed = seed.wrapping_add(1);
|
seed = seed.wrapping_add(1);
|
||||||
@@ -451,7 +451,7 @@ fn handle_new_game(
|
|||||||
// where SettingsPlugin is not installed.
|
// where SettingsPlugin is not installed.
|
||||||
let draw_mode = settings
|
let draw_mode = settings
|
||||||
.as_ref()
|
.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);
|
let mode = ev.mode.unwrap_or(game.0.mode);
|
||||||
|
|
||||||
// Solver-backed retry: when the player has opted in to
|
// Solver-backed retry: when the player has opted in to
|
||||||
@@ -473,9 +473,8 @@ fn handle_new_game(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|s| s.0.winnable_deals_only);
|
.is_some_and(|s| s.0.winnable_deals_only);
|
||||||
if winnable_only && mode == GameMode::Classic && ev.seed.is_none() {
|
if winnable_only && mode == GameMode::Classic && ev.seed.is_none() {
|
||||||
let dm = draw_mode.clone();
|
|
||||||
let task = AsyncComputeTaskPool::get()
|
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 {
|
pending_seed.inner = Some(PendingSeedTask {
|
||||||
handle: task,
|
handle: task,
|
||||||
mode: ev.mode,
|
mode: ev.mode,
|
||||||
@@ -970,7 +969,7 @@ pub fn record_replay_on_win(
|
|||||||
let win_move_index = recording.moves.len().checked_sub(1);
|
let win_move_index = recording.moves.len().checked_sub(1);
|
||||||
let replay = Replay::new(
|
let replay = Replay::new(
|
||||||
game.0.seed,
|
game.0.seed,
|
||||||
game.0.draw_mode.clone(),
|
game.0.draw_mode,
|
||||||
game.0.mode,
|
game.0.mode,
|
||||||
ev.time_seconds,
|
ev.time_seconds,
|
||||||
ev.score,
|
ev.score,
|
||||||
@@ -2647,7 +2646,7 @@ mod tests {
|
|||||||
// resolves as Inconclusive — the engine treats Inconclusive
|
// resolves as Inconclusive — the engine treats Inconclusive
|
||||||
// as winnable (see `choose_winnable_seed` doc), so the
|
// as winnable (see `choose_winnable_seed` doc), so the
|
||||||
// helper must return 395 when started at 394.
|
// 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!(
|
assert_eq!(
|
||||||
chosen, 395,
|
chosen, 395,
|
||||||
"seed 394 is Unwinnable; the next seed (395, Inconclusive) must be accepted"
|
"seed 394 is Unwinnable; the next seed (395, Inconclusive) must be accepted"
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ fn build_home_context<'a>(
|
|||||||
challenge_best: stats.map_or(0, |s| s.0.challenge_best_score),
|
challenge_best: stats.map_or(0, |s| s.0.challenge_best_score),
|
||||||
daily_today,
|
daily_today,
|
||||||
draw_mode: settings
|
draw_mode: settings
|
||||||
.map(|s| s.0.draw_mode.clone())
|
.map(|s| s.0.draw_mode)
|
||||||
.unwrap_or(DrawMode::DrawOne),
|
.unwrap_or(DrawMode::DrawOne),
|
||||||
font_res,
|
font_res,
|
||||||
difficulty_expanded,
|
difficulty_expanded,
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ fn toggle_pause(
|
|||||||
// Snapshot current level and streak at pause time.
|
// Snapshot current level and streak at pause time.
|
||||||
let level = progress.as_deref().map(|p| p.0.level);
|
let level = progress.as_deref().map(|p| p.0.level);
|
||||||
let streak = stats.as_deref().map(|s| s.0.win_streak_current);
|
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(
|
spawn_pause_screen(
|
||||||
&mut commands,
|
&mut commands,
|
||||||
level,
|
level,
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ fn tick_debounce_and_spawn_solver_task(
|
|||||||
|
|
||||||
let draw_mode = settings
|
let draw_mode = settings
|
||||||
.as_ref()
|
.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 cfg = SolverConfig::default();
|
||||||
let task = AsyncComputeTaskPool::get()
|
let task = AsyncComputeTaskPool::get()
|
||||||
.spawn(async move { try_solve(seed, draw_mode, &cfg) });
|
.spawn(async move { try_solve(seed, draw_mode, &cfg) });
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ pub fn start_replay_playback(
|
|||||||
) {
|
) {
|
||||||
use solitaire_core::game_state::GameState;
|
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));
|
commands.insert_resource(GameStateResource(fresh));
|
||||||
|
|
||||||
// Initial `secs_to_next` uses the constant rather than reading
|
// Initial `secs_to_next` uses the constant rather than reading
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ fn push_replay_on_win(
|
|||||||
}
|
}
|
||||||
let replay = Replay::new(
|
let replay = Replay::new(
|
||||||
game.0.seed,
|
game.0.seed,
|
||||||
game.0.draw_mode.clone(),
|
game.0.draw_mode,
|
||||||
game.0.mode,
|
game.0.mode,
|
||||||
ev.time_seconds,
|
ev.time_seconds,
|
||||||
ev.score,
|
ev.score,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ fn evaluate_weekly_goals(
|
|||||||
let ctx = WeeklyGoalContext {
|
let ctx = WeeklyGoalContext {
|
||||||
time_seconds: ev.time_seconds,
|
time_seconds: ev.time_seconds,
|
||||||
used_undo: game.0.undo_count > 0,
|
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 {
|
for def in WEEKLY_GOALS {
|
||||||
if !def.matches(&ctx) {
|
if !def.matches(&ctx) {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl ReplayPlayer {
|
|||||||
let replay: Replay =
|
let replay: Replay =
|
||||||
serde_json::from_str(replay_json).map_err(|e| format!("invalid replay JSON: {e}"))?;
|
serde_json::from_str(replay_json).map_err(|e| format!("invalid replay JSON: {e}"))?;
|
||||||
let game =
|
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 {
|
Ok(Self {
|
||||||
game,
|
game,
|
||||||
moves: replay.moves,
|
moves: replay.moves,
|
||||||
|
|||||||
Reference in New Issue
Block a user