chore: cargo fmt across workspace; add analytics domain to CSP
Build and Deploy / build-and-push (push) Successful in 4m46s
Build and Deploy / build-and-push (push) Successful in 4m46s
- Apply cargo fmt to solitaire_engine, solitaire_server formatting. - solitaire_server/src/lib.rs: add https://analytics.aleshym.co to script-src, img-src, and connect-src so the analytics beacon loads without a CSP violation. - docs and README updates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,8 @@ use chrono::Utc;
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::{AsyncComputeTaskPool, Task, futures_lite::future};
|
||||
use bevy::window::AppLifecycle;
|
||||
use solitaire_core::game_state::{DrawMode, GameMode, GameState};
|
||||
use klondike::KlondikePile;
|
||||
use solitaire_core::game_state::{DrawMode, GameMode, GameState};
|
||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
||||
#[allow(deprecated)]
|
||||
use solitaire_data::latest_replay_path;
|
||||
@@ -521,10 +521,7 @@ fn handle_new_game(
|
||||
// hides that information and reads naturally as "dealt from the
|
||||
// deck." Skipped when LayoutResource isn't present (headless tests).
|
||||
if let Some(layout) = layout.as_ref()
|
||||
&& let Some(stock) = layout
|
||||
.0
|
||||
.pile_positions
|
||||
.get(&klondike::KlondikePile::Stock)
|
||||
&& let Some(stock) = layout.0.pile_positions.get(&klondike::KlondikePile::Stock)
|
||||
{
|
||||
for mut tx in &mut card_transforms {
|
||||
tx.translation.x = stock.x;
|
||||
@@ -1047,17 +1044,11 @@ fn foundation_slot(foundation: klondike::Foundation) -> Option<u8> {
|
||||
/// previous heuristic incorrectly did (Quat hit this with 4 cards
|
||||
/// remaining and the game just sat there).
|
||||
pub fn has_legal_moves(game: &GameState) -> bool {
|
||||
|
||||
|
||||
// Drawing from a non-empty stock, and recycling a non-empty waste back to
|
||||
// stock, are always legal moves in standard Klondike (unlimited recycles).
|
||||
// A game can only be genuinely stuck when both stock AND waste are exhausted.
|
||||
let stock_empty = game
|
||||
.stock_cards()
|
||||
.is_empty();
|
||||
let waste_empty = game
|
||||
.waste_cards()
|
||||
.is_empty();
|
||||
let stock_empty = game.stock_cards().is_empty();
|
||||
let waste_empty = game.waste_cards().is_empty();
|
||||
if !stock_empty || !waste_empty {
|
||||
return true;
|
||||
}
|
||||
@@ -1191,7 +1182,10 @@ fn handle_game_over_input(
|
||||
|
||||
if keys.just_pressed(KeyCode::KeyN) || keys.just_pressed(KeyCode::Escape) {
|
||||
// confirmed: true — the game is already stuck; no abandon-confirmation needed.
|
||||
new_game.write(NewGameRequestEvent { confirmed: true, ..default() });
|
||||
new_game.write(NewGameRequestEvent {
|
||||
confirmed: true,
|
||||
..default()
|
||||
});
|
||||
} else if keys.just_pressed(KeyCode::KeyU) {
|
||||
for entity in &screens {
|
||||
commands.entity(entity).despawn();
|
||||
@@ -1219,7 +1213,10 @@ fn handle_game_over_button_input(
|
||||
}
|
||||
if new_game_buttons.iter().any(|i| *i == Interaction::Pressed) {
|
||||
// confirmed: true — the game is already stuck; no abandon-confirmation needed.
|
||||
new_game.write(NewGameRequestEvent { confirmed: true, ..default() });
|
||||
new_game.write(NewGameRequestEvent {
|
||||
confirmed: true,
|
||||
..default()
|
||||
});
|
||||
} else if undo_buttons.iter().any(|i| *i == Interaction::Pressed) {
|
||||
for entity in &screens {
|
||||
commands.entity(entity).despawn();
|
||||
@@ -1388,9 +1385,11 @@ mod tests {
|
||||
#[test]
|
||||
fn new_game_request_reseeds() {
|
||||
let mut app = test_app(1);
|
||||
let before: Vec<u32> = app.world().resource::<GameStateResource>().0.pile(KlondikePile::Tableau(
|
||||
Tableau::Tableau1,
|
||||
))
|
||||
let before: Vec<u32> = app
|
||||
.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.pile(KlondikePile::Tableau(Tableau::Tableau1))
|
||||
.iter()
|
||||
.map(|c| c.id)
|
||||
.collect();
|
||||
@@ -1402,9 +1401,11 @@ mod tests {
|
||||
});
|
||||
app.update();
|
||||
|
||||
let after: Vec<u32> = app.world().resource::<GameStateResource>().0.pile(KlondikePile::Tableau(
|
||||
Tableau::Tableau1,
|
||||
))
|
||||
let after: Vec<u32> = app
|
||||
.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.pile(KlondikePile::Tableau(Tableau::Tableau1))
|
||||
.iter()
|
||||
.map(|c| c.id)
|
||||
.collect();
|
||||
@@ -1415,17 +1416,25 @@ mod tests {
|
||||
fn settings_changed_updates_take_from_foundation_flag() {
|
||||
let mut app = test_app(1);
|
||||
assert!(
|
||||
app.world().resource::<GameStateResource>().0.take_from_foundation,
|
||||
app.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.take_from_foundation,
|
||||
"fresh game should inherit default take_from_foundation=true",
|
||||
);
|
||||
|
||||
let mut settings = solitaire_data::Settings::default();
|
||||
settings.take_from_foundation = false;
|
||||
app.world_mut()
|
||||
.write_message(crate::settings_plugin::SettingsChangedEvent(settings.clone()));
|
||||
.write_message(crate::settings_plugin::SettingsChangedEvent(
|
||||
settings.clone(),
|
||||
));
|
||||
app.update();
|
||||
assert!(
|
||||
!app.world().resource::<GameStateResource>().0.take_from_foundation,
|
||||
!app.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.take_from_foundation,
|
||||
"settings event must forward take_from_foundation=false into live game state",
|
||||
);
|
||||
|
||||
@@ -1434,7 +1443,10 @@ mod tests {
|
||||
.write_message(crate::settings_plugin::SettingsChangedEvent(settings));
|
||||
app.update();
|
||||
assert!(
|
||||
app.world().resource::<GameStateResource>().0.take_from_foundation,
|
||||
app.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.take_from_foundation,
|
||||
"settings event must forward take_from_foundation=true into live game state",
|
||||
);
|
||||
}
|
||||
@@ -1557,7 +1569,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// auto_save_game_state writes to disk once the accumulator crosses 30 s.
|
||||
/// auto_save_game_state writes to disk once the accumulator crosses 30 s.
|
||||
///
|
||||
/// The timer is pre-seeded just past the threshold and the test
|
||||
/// re-arms it before each `app.update()` in a small bounded loop:
|
||||
@@ -1634,20 +1646,23 @@ mod tests {
|
||||
// Build a tableau with two face-up cards.
|
||||
{
|
||||
let mut gs = app.world_mut().resource_mut::<GameStateResource>();
|
||||
gs.0.set_test_tableau_cards(Tableau::Tableau1, vec![
|
||||
Card {
|
||||
id: 910,
|
||||
suit: Suit::Clubs,
|
||||
rank: Rank::King,
|
||||
face_up: true,
|
||||
},
|
||||
Card {
|
||||
id: 911,
|
||||
suit: Suit::Hearts,
|
||||
rank: Rank::Queen,
|
||||
face_up: true,
|
||||
},
|
||||
]);
|
||||
gs.0.set_test_tableau_cards(
|
||||
Tableau::Tableau1,
|
||||
vec![
|
||||
Card {
|
||||
id: 910,
|
||||
suit: Suit::Clubs,
|
||||
rank: Rank::King,
|
||||
face_up: true,
|
||||
},
|
||||
Card {
|
||||
id: 911,
|
||||
suit: Suit::Hearts,
|
||||
rank: Rank::Queen,
|
||||
face_up: true,
|
||||
},
|
||||
],
|
||||
);
|
||||
gs.0.set_test_tableau_cards(
|
||||
Tableau::Tableau2,
|
||||
vec![Card {
|
||||
@@ -1782,7 +1797,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn has_legal_moves_detects_non_top_face_up_card_as_source() {
|
||||
// Regression: the bug only checked t.cards.last() (top face-up card).
|
||||
// If the only legal move involves a face-up card that is NOT the top
|
||||
@@ -1936,16 +1951,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Verify that the game-over overlay contains the expected header text and
|
||||
/// Verify that the game-over overlay contains the expected header text and
|
||||
/// action-hint strings so players understand why the overlay appeared and
|
||||
/// what keys to press.
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// Task #56 — Escape dismisses GameOverScreen and starts new game
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// Pressing Escape while `GameOverScreen` is visible must fire
|
||||
/// `NewGameRequestEvent` — identical behaviour to pressing N.
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// Task #48 — Undo with empty stack fires InfoToastEvent
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -1988,7 +2003,7 @@ mod tests {
|
||||
/// When a King lands on a foundation that already holds Ace through
|
||||
/// Queen, exactly one `FoundationCompletedEvent` must fire and carry
|
||||
/// the matching slot + suit.
|
||||
/// Moving a card to a tableau pile must never produce a
|
||||
/// Moving a card to a tableau pile must never produce a
|
||||
/// `FoundationCompletedEvent`, even if the source tableau happened
|
||||
/// to have been a King.
|
||||
#[test]
|
||||
@@ -2051,7 +2066,7 @@ mod tests {
|
||||
/// At 12 cards on a foundation (Ace–Jack on the pile, Queen in
|
||||
/// flight), the event must NOT fire — the flourish is only for the
|
||||
/// final 13th completion.
|
||||
/// A successful undo must NOT fire an `InfoToastEvent`.
|
||||
/// A successful undo must NOT fire an `InfoToastEvent`.
|
||||
#[test]
|
||||
fn undo_after_draw_does_not_fire_info_toast() {
|
||||
let mut app = test_app(42);
|
||||
@@ -2086,7 +2101,7 @@ mod tests {
|
||||
/// Drive a fresh game through a draw + a tableau→foundation move,
|
||||
/// then assert the recording resource captured both, in order, with
|
||||
/// the correct shape.
|
||||
/// Invalid moves must not appear in the recording — the recording is
|
||||
/// Invalid moves must not appear in the recording — the recording is
|
||||
/// "what successfully happened", not "what was requested".
|
||||
#[test]
|
||||
fn replay_does_not_record_rejected_moves() {
|
||||
@@ -2359,7 +2374,10 @@ mod tests {
|
||||
Tableau::Tableau7,
|
||||
] {
|
||||
assert_eq!(
|
||||
app.world().resource::<GameStateResource>().0.pile(KlondikePile::Tableau(tableau)),
|
||||
app.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
.pile(KlondikePile::Tableau(tableau)),
|
||||
expected.pile(KlondikePile::Tableau(tableau)),
|
||||
"tableau column {tableau:?} must match the unfiltered seed",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user