openpql_prelude/card/
rank16.rs

1use super::{
2    BitAnd, BitAndAssign, BitOr, BitOrAssign, Card64, CardCount, Hash, Idx,
3    N_STRAIGHT, N_STRAIGHT_SD, Not, Rank, Rank16Inner, RankIdx, Suit, fmt, ops,
4};
5
6#[macro_export]
7macro_rules! r16 {
8    ($s:expr) => {
9        $crate::Rank16::from(
10            $s.chars()
11                .filter(|c| !c.is_whitespace())
12                .map(|c| $crate::Rank::from_char(c).unwrap())
13                .collect::<Vec<_>>()
14                .as_slice(),
15        )
16    };
17}
18
19/// Bitset representation of rank collections.
20///
21/// A 16-bit bitset for efficient rank set operations. Each bit represents a specific rank,
22/// enabling fast membership tests and set operations.
23///
24/// # Memory Layout
25/// ```text
26/// [15, 0]:   xxxAKQJT 98765432  // x: unused
27/// ```
28#[derive(
29    Copy,
30    Clone,
31    derive_more::Debug,
32    PartialEq,
33    Eq,
34    BitAnd,
35    BitOr,
36    PartialOrd,
37    Ord,
38    Hash,
39    Default,
40    BitOrAssign,
41    BitAndAssign,
42)]
43#[debug("Rank16({})", self)]
44pub struct Rank16(pub(crate) Rank16Inner);
45
46impl Rank16 {
47    /// Set containing all 13 ranks.
48    pub(crate) const ALL: Self = Self(0b0001_1111_1111_1111);
49    /// Set containing all 9 short deck ranks (6+).
50    pub(crate) const ALL_SD: Self = Self(0b0001_1111_1111_0000);
51
52    pub const STRAIGHT_A6789: Self = Self(0b0001_0000_1111_0000);
53    pub const STRAIGHT_A2345: Self = Self(0b0001_0000_0000_1111);
54    pub const STRAIGHT_23456: Self = Self(0b0000_0000_0001_1111);
55    pub const STRAIGHT_34567: Self = Self(0b0000_0000_0011_1110);
56    pub const STRAIGHT_45678: Self = Self(0b0000_0000_0111_1100);
57    pub const STRAIGHT_56789: Self = Self(0b0000_0000_1111_1000);
58    pub const STRAIGHT_6789T: Self = Self(0b0000_0001_1111_0000);
59    pub const STRAIGHT_789TJ: Self = Self(0b0000_0011_1110_0000);
60    pub const STRAIGHT_89TJQ: Self = Self(0b0000_0111_1100_0000);
61    pub const STRAIGHT_9TJQK: Self = Self(0b0000_1111_1000_0000);
62    pub const STRAIGHT_TJQKA: Self = Self(0b0001_1111_0000_0000);
63
64    const ALL_STRAIGHT: [Self; N_STRAIGHT] = [
65        Self::STRAIGHT_A2345,
66        Self::STRAIGHT_23456,
67        Self::STRAIGHT_34567,
68        Self::STRAIGHT_45678,
69        Self::STRAIGHT_56789,
70        Self::STRAIGHT_6789T,
71        Self::STRAIGHT_789TJ,
72        Self::STRAIGHT_89TJQ,
73        Self::STRAIGHT_9TJQK,
74        Self::STRAIGHT_TJQKA,
75    ];
76
77    const ALL_STRAIGHT_SD: [Self; N_STRAIGHT_SD] = [
78        Self::STRAIGHT_A6789,
79        Self::STRAIGHT_6789T,
80        Self::STRAIGHT_789TJ,
81        Self::STRAIGHT_89TJQ,
82        Self::STRAIGHT_9TJQK,
83        Self::STRAIGHT_TJQKA,
84    ];
85
86    #[inline]
87    pub const fn all<const SD: bool>() -> Self {
88        const { if SD { Self::ALL_SD } else { Self::ALL } }
89    }
90
91    #[inline]
92    pub const fn all_straights<const SD: bool>() -> &'static [Self] {
93        const {
94            if SD {
95                &Self::ALL_STRAIGHT_SD
96            } else {
97                &Self::ALL_STRAIGHT
98            }
99        }
100    }
101
102    /// Returns `true` if the set contains no ranks.
103    #[must_use]
104    #[inline]
105    pub const fn is_empty(self) -> bool {
106        self.0 == 0
107    }
108
109    /// Adds the specified rank to this set.
110    #[inline]
111    pub const fn set(&mut self, r: Rank) {
112        self.0 |= 1 << r as Idx;
113    }
114
115    /// Removes the specified rank from this set.
116    #[inline]
117    pub const fn unset(&mut self, r: Rank) {
118        self.0 &= !(1 << r as Idx);
119    }
120
121    /// Returns `true` if this set contains the specified rank.
122    #[must_use]
123    #[inline]
124    pub const fn contains_rank(self, r: Rank) -> bool {
125        let v = 1 << (r as Idx);
126        v == v & self.0
127    }
128
129    /// Returns the number of ranks in this set.
130    #[must_use]
131    #[inline]
132    pub const fn count(self) -> CardCount {
133        self.0.count_ones().to_le_bytes()[0]
134    }
135
136    /// Returns the lowest rank in this set, or `None` if empty.
137    #[must_use]
138    #[inline]
139    #[allow(clippy::cast_possible_truncation)]
140    pub const fn min_rank(self) -> Option<Rank> {
141        RankIdx(self.0.trailing_zeros() as Idx).to_rank()
142    }
143
144    /// Returns the highest rank in this set, or `None` if empty.
145    #[must_use]
146    #[inline]
147    #[allow(clippy::cast_possible_truncation)]
148    pub const fn max_rank(self) -> Option<Rank> {
149        const N_ZEROS_R2: Idx = 15;
150
151        RankIdx(N_ZEROS_R2 - (Self::ALL.0 & self.0).leading_zeros() as Idx)
152            .to_rank()
153    }
154
155    /// Returns the nth highest rank in this set (1-indexed), or `None` if not found.
156    #[must_use]
157    #[inline]
158    pub const fn nth_rank(self, mut n: CardCount) -> Option<Rank> {
159        let ranks = Rank::all::<false>();
160        let mut i = ranks.len();
161
162        while i > 0 {
163            i -= 1;
164            let rank = ranks[i];
165
166            if self.contains_rank(rank) {
167                if n == 1 {
168                    return Some(rank);
169                } else if n == 0 {
170                    return None;
171                }
172
173                n -= 1;
174            }
175        }
176
177        None
178    }
179
180    #[inline]
181    pub(crate) const fn from_rank(rank: Rank) -> Self {
182        Self(1 << rank as Idx)
183    }
184}
185
186impl fmt::Display for Rank16 {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        let ranks: String = Rank::all::<false>()
189            .iter()
190            .filter(|&r| self.contains_rank(*r))
191            .map(|r| r.to_char())
192            .collect();
193
194        write!(f, "{ranks}")
195    }
196}
197
198impl Not for Rank16 {
199    type Output = Self;
200
201    fn not(self) -> Self::Output {
202        Self(!self.0 & Self::ALL.0)
203    }
204}
205
206impl From<Rank> for Rank16 {
207    fn from(r: Rank) -> Self {
208        Self(1 << r as Idx)
209    }
210}
211
212impl FromIterator<Rank> for Rank16 {
213    fn from_iter<T: IntoIterator<Item = Rank>>(iter: T) -> Self {
214        let mut res = Self::default();
215
216        for rank in iter {
217            res.set(rank);
218        }
219
220        res
221    }
222}
223
224impl From<&[Rank]> for Rank16 {
225    fn from(ranks: &[Rank]) -> Self {
226        ranks.iter().copied().collect()
227    }
228}
229
230impl From<Card64> for Rank16 {
231    fn from(c: Card64) -> Self {
232        c.ranks_by_suit(Suit::S)
233            | c.ranks_by_suit(Suit::H)
234            | c.ranks_by_suit(Suit::D)
235            | c.ranks_by_suit(Suit::C)
236    }
237}
238
239impl From<Rank16Inner> for Rank16 {
240    fn from(i: Rank16Inner) -> Self {
241        Self(i & Self::ALL.0)
242    }
243}
244
245impl From<Rank16> for Rank16Inner {
246    fn from(v: Rank16) -> Self {
247        v.0
248    }
249}
250
251impl ops::BitOrAssign<Rank> for Rank16 {
252    fn bitor_assign(&mut self, rhs: Rank) {
253        self.set(rhs);
254    }
255}
256
257#[cfg(any(test, feature = "quickcheck"))]
258impl quickcheck::Arbitrary for Rank16 {
259    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
260        let inner = Rank16Inner::arbitrary(g);
261
262        Self(Self::ALL.0 & inner)
263    }
264}
265
266#[cfg(test)]
267#[cfg_attr(coverage_nightly, coverage(off))]
268mod tests {
269    use super::*;
270    use crate::*;
271
272    #[quickcheck]
273    fn test_all(rank: Rank) {
274        if rank >= Rank::R6 {
275            assert!(Rank16::all::<true>().contains_rank(rank));
276        }
277        assert!(Rank16::all::<false>().contains_rank(rank));
278    }
279
280    #[test]
281    fn test_all_straights() {
282        for s in ["23456", "34567", "45678", "56789"] {
283            assert!(Rank16::all_straights::<false>().contains(&r16!(s)));
284        }
285        for s in ["6789T", "789TJ", "89TJQ", "9TJQK", "TJQKA"] {
286            assert!(Rank16::all_straights::<false>().contains(&r16!(s)));
287            assert!(Rank16::all_straights::<true>().contains(&r16!(s)));
288        }
289
290        assert!(Rank16::all_straights::<false>().contains(&r16!("A2345")));
291        assert!(Rank16::all_straights::<true>().contains(&r16!("A6789")));
292    }
293
294    #[test]
295    fn test_empty() {
296        assert!(Rank16::default().is_empty());
297        assert!(!Rank16::all::<false>().is_empty());
298    }
299
300    #[quickcheck]
301    fn test_set(rank: Rank) {
302        let mut res = Rank16::default();
303        res.set(rank);
304
305        assert!(res.contains_rank(rank));
306    }
307
308    #[quickcheck]
309    fn test_unset(rank: Rank) {
310        let mut res = Rank16::all::<false>();
311        res.unset(rank);
312
313        assert!(!res.contains_rank(rank));
314    }
315
316    #[quickcheck]
317    #[allow(clippy::cast_possible_truncation)]
318    fn test_count(r16: Rank16) {
319        assert_eq!(
320            r16.count(),
321            Rank::all::<false>()
322                .iter()
323                .filter(|&r| r16.contains_rank(*r))
324                .count() as CardCount
325        );
326    }
327
328    #[quickcheck]
329    fn test_min_rank_and_max_rank(ranks: Distinct<5, Rank>) {
330        let r16 = Rank16::from(ranks.as_slice());
331
332        let max = ranks.iter().max().copied();
333        let min = ranks.iter().min().copied();
334
335        assert_eq!(r16.max_rank(), max);
336        assert_eq!(r16.min_rank(), min);
337    }
338
339    #[test]
340    fn test_nth_rank() {
341        let ranks = r16!("26K");
342
343        assert_eq!(ranks.nth_rank(0), None);
344        assert_eq!(ranks.nth_rank(1), Some(Rank::RK));
345        assert_eq!(ranks.nth_rank(2), Some(Rank::R6));
346        assert_eq!(ranks.nth_rank(3), Some(Rank::R2));
347        assert_eq!(ranks.nth_rank(4), None);
348    }
349
350    #[test]
351    fn test_display() {
352        assert_eq!(format!("{:?}", r16!("ATJ")), "Rank16(TJA)");
353    }
354
355    #[quickcheck]
356    fn test_bit_not(r16: Rank16) {
357        assert_eq!((!r16).0, Rank16::ALL.0 & !(r16.0));
358    }
359
360    #[quickcheck]
361    fn test_bit_and(r1: Rank, r2: Rank) {
362        let a = Rank16::from(r1);
363        let b = Rank16::from(r2);
364
365        assert_eq!((a & b).is_empty(), r1 != r2);
366    }
367
368    #[quickcheck]
369    fn test_bit_or(r1: Rank, r2: Rank) {
370        let a = Rank16::from(r1);
371        let b = Rank16::from(r2);
372
373        assert!((a | b).contains_rank(r1));
374        assert!((a | b).contains_rank(r2));
375
376        let mut ab = Rank16::default();
377        ab |= r1;
378        ab |= r2;
379        assert_eq!(ab, a | b);
380    }
381
382    #[quickcheck]
383    fn test_from_cards_and_card64(cards: Vec<Card>) {
384        let mut ranks = Rank16::default();
385
386        for i in 0..cards.len() {
387            ranks.set(cards[i].rank);
388
389            let c64 = Card64::from(&cards[0..=i]);
390
391            assert_eq!(Rank16::from(c64), ranks);
392        }
393    }
394
395    #[quickcheck]
396    fn test_from_and_to_int(i: Rank16Inner) {
397        let obj = Rank16::from(i);
398        let mask = Rank16::ALL.0;
399
400        assert_eq!(obj.0, i & mask);
401        assert_eq!(i & mask, Rank16Inner::from(obj));
402    }
403}