fix(engine): resolve all clippy warnings introduced by PNG asset pipeline
- Collapse nested-if patterns into let-chains across 13 plugins (42 instances) - Add #[allow(clippy::too_many_arguments)] to 5 Bevy systems in card_plugin and input_plugin where ECS parameter count exceeds the lint threshold - Gate Theme import in table_plugin under #[cfg(test)] — only used by test-only colour helpers; removing the unconditional import silences the unused-import lint without breaking the test suite - Wrap ButtonInput<MouseButton> in Option<> in update_input_platform so that tests using MinimalPlugins (no InputPlugin) no longer panic on startup All 789 tests pass; cargo clippy --workspace -- -D warnings is clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -176,21 +176,17 @@ fn evaluate_on_win(
|
||||
unlocks.write(AchievementUnlockedEvent(record.clone()));
|
||||
}
|
||||
|
||||
if achievements_changed {
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_achievements_to(target, &achievements.0) {
|
||||
if achievements_changed
|
||||
&& let Some(target) = &path.0
|
||||
&& let Err(e) = save_achievements_to(target, &achievements.0) {
|
||||
warn!("failed to save achievements: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if progress_changed {
|
||||
if let Some(target) = &progress_path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if progress_changed
|
||||
&& let Some(target) = &progress_path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress after reward: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: resolve an achievement ID to its human-readable name.
|
||||
|
||||
@@ -472,13 +472,12 @@ fn drive_toast_display(
|
||||
}
|
||||
|
||||
// If no active toast and the queue has messages, show the next one.
|
||||
if active.entity.is_none() {
|
||||
if let Some(message) = queue.0.pop_front() {
|
||||
if active.entity.is_none()
|
||||
&& let Some(message) = queue.0.pop_front() {
|
||||
let entity = spawn_queued_toast(&mut commands, message);
|
||||
active.entity = Some(entity);
|
||||
active.timer = QUEUED_TOAST_SECS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a centered top-of-screen `ToastEntity` for the queued toast system.
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Default for AnimationTuning {
|
||||
/// running under `MinimalPlugins` (which does not register the touch subsystem).
|
||||
pub(crate) fn update_input_platform(
|
||||
touches: Option<Res<Touches>>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
mouse_buttons: Option<Res<ButtonInput<MouseButton>>>,
|
||||
mut tuning: ResMut<AnimationTuning>,
|
||||
) {
|
||||
let touch_active = touches.as_ref().is_some_and(|t| {
|
||||
@@ -157,8 +157,9 @@ pub(crate) fn update_input_platform(
|
||||
|| t.iter_just_released().next().is_some()
|
||||
});
|
||||
|
||||
let mouse_active = mouse_buttons.get_just_pressed().next().is_some()
|
||||
|| mouse_buttons.get_pressed().next().is_some();
|
||||
let mouse_active = mouse_buttons.as_ref().is_some_and(|mb| {
|
||||
mb.get_just_pressed().next().is_some() || mb.get_pressed().next().is_some()
|
||||
});
|
||||
|
||||
if touch_active && tuning.platform != InputPlatform::Touch {
|
||||
*tuning = AnimationTuning::mobile();
|
||||
|
||||
@@ -394,6 +394,7 @@ fn sync_cards_startup(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn sync_cards_on_change(
|
||||
mut events: MessageReader<StateChangedEvent>,
|
||||
commands: Commands,
|
||||
@@ -416,6 +417,7 @@ fn sync_cards_on_change(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn sync_cards(
|
||||
mut commands: Commands,
|
||||
game: &GameState,
|
||||
@@ -542,6 +544,7 @@ fn face_colour(card: &Card, color_blind: bool) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn spawn_card_entity(
|
||||
commands: &mut Commands,
|
||||
card: &Card,
|
||||
|
||||
@@ -54,11 +54,10 @@ fn advance_on_challenge_win(
|
||||
}
|
||||
let prev = progress.0.challenge_index;
|
||||
progress.0.challenge_index = prev.saturating_add(1);
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress after challenge advance: {e}");
|
||||
}
|
||||
}
|
||||
// Human-readable level is 1-based (index 0 → "Challenge 1").
|
||||
let level_number = prev.saturating_add(1);
|
||||
toast.write(InfoToastEvent(format!("Challenge {level_number} complete!")));
|
||||
|
||||
@@ -161,27 +161,24 @@ fn handle_daily_completion(
|
||||
continue;
|
||||
}
|
||||
// Enforce server-supplied goal constraints when present.
|
||||
if let Some(target) = daily.target_score {
|
||||
if ev.score < target {
|
||||
if let Some(target) = daily.target_score
|
||||
&& ev.score < target {
|
||||
continue; // score goal not met
|
||||
}
|
||||
}
|
||||
if let Some(max_secs) = daily.max_time_secs {
|
||||
if ev.time_seconds > max_secs {
|
||||
if let Some(max_secs) = daily.max_time_secs
|
||||
&& ev.time_seconds > max_secs {
|
||||
continue; // time limit exceeded
|
||||
}
|
||||
}
|
||||
if !progress.0.record_daily_completion(daily.date) {
|
||||
// Already counted today — no-op.
|
||||
continue;
|
||||
}
|
||||
progress.0.add_xp(DAILY_BONUS_XP);
|
||||
xp_awarded.write(XpAwardedEvent { amount: DAILY_BONUS_XP });
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress after daily completion: {e}");
|
||||
}
|
||||
}
|
||||
completed.write(DailyChallengeCompletedEvent {
|
||||
date: daily.date,
|
||||
streak: progress.0.daily_challenge_streak,
|
||||
|
||||
@@ -194,11 +194,10 @@ fn handle_new_game(
|
||||
let mode = ev.mode.unwrap_or(game.0.mode);
|
||||
game.0 = GameState::new_with_mode(seed, draw_mode, mode);
|
||||
// Delete any previously saved in-progress state — this is a fresh game.
|
||||
if let Some(p) = path.as_ref().and_then(|r| r.0.as_deref()) {
|
||||
if let Err(e) = delete_game_state_at(p) {
|
||||
if let Some(p) = path.as_ref().and_then(|r| r.0.as_deref())
|
||||
&& let Err(e) = delete_game_state_at(p) {
|
||||
warn!("game_state: failed to delete saved game: {e}");
|
||||
}
|
||||
}
|
||||
changed.write(StateChangedEvent);
|
||||
}
|
||||
}
|
||||
@@ -380,14 +379,13 @@ fn handle_move(
|
||||
match game.0.move_cards(ev.from.clone(), ev.to.clone(), ev.count) {
|
||||
Ok(()) => {
|
||||
// Fire flip event if the candidate card is now face-up.
|
||||
if let Some(fid) = flip_candidate_id {
|
||||
if game.0.piles.get(&ev.from)
|
||||
if let Some(fid) = flip_candidate_id
|
||||
&& game.0.piles.get(&ev.from)
|
||||
.and_then(|p| p.cards.last())
|
||||
.is_some_and(|c| c.id == fid && c.face_up)
|
||||
{
|
||||
flipped.write(crate::events::CardFlippedEvent(fid));
|
||||
}
|
||||
}
|
||||
changed.write(StateChangedEvent);
|
||||
if !was_won && game.0.is_won {
|
||||
won.write(GameWonEvent {
|
||||
@@ -395,13 +393,12 @@ fn handle_move(
|
||||
time_seconds: game.0.elapsed_seconds,
|
||||
});
|
||||
// Delete the saved state — a won game should not be resumed.
|
||||
if let Some(p) = path.as_ref().and_then(|r| r.0.as_deref()) {
|
||||
if let Err(e) = delete_game_state_at(p) {
|
||||
if let Some(p) = path.as_ref().and_then(|r| r.0.as_deref())
|
||||
&& let Err(e) = delete_game_state_at(p) {
|
||||
warn!("game_state: failed to delete on win: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("move rejected {:?} -> {:?} x{}: {e}", ev.from, ev.to, ev.count),
|
||||
}
|
||||
}
|
||||
@@ -468,12 +465,11 @@ pub fn has_legal_moves(game: &GameState) -> bool {
|
||||
// Check foundations.
|
||||
for &suit in &suits {
|
||||
let dest = PileType::Foundation(suit);
|
||||
if let Some(dest_pile) = game.piles.get(&dest) {
|
||||
if can_place_on_foundation(card, dest_pile, suit) {
|
||||
if let Some(dest_pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(card, dest_pile, suit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check tableau piles.
|
||||
for i in 0..7_usize {
|
||||
@@ -481,13 +477,12 @@ pub fn has_legal_moves(game: &GameState) -> bool {
|
||||
if dest == *from {
|
||||
continue;
|
||||
}
|
||||
if let Some(dest_pile) = game.piles.get(&dest) {
|
||||
if can_place_on_tableau(card, dest_pile) {
|
||||
if let Some(dest_pile) = game.piles.get(&dest)
|
||||
&& can_place_on_tableau(card, dest_pile) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -437,15 +437,14 @@ fn update_hud(
|
||||
// Reflects the AutoCompleteState resource; update whenever it changes or game changes.
|
||||
let ac_active = auto_complete.as_ref().is_some_and(|ac| ac.active);
|
||||
let ac_changed = auto_complete.as_ref().is_some_and(|ac| ac.is_changed());
|
||||
if ac_changed || game.is_changed() {
|
||||
if let Ok(mut t) = auto_q.single_mut() {
|
||||
if (ac_changed || game.is_changed())
|
||||
&& let Ok(mut t) = auto_q.single_mut() {
|
||||
**t = if ac_active {
|
||||
"AUTO".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `HudSelection` text node to show which pile is Tab-selected.
|
||||
|
||||
@@ -136,6 +136,7 @@ struct CoreKeyboardMessages<'w> {
|
||||
///
|
||||
/// Also resets `forfeit_countdown` whenever U, D, Z, or N are pressed so that
|
||||
/// an in-flight forfeit confirmation is cancelled by any other action.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_keyboard_core(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
paused: Option<Res<PausedResource>>,
|
||||
@@ -174,8 +175,8 @@ fn handle_keyboard_core(
|
||||
confirm.forfeit_countdown = 0.0;
|
||||
|
||||
// If a Time Attack session is running, cancel it and start a Classic game.
|
||||
if let Some(ref mut session) = time_attack {
|
||||
if session.active {
|
||||
if let Some(ref mut session) = time_attack
|
||||
&& session.active {
|
||||
session.active = false;
|
||||
session.remaining_secs = 0.0;
|
||||
ev.info_toast.write(InfoToastEvent("Time Attack ended".to_string()));
|
||||
@@ -186,7 +187,6 @@ fn handle_keyboard_core(
|
||||
confirm.new_game_countdown = 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let active_game = game.as_ref().is_some_and(|g| g.0.move_count > 0 && !g.0.is_won);
|
||||
let shift_held = keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight);
|
||||
@@ -244,6 +244,7 @@ fn handle_keyboard_core(
|
||||
///
|
||||
/// The hint index wraps around once all hints have been cycled through. When no
|
||||
/// moves are available a "No hints available" toast is shown instead.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_keyboard_hint(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
paused: Option<Res<PausedResource>>,
|
||||
@@ -273,7 +274,7 @@ fn handle_keyboard_hint(
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ref layout_res) = layout else { return };
|
||||
let Some(_layout_res) = layout else { return };
|
||||
|
||||
let hints = all_hints(&g.0);
|
||||
if hints.is_empty() {
|
||||
@@ -661,8 +662,8 @@ fn end_drag(
|
||||
// the placement is illegal, fire MoveRejectedEvent so AudioPlugin can
|
||||
// play card_invalid.wav.
|
||||
let mut fired = false;
|
||||
if let Some(target) = target {
|
||||
if target != origin {
|
||||
if let Some(target) = target
|
||||
&& target != origin {
|
||||
let bottom_card_id = drag.cards[0];
|
||||
if let Some(bottom_card) = card_by_id(&game.0, bottom_card_id) {
|
||||
let ok = match &target {
|
||||
@@ -708,7 +709,6 @@ fn end_drag(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drag.clear();
|
||||
|
||||
@@ -892,8 +892,8 @@ fn touch_end_drag(
|
||||
world.and_then(|w| find_drop_target(w, &game.0, &layout.0, &origin));
|
||||
|
||||
let mut fired = false;
|
||||
if let Some(target) = target {
|
||||
if target != origin {
|
||||
if let Some(target) = target
|
||||
&& target != origin {
|
||||
let bottom_card_id = drag.cards[0];
|
||||
if let Some(bottom_card) = card_by_id(&game.0, bottom_card_id) {
|
||||
let ok = match &target {
|
||||
@@ -924,7 +924,6 @@ fn touch_end_drag(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drag.clear();
|
||||
changed.write(StateChangedEvent);
|
||||
@@ -1132,21 +1131,19 @@ pub fn best_destination(card: &Card, game: &GameState) -> Option<PileType> {
|
||||
// Try all four foundations first.
|
||||
for suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {
|
||||
let dest = PileType::Foundation(suit);
|
||||
if let Some(pile) = game.piles.get(&dest) {
|
||||
if can_place_on_foundation(card, pile, suit) {
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(card, pile, suit) {
|
||||
return Some(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Then try all seven tableau piles.
|
||||
for i in 0..7_usize {
|
||||
let dest = PileType::Tableau(i);
|
||||
if let Some(pile) = game.piles.get(&dest) {
|
||||
if can_place_on_tableau(card, pile) {
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_tableau(card, pile) {
|
||||
return Some(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1167,12 +1164,11 @@ pub fn best_tableau_destination_for_stack(
|
||||
if dest == *from {
|
||||
continue;
|
||||
}
|
||||
if let Some(pile) = game.piles.get(&dest) {
|
||||
if can_place_on_tableau(bottom_card, pile) {
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_tableau(bottom_card, pile) {
|
||||
return Some((dest, stack_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1309,8 +1305,8 @@ pub fn all_hints(game: &GameState) -> Vec<(PileType, PileType, usize)> {
|
||||
let Some(card) = from_pile.cards.last().filter(|c| c.face_up) else { continue };
|
||||
for &suit in &suits {
|
||||
let dest = PileType::Foundation(suit);
|
||||
if let Some(dest_pile) = game.piles.get(&dest) {
|
||||
if can_place_on_foundation(card, dest_pile, suit) {
|
||||
if let Some(dest_pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(card, dest_pile, suit) {
|
||||
hints.push((from.clone(), dest, 1));
|
||||
// Each source card can go to at most one foundation suit;
|
||||
// no need to check the remaining three for this card.
|
||||
@@ -1318,7 +1314,6 @@ pub fn all_hints(game: &GameState) -> Vec<(PileType, PileType, usize)> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2 — tableau moves (deduplicated by source pile so we don't
|
||||
// repeat the same source card multiple times for different destinations).
|
||||
@@ -1338,8 +1333,8 @@ pub fn all_hints(game: &GameState) -> Vec<(PileType, PileType, usize)> {
|
||||
if dest == *from {
|
||||
continue;
|
||||
}
|
||||
if let Some(dest_pile) = game.piles.get(&dest) {
|
||||
if can_place_on_tableau(card, dest_pile) {
|
||||
if let Some(dest_pile) = game.piles.get(&dest)
|
||||
&& can_place_on_tableau(card, dest_pile) {
|
||||
hints.push((from.clone(), dest, 1));
|
||||
// One tableau destination per source card is enough for the
|
||||
// hint list — the player can see where else a card can go
|
||||
@@ -1348,7 +1343,6 @@ pub fn all_hints(game: &GameState) -> Vec<(PileType, PileType, usize)> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3 — suggest drawing from the stock when no other hint was found.
|
||||
if hints.is_empty() {
|
||||
|
||||
@@ -123,15 +123,14 @@ fn toggle_leaderboard_screen(
|
||||
spawn_leaderboard_screen(&mut commands, data.0.as_deref());
|
||||
|
||||
// Start a background fetch if not already in flight.
|
||||
if task_res.0.is_none() {
|
||||
if let Some(p) = provider {
|
||||
if task_res.0.is_none()
|
||||
&& let Some(p) = provider {
|
||||
let provider = p.0.clone();
|
||||
let task = AsyncComputeTaskPool::get().spawn(async move {
|
||||
provider.fetch_leaderboard().await.map_err(|e| e.to_string())
|
||||
});
|
||||
task_res.0 = Some(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll the background fetch task; store results when complete.
|
||||
|
||||
@@ -103,13 +103,12 @@ fn toggle_pause(
|
||||
// If a drag is in progress, cancel it instead of opening the pause overlay.
|
||||
// Clearing DragState and emitting StateChangedEvent snaps the dragged cards
|
||||
// back to their resting positions exactly as a rejected drop does.
|
||||
if let Some(ref mut d) = drag {
|
||||
if !d.is_idle() {
|
||||
if let Some(ref mut d) = drag
|
||||
&& !d.is_idle() {
|
||||
d.clear();
|
||||
changed.write(StateChangedEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(entity) = screens.single() {
|
||||
commands.entity(entity).despawn();
|
||||
paused.0 = false;
|
||||
@@ -122,14 +121,12 @@ fn toggle_pause(
|
||||
paused.0 = true;
|
||||
// Persist the current game state whenever the player opens the pause
|
||||
// overlay so an OS-level kill still leaves a resumable save.
|
||||
if let (Some(g), Some(p)) = (game, path) {
|
||||
if let Some(disk_path) = p.0.as_deref() {
|
||||
if let Err(e) = save_game_state_to(disk_path, &g.0) {
|
||||
if let (Some(g), Some(p)) = (game, path)
|
||||
&& let Some(disk_path) = p.0.as_deref()
|
||||
&& let Err(e) = save_game_state_to(disk_path, &g.0) {
|
||||
warn!("game_state: failed to save on pause: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the draw-mode toggle button on the pause overlay.
|
||||
@@ -155,13 +152,11 @@ fn handle_pause_draw_toggle(
|
||||
DrawMode::DrawOne => DrawMode::DrawThree,
|
||||
DrawMode::DrawThree => DrawMode::DrawOne,
|
||||
};
|
||||
if let Some(p) = &path {
|
||||
if let Some(target) = &p.0 {
|
||||
if let Err(e) = solitaire_data::save_settings_to(target, &settings.0) {
|
||||
if let Some(p) = &path
|
||||
&& let Some(target) = &p.0
|
||||
&& let Err(e) = solitaire_data::save_settings_to(target, &settings.0) {
|
||||
warn!("failed to save settings after draw-mode toggle: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
changed.write(SettingsChangedEvent(settings.0.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +101,11 @@ fn award_xp_on_win(
|
||||
total_xp: progress.0.total_xp,
|
||||
});
|
||||
}
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -234,9 +234,9 @@ fn handle_selection_keys(
|
||||
// 2. Tableau stack move — count = full face-up run length from the source.
|
||||
let activate =
|
||||
keys.just_pressed(KeyCode::Enter) || keys.just_pressed(KeyCode::Space);
|
||||
if activate {
|
||||
if let Some(ref pile) = selection.selected_pile.clone() {
|
||||
if let Some(card) = game
|
||||
if activate
|
||||
&& let Some(ref pile) = selection.selected_pile.clone()
|
||||
&& let Some(card) = game
|
||||
.0
|
||||
.piles
|
||||
.get(pile)
|
||||
@@ -266,8 +266,8 @@ fn handle_selection_keys(
|
||||
let start = p.cards.len().saturating_sub(run_len);
|
||||
p.cards.get(start)
|
||||
});
|
||||
if let Some(bottom) = bottom_card {
|
||||
if let Some((dest, count)) =
|
||||
if let Some(bottom) = bottom_card
|
||||
&& let Some((dest, count)) =
|
||||
best_tableau_destination_for_stack(bottom, pile, &game.0, run_len)
|
||||
{
|
||||
moves.write(MoveRequestEvent {
|
||||
@@ -278,7 +278,6 @@ fn handle_selection_keys(
|
||||
selection.selected_pile = None;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback: single-card move to any destination ---
|
||||
// Covers non-tableau sources (Waste, Foundation) that have no
|
||||
@@ -292,8 +291,6 @@ fn handle_selection_keys(
|
||||
selection.selected_pile = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -330,12 +327,11 @@ fn try_foundation_dest(
|
||||
use solitaire_core::rules::can_place_on_foundation;
|
||||
for suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {
|
||||
let dest = PileType::Foundation(suit);
|
||||
if let Some(pile) = game.piles.get(&dest) {
|
||||
if can_place_on_foundation(card, pile, suit) {
|
||||
if let Some(pile) = game.piles.get(&dest)
|
||||
&& can_place_on_foundation(card, pile, suit) {
|
||||
return Some(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -326,8 +326,8 @@ fn spawn_stats_screen(
|
||||
}
|
||||
|
||||
// Time Attack section
|
||||
if let Some(ta) = time_attack {
|
||||
if ta.active {
|
||||
if let Some(ta) = time_attack
|
||||
&& ta.active {
|
||||
let mins = (ta.remaining_secs / 60.0).floor() as u64;
|
||||
let secs = (ta.remaining_secs % 60.0).floor() as u64;
|
||||
root.spawn((
|
||||
@@ -336,7 +336,6 @@ fn spawn_stats_screen(
|
||||
TextColor(Color::srgb(1.0, 0.6, 0.2)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss hint
|
||||
root.spawn((
|
||||
|
||||
@@ -176,21 +176,18 @@ fn poll_pull_result(
|
||||
let (merged, _conflicts) = merge(&local, &remote);
|
||||
|
||||
// Persist merged state atomically.
|
||||
if let Some(p) = &stats_path.0 {
|
||||
if let Err(e) = save_stats_to(p, &merged.stats) {
|
||||
if let Some(p) = &stats_path.0
|
||||
&& let Err(e) = save_stats_to(p, &merged.stats) {
|
||||
warn!("sync: failed to persist stats: {e}");
|
||||
}
|
||||
}
|
||||
if let Some(p) = &achievements_path.0 {
|
||||
if let Err(e) = save_achievements_to(p, &merged.achievements) {
|
||||
if let Some(p) = &achievements_path.0
|
||||
&& let Err(e) = save_achievements_to(p, &merged.achievements) {
|
||||
warn!("sync: failed to persist achievements: {e}");
|
||||
}
|
||||
}
|
||||
if let Some(p) = &progress_path.0 {
|
||||
if let Err(e) = save_progress_to(p, &merged.progress) {
|
||||
if let Some(p) = &progress_path.0
|
||||
&& let Err(e) = save_progress_to(p, &merged.progress) {
|
||||
warn!("sync: failed to persist progress: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Update in-world resources.
|
||||
stats.0 = merged.stats;
|
||||
|
||||
@@ -8,13 +8,14 @@ use bevy::prelude::*;
|
||||
use bevy::window::WindowResized;
|
||||
use solitaire_core::card::Suit;
|
||||
use solitaire_core::pile::PileType;
|
||||
use solitaire_data::settings::Theme;
|
||||
|
||||
use crate::events::HintVisualEvent;
|
||||
use crate::layout::{compute_layout, Layout, LayoutResource};
|
||||
#[cfg(test)]
|
||||
use crate::layout::TABLE_COLOUR;
|
||||
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource};
|
||||
#[cfg(test)]
|
||||
use solitaire_data::Theme;
|
||||
|
||||
/// Holds pre-loaded [`Handle<Image>`]s for the 5 selectable table backgrounds.
|
||||
///
|
||||
|
||||
@@ -49,13 +49,11 @@ fn roll_weekly_goals_on_startup(
|
||||
path: Res<ProgressStoragePath>,
|
||||
) {
|
||||
let week_key = current_iso_week_key(Local::now().date_naive());
|
||||
if progress.0.roll_weekly_goals_if_new_week(&week_key) {
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if progress.0.roll_weekly_goals_if_new_week(&week_key)
|
||||
&& let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress after weekly reset on startup: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_weekly_goals(
|
||||
@@ -114,13 +112,11 @@ fn evaluate_weekly_goals(
|
||||
}
|
||||
}
|
||||
|
||||
if any_change {
|
||||
if let Some(target) = &path.0 {
|
||||
if let Err(e) = save_progress_to(target, &progress.0) {
|
||||
if any_change
|
||||
&& let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress after weekly goal update: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a goal id to its description (used for toasts).
|
||||
|
||||
Reference in New Issue
Block a user