pokerface/
card.rs

1use rand::seq::SliceRandom;
2use rand::thread_rng;
3use std::fmt::Display;
4use std::hash::Hash;
5use strum::IntoEnumIterator;
6use strum_macros::EnumIter;
7
8use thiserror::Error;
9
10/// The [`Suit`] enum. It represents the categories into which the cards of a deck are divided: [`Suit::Hearts`], [`Suit::Diamonds`], [`Suit::Spades`], [`Suit::Clubs`]
11///
12#[derive(Debug, PartialEq, Clone, Copy, EnumIter, Eq, Hash)]
13pub enum Suit {
14    /// ♥️
15    Hearts,
16    /// ♦️
17    Diamonds,
18    /// ♠️
19    Spades,
20    /// ♣️
21    Clubs,
22}
23
24/// The [`Card`] struct. It represents a card, composed by a [`Suit`] enum and a rank value.
25///
26/// The range of rank values is described as follows:
27///  A,  K,  Q,  J, 10, 9, 8, 7, 6, 5, 4, 3, 2
28/// 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2
29///
30#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
31pub struct Card {
32    pub val: u8,
33    pub suit: Suit,
34}
35
36impl Card {
37    pub fn new(val: u8, suit: Suit) -> Self {
38        Card { suit, val }
39    }
40}
41
42impl PartialOrd for Card {
43    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
44        self.val.partial_cmp(&other.val)
45    }
46}
47
48impl Ord for Card {
49    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
50        self.val.cmp(&other.val)
51    }
52}
53
54#[derive(Error, Debug)]
55pub enum CardError {
56    #[error("The card string is not the correct length")]
57    InvalidLength,
58
59    #[error("The card Value Rank is invalid")]
60    InvalidValue,
61
62    #[error("The card Suit is invalid")]
63    InvalidSuit,
64}
65
66impl TryFrom<&str> for Card {
67    type Error = CardError;
68
69    fn try_from(card: &str) -> Result<Self, Self::Error> {
70        if card.len() != 2 && card.len() != 3 {
71            return Err(CardError::InvalidLength);
72        }
73
74        let chars: Vec<char> = card.chars().collect();
75        let val = chars[0];
76
77        let val = match val {
78            'A' => 14,
79            'K' => 13,
80            'Q' => 12,
81            'J' => 11,
82            '1' => 10,
83            '2'..='9' => val.to_digit(10).unwrap() as u8,
84            _ => return Err(CardError::InvalidValue),
85        };
86
87        if val == 1 && card.len() == 2 {
88            return Err(CardError::InvalidValue);
89        }
90
91        let suit = chars[if val == 10 { 2 } else { 1 }];
92
93        let suit = match suit.to_ascii_lowercase() {
94            'h' => Suit::Hearts,
95            's' => Suit::Spades,
96            'd' => Suit::Diamonds,
97            'c' => Suit::Clubs,
98            _ => return Err(CardError::InvalidSuit),
99        };
100
101        Ok(Card::new(val, suit))
102    }
103}
104
105impl Display for Card {
106    
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, " ")?;
109        match self.val {
110            n @ 2..=10 => write!(f, "{n}"),
111            14 => write!(f, "A"),
112            13 => write!(f, "K"),
113            12 => write!(f, "Q"),
114            11 => write!(f, "J"),
115            _ => write!(f, "?"),
116        }?;
117        match self.suit {
118            //
119            Suit::Hearts => write!(f, "♥️"),   //H
120            Suit::Spades => write!(f, "♠️"),   //S
121            Suit::Diamonds => write!(f, "♦️"), //D
122            Suit::Clubs => write!(f, "♣️"),    //C
123        }?;
124        write!(f, "{}", if self.val != 10 {" "} else {""})
125    }
126}
127
128/// An array of 5 cards compose a [`Hand`].
129///
130#[derive(Debug, PartialEq, Clone, Eq)]
131pub struct Hand {
132    hand: [Card; 5],
133}
134
135impl Hand {
136    /// sorting the hand in a descenting order
137    ///
138    pub fn sort(&mut self) -> () {
139        self.hand.sort();
140        self.hand.reverse();
141    }
142
143    /// Borrowing the cards of this [`Hand`].
144    ///
145    pub const fn get_hand_slice(&self) -> &[Card; 5] {
146        &self.hand
147    }
148
149    /// An handy constructor for tests and macros
150    ///
151    pub fn new(hand: [Card; 5]) -> Hand {
152        Hand { hand }
153    }
154}
155
156/// A vector of 52 cards compose a [`Deck`], plus an iterator to help getting (/borrowing) cards out of the deck.
157///
158#[derive(Debug, PartialEq, Clone)]
159pub struct Deck {
160    deck: Vec<Card>,
161    it: usize,
162}
163
164impl Deck {
165    /// It creates a shuffled deck, ready to play.
166    ///
167    pub fn create_shuffled_deck() -> Deck {
168        let mut deck: Vec<Card> = Vec::new();
169
170        for suit in Suit::iter() {
171            for val in 2..=14 {
172                deck.push(Card::new(val, suit));
173            }
174        }
175        let mut rng = thread_rng();
176        deck.shuffle(&mut rng);
177        Deck { deck, it: 0 }
178    }
179
180    /// It gets a [Hand] of 5 cards from the deck.
181    ///
182    pub fn hand(&mut self) -> Option<Hand> {
183        if self.it == 52 {
184            return None; // deck is finisced!
185        }
186        let hand: Hand = Hand {
187            hand: self.deck[self.it..=(self.it + 4)].try_into().ok()?,
188        };
189        self.it += 5; // got 5 cards from the deck
190        Some(hand)
191    }
192}
193
194impl Display for Hand {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        for i in 0..=4 {
197            write!(f, "{} ", self.hand[i])?;
198        }
199        writeln!(f, "")
200    }
201}
202
203impl Display for Deck {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        for i in 0..52 {
206            write!(f, "{} ", self.deck[i])?;
207            if (i + 1) % 13 == 0 {
208                writeln!(f, "")?;
209            }
210        }
211        write!(f, "")
212    }
213}
214
215#[macro_export]
216macro_rules! newcard {
217    ($val:expr, $suit:tt) => {
218        Card::new($val, $suit)
219    };
220    ($val:expr) => {
221        Card::try_from($val).unwrap_or_else(|err| panic!("{}", err))
222    };
223}
224
225#[macro_export]
226macro_rules! hand {
227    ($c:expr,$c1:expr,$c2:expr,$c3:expr,$c4:expr) => {
228        Hand::new([
229            newcard![$c],
230            newcard![$c1],
231            newcard![$c2],
232            newcard![$c3],
233            newcard![$c4],
234        ])
235    };
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_try_from_valid_card() {
244        let card = newcard!["Ad"];
245        assert_eq!(card.val, 14);
246        assert_eq!(card.suit, Suit::Diamonds);
247
248        assert_eq!(newcard!["Ah"], Card::new(14, Suit::Hearts));
249        assert_eq!(newcard!["Kh"], Card::new(13, Suit::Hearts));
250        assert_eq!(newcard!["Qh"], Card::new(12, Suit::Hearts));
251        assert_eq!(newcard!["Jh"], Card::new(11, Suit::Hearts));
252        assert_eq!(newcard!["10h"], Card::new(10, Suit::Hearts));
253        assert_eq!(newcard!["9h"], Card::new(9, Suit::Hearts));
254    }
255
256    #[test]
257    fn test_try_from_valid_hand() {
258        let hand = hand!["Ad", "Kd", "Qd", "Jd", "10d"];
259        assert_eq!(hand.hand[0], Card::new(14, Suit::Diamonds));
260    }
261}