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:
funman300
2026-06-11 19:33:47 -07:00
parent 5c992cbdca
commit e0a858d4e8
23 changed files with 132 additions and 137 deletions
+11 -11
View File
@@ -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();