refactor: remove card.rs / card_to_id; use card_game::Card directly (#83)
card_to_id was a frankenstein 0..=51 id shim. Replace it with card_game::Card: - feedback_anim deal jitter now seeds off a hash of the Card itself - radial_menu RightClickRadialState.cards: Vec<u32> -> Vec<Card> - wasm CardSnapshot.id: u32 -> Card (serialises transparently as a plain JS number, the same opaque key the renderer already used; new test asserts the JSON id field is a number) - wasm DebugInvariantReport deck-completeness check reworked from a [bool;52] index into a HashSet<Card> + Card::new reference deck; the out-of-range check is dropped since a Card is always valid Delete card.rs entirely: the Card/Deck/Rank/Suit re-exports move to the crate root and the 69 `solitaire_core::card::` import paths flatten to `solitaire_core::`. The JS card.id is purely an opaque identity key (Map key / dataset.cardId, no arithmetic, card faces render from rank+suit), so the value change is safe. cargo test --workspace and clippy --workspace --all-targets -- -D warnings green. Closes #83 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
//! red/black colour split.
|
||||
|
||||
use bevy::math::UVec2;
|
||||
use solitaire_core::card::{Rank, Suit};
|
||||
use solitaire_core::{Rank, Suit};
|
||||
|
||||
/// Target rasterisation size in pixels (2:3 aspect, half the default
|
||||
/// `SvgLoaderSettings` resolution).
|
||||
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
use crate::game_plugin::GamePlugin;
|
||||
use crate::table_plugin::TablePlugin;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Deck, Rank, Suit};
|
||||
use solitaire_core::{Deck, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
fn headless_app() -> App {
|
||||
@@ -207,7 +207,7 @@ mod tests {
|
||||
}
|
||||
g.set_test_tableau_cards(
|
||||
Tableau::Tableau1,
|
||||
vec![solitaire_core::card::Card::new(Deck::Deck1, Suit::Clubs, Rank::Ace)],
|
||||
vec![solitaire_core::Card::new(Deck::Deck1, Suit::Clubs, Rank::Ace)],
|
||||
);
|
||||
g.set_test_auto_completable(true);
|
||||
let expected = (
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::collections::VecDeque;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::PrimaryWindow;
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
|
||||
use super::animation::CardAnimation;
|
||||
use super::tuning::AnimationTuning;
|
||||
|
||||
@@ -17,7 +17,7 @@ use bevy::prelude::*;
|
||||
use bevy::sprite::Anchor;
|
||||
use bevy::window::WindowResized;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
use crate::animation_plugin::{CARD_ANIM_Z_LIFT, CardAnim, EffectiveSlideDuration};
|
||||
@@ -2472,7 +2472,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::game_plugin::GamePlugin;
|
||||
use crate::table_plugin::TablePlugin;
|
||||
use solitaire_core::card::Deck;
|
||||
use solitaire_core::Deck;
|
||||
|
||||
/// Convenience constructor — all unit tests use Deck1.
|
||||
fn make_card(suit: Suit, rank: Rank) -> Card {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon};
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
@@ -580,7 +580,7 @@ mod tests {
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
use crate::layout::compute_layout;
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::{GameMode, GameState}};
|
||||
|
||||
/// Builds an `App` with `MinimalPlugins` and the overlay system
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use bevy::prelude::Message;
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::card::{Card, Suit};
|
||||
use solitaire_core::{Card, Suit};
|
||||
use solitaire_core::game_state::GameMode;
|
||||
use solitaire_data::AchievementRecord;
|
||||
use solitaire_sync::SyncResponse;
|
||||
|
||||
@@ -43,7 +43,7 @@ use std::hash::{Hash, Hasher};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::RequestRedraw;
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::klondike_adapter::foundation_from_slot;
|
||||
use solitaire_data::AnimSpeed;
|
||||
@@ -189,10 +189,6 @@ pub fn deal_stagger_jitter(card_id: u32) -> f32 {
|
||||
(jitter_norm - 0.5) * 0.2 // ±0.1 == ±10 %
|
||||
}
|
||||
|
||||
// Per-card jitter keys off the shared stable card id so it matches the
|
||||
// numeric identity used elsewhere (and on the WASM replay side).
|
||||
use solitaire_core::card::card_to_id;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Plugin
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -413,10 +409,14 @@ fn start_deal_anim(
|
||||
|
||||
for (index, (entity, card_marker, transform)) in card_entities.iter().enumerate() {
|
||||
let final_pos = transform.translation;
|
||||
// ±10 % jitter, deterministic per card id, so the deal feels organic
|
||||
// without losing reproducibility (a given seed still produces the
|
||||
// same per-card stagger pattern across runs).
|
||||
let per_card_stagger = stagger_secs * (1.0 + deal_stagger_jitter(card_to_id(&card_marker.card)));
|
||||
// ±10 % jitter, deterministic per card, so the deal feels organic
|
||||
// without losing reproducibility (a given deal produces the same
|
||||
// per-card stagger pattern across runs). The seed is a hash of the
|
||||
// card's own identity — no separate numeric id needed.
|
||||
let mut card_hasher = DefaultHasher::new();
|
||||
card_marker.card.hash(&mut card_hasher);
|
||||
let per_card_stagger =
|
||||
stagger_secs * (1.0 + deal_stagger_jitter(card_hasher.finish() as u32));
|
||||
commands.entity(entity).insert((
|
||||
Transform::from_translation(stock_start.with_z(final_pos.z)),
|
||||
CardAnim {
|
||||
@@ -639,7 +639,7 @@ fn lerp_color(from: Color, to: Color, t: f32) -> Color {
|
||||
fn pile_cards(
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
pile: &KlondikePile,
|
||||
) -> Vec<(solitaire_core::card::Card, bool)> {
|
||||
) -> Vec<(solitaire_core::Card, bool)> {
|
||||
match pile {
|
||||
KlondikePile::Stock => game.waste_cards(),
|
||||
_ => game.pile(*pile),
|
||||
@@ -917,7 +917,7 @@ mod tests {
|
||||
.resource_mut::<Messages<FoundationCompletedEvent>>()
|
||||
.write(FoundationCompletedEvent {
|
||||
slot: 0,
|
||||
suit: solitaire_core::card::Suit::Spades,
|
||||
suit: solitaire_core::Suit::Spades,
|
||||
});
|
||||
app.update();
|
||||
|
||||
|
||||
@@ -824,7 +824,7 @@ fn handle_draw(
|
||||
// so we can fire flip events after they land face-up in the waste.
|
||||
// Only relevant when stock is non-empty; a recycle moves waste back to
|
||||
// stock face-down, so no flip events are needed in that case.
|
||||
let drawn_cards: Vec<solitaire_core::card::Card> = {
|
||||
let drawn_cards: Vec<solitaire_core::Card> = {
|
||||
let stock = game.0.stock_cards();
|
||||
if stock.is_empty() {
|
||||
Vec::new()
|
||||
@@ -1013,7 +1013,7 @@ pub fn record_replay_on_win(
|
||||
}
|
||||
}
|
||||
|
||||
fn pile_cards(game: &GameState, pile: &KlondikePile) -> Vec<(solitaire_core::card::Card, bool)> {
|
||||
fn pile_cards(game: &GameState, pile: &KlondikePile) -> Vec<(solitaire_core::Card, bool)> {
|
||||
match pile {
|
||||
KlondikePile::Stock => game.waste_cards(),
|
||||
_ => game.pile(*pile),
|
||||
@@ -1391,7 +1391,7 @@ mod tests {
|
||||
#[test]
|
||||
fn new_game_request_reseeds() {
|
||||
let mut app = test_app(1);
|
||||
let before: Vec<solitaire_core::card::Card> = app
|
||||
let before: Vec<solitaire_core::Card> = app
|
||||
.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
@@ -1407,7 +1407,7 @@ mod tests {
|
||||
});
|
||||
app.update();
|
||||
|
||||
let after: Vec<solitaire_core::card::Card> = app
|
||||
let after: Vec<solitaire_core::Card> = app
|
||||
.world()
|
||||
.resource::<GameStateResource>()
|
||||
.0
|
||||
@@ -1649,7 +1649,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn moving_cards_off_face_up_card_does_not_fire_card_flipped_event() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let mut app = test_app(1);
|
||||
// Build a tableau with two face-up cards.
|
||||
{
|
||||
@@ -1706,7 +1706,7 @@ mod tests {
|
||||
// Klondike (unlimited recycles), even if the drawn card cannot be
|
||||
// immediately placed. The game is only stuck when both stock AND waste
|
||||
// are exhausted and no visible card can be moved.
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
for foundation in [
|
||||
Foundation::Foundation1,
|
||||
@@ -1742,7 +1742,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn has_legal_moves_returns_true_when_ace_can_go_to_foundation() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
// Empty stock and waste so draw is NOT available.
|
||||
@@ -1786,7 +1786,7 @@ mod tests {
|
||||
// If the only legal move involves a face-up card that is NOT the top
|
||||
// card of its column the previous code would return false (softlock)
|
||||
// even though the player can still move that run.
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
game.set_test_stock_cards(Vec::new());
|
||||
@@ -1976,7 +1976,7 @@ mod tests {
|
||||
/// to have been a King.
|
||||
#[test]
|
||||
fn foundation_completed_event_does_not_fire_for_non_foundation_moves() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
|
||||
let mut app = test_app(1);
|
||||
// Reset the world: clear stock + waste so a draw isn't possible,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::WindowResized;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::Suit;
|
||||
use solitaire_core::Suit;
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameMode};
|
||||
|
||||
use crate::auto_complete_plugin::AutoCompleteState;
|
||||
|
||||
@@ -27,7 +27,7 @@ use bevy::window::PrimaryWindow;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use bevy::window::{MonitorSelection, WindowMode};
|
||||
use solitaire_core::{Foundation, KlondikeInstruction, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Card, Suit};
|
||||
use solitaire_core::{Card, Suit};
|
||||
use solitaire_core::game_state::GameState;
|
||||
|
||||
use crate::auto_complete_plugin::AutoCompleteState;
|
||||
@@ -1953,8 +1953,8 @@ mod tests {
|
||||
fn find_draggable_returns_run_when_picking_mid_stack() {
|
||||
// Manually construct a tableau with three face-up cards all stacked.
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let king = Card::new(D::Deck1, Suit::Spades, Rank::King);
|
||||
let queen = Card::new(D::Deck1, Suit::Hearts, Rank::Queen);
|
||||
let jack = Card::new(D::Deck1, Suit::Clubs, Rank::Jack);
|
||||
@@ -1980,8 +1980,8 @@ mod tests {
|
||||
#[test]
|
||||
fn find_draggable_skips_non_top_waste_card() {
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let two_spades = Card::new(D::Deck1, Suit::Spades, Rank::Two);
|
||||
let three_hearts = Card::new(D::Deck1, Suit::Hearts, Rank::Three);
|
||||
game.set_test_waste_cards(vec![two_spades, three_hearts.clone()]);
|
||||
@@ -2044,8 +2044,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn find_draggable_draw_three_waste_top_card_hit_at_fanned_position() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameMode};
|
||||
let mut game = GameState::new_with_mode(1, DrawStockConfig::DrawThree, GameMode::Classic);
|
||||
// Three waste cards; top (four_clubs) is rightmost in the fan.
|
||||
@@ -2103,8 +2103,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn best_destination_returns_none_when_no_legal_move() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
// Clear everything except one card that has nowhere to go.
|
||||
@@ -2121,8 +2121,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn best_tableau_destination_for_stack_skips_source_pile() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
clear_test_piles(&mut game);
|
||||
@@ -2147,8 +2147,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn best_tableau_destination_for_stack_returns_none_when_no_legal_move() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
clear_test_piles(&mut game);
|
||||
@@ -2176,8 +2176,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn find_hint_finds_ace_to_foundation() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
// Place Ace of Clubs on top of tableau 0.
|
||||
@@ -2220,8 +2220,8 @@ mod tests {
|
||||
/// are no other moves and the stock is non-empty.
|
||||
#[test]
|
||||
fn all_hints_suggests_draw_when_no_moves_and_stock_nonempty() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
let mut game = GameState::new(1, DrawStockConfig::DrawOne);
|
||||
|
||||
// Remove all foundation, tableau, and waste cards so no pile-to-pile
|
||||
@@ -2273,8 +2273,8 @@ mod tests {
|
||||
/// gets a CardAnimation" — same coverage, new component.
|
||||
#[test]
|
||||
fn rejected_drag_inserts_card_animation_on_each_dragged_card() {
|
||||
use solitaire_core::card::Deck as D;
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::Deck as D;
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
// Simulate a stack drag of two cards.
|
||||
let dragged_cards: Vec<Card> = vec![
|
||||
Card::new(D::Deck1, Suit::Hearts, Rank::King),
|
||||
|
||||
@@ -179,7 +179,7 @@ mod tests {
|
||||
use crate::events::HintVisualEvent;
|
||||
use crate::input_plugin::HintSolverConfig;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
/// Build a minimal Bevy app exercising only the polling system
|
||||
|
||||
@@ -48,7 +48,7 @@ use bevy::math::Vec2;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::PrimaryWindow;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
use solitaire_core::game_state::GameState;
|
||||
|
||||
use crate::card_plugin::TABLEAU_FACEDOWN_FAN_FRAC;
|
||||
@@ -113,9 +113,9 @@ pub enum RightClickRadialState {
|
||||
/// radial is built around single-card foundation/tableau
|
||||
/// shortcuts and that matches the right-click highlight set).
|
||||
count: usize,
|
||||
/// Card ids that would be moved (bottom-to-top order). Length
|
||||
/// Cards that would be moved (bottom-to-top order). Length
|
||||
/// always equals `count`. Currently always one element.
|
||||
cards: Vec<u32>,
|
||||
cards: Vec<Card>,
|
||||
/// Pre-computed `(destination, icon_anchor_world_pos)` pairs.
|
||||
///
|
||||
/// Anchors are evenly spaced around a ring of radius
|
||||
@@ -359,7 +359,6 @@ fn pile_cards(game: &GameState, pile: &KlondikePile) -> Vec<(Card, bool)> {
|
||||
}
|
||||
}
|
||||
|
||||
use solitaire_core::card::card_to_id;
|
||||
|
||||
const fn foundations() -> [Foundation; 4] {
|
||||
[
|
||||
@@ -500,7 +499,7 @@ fn radial_open_on_right_click(
|
||||
*state = RightClickRadialState::Active {
|
||||
source_pile,
|
||||
count: 1,
|
||||
cards: vec![card_to_id(&card)],
|
||||
cards: vec![card.clone()],
|
||||
legal_destinations,
|
||||
centre: world,
|
||||
hovered_index: None,
|
||||
@@ -573,7 +572,7 @@ fn radial_open_on_long_press(
|
||||
*state = RightClickRadialState::Active {
|
||||
source_pile,
|
||||
count: 1,
|
||||
cards: vec![card_to_id(&card)],
|
||||
cards: vec![card.clone()],
|
||||
legal_destinations,
|
||||
centre: world,
|
||||
hovered_index: None,
|
||||
@@ -796,7 +795,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::layout::compute_layout;
|
||||
use bevy::ecs::message::Messages;
|
||||
use solitaire_core::card::{Card as CoreCard, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card as CoreCard, Deck, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
/// Build a minimal Bevy app wired with `RadialMenuPlugin` and the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ReplayPlaybackState;
|
||||
use chrono::Datelike;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Card, Rank, Suit};
|
||||
use solitaire_core::{Card, Rank, Suit};
|
||||
use solitaire_core::game_state::GameState;
|
||||
use solitaire_core::klondike_adapter::SavedKlondikePile;
|
||||
use solitaire_data::ReplayMove;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use chrono::NaiveDate;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::{Rank, Suit};
|
||||
use solitaire_core::{Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameMode};
|
||||
use solitaire_core::klondike_adapter::{SavedKlondikePile, SavedTableau};
|
||||
use solitaire_data::{Replay, ReplayMove};
|
||||
|
||||
@@ -7,7 +7,7 @@ use bevy::math::Vec2;
|
||||
use bevy::prelude::Resource;
|
||||
use chrono::{DateTime, Utc};
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
use solitaire_core::game_state::GameState;
|
||||
|
||||
/// Wraps the currently active `GameState`. Single source of truth for the in-progress game.
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
use bevy::input::ButtonInput;
|
||||
use bevy::prelude::*;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
use solitaire_core::game_state::GameState;
|
||||
|
||||
use crate::card_plugin::CardEntityIndex;
|
||||
@@ -534,7 +534,7 @@ fn handle_selection_keys(
|
||||
/// destination after a lift. Players who want a different column simply
|
||||
/// press the right-arrow key once or twice.
|
||||
pub(crate) fn legal_destinations_for(
|
||||
_bottom: &solitaire_core::card::Card,
|
||||
_bottom: &solitaire_core::Card,
|
||||
source: &KlondikePile,
|
||||
game: &GameState,
|
||||
stack_count: usize,
|
||||
@@ -579,7 +579,7 @@ pub(crate) fn legal_destinations_for(
|
||||
/// Walks backwards from the last element and stops at the first face-down card
|
||||
/// (or when the slice is exhausted). Returns at least `1` when the top card is
|
||||
/// face-up; returns `0` for an empty slice or when the top card is face-down.
|
||||
fn face_up_run_len(cards: &[(solitaire_core::card::Card, bool)]) -> usize {
|
||||
fn face_up_run_len(cards: &[(solitaire_core::Card, bool)]) -> usize {
|
||||
let mut count = 0;
|
||||
for (_, face_up) in cards.iter().rev() {
|
||||
if *face_up {
|
||||
@@ -598,7 +598,7 @@ fn face_up_run_len(cards: &[(solitaire_core::card::Card, bool)]) -> usize {
|
||||
/// handler can attempt a foundation move first and fall through to a
|
||||
/// multi-card stack move rather than accepting a single-card tableau move.
|
||||
fn try_foundation_dest(
|
||||
card: &solitaire_core::card::Card,
|
||||
card: &solitaire_core::Card,
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
) -> Option<KlondikePile> {
|
||||
let source = game.pile_containing_card(card.clone())?;
|
||||
@@ -886,7 +886,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn face_up_run_len_all_face_up() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let cards = vec![
|
||||
(Card::new(Deck::Deck1, Suit::Clubs, Rank::King), true),
|
||||
(Card::new(Deck::Deck1, Suit::Hearts, Rank::Queen), true),
|
||||
@@ -897,7 +897,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn face_up_run_len_mixed_stops_at_face_down() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let cards = vec![
|
||||
(Card::new(Deck::Deck1, Suit::Clubs, Rank::King), false),
|
||||
(Card::new(Deck::Deck1, Suit::Hearts, Rank::Queen), false),
|
||||
@@ -910,7 +910,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn face_up_run_len_top_card_face_down_is_zero() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let cards = vec![
|
||||
(Card::new(Deck::Deck1, Suit::Clubs, Rank::King), true),
|
||||
(Card::new(Deck::Deck1, Suit::Hearts, Rank::Queen), false),
|
||||
@@ -920,7 +920,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn face_up_run_len_single_face_up_card() {
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
let cards = vec![(Card::new(Deck::Deck1, Suit::Hearts, Rank::Ace), true)];
|
||||
assert_eq!(face_up_run_len(&cards), 1);
|
||||
}
|
||||
@@ -934,7 +934,7 @@ mod tests {
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
use bevy::ecs::message::Messages;
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{DrawStockConfig, game_state::GameState};
|
||||
|
||||
/// Build a minimal app with `SelectionPlugin` only — no GamePlugin, no
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::WindowResized;
|
||||
use solitaire_core::{Foundation, KlondikePile, Tableau};
|
||||
use solitaire_core::card::Suit;
|
||||
use solitaire_core::Suit;
|
||||
|
||||
use crate::events::{HintVisualEvent, StateChangedEvent};
|
||||
use crate::hud_plugin::HudVisibility;
|
||||
@@ -520,7 +520,7 @@ fn sync_pile_marker_visibility(
|
||||
fn pile_cards(
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
pile: &KlondikePile,
|
||||
) -> Vec<(solitaire_core::card::Card, bool)> {
|
||||
) -> Vec<(solitaire_core::Card, bool)> {
|
||||
match pile {
|
||||
KlondikePile::Stock => {
|
||||
let stock = game.stock_cards();
|
||||
|
||||
@@ -27,7 +27,7 @@ use bevy::reflect::TypePath;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use solitaire_core::card::{Rank, Suit};
|
||||
use solitaire_core::{Rank, Suit};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use importer::{ImportError, ThemeId, import_theme, import_theme_into};
|
||||
|
||||
@@ -12,7 +12,7 @@ use bevy::asset::AssetEvent;
|
||||
use bevy::ecs::message::MessageReader;
|
||||
use bevy::math::UVec2;
|
||||
use bevy::prelude::*;
|
||||
use solitaire_core::card::{Rank, Suit};
|
||||
use solitaire_core::{Rank, Suit};
|
||||
|
||||
use crate::assets::{
|
||||
bundled_theme_url, classic_theme_svg_bytes, dark_theme_svg_bytes, rasterize_svg, user_theme_dir,
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
use bevy::ecs::message::MessageReader;
|
||||
use bevy::prelude::*;
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::card::Card;
|
||||
use solitaire_core::Card;
|
||||
|
||||
use crate::card_plugin::CardEntity;
|
||||
use crate::events::StateChangedEvent;
|
||||
@@ -194,7 +194,7 @@ fn spawn_touch_highlight(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solitaire_core::Tableau;
|
||||
use solitaire_core::card::{Card, Deck, Rank, Suit};
|
||||
use solitaire_core::{Card, Deck, Rank, Suit};
|
||||
|
||||
/// Three distinct test cards, used in place of the old `vec![1, 2, 3]`
|
||||
/// numeric ids. Identity is now the `Card` value.
|
||||
|
||||
Reference in New Issue
Block a user