From 1f623cca3ea0937508b8c50f4c32a0972271e8f4 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Tue, 15 Nov 2022 15:06:07 -0500 Subject: Move shared bridge library code into `protocol` crate --- protocol/src/card.rs | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 protocol/src/card.rs (limited to 'protocol/src/card.rs') diff --git a/protocol/src/card.rs b/protocol/src/card.rs new file mode 100644 index 0000000..621bae1 --- /dev/null +++ b/protocol/src/card.rs @@ -0,0 +1,277 @@ +use anyhow::anyhow; +use rand::prelude::SliceRandom; +use rand::Rng; +use std::fmt; +use strum::EnumCount; +use strum::IntoEnumIterator; +use strum_macros::EnumCount; +use strum_macros::EnumIter; + +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter, EnumCount)] +pub enum Suit { + Club, + Diamond, + Heart, + Spade, +} + +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter)] +pub enum Rank { + Two = 2, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Jack, + Queen, + King, + Ace, +} + +impl fmt::Display for Suit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.write_str(match self { + Suit::Club => "♣", + Suit::Diamond => "♢", + Suit::Heart => "♡", + Suit::Spade => "♠", + }) + } +} + +impl std::str::FromStr for Suit { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s.trim() { + "♣" => Ok(Suit::Club), + "C" => Ok(Suit::Club), + "♢" => Ok(Suit::Diamond), + "♦" => Ok(Suit::Diamond), + "D" => Ok(Suit::Diamond), + "♡" => Ok(Suit::Heart), + "♥" => Ok(Suit::Heart), + "H" => Ok(Suit::Heart), + "♠" => Ok(Suit::Spade), + "S" => Ok(Suit::Spade), + _ => Err(anyhow!("invalid suit: {}", s)), + } + } +} + +impl fmt::Debug for Suit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl fmt::Display for Rank { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.write_str(match self { + Rank::Ace => "A", + Rank::King => "K", + Rank::Queen => "Q", + Rank::Jack => "J", + Rank::Ten => "10", + Rank::Nine => "9", + Rank::Eight => "8", + Rank::Seven => "7", + Rank::Six => "6", + Rank::Five => "5", + Rank::Four => "4", + Rank::Three => "3", + Rank::Two => "2", + }) + } +} + +impl fmt::Debug for Rank { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl std::str::FromStr for Rank { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s.trim().to_ascii_uppercase().as_str() { + "A" => Ok(Rank::Ace), + "K" => Ok(Rank::King), + "Q" => Ok(Rank::Queen), + "J" => Ok(Rank::Jack), + "10" => Ok(Rank::Ten), + "T" => Ok(Rank::Ten), + "9" => Ok(Rank::Nine), + "8" => Ok(Rank::Eight), + "7" => Ok(Rank::Seven), + "6" => Ok(Rank::Six), + "5" => Ok(Rank::Five), + "4" => Ok(Rank::Four), + "3" => Ok(Rank::Three), + "2" => Ok(Rank::Two), + _ => Err(anyhow!("invalid rank: {}", s)), + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Card(pub Suit, pub Rank); + +impl fmt::Display for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + let Card(suit, rank) = self; + write!(f, "{}{}", suit, rank) + } +} + +impl fmt::Debug for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self) + } +} + +impl std::str::FromStr for Card { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + let stripped = s.replace(" ", ""); + let mut chars = stripped.chars(); + let suit = chars + .next() + .ok_or(anyhow!("missing parts: {}", s))? + .to_string() + .parse()?; + let rank = chars.collect::().parse()?; + Ok(Card(suit, rank)) + } +} + +fn make_deck() -> Vec { + let mut result = vec![]; + for suit in Suit::iter() { + for rank in Rank::iter() { + result.push(Card(suit, rank)); + } + } + result +} + +#[derive(Default, PartialEq, Eq, Copy, Clone)] +pub enum RankOrder { + #[default] + Descending, + Ascending, +} + +pub fn sort_cards(suits: &[Suit; 4], ord: RankOrder, cards: &mut [Card]) { + let mut score: [u8; Suit::COUNT] = [0; Suit::COUNT]; + for (i, suit) in suits.iter().enumerate() { + score[*suit as usize] = i as u8; + } + cards.sort_by(|&Card(s1, r1), &Card(s2, r2)| { + let order = { + if s1 == s2 { + r1.cmp(&r2) + } else { + score[s1 as usize].cmp(&score[s2 as usize]) + } + }; + if ord == RankOrder::Descending { + order.reverse() + } else { + order + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use log::info; + + #[test] + fn sorting_cards() { + let card = |s: &str| s.parse::().unwrap(); + assert_eq!([card("♥2"), card("♥3"), card("♥4"),], { + let mut cards = [card("♥2"), card("♥4"), card("♥3")]; + sort_cards( + &[Suit::Heart, Suit::Spade, Suit::Club, Suit::Diamond], + RankOrder::Ascending, + &mut cards, + ); + cards + }); + assert_eq!([card("♥A"), card("♥3"), card("♥2"),], { + let mut cards = [card("♥2"), card("♥A"), card("♥3")]; + sort_cards( + &[Suit::Heart, Suit::Spade, Suit::Club, Suit::Diamond], + RankOrder::Descending, + &mut cards, + ); + cards + }); + assert_eq!([card("♠A"), card("♥A"), card("♣A"), card("♦A"),], { + let mut cards = [card("♣A"), card("♠A"), card("♥A"), card("♦A")]; + sort_cards( + &[Suit::Diamond, Suit::Club, Suit::Heart, Suit::Spade], + RankOrder::Descending, + &mut cards, + ); + cards + }); + } + + #[test] + fn string_conversion() { + crate::tests::test_setup(); + info!("deck: {:?}", make_deck()); + assert_eq!( + make_deck(), + make_deck() + .iter() + .map(|card| format!("{}", card).parse().unwrap()) + .collect::>(), + ); + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Deal { + pub north: Vec, + pub west: Vec, + pub south: Vec, + pub east: Vec, +} + +impl Deal { + pub fn sort(&mut self, suits: &[Suit; 4], ord: RankOrder) { + sort_cards(suits, ord, self.north.as_mut_slice()); + sort_cards(suits, ord, self.west.as_mut_slice()); + sort_cards(suits, ord, self.south.as_mut_slice()); + sort_cards(suits, ord, self.east.as_mut_slice()); + } +} + +pub fn deal(rng: &mut R) -> Deal +where + R: Rng, +{ + let mut deck = make_deck(); + deck.shuffle(rng); + let mut deck = deck.iter(); + let north = deck.by_ref().take(13).cloned().collect(); + let west = deck.by_ref().take(13).cloned().collect(); + let south = deck.by_ref().take(13).cloned().collect(); + let east = deck.by_ref().take(13).cloned().collect(); + Deal { + north, + west, + south, + east, + } +} -- cgit v1.2.3