From 5f81f28160d00e4a1940daa8aa7953aaa88db571 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Mon, 18 May 2026 11:01:48 -0700 Subject: [PATCH] refactor CardValue into Rank enum --- card_game/src/lib.rs | 140 ++++++++++++++++++++++++++------------- klondike-cli/src/main.rs | 16 ++--- klondike/src/lib.rs | 12 ++-- 3 files changed, 109 insertions(+), 59 deletions(-) diff --git a/card_game/src/lib.rs b/card_game/src/lib.rs index c556506..12305ad 100644 --- a/card_game/src/lib.rs +++ b/card_game/src/lib.rs @@ -16,6 +16,27 @@ pub trait Game { fn is_win(&self) -> bool; } +/// card_game supports up to 4 identifiably separate decks. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Deck { + Deck1, + Deck2, + Deck3, + Deck4, +} +impl Deck { + pub const fn new(deck: u8) -> Option { + use Deck::*; + Some(match deck { + 1 => Deck1, + 2 => Deck2, + 3 => Deck3, + 4 => Deck4, + _ => return None, + }) + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Suit { Spades = 0b00, @@ -36,38 +57,66 @@ impl Suit { } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct CardValue(u8); -impl CardValue { - pub const ACE: Self = CardValue(1); - pub const TWO: Self = CardValue(2); - pub const THREE: Self = CardValue(3); - pub const FOUR: Self = CardValue(4); - pub const FIVE: Self = CardValue(5); - pub const SIX: Self = CardValue(6); - pub const SEVEN: Self = CardValue(7); - pub const EIGHT: Self = CardValue(8); - pub const NINE: Self = CardValue(9); - pub const TEN: Self = CardValue(10); - pub const JACK: Self = CardValue(11); - pub const QUEEN: Self = CardValue(12); - pub const KING: Self = CardValue(13); - pub fn get(self) -> u8 { - self.0 +pub enum Rank { + 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 { + 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, + ]; + pub const fn new(rank: u8) -> Option { + use Rank::*; + Some(match rank { + 1 => Ace, + 2 => Two, + 3 => Three, + 4 => Four, + 5 => Five, + 6 => Six, + 7 => Seven, + 8 => Eight, + 9 => Nine, + 10 => Ten, + 11 => Jack, + 12 => Queen, + 13 => King, + _ => return None, + }) } - pub fn checked_add(self, offset: u8) -> Option { - let new_value = self.0.checked_add(offset)?; - if 13 < new_value { - None - } else { - Some(CardValue(new_value)) + pub const fn checked_add(self, offset: u8) -> Option { + match (self as u8).checked_add(offset) { + Some(rank) => Self::new(rank), + None => None, } } - pub fn checked_sub(self, offset: u8) -> Option { - let new_value = self.0.checked_sub(offset)?; - if new_value < 1 { - None - } else { - Some(CardValue(new_value)) + pub const fn checked_sub(self, offset: u8) -> Option { + match (self as u8).checked_sub(offset) { + Some(rank) => Self::new(rank), + None => None, } } } @@ -77,16 +126,17 @@ impl CardValue { /// 4 bits for card Value /// TODO: better encoding for slightly more decks #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct Card(u8); +pub struct Card(core::num::NonZeroU8); impl Card { - pub fn new(deck: u8, suit: Suit, CardValue(value): CardValue) -> Self { - Self(deck << 6 | (suit as u8) << 4 | value) + pub const fn new(deck: Deck, suit: Suit, rank: Rank) -> Self { + let packed = (deck as u8) << 6 | (suit as u8) << 4 | (rank as u8); + Self(core::num::NonZeroU8::new(packed).unwrap()) } - pub fn value(&self) -> CardValue { - let masked = self.0 & 0b1111; - CardValue(masked) + pub const fn rank(&self) -> Rank { + let masked = self.0.get() & 0b1111; + Rank::new(masked).unwrap() } - pub fn suit(&self) -> Suit { + pub const fn suit(&self) -> Suit { let red = self.is_red(); let kiki = self.is_kiki(); match (kiki, red) { @@ -97,15 +147,15 @@ impl Card { } } /// Is the suit red. - pub fn is_red(&self) -> bool { - self.0 & 0b010000 != 0 + pub const fn is_red(&self) -> bool { + self.0.get() & 0b010000 != 0 } /// Is the suit shape spikey. (Bouba/kiki) - pub fn is_kiki(&self) -> bool { - self.0 & 0b100000 != 0 + pub const fn is_kiki(&self) -> bool { + self.0.get() & 0b100000 != 0 } - pub fn deck(&self) -> u8 { - self.0 >> 6 + pub const fn deck(&self) -> Deck { + Deck::new(self.0.get() >> 6).unwrap() } } @@ -121,11 +171,11 @@ impl Stack { } impl Stack<52> { /// Generate a full deck of cards with the specified deck id. - pub fn full_deck(deck: u8) -> Self { + pub fn full_deck(deck: Deck) -> Self { let mut stack = arrayvec::ArrayVec::new(); for suit in Suit::SUITS { - for value in 1..=13 { - stack.push(Card::new(deck, suit, CardValue(value))); + for rank in Rank::RANKS { + stack.push(Card::new(deck, suit, rank)); } } Stack(stack) diff --git a/klondike-cli/src/main.rs b/klondike-cli/src/main.rs index 8b31111..1821081 100644 --- a/klondike-cli/src/main.rs +++ b/klondike-cli/src/main.rs @@ -1,4 +1,4 @@ -use card_game::{Card, CardValue, Game, Pile, Session, SessionStats, Suit}; +use card_game::{Card, Game, Pile, Rank, Session, SessionStats, Suit}; use klondike::{ DstFoundation, DstTableau, Foundation, Klondike, KlondikeConfig, KlondikeInstruction, KlondikePile, KlondikePileStack, KlondikeStats, SkipCards, Tableau, TableauStack, @@ -9,12 +9,12 @@ struct Displayed(T); impl Display for Displayed<&Card> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.value().get() { - 1 => write!(f, "A"), - 11 => write!(f, "J"), - 12 => write!(f, "Q"), - 13 => write!(f, "K"), - other => write!(f, "{other}"), + match self.0.rank() { + Rank::Ace => write!(f, "A"), + Rank::Jack => write!(f, "J"), + Rank::Queen => write!(f, "Q"), + Rank::King => write!(f, "K"), + other => write!(f, "{}", other as u8), }?; match self.0.suit() { Suit::Spades => write!(f, "♠"), @@ -236,7 +236,7 @@ fn get_good_move(state: &Klondike) -> Option { || state .state() .stack_bottom_card(dst_tableau.src) - .is_some_and(|card| card.value() != CardValue::KING) => + .is_some_and(|card| card.rank() != Rank::King) => { 2 } diff --git a/klondike/src/lib.rs b/klondike/src/lib.rs index b3e7d0a..bfef7af 100644 --- a/klondike/src/lib.rs +++ b/klondike/src/lib.rs @@ -1,6 +1,6 @@ pub type Rng = rand::rngs::ThreadRng; -use card_game::{Card, CardValue, Game, Pile, Stack}; +use card_game::{Card, Game, Pile, Rank, Stack}; #[cfg(test)] mod test; @@ -475,10 +475,10 @@ impl KlondikeState { // suit matches? src_card.suit() == dst_card.suit() // value is +1? - && dst_card.value().checked_add(1) == Some(src_card.value()) + && dst_card.rank().checked_add(1) == Some(src_card.rank()) } // only ace is allowed to go onto empty foundation - None => src_card.value() == CardValue::ACE, + None => src_card.rank() == Rank::Ace, } } else { false @@ -494,10 +494,10 @@ impl KlondikeState { // red-ness is opposite? src_card.is_red() != dst_card.is_red() // value is -1? - && dst_card.value().checked_sub(1) == Some(src_card.value()) + && dst_card.rank().checked_sub(1) == Some(src_card.rank()) } // only king is allowed to go onto empty tableau - None => src_card.value() == CardValue::KING, + None => src_card.rank() == Rank::King, } } else { false @@ -536,7 +536,7 @@ impl Klondike { } pub fn new(mut seed: Rng) -> Self { // shuffle a new deck - let mut deck = Stack::full_deck(0); + let mut deck = Stack::full_deck(card_game::Deck::Deck1); use rand::seq::SliceRandom; deck.shuffle(&mut seed); let mut deck = deck.into_iter();