openpql_prelude/card/
suit4.rs

1use super::{
2    BitAnd, BitOr, Card64, CardCount, Idx, Suit, Suit4Inner, fmt, ops,
3};
4
5#[macro_export]
6macro_rules! s4 {
7    ($s:expr) => {
8        $crate::Suit4::from(
9            $s.chars()
10                .filter(|c| !c.is_whitespace())
11                .map(|c| $crate::Suit::from_char(c).unwrap())
12                .collect::<Vec<_>>()
13                .as_ref(),
14        )
15    };
16}
17
18/// Bitset representation of suit collections.
19///
20/// A 4-bit bitset for efficient suit set operations. Each bit represents a specific suit,
21/// enabling fast membership tests and set operations.
22///
23/// # Memory Layout
24/// ```text
25/// [8, 0]:   xxxxcdhs // x: unused
26/// ```
27#[derive(
28    Copy, Clone, derive_more::Debug, PartialEq, Eq, BitAnd, BitOr, Default,
29)]
30#[debug("Suit4({})", self)]
31pub struct Suit4(pub(crate) Suit4Inner);
32
33impl Suit4 {
34    /// Set containing all 4 suits.
35    pub const ALL: Self = Self(0b1111);
36
37    /// Returns `true` if the set contains no suits.
38    pub const fn is_empty(self) -> bool {
39        self.0 == 0
40    }
41
42    /// Adds the specified suit to this set.
43    #[inline]
44    pub const fn set(&mut self, s: Suit) {
45        self.0 |= 1 << s as Idx;
46    }
47
48    /// Removes the specified suit from this set.
49    #[inline]
50    pub const fn unset(&mut self, s: Suit) {
51        self.0 &= !(1 << s as Idx);
52    }
53
54    /// Returns `true` if this set contains the specified suit.
55    #[must_use]
56    #[inline]
57    pub const fn contains_suit(self, s: Suit) -> bool {
58        let v = 1 << (s as Idx);
59        v == v & self.0
60    }
61
62    /// Returns the number of suits in this set.
63    #[must_use]
64    #[inline]
65    pub const fn count(&self) -> CardCount {
66        self.0.count_ones().to_le_bytes()[0]
67    }
68}
69
70impl fmt::Display for Suit4 {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        let suits: String = Suit::ARR_ALL
73            .iter()
74            .filter(|&s| self.contains_suit(*s))
75            .map(|s| s.to_char())
76            .collect();
77
78        write!(f, "{suits}")
79    }
80}
81
82impl From<Suit> for Suit4 {
83    fn from(s: Suit) -> Self {
84        Self(1 << s as Idx)
85    }
86}
87
88impl FromIterator<Suit> for Suit4 {
89    fn from_iter<T: IntoIterator<Item = Suit>>(iter: T) -> Self {
90        let mut res = Self::default();
91
92        for suit in iter {
93            res.set(suit);
94        }
95
96        res
97    }
98}
99
100impl From<&[Suit]> for Suit4 {
101    fn from(suits: &[Suit]) -> Self {
102        suits.iter().copied().collect()
103    }
104}
105
106impl ops::BitOrAssign<Suit> for Suit4 {
107    fn bitor_assign(&mut self, rhs: Suit) {
108        self.set(rhs);
109    }
110}
111
112impl From<Card64> for Suit4 {
113    fn from(c: Card64) -> Self {
114        let [s, h, d, c] = Suit::ARR_ALL.map(|s| c.count_by_suit(s) > 0);
115
116        Self(
117            Suit4Inner::from(s)
118                | Suit4Inner::from(h) << 1
119                | Suit4Inner::from(d) << 2
120                | Suit4Inner::from(c) << 3,
121        )
122    }
123}
124
125impl From<Suit4Inner> for Suit4 {
126    fn from(i: Suit4Inner) -> Self {
127        Self(i & Self::ALL.0)
128    }
129}
130
131impl From<Suit4> for Suit4Inner {
132    fn from(v: Suit4) -> Self {
133        v.0
134    }
135}
136
137#[cfg(any(test, feature = "quickcheck"))]
138impl quickcheck::Arbitrary for Suit4 {
139    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
140        let inner = Suit4Inner::arbitrary(g);
141
142        Self(Self::ALL.0 & inner)
143    }
144}
145
146#[cfg(test)]
147#[cfg_attr(coverage_nightly, coverage(off))]
148mod tests {
149    use super::*;
150    use crate::*;
151
152    #[test]
153    fn test_all() {
154        assert_eq!(Suit4::ALL, Suit4(0b1111));
155    }
156
157    #[test]
158    fn test_empty() {
159        assert!(Suit4::default().is_empty());
160        assert!(!Suit4(1).is_empty());
161    }
162
163    #[quickcheck]
164    fn test_set(suit: Suit) {
165        let mut res = Suit4::default();
166        res.set(suit);
167
168        assert!(res.contains_suit(suit));
169    }
170
171    #[quickcheck]
172    fn test_unset(suit: Suit) {
173        let mut res = Suit4::ALL;
174        res.unset(suit);
175
176        assert!(!res.contains_suit(suit));
177    }
178
179    #[quickcheck]
180    #[allow(clippy::cast_possible_truncation)]
181    fn test_count(s4: Suit4) {
182        assert_eq!(
183            s4.count(),
184            Suit::ARR_ALL
185                .iter()
186                .filter(|&s| s4.contains_suit(*s))
187                .count() as CardCount
188        );
189    }
190
191    #[test]
192    fn test_display() {
193        assert_eq!(format!("{:?}", s4!("ds")), "Suit4(sd)");
194    }
195
196    #[quickcheck]
197    fn test_bit_and(s1: Suit, s2: Suit) {
198        let a = Suit4::from(s1);
199        let b = Suit4::from(s2);
200
201        assert_eq!((a & b).is_empty(), s1 != s2);
202    }
203
204    #[quickcheck]
205    fn test_bit_or(s1: Suit, s2: Suit) {
206        let a = Suit4::from(s1);
207        let b = Suit4::from(s2);
208
209        assert!((a | b).contains_suit(s1));
210        assert!((a | b).contains_suit(s2));
211
212        let mut ab = Suit4::default();
213        ab |= s1;
214        ab |= s2;
215        assert_eq!(ab, a | b);
216    }
217
218    #[quickcheck]
219    fn test_from_suit(s1: Suit, s2: Suit) {
220        let suits = Suit4::from(s1);
221
222        assert!(suits.contains_suit(s1));
223
224        let suits = Suit4::from([s1, s2].as_ref());
225
226        assert!(suits.contains_suit(s1));
227        assert!(suits.contains_suit(s2));
228    }
229
230    #[quickcheck]
231    fn test_from_card64(cards: Vec<Card>) {
232        let mut suits = Suit4::default();
233
234        for i in 0..cards.len() {
235            suits.set(cards[i].suit);
236
237            let c64: Card64 = cards[0..=i].into();
238
239            assert_eq!(Suit4::from(c64), suits);
240        }
241    }
242
243    #[quickcheck]
244    fn test_from_and_to_int(i: Suit4Inner) {
245        let obj = Suit4::from(i);
246        let mask = Suit4::ALL.0;
247
248        assert_eq!(obj.0, i & mask);
249        assert_eq!(i & mask, Suit4Inner::from(obj));
250    }
251}