use anyhow::anyhow; pub(crate) use serde::{Deserialize, Serialize}; #[cfg(feature = "db")] use sqlx::{ encode::IsNull, error::BoxDynError, postgres::PgArgumentBuffer, postgres::PgTypeInfo, postgres::PgValueRef, Postgres, }; use std::fmt; use strum::EnumCount; use strum::IntoEnumIterator; use strum_macros::EnumCount; use strum_macros::EnumIter; use strum_macros::FromRepr; #[derive( PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter, EnumCount, Serialize, Deserialize, FromRepr, )] #[repr(u8)] pub enum Suit { Club, Diamond, Heart, Spade, } #[derive( PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter, Serialize, Deserialize, FromRepr, )] #[repr(u8)] 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 => "♠", }) } } #[cfg(feature = "db")] impl sqlx::Type for Rank { fn type_info() -> PgTypeInfo { >::type_info() } } #[cfg(feature = "db")] impl sqlx::Decode<'_, Postgres> for Rank { fn decode(value: PgValueRef<'_>) -> Result { let value = >::decode(value)?; Ok(Rank::from_repr(u8::try_from(value).expect("domain check")) .expect("domain check")) } } #[cfg(feature = "db")] impl sqlx::Encode<'_, Postgres> for Rank { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { let pg_value = *self as i16; >::encode_by_ref(&pg_value, buf) } } #[cfg(feature = "db")] impl sqlx::Type for Suit { fn type_info() -> PgTypeInfo { <&str as sqlx::Type>::type_info() } } #[cfg(feature = "db")] impl sqlx::Decode<'_, Postgres> for Suit { fn decode(value: PgValueRef<'_>) -> Result { let value = <&str as sqlx::Decode>::decode(value)?; match value { "club" => Ok(Suit::Club), "diamond" => Ok(Suit::Diamond), "heart" => Ok(Suit::Heart), "spade" => Ok(Suit::Spade), _ => panic!("invalid suit enum value"), } } } #[cfg(feature = "db")] impl sqlx::Encode<'_, Postgres> for Suit { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { let pg_value = match *self { Suit::Club => "club", Suit::Diamond => "diamond", Suit::Heart => "heart", Suit::Spade => "spade", }; <&str as sqlx::Encode<'_, Postgres>>::encode_by_ref(&pg_value, buf) } } 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, Serialize, Deserialize)] pub struct Card(pub Suit, pub Rank); impl Card { pub fn suit(&self) -> Suit { self.0 } pub fn rank(&self) -> Rank { self.1 } } 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_else(|| anyhow!("missing parts: {}", s))? .to_string() .parse()?; let rank = chars.collect::().parse()?; Ok(Card(suit, rank)) } } pub 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::>(), ); } }