refactor(core,engine,wasm): canonical FOUNDATIONS/TABLEAUS consts; adopt upstream SUITS/RANKS
Review findings 1+2 (Quat-underuse lens, 2026-07-06):
- solitaire_core gains pub const FOUNDATIONS / TABLEAUS — the canonical
iteration source for the upstream pile enums (upstream klondike has no
Foundation::ALL, and inherent impls cannot be added to foreign types).
Deletes three identical private const-fn copies (radial_menu,
table_plugin, input_plugin) and the hand-enumerated variants in
card_plugin::sync::all_cards and solitaire_wasm.
- Hand-rolled [Suit; 4] / [Rank; 13] arrays replaced with upstream
Suit::SUITS / Rank::RANKS. The order-sensitive CardImageSet indexing
is re-keyed through canonical card_plugin::{suit_index, rank_index}
helpers that match upstream order, with regression tests asserting
the correspondence — one ordering everywhere instead of three
divergent local ones.
Net -177 lines. No behaviour change; all consumers go through the
canonical helpers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -137,6 +137,23 @@ pub const RED_SUIT_COLOUR_HC: Color = Color::srgb(1.000, 0.408, 0.408);
|
||||
/// high-contrast boost path.
|
||||
pub const BLACK_SUIT_COLOUR: Color = Color::srgb(0.910, 0.910, 0.910);
|
||||
|
||||
/// Canonical outer index of `s` in [`CardImageSet::faces`].
|
||||
///
|
||||
/// Derived from the upstream `card_game::Suit` discriminants (0..=3 in
|
||||
/// `Suit::SUITS` order), so every reader and writer of `faces` computes
|
||||
/// the same layout from the same source. Three hand-rolled copies of this
|
||||
/// mapping once lived in card_plugin and theme/plugin and were one
|
||||
/// reorder away from drawing the wrong art.
|
||||
pub(crate) const fn suit_index(s: solitaire_core::Suit) -> usize {
|
||||
s as usize
|
||||
}
|
||||
|
||||
/// Canonical inner index of `r` in [`CardImageSet::faces`] — upstream
|
||||
/// `card_game::Rank` discriminants are 1..=13 in `Rank::RANKS` order.
|
||||
pub(crate) const fn rank_index(r: solitaire_core::Rank) -> usize {
|
||||
r as usize - 1
|
||||
}
|
||||
|
||||
/// Pre-loaded [`Handle<Image>`]s for card face and back PNG textures.
|
||||
///
|
||||
/// Loaded once at startup by [`load_card_images`]. When this resource is
|
||||
@@ -146,8 +163,10 @@ pub const BLACK_SUIT_COLOUR: Color = Color::srgb(0.910, 0.910, 0.910);
|
||||
pub struct CardImageSet {
|
||||
/// Per-card face images indexed by `[suit][rank]`.
|
||||
///
|
||||
/// Suit order: Clubs=0, Diamonds=1, Hearts=2, Spades=3.
|
||||
/// Rank order: Ace=0, Two=1 … King=12.
|
||||
/// Layout is pinned to the upstream declaration order — index with
|
||||
/// [`suit_index`] / [`rank_index`], never a hand-rolled match.
|
||||
/// Suit order: `Suit::SUITS` (Spades=0, Hearts=1, Clubs=2, Diamonds=3).
|
||||
/// Rank order: `Rank::RANKS` (Ace=0 … King=12).
|
||||
pub faces: [[Handle<Image>; 13]; 4],
|
||||
/// One handle per unlockable card-back design (indices 0–4). These
|
||||
/// correspond to the legacy `assets/cards/backs/back_N.png` art, indexed
|
||||
|
||||
@@ -105,25 +105,12 @@ pub(super) fn load_card_images(asset_server: Option<Res<AssetServer>>, mut comma
|
||||
return;
|
||||
};
|
||||
|
||||
const SUITS: [Suit; 4] = [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades];
|
||||
const RANKS: [Rank; 13] = [
|
||||
Rank::Ace,
|
||||
Rank::Two,
|
||||
Rank::Three,
|
||||
Rank::Four,
|
||||
Rank::Five,
|
||||
Rank::Six,
|
||||
Rank::Seven,
|
||||
Rank::Eight,
|
||||
Rank::Nine,
|
||||
Rank::Ten,
|
||||
Rank::Jack,
|
||||
Rank::Queen,
|
||||
Rank::King,
|
||||
];
|
||||
|
||||
// faces[suit_index(s)][rank_index(r)] — see the canonical helpers in
|
||||
// card_plugin::mod; building from SUITS/RANKS order matches them.
|
||||
let faces: [[Handle<Image>; 13]; 4] = std::array::from_fn(|si| {
|
||||
std::array::from_fn(|ri| asset_server.load(card_face_asset_path(RANKS[ri], SUITS[si])))
|
||||
std::array::from_fn(|ri| {
|
||||
asset_server.load(card_face_asset_path(Rank::RANKS[ri], Suit::SUITS[si]))
|
||||
})
|
||||
});
|
||||
let backs =
|
||||
std::array::from_fn(|i| asset_server.load(format!("cards/backs/classic/back_{i}.png")));
|
||||
@@ -149,27 +136,8 @@ pub(super) fn card_sprite(
|
||||
) -> Sprite {
|
||||
if let Some(set) = card_images {
|
||||
let image = if face_up {
|
||||
let suit_idx = match card.suit() {
|
||||
Suit::Clubs => 0,
|
||||
Suit::Diamonds => 1,
|
||||
Suit::Hearts => 2,
|
||||
Suit::Spades => 3,
|
||||
};
|
||||
let rank_idx = match card.rank() {
|
||||
Rank::Ace => 0,
|
||||
Rank::Two => 1,
|
||||
Rank::Three => 2,
|
||||
Rank::Four => 3,
|
||||
Rank::Five => 4,
|
||||
Rank::Six => 5,
|
||||
Rank::Seven => 6,
|
||||
Rank::Eight => 7,
|
||||
Rank::Nine => 8,
|
||||
Rank::Ten => 9,
|
||||
Rank::Jack => 10,
|
||||
Rank::Queen => 11,
|
||||
Rank::King => 12,
|
||||
};
|
||||
let suit_idx = suit_index(card.suit());
|
||||
let rank_idx = rank_index(card.rank());
|
||||
set.faces[suit_idx][rank_idx].clone()
|
||||
} else if let Some(theme_back) = &set.theme_back {
|
||||
// Active theme provides its own back — always wins over the
|
||||
@@ -519,23 +487,10 @@ pub(super) fn all_cards(game: &GameState) -> Vec<(Card, bool)> {
|
||||
let mut cards: Vec<(Card, bool)> = Vec::with_capacity(52);
|
||||
cards.extend(game.stock_cards());
|
||||
cards.extend(game.waste_cards());
|
||||
for foundation in [
|
||||
Foundation::Foundation1,
|
||||
Foundation::Foundation2,
|
||||
Foundation::Foundation3,
|
||||
Foundation::Foundation4,
|
||||
] {
|
||||
for foundation in solitaire_core::FOUNDATIONS {
|
||||
cards.extend(game.pile(KlondikePile::Foundation(foundation)));
|
||||
}
|
||||
for tableau in [
|
||||
Tableau::Tableau1,
|
||||
Tableau::Tableau2,
|
||||
Tableau::Tableau3,
|
||||
Tableau::Tableau4,
|
||||
Tableau::Tableau5,
|
||||
Tableau::Tableau6,
|
||||
Tableau::Tableau7,
|
||||
] {
|
||||
for tableau in solitaire_core::TABLEAUS {
|
||||
cards.extend(game.pile(KlondikePile::Tableau(tableau)));
|
||||
}
|
||||
cards
|
||||
|
||||
Reference in New Issue
Block a user