openpql_prelude/card/
card.rs

1use super::{
2    Card64, CardCount, Display, FromStr, Hash, ParseError, Rank, Suit,
3};
4
5#[macro_export]
6macro_rules! card {
7    ($s:expr) => {
8        $s.parse::<$crate::Card>().unwrap()
9    };
10}
11
12#[macro_export]
13macro_rules! cards {
14    ($s:expr) => {{
15        let s: &str = $s;
16        let mut cards = Vec::new();
17        let mut chars = s.chars().filter(|c| !c.is_whitespace());
18        while let (Some(r), Some(s)) = (chars.next(), chars.next()) {
19            cards.push($crate::card![format!("{r}{s}")]);
20        }
21        cards
22    }};
23}
24
25/// Playing card representation.
26///
27/// Represents a single playing card with a rank and suit, with macros for convenient creation.
28#[derive(Clone, Copy, Debug, Display, Hash, PartialEq, Eq, PartialOrd, Ord)]
29#[display("{rank}{suit}")]
30pub struct Card {
31    pub rank: Rank,
32    pub suit: Suit,
33}
34
35impl Card {
36    /// Total number of cards in a standard deck
37    pub const N_CARDS: CardCount = Suit::N_SUITS * Rank::N_RANKS;
38    /// Total number of cards in a short deck
39    pub const N_CARDS_SD: CardCount = Suit::N_SUITS * Rank::N_RANKS_SD;
40
41    const ARR_ALL: [Self; Self::N_CARDS as usize] = sealed::all_cards();
42    const ARR_ALL_SD: [Self; Self::N_CARDS_SD as usize] =
43        sealed::all_cards_sd();
44
45    /// Creates a new card with the specified rank and suit.
46    #[must_use]
47    #[inline]
48    pub const fn new(r: Rank, s: Suit) -> Self {
49        Self { rank: r, suit: s }
50    }
51
52    /// Returns a slice of all cards
53    #[inline]
54    pub const fn all<const SD: bool>() -> &'static [Self] {
55        const {
56            if SD {
57                &Self::ARR_ALL_SD
58            } else {
59                &Self::ARR_ALL
60            }
61        }
62    }
63
64    #[inline]
65    pub(crate) const fn to_c64(self) -> Card64 {
66        let mut res = Card64::EMPTY;
67        res.set(self);
68
69        res
70    }
71
72    #[inline]
73    pub(crate) const fn eq(self, other: Self) -> bool {
74        self.rank.eq(other.rank) && self.suit.eq(other.suit)
75    }
76}
77
78// compiler-time functions
79#[cfg_attr(coverage_nightly, coverage(off))]
80mod sealed {
81    use super::{Card, CardCount, Rank, Suit};
82
83    const fn mk_card(r: CardCount, s: CardCount) -> Card {
84        Card::new(Rank::all::<false>()[r as usize], Suit::ARR_ALL[s as usize])
85    }
86
87    pub(super) const fn all_cards() -> [Card; Card::N_CARDS as usize] {
88        let mut res = [mk_card(0, 0); Card::N_CARDS as usize];
89        let mut i = 0;
90
91        while i < Card::N_CARDS {
92            res[i as usize] = mk_card(i / Suit::N_SUITS, i % Suit::N_SUITS);
93
94            i += 1;
95        }
96
97        res
98    }
99
100    pub(super) const fn all_cards_sd() -> [Card; Card::N_CARDS_SD as usize] {
101        const SHIFT: CardCount = 4;
102        let mut res = [mk_card(0, 0); Card::N_CARDS_SD as usize];
103        let mut i = 0;
104
105        while i < Card::N_CARDS_SD {
106            res[i as usize] =
107                mk_card(i / Suit::N_SUITS + SHIFT, i % Suit::N_SUITS);
108
109            i += 1;
110        }
111
112        res
113    }
114}
115
116impl Default for Card {
117    fn default() -> Self {
118        Self::new(Rank::R2, Suit::S)
119    }
120}
121
122impl FromStr for Card {
123    type Err = ParseError;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        let mut cs = s.chars().filter(|c| !c.is_whitespace());
127
128        if let Some(c) = cs.next()
129            && let Ok(r) = Rank::try_from(c)
130            && let Some(c) = cs.next()
131            && let Ok(s) = Suit::try_from(c)
132            && cs.next().is_none()
133        {
134            return Ok(Self::new(r, s));
135        }
136
137        Err(ParseError::InvalidCard(s.into()))
138    }
139}
140
141#[cfg(any(test, feature = "quickcheck"))]
142impl quickcheck::Arbitrary for Card {
143    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
144        *g.choose(&Self::ARR_ALL).unwrap()
145    }
146}
147
148#[cfg(test)]
149#[cfg_attr(coverage_nightly, coverage(off))]
150mod tests {
151    use super::*;
152    use crate::*;
153
154    #[quickcheck]
155    fn test_all(cards: CardN<3>) {
156        for c in cards {
157            if c.rank >= Rank::R6 {
158                assert!(Card::all::<true>().contains(&c));
159            }
160
161            assert!(Card::all::<false>().contains(&c));
162        }
163    }
164
165    #[test]
166    fn test_default() {
167        assert_eq!(card!("2s"), Card::default());
168    }
169
170    #[test]
171    fn test_from_str() {
172        let c = |s| Ok(cards!(s)[0]);
173
174        assert_eq!(c("2s"), "2s".parse());
175        assert_eq!(c("2s"), " 2 S ".parse());
176        assert_eq!(
177            Err(ParseError::InvalidCard("2s?".to_owned())),
178            "2s?".parse::<Card>()
179        );
180        assert!("".parse::<Card>().is_err());
181        assert!("?".parse::<Card>().is_err());
182        assert!("2".parse::<Card>().is_err());
183        assert!("2k".parse::<Card>().is_err());
184    }
185
186    #[quickcheck]
187    fn test_to_string(c: Card) {
188        assert_eq!(format!("{}{}", c.rank, c.suit), c.to_string());
189    }
190
191    #[quickcheck]
192    fn test_macro(cs: CardN<3>) {
193        assert_eq!(cs[0], card!(&cs[0].to_string()));
194        let v = cs.as_slice().to_vec();
195        let s: String = v.iter().map(ToString::to_string).collect();
196        assert_eq!(v, cards!(&s));
197    }
198
199    #[test]
200    fn test_ord() {
201        let mut sorted = Card::ARR_ALL.to_vec();
202        sorted.sort_unstable();
203
204        assert_eq!(sorted, Card::ARR_ALL);
205    }
206}