refactor: consolidate card_to_id into solitaire_core
Three byte-identical copies of the stable 0..=51 card-id helper (suit_index*13 + rank-1) lived in feedback_anim_plugin, radial_menu, and solitaire_wasm. The WASM copy's own comment notes it MUST match the engine for cross-platform replay parity — exactly the kind of invariant a single source of truth should enforce. Add `solitaire_core::card::card_to_id(&Card) -> u32` and have all three call sites import it. No behaviour change (same formula). cargo test --workspace and cargo clippy --workspace --all-targets -- -D warnings pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,23 @@
|
|||||||
pub use card_game::{Card, Deck, Rank, Suit};
|
pub use card_game::{Card, Deck, Rank, Suit};
|
||||||
|
|
||||||
|
/// Maps a [`Card`] to a stable `0..=51` numeric identity, independent of the
|
||||||
|
/// upstream `card_game::Card` bit-packing.
|
||||||
|
///
|
||||||
|
/// Encoding: `suit_index * 13 + (rank as u32 - 1)`, where `suit_index` is
|
||||||
|
/// Clubs=0, Diamonds=1, Hearts=2, Spades=3 and `rank` is 1 (Ace) ..= 13 (King).
|
||||||
|
/// The deck id is intentionally ignored so the id depends only on the visible
|
||||||
|
/// face.
|
||||||
|
///
|
||||||
|
/// This is the single source of truth shared by `CardEntity` numeric tracking,
|
||||||
|
/// deterministic per-card animation jitter, and the WASM replay layer — those
|
||||||
|
/// must agree byte-for-byte so replay snapshots are identical across the
|
||||||
|
/// desktop and browser builds.
|
||||||
|
pub fn card_to_id(card: &Card) -> u32 {
|
||||||
|
let suit_index: u32 = match card.suit() {
|
||||||
|
Suit::Clubs => 0,
|
||||||
|
Suit::Diamonds => 1,
|
||||||
|
Suit::Hearts => 2,
|
||||||
|
Suit::Spades => 3,
|
||||||
|
};
|
||||||
|
suit_index * 13 + (card.rank() as u32 - 1)
|
||||||
|
}
|
||||||
|
|||||||
@@ -188,19 +188,9 @@ pub fn deal_stagger_jitter(card_id: u32) -> f32 {
|
|||||||
(jitter_norm - 0.5) * 0.2 // ±0.1 == ±10 %
|
(jitter_norm - 0.5) * 0.2 // ±0.1 == ±10 %
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a `Card` to a `u32` seed suitable for deterministic per-card
|
// Per-card jitter keys off the shared stable card id so it matches the
|
||||||
/// jitter. Uses suit index × 13 + (rank value − 1) to produce a stable 0–51
|
// numeric identity used elsewhere (and on the WASM replay side).
|
||||||
/// integer that survives changes to the internal `Card` representation.
|
use solitaire_core::card::card_to_id;
|
||||||
fn card_to_id(card: &Card) -> u32 {
|
|
||||||
use solitaire_core::card::Suit;
|
|
||||||
let suit_index = match card.suit() {
|
|
||||||
Suit::Clubs => 0,
|
|
||||||
Suit::Diamonds => 1,
|
|
||||||
Suit::Hearts => 2,
|
|
||||||
Suit::Spades => 3,
|
|
||||||
};
|
|
||||||
suit_index * 13 + (card.rank() as u32 - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Plugin
|
// Plugin
|
||||||
|
|||||||
@@ -359,19 +359,7 @@ fn pile_cards(game: &GameState, pile: &KlondikePile) -> Vec<(Card, bool)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps a `card_game::Card` to a stable `u32` identity used by `CardEntity`
|
use solitaire_core::card::card_to_id;
|
||||||
/// and systems that still track cards by numeric ID.
|
|
||||||
/// Encoding: `suit_index * 13 + (rank as u8 - 1)`, range 0..=51.
|
|
||||||
fn card_to_id(card: &Card) -> u32 {
|
|
||||||
use solitaire_core::card::Suit;
|
|
||||||
let suit_index: u32 = match card.suit() {
|
|
||||||
Suit::Clubs => 0,
|
|
||||||
Suit::Diamonds => 1,
|
|
||||||
Suit::Hearts => 2,
|
|
||||||
Suit::Spades => 3,
|
|
||||||
};
|
|
||||||
suit_index * 13 + (card.rank() as u32 - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn foundations() -> [Foundation; 4] {
|
const fn foundations() -> [Foundation; 4] {
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -87,18 +87,9 @@ pub struct CardSnapshot {
|
|||||||
pub face_up: bool,
|
pub face_up: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stable 0..=51 identifier derived from suit and rank. Mirrors the desktop
|
// Stable 0..=51 card identity, shared with the desktop engine via
|
||||||
/// engine's `card_to_id` so replay snapshots are identical across platforms —
|
// solitaire_core so replay snapshots are identical across platforms.
|
||||||
/// `Card` itself carries no id field (suit + rank are unique within a deck).
|
use solitaire_core::card::card_to_id;
|
||||||
fn card_to_id(card: &solitaire_core::card::Card) -> u32 {
|
|
||||||
let suit_index = match card.suit() {
|
|
||||||
Suit::Clubs => 0,
|
|
||||||
Suit::Diamonds => 1,
|
|
||||||
Suit::Hearts => 2,
|
|
||||||
Suit::Spades => 3,
|
|
||||||
};
|
|
||||||
suit_index * 13 + (card.rank() as u32 - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&(solitaire_core::card::Card, bool)> for CardSnapshot {
|
impl From<&(solitaire_core::card::Card, bool)> for CardSnapshot {
|
||||||
fn from((card, face_up): &(solitaire_core::card::Card, bool)) -> Self {
|
fn from((card, face_up): &(solitaire_core::card::Card, bool)) -> Self {
|
||||||
|
|||||||
Reference in New Issue
Block a user