openpql_prelude/card/
card64.rs

1use super::{
2    BitAnd, BitAndAssign, BitOr, BitOrAssign, Card, Card64Inner, CardCount,
3    CardIdx, CardIter, Hash, Idx, Not, Rank, Rank16, Rank16Inner, Suit, fmt,
4    ops,
5};
6
7#[macro_export]
8macro_rules! c64 {
9    ($s:expr) => {
10        $crate::Card64::from($crate::cards![$s].as_ref())
11    };
12}
13
14/// Bitset representation of card collections.
15///
16/// A 64-bit bitset for efficient card set operations. Each bit represents a specific card,
17/// enabling fast membership tests and set operations.
18///
19/// # Memory Layout
20/// ```text
21/// [63, 48]:  xxxAKQJT 98765432  // Club
22/// [47, 32]:  xxxAKQJT 98765432  // Diamond
23/// [31, 16]:  xxxAKQJT 98765432  // Heart
24/// [15, 0]:   xxxAKQJT 98765432  // Spade, x: unused
25/// ```
26#[derive(
27    Copy,
28    Clone,
29    PartialEq,
30    Eq,
31    Hash,
32    BitAnd,
33    BitAndAssign,
34    BitOr,
35    BitOrAssign,
36    Default,
37)]
38pub struct Card64(pub(crate) Card64Inner);
39
40impl Card64 {
41    const OFFSET_SUIT: Idx = 16;
42
43    pub(crate) const EMPTY: Self = Self(0);
44
45    const ALL: Self = sealed::all::<false>();
46    const ALL_SD: Self = sealed::all::<true>();
47
48    #[must_use]
49    #[inline]
50    pub const fn all<const SD: bool>() -> Self {
51        const { if SD { Self::ALL_SD } else { Self::ALL } }
52    }
53
54    /// Returns `true` if the set contains no cards.
55    #[must_use]
56    #[inline]
57    pub const fn is_empty(self) -> bool {
58        self.0 == 0
59    }
60
61    /// Adds the specified card to this set.
62    #[inline]
63    pub const fn set(&mut self, card: Card) {
64        self.0 |= Self::from_card(card).0;
65    }
66
67    /// Removes the specified card from this set.
68    #[inline]
69    pub const fn unset(&mut self, card: Card) {
70        self.0 &= !Self::from_card(card).0;
71    }
72
73    /// Returns `true` if this set contains all cards in `other`.
74    #[must_use]
75    #[inline]
76    pub fn contains(self, other: Self) -> bool {
77        other & self == other
78    }
79
80    /// Returns `true` if this set contains the specified card.
81    #[must_use]
82    #[inline]
83    pub const fn contains_card(self, card: Card) -> bool {
84        let v = Self::from_card(card).0;
85        v & self.0 == v
86    }
87
88    /// Returns the number of cards in this set.
89    #[must_use]
90    #[inline]
91    #[allow(clippy::cast_possible_truncation)]
92    pub const fn count(&self) -> CardCount {
93        self.0.count_ones() as CardCount
94    }
95
96    /// Returns the number of cards with the specified rank.
97    #[must_use]
98    #[inline]
99    pub const fn count_by_rank(self, rank: Rank) -> CardCount {
100        Self(self.0 & Self::from_ranks(Rank16::from_rank(rank)).0).count()
101    }
102
103    /// Returns the number of cards with the specified suit.
104    #[must_use]
105    #[inline]
106    pub const fn count_by_suit(self, suit: Suit) -> CardCount {
107        self.ranks_by_suit(suit).count()
108    }
109
110    #[inline]
111    #[allow(clippy::cast_possible_truncation)]
112    pub(crate) const fn ranks_by_suit(self, suit: Suit) -> Rank16 {
113        Rank16((self.0 >> (Self::OFFSET_SUIT * suit as Idx)) as Rank16Inner)
114    }
115
116    /// Creates a card set containing all specified ranks in all suits.
117    #[inline]
118    #[must_use]
119    pub const fn from_ranks(rs: Rank16) -> Self {
120        let [l, h] = rs.0.to_le_bytes();
121
122        Self(Card64Inner::from_le_bytes([l, h, l, h, l, h, l, h]))
123    }
124
125    /// Creates a card set containing all ranks in a specified suit.
126    #[inline]
127    #[must_use]
128    pub const fn from_suit(suit: Suit) -> Self {
129        let v = Rank16::all::<false>().0 as Card64Inner;
130
131        Self(v << (Self::OFFSET_SUIT * (suit as Idx)))
132    }
133
134    /// Returns the set of ranks present in this card set.
135    #[inline]
136    #[must_use]
137    pub const fn ranks(self) -> Rank16 {
138        Rank16(
139            self.ranks_by_suit(Suit::S).0
140                | self.ranks_by_suit(Suit::H).0
141                | self.ranks_by_suit(Suit::D).0
142                | self.ranks_by_suit(Suit::C).0,
143        )
144    }
145
146    /// Returns an iterator over all cards in this set.
147    pub const fn iter(self) -> CardIter {
148        CardIter::new(self)
149    }
150
151    const fn from_indices(r: Idx, s: Idx) -> Self {
152        Self(1 << r << (s * Self::OFFSET_SUIT))
153    }
154
155    const fn from_card(card: Card) -> Self {
156        Self::from_indices(card.rank as Idx, card.suit as Idx)
157    }
158}
159
160// compiler-time functions
161#[allow(clippy::cast_possible_wrap)]
162#[cfg_attr(coverage_nightly, coverage(off))]
163mod sealed {
164    use super::{Card, Card64, CardIdx, Idx};
165
166    pub(super) const fn all<const SD: bool>() -> Card64 {
167        let start_idx = if SD {
168            Card::N_CARDS - Card::N_CARDS_SD
169        } else {
170            0
171        };
172
173        let mut res = Card64::EMPTY;
174        let mut i = 0;
175
176        while i < Card::N_CARDS {
177            if i >= start_idx {
178                res.set(CardIdx(i as Idx).to_card().unwrap());
179            }
180            i += 1;
181        }
182
183        res
184    }
185}
186
187impl Not for Card64 {
188    type Output = Self;
189
190    fn not(self) -> Self::Output {
191        Self(!self.0 & Self::ALL.0)
192    }
193}
194
195impl fmt::Debug for Card64 {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        fn ranks_str(r16: Rank16) -> String {
198            if r16.is_empty() {
199                "_".to_string()
200            } else {
201                r16.to_string()
202            }
203        }
204
205        if self.count() == 1 {
206            write!(f, "Card64<{}>", self.iter().next().unwrap())
207        } else {
208            f.debug_tuple("Card64")
209                .field(&format_args!(
210                    "{}",
211                    ranks_str(self.ranks_by_suit(Suit::S))
212                ))
213                .field(&format_args!(
214                    "{}",
215                    ranks_str(self.ranks_by_suit(Suit::H))
216                ))
217                .field(&format_args!(
218                    "{}",
219                    ranks_str(self.ranks_by_suit(Suit::D))
220                ))
221                .field(&format_args!(
222                    "{}",
223                    ranks_str(self.ranks_by_suit(Suit::C))
224                ))
225                .finish()
226        }
227    }
228}
229
230impl From<Card> for Card64 {
231    fn from(c: Card) -> Self {
232        Self::from_card(c)
233    }
234}
235
236impl From<Card64Inner> for Card64 {
237    fn from(i: Card64Inner) -> Self {
238        Self(i & Self::ALL.0)
239    }
240}
241
242impl From<Card64> for Card64Inner {
243    fn from(v: Card64) -> Self {
244        v.0
245    }
246}
247
248impl FromIterator<Card> for Card64 {
249    fn from_iter<T: IntoIterator<Item = Card>>(iter: T) -> Self {
250        let mut res = Self::default();
251
252        for card in iter {
253            res.set(card);
254        }
255
256        res
257    }
258}
259
260impl From<&[Card]> for Card64 {
261    fn from(cards: &[Card]) -> Self {
262        cards.iter().copied().collect()
263    }
264}
265
266impl ops::BitOrAssign<Card> for Card64 {
267    fn bitor_assign(&mut self, rhs: Card) {
268        self.set(rhs);
269    }
270}
271
272#[cfg(any(test, feature = "quickcheck"))]
273impl quickcheck::Arbitrary for Card64 {
274    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
275        let inner = Card64Inner::arbitrary(g);
276
277        Self(Self::ALL.0 & inner)
278    }
279}
280
281#[cfg(test)]
282#[cfg_attr(coverage_nightly, coverage(off))]
283mod tests {
284    use super::*;
285    use crate::*;
286
287    #[test]
288    fn test_all() {
289        for c in Card::all::<false>() {
290            if c.rank >= Rank::R6 {
291                assert!(Card64::all::<true>().contains_card(*c));
292            }
293            assert!(Card64::all::<false>().contains_card(*c));
294        }
295    }
296
297    #[test]
298    fn test_empty() {
299        assert!(Card64::default().is_empty());
300        assert!(!Card64::all::<false>().is_empty());
301    }
302
303    #[quickcheck]
304    fn test_set(card: Card) {
305        let mut res = Card64::default();
306        res.set(card);
307
308        assert!(res.contains_card(card));
309    }
310
311    #[quickcheck]
312    fn test_unset(card: Card) {
313        let mut res = Card64::all::<false>();
314        res.unset(card);
315
316        assert!(!res.contains_card(card));
317    }
318
319    #[quickcheck]
320    fn test_contains(cards: Vec<Card>) {
321        let all = Card64::from(cards.as_slice());
322        let half = Card64::from(&cards[..cards.len() / 2]);
323
324        assert!(all.contains(half));
325    }
326
327    #[quickcheck]
328    #[allow(clippy::cast_possible_truncation)]
329    fn test_count(c64: Card64) {
330        assert_eq!(
331            c64.count(),
332            Card::all::<false>()
333                .iter()
334                .filter(|&c| c64.contains_card(*c))
335                .count() as CardCount
336        );
337    }
338
339    #[quickcheck]
340    #[allow(clippy::cast_possible_truncation)]
341    fn test_count_by_rank(c64: Card64, rank: Rank) {
342        assert_eq!(
343            c64.count_by_rank(rank),
344            Card::all::<false>()
345                .iter()
346                .filter(|&c| c64.contains_card(*c) && c.rank == rank)
347                .count() as CardCount
348        );
349    }
350
351    #[quickcheck]
352    #[allow(clippy::cast_possible_truncation)]
353    fn test_count_by_suit(c64: Card64, suit: Suit) {
354        assert_eq!(
355            c64.count_by_suit(suit),
356            Card::all::<false>()
357                .iter()
358                .filter(|&c| c64.contains_card(*c) && c.suit == suit)
359                .count() as CardCount
360        );
361    }
362
363    #[quickcheck]
364    fn test_ranks_by_suit(c64: Card64, suit: Suit) {
365        let expected = Rank::all::<false>()
366            .iter()
367            .filter(|&&r| c64.contains_card(Card::new(r, suit)))
368            .copied()
369            .collect();
370
371        assert_eq!(c64.ranks_by_suit(suit), expected);
372    }
373
374    #[quickcheck]
375    fn test_from_ranks(r16: Rank16) {
376        let expected = Card::all::<false>()
377            .iter()
378            .filter(|c| r16.contains_rank(c.rank))
379            .copied()
380            .collect();
381
382        assert_eq!(Card64::from_ranks(r16), expected);
383    }
384
385    #[quickcheck]
386    fn test_from_suit(suit: Suit) {
387        let expected = Card::all::<false>()
388            .iter()
389            .filter(|c| c.suit == suit)
390            .copied()
391            .collect();
392
393        assert_eq!(Card64::from_suit(suit), expected);
394    }
395
396    #[quickcheck]
397    fn test_ranks(c64: Card64) {
398        let expected = Card::all::<false>()
399            .iter()
400            .filter(|&&c| c64.contains_card(c))
401            .map(|c| c.rank)
402            .collect();
403
404        assert_eq!(c64.ranks(), expected);
405    }
406
407    #[test]
408    fn test_iter() {
409        fn assert_iter(s: &str) {
410            let card64 = c64!(s);
411            let v = cards!(s);
412
413            for c in card64.iter() {
414                assert!(v.contains(&c));
415            }
416
417            assert_eq!(card64.iter().count(), v.len());
418        }
419
420        assert_iter("");
421        assert_iter("As");
422        assert_iter("As Kh 2d");
423    }
424
425    #[test]
426    fn test_display() {
427        assert_eq!(format!("{:?}", c64!("As")), "Card64<As>");
428        assert_eq!(format!("{:?}", c64!("As 9h")), "Card64(A, 9, _, _)");
429    }
430
431    #[quickcheck]
432    fn test_bit_not(c64: Card64) {
433        assert_eq!((!c64).0, Card64::ALL.0 & !(c64.0));
434    }
435
436    #[quickcheck]
437    fn test_bit_and(c1: Card, c2: Card) {
438        let a = Card64::from(c1);
439        let b = Card64::from(c2);
440
441        assert_eq!((a & b).is_empty(), c1 != c2);
442    }
443
444    #[quickcheck]
445    fn test_bit_or(c1: Card, c2: Card) {
446        let a = Card64::from(c1);
447        let b = Card64::from(c2);
448
449        assert!((a | b).contains_card(c1));
450        assert!((a | b).contains_card(c2));
451
452        let mut ab = Card64::default();
453        ab |= c1;
454        ab |= c2;
455        assert_eq!(ab, a | b);
456    }
457
458    #[quickcheck]
459    fn test_from_and_to_int(i: Card64Inner) {
460        let obj = Card64::from(i);
461        let mask = Card64::ALL.0;
462
463        assert_eq!(obj.0, i & mask);
464        assert_eq!(i & mask, Card64Inner::from(obj));
465    }
466}