summaryrefslogtreecommitdiff
path: root/protocol/src/card.rs
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-15 15:06:07 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-15 15:06:07 -0500
commit1f623cca3ea0937508b8c50f4c32a0972271e8f4 (patch)
tree3f5a36296ffc2afb1199cd65f9758c9667115450 /protocol/src/card.rs
parent8fa1b37bb705371bf5dee574f1f136019d3db9d1 (diff)
Move shared bridge library code into `protocol` crate
Diffstat (limited to 'protocol/src/card.rs')
-rw-r--r--protocol/src/card.rs277
1 files changed, 277 insertions, 0 deletions
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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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::<String>().parse()?;
+ Ok(Card(suit, rank))
+ }
+}
+
+fn make_deck() -> Vec<Card> {
+ 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::<Card>().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::<Vec<Card>>(),
+ );
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Deal {
+ pub north: Vec<Card>,
+ pub west: Vec<Card>,
+ pub south: Vec<Card>,
+ pub east: Vec<Card>,
+}
+
+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<R>(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,
+ }
+}