open_pql/base/
suit4.rs

1use super::*;
2
3#[cfg(any(test, feature = "benchmark"))]
4#[macro_export]
5macro_rules! s4 {
6    ($s:expr) => {
7        $crate::Suit4::from(
8            $s.chars()
9                .filter(|c| !c.is_whitespace())
10                .map(|c| $crate::Suit::try_from(c).unwrap())
11                .collect::<Vec<_>>()
12                .as_ref(),
13        )
14    };
15}
16
17/// Suit Masks
18/// # Memory Layout:
19/// ```text
20/// [8, 0]:   xxxxCDHS // x: unused
21/// ```
22#[derive(Copy, Clone, PartialEq, Eq, BitAnd, BitOr, Default)]
23pub struct Suit4(u8);
24
25impl Suit4 {
26    /// Constructs [Suit4] from [u8]
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use open_pql::{Suit, Suit4};
32    ///
33    /// let i: u8 = 0b0011;
34    /// let suits: Suit4 = Suit4::from_u8(i);
35    ///
36    /// assert_eq!(suits, Suit4::from([Suit::S, Suit::H].as_ref()));
37    /// ```
38    #[must_use]
39    #[inline]
40    pub const fn from_u8(v: u8) -> Self {
41        Self(v)
42    }
43
44    /// Returns the inner [u8]
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use open_pql::Suit4;
50    ///
51    /// let i: u8 = 0b0011;
52    /// let suits: Suit4 = Suit4::from_u8(i);
53    ///
54    /// assert_eq!(i, suits.to_u8());
55    /// ```
56    #[must_use]
57    #[inline]
58    pub const fn to_u8(self) -> u8 {
59        self.0
60    }
61
62    #[inline]
63    const fn from_suit(s: Suit) -> Self {
64        Self(1 << s as u8)
65    }
66
67    /// Constructs an empty [Suit4]
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use open_pql::Suit4;
73    ///
74    /// let suits: Suit4 = Suit4::empty();
75    ///
76    /// assert_eq!(suits, Suit4::from([].as_ref()));
77    /// ```
78    #[must_use]
79    #[inline]
80    pub const fn empty() -> Self {
81        Self(0)
82    }
83
84    /// Checks whether all suit masks are unset
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use open_pql::{Suit, Suit4};
90    ///
91    /// let empty: Suit4 = Suit4::empty();
92    /// let not_empty: Suit4 = Suit4::from(Suit::D);
93    ///
94    /// assert!(empty.is_empty());
95    /// assert!(!not_empty.is_empty());
96    /// ```
97    pub const fn is_empty(self) -> bool {
98        self.0 == 0
99    }
100
101    /// Mark a [Suit]
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use open_pql::{Suit, Suit4};
107    ///
108    /// let mut suits: Suit4 = Suit4::empty();
109    /// suits.set(Suit::D);
110    ///
111    /// assert_eq!(suits, Suit4::from(Suit::D));
112    /// ```
113    #[inline]
114    pub const fn set(&mut self, s: Suit) {
115        self.0 |= 1 << s as u8;
116    }
117
118    /// Unmark a [Suit]
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use open_pql::{Suit, Suit4};
124    ///
125    /// let mut suits: Suit4 = Suit4::from(Suit::D);
126    /// suits.unset(Suit::D);
127    ///
128    /// assert_eq!(suits, Suit4::empty());
129    /// ```
130    #[inline]
131    pub const fn unset(&mut self, s: Suit) {
132        self.0 &= !(1 << s as u8);
133    }
134
135    /// Checks whether a [Suit] is marked
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// use open_pql::{Suit, Suit4};
141    ///
142    /// let mut suits: Suit4 = Suit4::from(Suit::D);
143    ///
144    /// assert!(suits.contains_suit(Suit::D));
145    /// assert!(!suits.contains_suit(Suit::H));
146    /// ```
147    #[must_use]
148    #[inline]
149    pub const fn contains_suit(self, s: Suit) -> bool {
150        let v = 1u8 << (s as u8);
151        v == v & self.0
152    }
153
154    /// Returns the number of marked suits
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use open_pql::{Suit, Suit4};
160    ///
161    /// let mut suits: Suit4 = Suit4::from(Suit::D);
162    ///
163    /// assert_eq!(suits.count(), 1);
164    /// ```
165    #[must_use]
166    #[inline]
167    pub const fn count(&self) -> PQLCardCount {
168        self.0.count_ones().to_le_bytes()[0]
169    }
170
171    /// Checks if the collection is empty
172    #[must_use]
173    #[inline]
174    pub const fn is_empty_alt(self) -> bool {
175        self.0 == 0
176    }
177}
178
179impl From<Suit> for Suit4 {
180    fn from(s: Suit) -> Self {
181        Self::from_suit(s)
182    }
183}
184
185impl From<&[Suit]> for Suit4 {
186    fn from(ss: &[Suit]) -> Self {
187        let mut res = Self::empty();
188
189        for s in ss {
190            res.set(*s);
191        }
192
193        res
194    }
195}
196
197pub fn u8_to_suit_str(v: u8) -> String {
198    let to_c = |i: u8| {
199        if v & 1 << i == 0 {
200            '\0'
201        } else {
202            SUIT_NAMES[i as usize]
203        }
204    };
205
206    (0..N_SUITS)
207        .map(to_c)
208        .filter(|c| c.is_alphanumeric())
209        .collect::<String>()
210}
211
212impl fmt::Debug for Suit4 {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        f.debug_tuple("Suit4")
215            .field(&format_args!("{}", u8_to_suit_str(self.0)))
216            .finish()
217    }
218}
219
220impl From<Card64> for Suit4 {
221    fn from(c: Card64) -> Self {
222        // TODO: refactor
223
224        let [s, h, d, c] = Suit::ARR_ALL.map(|s| c.count_by_suit(s) > 0);
225
226        Self(
227            u8::from(s)
228                | u8::from(h) << 1
229                | u8::from(d) << 2
230                | u8::from(c) << 3,
231        )
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_empty() {
241        assert_eq!(Suit4::empty(), Suit4(0));
242        assert_eq!(Suit4::default(), Suit4(0));
243        assert!(Suit4::empty().is_empty());
244        assert!(Suit4::default().is_empty());
245        assert!(!Suit4(1).is_empty());
246    }
247
248    #[quickcheck]
249    fn test_set_and_contains(s: Suit) {
250        let mut suits = Suit4::empty();
251
252        suits.set(s);
253
254        assert!(!suits.is_empty());
255        assert!(suits.contains_suit(s));
256
257        suits.unset(s);
258
259        assert!(suits.is_empty());
260        assert!(!suits.contains_suit(s));
261    }
262
263    #[quickcheck]
264    fn test_u8(i: u8) -> TestResult {
265        if i > 0b1111 {
266            return TestResult::discard();
267        }
268
269        assert_eq!(Suit4(i), Suit4::from_u8(i));
270        assert_eq!(i, Suit4(i).to_u8());
271
272        TestResult::passed()
273    }
274
275    #[quickcheck]
276    fn test_from_suit(s1: Suit, s2: Suit) {
277        let suits = Suit4::from(s1);
278
279        assert!(suits.contains_suit(s1));
280
281        let suits = Suit4::from([s1, s2].as_ref());
282
283        assert!(suits.contains_suit(s1));
284        assert!(suits.contains_suit(s2));
285    }
286
287    #[quickcheck]
288    fn test_bit_and(s1: Suit, s2: Suit) {
289        let a = Suit4::from(s1);
290        let b = Suit4::from(s2);
291
292        assert_eq!((a & b).is_empty(), s1 != s2);
293    }
294
295    #[quickcheck]
296    fn test_bit_or(s1: Suit, s2: Suit) {
297        let a = Suit4::from(s1);
298        let b = Suit4::from(s2);
299
300        assert!((a | b).contains_suit(s1));
301        assert!((a | b).contains_suit(s2));
302    }
303
304    #[quickcheck]
305    fn test_count(s1: Suit, s2: Suit) {
306        let suits = Suit4::from([s1, s2].as_ref());
307
308        let count = if s1 == s2 { 1 } else { 2 };
309
310        assert_eq!(count, suits.count());
311    }
312
313    #[quickcheck]
314    fn test_from_card64(cards: Vec<Card>) -> TestResult {
315        let mut suits = Suit4::empty();
316
317        for i in 0..cards.len() {
318            suits.set(cards[i].suit);
319
320            let c64: Card64 = cards[0..=i].into();
321
322            assert_eq!(Suit4::from(c64), suits);
323        }
324
325        TestResult::passed()
326    }
327
328    #[test]
329    fn test_debug() {
330        let s = format!("{:?}", s4!("s") | s4!("d"));
331        assert_eq!(s, "Suit4(sd)");
332    }
333
334    #[test]
335    #[cfg(debug_assertions)]
336    fn test_suit_from_u8_debug_assert() {
337        use std::panic;
338
339        for i in 0..N_SUITS {
340            let result = panic::catch_unwind(|| {
341                Suit::from_u8(i);
342            });
343            assert!(result.is_ok(), "Suit::from_u8({i}) should not panic");
344        }
345
346        let result = panic::catch_unwind(|| {
347            Suit::from_u8(N_SUITS);
348        });
349        assert!(result.is_err(), "Suit::from_u8({N_SUITS}) should panic");
350
351        let result = panic::catch_unwind(|| {
352            Suit::from_u8(100);
353        });
354        assert!(result.is_err(), "Suit::from_u8(100) should panic");
355    }
356}