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
+9 -9
View File
@@ -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