refactor(core): explicit Rank discriminants, checked arithmetic, possible_instructions
Build and Deploy / build-and-push (push) Successful in 3m55s
Android Release / build-apk (push) Successful in 4m37s

- Add Rank=1..13 explicit discriminants so `rank as u8 == rank.value()`; collapse 13-arm value() match to `self as u8`
- Add Rank::RANKS and Suit::SUITS iteration constants
- Add Rank::checked_add / checked_sub (const fn, type-safe boundary enforcement); update rules.rs to use them
- Add GameState::possible_instructions() enumerating all valid move_cards triples (foundation for hints/solver)
- Fix waste buffer card peeking through during draw-slide animation by setting Visibility::Hidden on the buffer entity in sync_cards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-18 13:25:13 -07:00
parent e1c8ae0743
commit 456b4d42e3
4 changed files with 314 additions and 44 deletions
+88 -33
View File
@@ -10,6 +10,9 @@ pub enum Suit {
}
impl Suit {
/// All four suits in declaration order.
pub const SUITS: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
/// Returns `true` for red suits (Diamonds, Hearts).
pub fn is_red(self) -> bool {
matches!(self, Suit::Diamonds | Suit::Hearts)
@@ -24,38 +27,63 @@ impl Suit {
/// Card rank, Ace through King.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Rank {
Ace,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
}
impl Rank {
/// All thirteen ranks in ascending order.
pub const RANKS: [Self; 13] = [
Self::Ace, Self::Two, Self::Three, Self::Four, Self::Five,
Self::Six, Self::Seven, Self::Eight, Self::Nine, Self::Ten,
Self::Jack, Self::Queen, Self::King,
];
/// Numeric value: Ace = 1, King = 13.
pub fn value(self) -> u8 {
match self {
Rank::Ace => 1,
Rank::Two => 2,
Rank::Three => 3,
Rank::Four => 4,
Rank::Five => 5,
Rank::Six => 6,
Rank::Seven => 7,
Rank::Eight => 8,
Rank::Nine => 9,
Rank::Ten => 10,
Rank::Jack => 11,
Rank::Queen => 12,
Rank::King => 13,
self as u8
}
const fn new(n: u8) -> Option<Self> {
match n {
1 => Some(Self::Ace),
2 => Some(Self::Two),
3 => Some(Self::Three),
4 => Some(Self::Four),
5 => Some(Self::Five),
6 => Some(Self::Six),
7 => Some(Self::Seven),
8 => Some(Self::Eight),
9 => Some(Self::Nine),
10 => Some(Self::Ten),
11 => Some(Self::Jack),
12 => Some(Self::Queen),
13 => Some(Self::King),
_ => None,
}
}
/// Returns the rank `n` steps above `self`, or `None` if it would exceed King.
pub const fn checked_add(self, n: u8) -> Option<Self> {
Self::new((self as u8).saturating_add(n))
}
/// Returns the rank `n` steps below `self`, or `None` if it would go below Ace.
pub const fn checked_sub(self, n: u8) -> Option<Self> {
match (self as u8).checked_sub(n) {
Some(v) => Self::new(v),
None => None,
}
}
}
@@ -79,16 +107,43 @@ mod tests {
#[test]
fn rank_values_are_sequential() {
let ranks = [
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,
];
for (i, r) in ranks.iter().enumerate() {
for (i, r) in Rank::RANKS.iter().enumerate() {
assert_eq!(r.value(), (i + 1) as u8);
}
}
#[test]
fn rank_as_u8_matches_value() {
for r in Rank::RANKS {
assert_eq!(r as u8, r.value());
}
}
#[test]
fn rank_checked_add_boundary() {
assert_eq!(Rank::King.checked_add(1), None);
assert_eq!(Rank::Queen.checked_add(1), Some(Rank::King));
assert_eq!(Rank::Ace.checked_add(1), Some(Rank::Two));
assert_eq!(Rank::Five.checked_add(3), Some(Rank::Eight));
}
#[test]
fn rank_checked_sub_boundary() {
assert_eq!(Rank::Ace.checked_sub(1), None);
assert_eq!(Rank::Two.checked_sub(1), Some(Rank::Ace));
assert_eq!(Rank::King.checked_sub(1), Some(Rank::Queen));
assert_eq!(Rank::Five.checked_sub(3), Some(Rank::Two));
}
#[test]
fn suit_suits_contains_all_four() {
assert_eq!(Suit::SUITS.len(), 4);
assert!(Suit::SUITS.contains(&Suit::Clubs));
assert!(Suit::SUITS.contains(&Suit::Diamonds));
assert!(Suit::SUITS.contains(&Suit::Hearts));
assert!(Suit::SUITS.contains(&Suit::Spades));
}
#[test]
fn suit_red_and_black_are_complementary() {
for suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {