From 52285de6be0f7d5b9d089772532ed57011a679f0 Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Wed, 1 Apr 2026 22:21:07 +0100 Subject: [PATCH] card: refactor to lift PlayingCard as it's own type Now that playing cards are their own type, we can map a sequence of Cards over to their PlayingCards, which should make the classifier a bit nicer. --- src/card.rs | 183 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 126 insertions(+), 57 deletions(-) diff --git a/src/card.rs b/src/card.rs index 951d350..8af2e68 100644 --- a/src/card.rs +++ b/src/card.rs @@ -1,4 +1,4 @@ -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] pub enum Rank { Three = 0, Four, @@ -15,7 +15,7 @@ pub enum Rank { Two, } -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)] pub enum Suit { Diamond = 0, Club, @@ -23,10 +23,17 @@ pub enum Suit { Spade, } -#[derive(Debug, Clone, Copy)] +#[derive(Eq, Debug, Clone, Copy)] +pub struct PlayingCard { + pub deck: i64, + pub rank: Rank, + pub suit: Suit, +} + +#[derive(Eq, Debug, Clone, Copy)] pub enum Card { Joker(i64), - PlayingCard { deck: i64, rank: Rank, suit: Suit }, + PlayingCard(PlayingCard), } impl Rank { @@ -35,12 +42,27 @@ impl Rank { } } +impl PlayingCard { + pub fn new(deck: i64, rank: Rank, suit: Suit) -> Self { + Self { deck, rank, suit } + } + + pub fn abs(&self) -> i64 { + let rank = self.rank as i64; + let suit = self.suit as i64; + (rank * 4) + suit + } +} + impl Card { pub fn new(rank: Rank, suit: Suit) -> Self { - Self::PlayingCard { - deck: 0, - rank, - suit, + Self::PlayingCard(PlayingCard::new(0, rank, suit)) + } + + pub fn deck_abs(&self) -> i64 { + match *self { + Self::Joker(x) => x, + Self::PlayingCard(card) => card.abs(), } } @@ -48,18 +70,19 @@ impl Card { matches!(self, Self::Joker(_)) } - pub fn rank(&self) -> Option { - match self { + pub fn playing_card(&self) -> Option { + match *self { Self::Joker(_) => None, - Self::PlayingCard { rank, .. } => Some(*rank), + Self::PlayingCard(card) => Some(card), } } + pub fn rank(&self) -> Option { + self.playing_card().and_then(|pc| Some(pc.rank)) + } + pub fn suit(&self) -> Option { - match self { - Self::Joker(_) => None, - Self::PlayingCard { suit, .. } => Some(*suit), - } + self.playing_card().and_then(|pc| Some(pc.suit)) } } @@ -70,13 +93,9 @@ pub fn make_decks(number_of_decks: usize) -> Vec { .collect::>() } -mod traits { - use std::cmp::Ordering; - use std::convert::TryFrom; - use std::fmt::{Display, Formatter}; - use std::hash::{Hash, Hasher}; - +mod trait_display { use super::*; + use std::fmt::{Display, Formatter}; impl Display for Rank { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -117,16 +136,25 @@ mod traits { } } + impl Display for PlayingCard { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}[{}]", self.rank, self.suit) + } + } + impl Display for Card { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Card::Joker(_) => write!(f, "Joker"), - Card::PlayingCard { rank, suit, .. } => { - write!(f, "{}[{}]", rank, suit) - } + Card::PlayingCard(card) => write!(f, "{}", card), } } } +} + +mod traits_numerics { + use super::*; + use std::convert::TryFrom; impl TryFrom for Rank { type Error = (); @@ -165,33 +193,64 @@ mod traits { } } - impl From for Card { - fn from(n: i64) -> Card { - if n < 0 { - Card::Joker(n) - } else { + impl TryFrom for PlayingCard { + type Error = (); + + fn try_from(n: i64) -> Result { + if n >= 0 { let deck = n / 52; let n = n % 52; // NOTE: If only Rust had Ada-like numeric contracts, this wouldn't // be necessary; n >= 0 => n % 52 in [0, 51] so Rank::try_from and - // Suit::try_from will always succeed + // Suit::try_from will always succeed. let rank = Rank::try_from(n / 4).unwrap(); let suit = Suit::try_from(n % 4).unwrap(); - Card::PlayingCard { deck, rank, suit } + Ok(Self { deck, rank, suit }) + } else { + Err(()) } } } + impl From for Card { + fn from(n: i64) -> Self { + if n < 0 { + Self::Joker(n) + } else { + // Since n >= 0, this should always succeed + let pc = PlayingCard::try_from(n).unwrap(); + Self::PlayingCard(pc) + } + } + } + + impl From for i64 { + fn from(card: PlayingCard) -> i64 { + let deck = card.deck as i64; + let rank = card.rank as i64; + let suit = card.suit as i64; + (deck * 52) + (rank * 4) + suit + } + } + impl From for i64 { fn from(card: Card) -> i64 { match card { Card::Joker(x) => x, - Card::PlayingCard { deck, rank, suit } => { - (deck * 52) + ((rank as i64) * 4) + (suit as i64) - } + Card::PlayingCard(pc) => i64::from(pc), } } } +} + +mod traits_ord { + use super::*; + use std::cmp::Ordering; + impl PartialEq for PlayingCard { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } + } impl PartialEq for Card { fn eq(&self, other: &Self) -> bool { @@ -199,29 +258,9 @@ mod traits { } } - impl Eq for Card {} - - impl Ord for Card { - fn cmp(&self, other: &Self) -> Ordering { - if self.is_joker() && other.is_joker() { - Ordering::Equal - } else if self.is_joker() { - Ordering::Less - } else if other.is_joker() { - Ordering::Greater - } else { - let self_val = i64::from(*self) % 52; - let other_val = i64::from(*other) % 52; - self_val.cmp(&other_val) - } - } - } - - impl Hash for Card { - fn hash(&self, state: &mut H) { - // NOTE: We're using the i64 conversion of card for the hash since that - // should generate unique numbers per card. - i64::from(*self).hash(state); + impl PartialOrd for PlayingCard { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } @@ -230,4 +269,34 @@ mod traits { Some(self.cmp(other)) } } + + impl Ord for PlayingCard { + fn cmp(&self, other: &Self) -> Ordering { + self.abs().cmp(&other.abs()) + } + } + + impl Ord for Card { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Self::PlayingCard(c1), Self::PlayingCard(c2)) => c1.cmp(c2), + (Self::Joker(_), Self::Joker(_)) => Ordering::Equal, + (Self::Joker(_), _) => Ordering::Less, + (_, Self::Joker(_)) => Ordering::Greater, + } + } + } +} + +mod traits_hash { + use super::*; + use std::hash::{Hash, Hasher}; + + impl Hash for Card { + fn hash(&self, state: &mut H) { + // NOTE: We're using the i64 conversion of card for the hash since that + // should generate unique numbers per card. + i64::from(*self).hash(state); + } + } }