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#[derive(Copy, Clone, PartialEq, Eq, BitAnd, BitOr, Default)]
23pub struct Suit4(u8);
24
25impl Suit4 {
26 #[must_use]
39 #[inline]
40 pub const fn from_u8(v: u8) -> Self {
41 Self(v)
42 }
43
44 #[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 #[must_use]
79 #[inline]
80 pub const fn empty() -> Self {
81 Self(0)
82 }
83
84 pub const fn is_empty(self) -> bool {
98 self.0 == 0
99 }
100
101 #[inline]
114 pub const fn set(&mut self, s: Suit) {
115 self.0 |= 1 << s as u8;
116 }
117
118 #[inline]
131 pub const fn unset(&mut self, s: Suit) {
132 self.0 &= !(1 << s as u8);
133 }
134
135 #[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 #[must_use]
166 #[inline]
167 pub const fn count(&self) -> PQLCardCount {
168 self.0.count_ones().to_le_bytes()[0]
169 }
170
171 #[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 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}