open_pql/base/
card.rs

1use super::{
2    Display, FromStr, Hash, ParseError, Rank, RankIdx, Suit, SuitIdx, fmt,
3};
4
5/// Creates a vector of cards from a string representation.
6///
7/// # Examples
8///
9/// ```
10/// use open_pql::cards;
11///
12/// let cards = cards!("As Kh 2d");
13/// assert_eq!(cards.len(), 3);
14/// ```
15#[cfg(any(test, feature = "benchmark"))]
16#[macro_export]
17macro_rules! cards {
18    ($s:expr) => {
19        $crate::Card::new_vec($s)
20    };
21}
22
23/// Creates a single card from a string representation.
24///
25/// # Examples
26///
27/// ```
28/// use open_pql::card;
29///
30/// let ace_spades = card!("As");
31/// ```
32#[cfg(any(test, feature = "benchmark"))]
33#[macro_export]
34macro_rules! card {
35    ($s:expr) => {
36        cards!($s)[0]
37    };
38}
39
40/// Single Card
41///
42/// Represents a single playing card with a rank and suit.
43/// Cards are ordered first by rank then by suit for consistent comparison.
44///
45/// # Examples
46///
47/// ```
48/// use open_pql::{Card, Rank::*, Suit::*};
49///
50/// let card = Card::new(RA, S);
51/// assert_eq!(card.rank, RA);
52/// assert_eq!(card.suit, S);
53/// assert_eq!(card.to_string(), "As");
54/// ```
55#[derive(Copy, Clone, Display, Hash, PartialEq, Eq, PartialOrd, Ord)]
56#[display("{rank}{suit}")]
57pub struct Card {
58    pub rank: Rank,
59    pub suit: Suit,
60}
61
62impl Card {
63    /// Array of all 52 playing cards in a standard deck.
64    /// Cards are ordered by rank (2-A) then by suit (S, H, D, C).
65    pub const ARR_ALL: [Self; 52] = [
66        Self::new(Rank::R2, Suit::S),
67        Self::new(Rank::R2, Suit::H),
68        Self::new(Rank::R2, Suit::D),
69        Self::new(Rank::R2, Suit::C),
70        Self::new(Rank::R3, Suit::S),
71        Self::new(Rank::R3, Suit::H),
72        Self::new(Rank::R3, Suit::D),
73        Self::new(Rank::R3, Suit::C),
74        Self::new(Rank::R4, Suit::S),
75        Self::new(Rank::R4, Suit::H),
76        Self::new(Rank::R4, Suit::D),
77        Self::new(Rank::R4, Suit::C),
78        Self::new(Rank::R5, Suit::S),
79        Self::new(Rank::R5, Suit::H),
80        Self::new(Rank::R5, Suit::D),
81        Self::new(Rank::R5, Suit::C),
82        Self::new(Rank::R6, Suit::S),
83        Self::new(Rank::R6, Suit::H),
84        Self::new(Rank::R6, Suit::D),
85        Self::new(Rank::R6, Suit::C),
86        Self::new(Rank::R7, Suit::S),
87        Self::new(Rank::R7, Suit::H),
88        Self::new(Rank::R7, Suit::D),
89        Self::new(Rank::R7, Suit::C),
90        Self::new(Rank::R8, Suit::S),
91        Self::new(Rank::R8, Suit::H),
92        Self::new(Rank::R8, Suit::D),
93        Self::new(Rank::R8, Suit::C),
94        Self::new(Rank::R9, Suit::S),
95        Self::new(Rank::R9, Suit::H),
96        Self::new(Rank::R9, Suit::D),
97        Self::new(Rank::R9, Suit::C),
98        Self::new(Rank::RT, Suit::S),
99        Self::new(Rank::RT, Suit::H),
100        Self::new(Rank::RT, Suit::D),
101        Self::new(Rank::RT, Suit::C),
102        Self::new(Rank::RJ, Suit::S),
103        Self::new(Rank::RJ, Suit::H),
104        Self::new(Rank::RJ, Suit::D),
105        Self::new(Rank::RJ, Suit::C),
106        Self::new(Rank::RQ, Suit::S),
107        Self::new(Rank::RQ, Suit::H),
108        Self::new(Rank::RQ, Suit::D),
109        Self::new(Rank::RQ, Suit::C),
110        Self::new(Rank::RK, Suit::S),
111        Self::new(Rank::RK, Suit::H),
112        Self::new(Rank::RK, Suit::D),
113        Self::new(Rank::RK, Suit::C),
114        Self::new(Rank::RA, Suit::S),
115        Self::new(Rank::RA, Suit::H),
116        Self::new(Rank::RA, Suit::D),
117        Self::new(Rank::RA, Suit::C),
118    ];
119
120    /// Array of 36 cards for short deck poker (6-A).
121    /// Excludes ranks 2-5, commonly used in some poker variants.
122    pub const ARR_ALL_SHORT: [Self; 36] = [
123        Self::new(Rank::R6, Suit::S),
124        Self::new(Rank::R6, Suit::H),
125        Self::new(Rank::R6, Suit::D),
126        Self::new(Rank::R6, Suit::C),
127        Self::new(Rank::R7, Suit::S),
128        Self::new(Rank::R7, Suit::H),
129        Self::new(Rank::R7, Suit::D),
130        Self::new(Rank::R7, Suit::C),
131        Self::new(Rank::R8, Suit::S),
132        Self::new(Rank::R8, Suit::H),
133        Self::new(Rank::R8, Suit::D),
134        Self::new(Rank::R8, Suit::C),
135        Self::new(Rank::R9, Suit::S),
136        Self::new(Rank::R9, Suit::H),
137        Self::new(Rank::R9, Suit::D),
138        Self::new(Rank::R9, Suit::C),
139        Self::new(Rank::RT, Suit::S),
140        Self::new(Rank::RT, Suit::H),
141        Self::new(Rank::RT, Suit::D),
142        Self::new(Rank::RT, Suit::C),
143        Self::new(Rank::RJ, Suit::S),
144        Self::new(Rank::RJ, Suit::H),
145        Self::new(Rank::RJ, Suit::D),
146        Self::new(Rank::RJ, Suit::C),
147        Self::new(Rank::RQ, Suit::S),
148        Self::new(Rank::RQ, Suit::H),
149        Self::new(Rank::RQ, Suit::D),
150        Self::new(Rank::RQ, Suit::C),
151        Self::new(Rank::RK, Suit::S),
152        Self::new(Rank::RK, Suit::H),
153        Self::new(Rank::RK, Suit::D),
154        Self::new(Rank::RK, Suit::C),
155        Self::new(Rank::RA, Suit::S),
156        Self::new(Rank::RA, Suit::H),
157        Self::new(Rank::RA, Suit::D),
158        Self::new(Rank::RA, Suit::C),
159    ];
160
161    /// Creates a new card with the specified rank and suit.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use open_pql::{Card, Rank, Suit};
167    ///
168    /// let ace_spades = Card::new(Rank::RA, Suit::S);
169    /// ```
170    #[must_use]
171    #[inline]
172    pub const fn new(r: Rank, s: Suit) -> Self {
173        Self { rank: r, suit: s }
174    }
175
176    /// Creates a card from rank and suit indices.
177    #[must_use]
178    #[inline]
179    pub(crate) fn from_indices(r: RankIdx, s: SuitIdx) -> Self {
180        Self {
181            rank: r.to_rank(),
182            suit: s.to_suit(),
183        }
184    }
185
186    /// Converts the card to a single u8 representation [xxSSRRRR].
187    pub(crate) const fn to_u8(self) -> u8 {
188        const SHIFT_SUIT: u8 = 4;
189        (self.rank as u8) | ((self.suit as u8) << SHIFT_SUIT)
190    }
191
192    /// Creates a card from a u8 [xxSSRRRR].
193    pub(crate) fn from_u8(v: u8) -> Self {
194        const SHIFT_SUIT: u8 = 4;
195        Self::from_indices(
196            RankIdx::new(v & 0b1111),
197            SuitIdx::new(v >> SHIFT_SUIT),
198        )
199    }
200}
201
202/// Default implementation for Card.
203///
204/// Returns the Two of Spades as the default card.
205impl Default for Card {
206    fn default() -> Self {
207        Self::new(Rank::R2, Suit::S)
208    }
209}
210
211/// Debug implementation for Card.
212///
213/// Uses the Display format for debug output.
214impl fmt::Debug for Card {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        write!(f, "{self}")
217    }
218}
219
220/// `FromStr` implementation for Card.
221///
222/// Parses a card from string format like "As" (Ace of Spades).
223/// Whitespace is ignored during parsing.
224///
225/// # Errors
226///
227/// Returns `ParseError::InvalidCard` if the string cannot be parsed as a valid card.
228///
229/// # Examples
230///
231/// ```
232/// use open_pql::Card;
233///
234/// let card: Card = "As".parse().unwrap();
235/// let card_with_spaces: Card = " A s ".parse().unwrap();
236/// ```
237impl FromStr for Card {
238    type Err = ParseError;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        let mut cs = s.chars().filter(|c| !c.is_whitespace());
242
243        if let Some(c) = cs.next()
244            && let Ok(r) = Rank::try_from(c)
245            && let Some(c) = cs.next()
246            && let Ok(s) = Suit::try_from(c)
247            && cs.next().is_none()
248        {
249            return Ok(Self::new(r, s));
250        }
251
252        Err(ParseError::InvalidCard(s.into()))
253    }
254}
255
256impl<T> From<T> for Rank
257where
258    Card: From<T>,
259{
260    fn from(v: T) -> Self {
261        Card::from(v).rank
262    }
263}
264
265impl<T> From<T> for Suit
266where
267    Card: From<T>,
268{
269    fn from(v: T) -> Self {
270        Card::from(v).suit
271    }
272}
273
274#[cfg(any(test, feature = "benchmark"))]
275impl Card {
276    /// Creates a card from a tuple of characters representing rank and suit.
277    ///
278    /// # Panics
279    ///
280    /// Panics if the characters cannot be converted to valid rank and suit.
281    /// This is intended to be used in tests and benchmarks only.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use open_pql::Card;
287    ///
288    /// let card = Card::from_tuple(('A', 's'));
289    /// ```
290    pub fn from_tuple((r, s): (char, char)) -> Self {
291        Self::new(r.try_into().unwrap(), s.try_into().unwrap())
292    }
293
294    /// Creates a vector of cards from a string representation.
295    ///
296    /// Parses pairs of characters as rank-suit combinations.
297    ///
298    /// # Examples
299    ///
300    /// ```
301    /// use open_pql::Card;
302    ///
303    /// let cards = Card::new_vec("As Kh Qd");
304    /// assert_eq!(cards.len(), 3);
305    /// ```
306    pub fn new_vec(s: &str) -> Vec<Self> {
307        use itertools::Itertools;
308
309        s.chars()
310            .filter(|c| !c.is_whitespace())
311            .tuples()
312            .map(Self::from_tuple)
313            .collect()
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use crate::*;
321
322    impl Arbitrary for Card {
323        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
324            *g.choose(&Self::ARR_ALL).unwrap()
325        }
326    }
327
328    #[quickcheck]
329    fn test_rank_suit(r: Rank, s: Suit) {
330        let card = Card::new(r, s);
331
332        assert_eq!(r, card.rank);
333        assert_eq!(s, card.suit);
334    }
335
336    #[test]
337    fn test_default() {
338        assert_eq!(Card::new(Rank::R2, Suit::S), Card::default());
339        assert_eq!(card!("2s"), Card::default());
340    }
341
342    #[quickcheck]
343    fn test_hash(c1: Card) {
344        use std::hash::DefaultHasher;
345
346        let c2 = c1;
347
348        let mut h1 = DefaultHasher::new();
349        let mut h2 = DefaultHasher::new();
350
351        c1.hash(&mut h1);
352        c2.hash(&mut h2);
353
354        assert_eq!(h1.finish(), h2.finish());
355    }
356
357    #[quickcheck]
358    fn test_into_rank_and_suit(c: Card) {
359        let r: Rank = c.into();
360        let s: Suit = c.into();
361
362        assert_eq!(r, c.rank);
363        assert_eq!(s, c.suit);
364    }
365
366    #[test]
367    fn test_from_str() {
368        let c = |s| Ok(cards!(s)[0]);
369
370        assert_eq!(c("2s"), "2s".parse());
371        assert_eq!(c("2s"), " 2 S ".parse());
372        assert_eq!(
373            Err(ParseError::InvalidCard("2s?".to_owned())),
374            "2s?".parse::<Card>()
375        );
376        assert!("".parse::<Card>().is_err());
377        assert!("?".parse::<Card>().is_err());
378        assert!("2".parse::<Card>().is_err());
379        assert!("2k".parse::<Card>().is_err());
380    }
381
382    #[quickcheck]
383    fn test_to_string(c: Card) {
384        assert_eq!(format!("{}{}", c.rank, c.suit), c.to_string());
385    }
386
387    #[quickcheck]
388    fn test_to_u8(c: Card) {
389        assert_eq!(c, Card::from_u8(c.to_u8()));
390    }
391
392    #[test]
393    fn test_macro() {
394        let cards = cards!("As Kh");
395        assert_eq!(
396            cards,
397            vec![Card::new(Rank::RA, Suit::S), Card::new(Rank::RK, Suit::H),]
398        );
399
400        let flop = flop!("2s 3s 4s");
401        assert_eq!(
402            flop,
403            (
404                Card::new(Rank::R2, Suit::S),
405                Card::new(Rank::R3, Suit::S),
406                Card::new(Rank::R4, Suit::S),
407            )
408                .into()
409        );
410
411        let board = board!("As Kh 3s 5h 6c");
412        assert_eq!(
413            board,
414            (
415                Card::new(Rank::RA, Suit::S),
416                Card::new(Rank::RK, Suit::H),
417                Card::new(Rank::R3, Suit::S),
418                Card::new(Rank::R5, Suit::H),
419                Card::new(Rank::R6, Suit::C),
420            )
421                .into(),
422        );
423    }
424
425    #[test]
426    fn test_ord() {
427        let mut sorted = Card::ARR_ALL.to_vec();
428        sorted.reverse();
429        sorted.sort_unstable();
430
431        assert_eq!(sorted, Card::ARR_ALL);
432    }
433}