diff --git a/src/card.rs b/src/card.rs new file mode 100644 index 0000000..5e106f7 --- /dev/null +++ b/src/card.rs @@ -0,0 +1,154 @@ +use std::cmp::Ordering; +use std::convert::TryFrom; +use std::hash::{Hash, Hasher}; + +#[derive(Debug, Copy, Clone)] +pub enum Rank { + Three = 0, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Jack, + Queen, + King, + Ace, + Two, +} + +#[derive(Debug, Copy, Clone)] +pub enum Suit { + Diamond = 0, + Club, + Heart, + Spade, +} + +#[derive(Debug, Clone, Copy)] +pub enum Card { + Joker(i64), + PlayingCard(i64, Rank, Suit), +} + +impl Card { + fn is_joker(&self) -> bool { + matches!(self, Card::Joker(_)) + } +} + +pub fn make_decks(number_of_decks: usize) -> Vec { + let number_of_decks: i64 = number_of_decks.try_into().unwrap(); + (-(number_of_decks * 2)..(52 * number_of_decks)) + .map(Card::from) + .collect::>() +} + +mod impls { + use super::*; + + impl TryFrom for Rank { + type Error = (); + + fn try_from(value: i64) -> Result { + match value { + 0 => Ok(Self::Three), + 1 => Ok(Self::Four), + 2 => Ok(Self::Five), + 3 => Ok(Self::Six), + 4 => Ok(Self::Seven), + 5 => Ok(Self::Eight), + 6 => Ok(Self::Nine), + 7 => Ok(Self::Ten), + 8 => Ok(Self::Jack), + 9 => Ok(Self::Queen), + 10 => Ok(Self::King), + 11 => Ok(Self::Ace), + 12 => Ok(Self::Two), + _ => Err(()), + } + } + } + + impl TryFrom for Suit { + type Error = (); + + fn try_from(value: i64) -> Result { + match value { + 0 => Ok(Self::Diamond), + 1 => Ok(Self::Club), + 2 => Ok(Self::Heart), + 3 => Ok(Self::Spade), + _ => Err(()), + } + } + } + + impl From for Card { + fn from(n: i64) -> Card { + if n < 0 { + Card::Joker(n) + } else { + 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 + let rank = Rank::try_from(n / 4).unwrap(); + let suit = Suit::try_from(n % 4).unwrap(); + Card::PlayingCard(deck, rank, 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) + } + } + } + } + + impl PartialEq for Card { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } + } + + 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 Card { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } +}