refactor CardValue into Rank enum

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