1use super::{
2 BitAnd, BitOr, Card64, N_SUITS, PQLCardCount, SUIT_NAMES, Suit, fmt,
3};
4
5#[cfg(any(test, feature = "benchmark"))]
6#[macro_export]
7macro_rules! s4 {
8 ($s:expr) => {
9 $crate::Suit4::from(
10 $s.chars()
11 .filter(|c| !c.is_whitespace())
12 .map(|c| $crate::Suit::try_from(c).unwrap())
13 .collect::<Vec<_>>()
14 .as_ref(),
15 )
16 };
17}
18
19#[derive(Copy, Clone, PartialEq, Eq, BitAnd, BitOr, Default)]
43pub struct Suit4(u8);
44
45impl Suit4 {
46 #[must_use]
48 #[inline]
49 pub(crate) const fn from_u8(v: u8) -> Self {
50 Self(v)
51 }
52
53 #[allow(unused)]
55 #[must_use]
56 #[inline]
57 pub(crate) const fn to_u8(self) -> u8 {
58 self.0
59 }
60
61 #[inline]
62 const fn from_suit(s: Suit) -> Self {
63 Self(1 << s as u8)
64 }
65
66 pub const fn is_empty(self) -> bool {
80 self.0 == 0
81 }
82
83 #[inline]
96 pub const fn set(&mut self, s: Suit) {
97 self.0 |= 1 << s as u8;
98 }
99
100 #[inline]
113 pub const fn unset(&mut self, s: Suit) {
114 self.0 &= !(1 << s as u8);
115 }
116
117 #[must_use]
130 #[inline]
131 pub const fn contains_suit(self, s: Suit) -> bool {
132 let v = 1u8 << (s as u8);
133 v == v & self.0
134 }
135
136 #[must_use]
148 #[inline]
149 pub const fn count(&self) -> PQLCardCount {
150 self.0.count_ones().to_le_bytes()[0]
151 }
152}
153
154impl From<Suit> for Suit4 {
155 fn from(s: Suit) -> Self {
156 Self::from_suit(s)
157 }
158}
159
160impl From<&[Suit]> for Suit4 {
161 fn from(ss: &[Suit]) -> Self {
162 let mut res = Self::default();
163
164 for s in ss {
165 res.set(*s);
166 }
167
168 res
169 }
170}
171
172pub fn u8_to_suit_str(v: u8) -> String {
173 let to_c = |i: u8| {
174 if v & 1 << i == 0 {
175 '\0'
176 } else {
177 SUIT_NAMES[i as usize]
178 }
179 };
180
181 (0..N_SUITS)
182 .map(to_c)
183 .filter(|c| c.is_alphanumeric())
184 .collect::<String>()
185}
186
187impl fmt::Debug for Suit4 {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 f.debug_tuple("Suit4")
190 .field(&format_args!("{}", u8_to_suit_str(self.0)))
191 .finish()
192 }
193}
194
195impl From<Card64> for Suit4 {
196 fn from(c: Card64) -> Self {
197 let [s, h, d, c] = Suit::ARR_ALL.map(|s| c.count_by_suit(s) > 0);
200
201 Self(
202 u8::from(s)
203 | u8::from(h) << 1
204 | u8::from(d) << 2
205 | u8::from(c) << 3,
206 )
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::*;
214
215 #[test]
216 fn test_empty() {
217 assert_eq!(Suit4::default(), Suit4(0));
218 assert_eq!(Suit4::default(), Suit4(0));
219 assert!(Suit4::default().is_empty());
220 assert!(Suit4::default().is_empty());
221 assert!(!Suit4(1).is_empty());
222 }
223
224 #[quickcheck]
225 fn test_set_and_contains(s: Suit) {
226 let mut suits = Suit4::default();
227
228 suits.set(s);
229
230 assert!(!suits.is_empty());
231 assert!(suits.contains_suit(s));
232
233 suits.unset(s);
234
235 assert!(suits.is_empty());
236 assert!(!suits.contains_suit(s));
237 }
238
239 #[quickcheck]
240 fn test_u8(i: u8) -> TestResult {
241 if i > 0b1111 {
242 return TestResult::discard();
243 }
244
245 assert_eq!(Suit4(i), Suit4::from_u8(i));
246 assert_eq!(i, Suit4(i).to_u8());
247
248 TestResult::passed()
249 }
250
251 #[quickcheck]
252 fn test_from_suit(s1: Suit, s2: Suit) {
253 let suits = Suit4::from(s1);
254
255 assert!(suits.contains_suit(s1));
256
257 let suits = Suit4::from([s1, s2].as_ref());
258
259 assert!(suits.contains_suit(s1));
260 assert!(suits.contains_suit(s2));
261 }
262
263 #[quickcheck]
264 fn test_bit_and(s1: Suit, s2: Suit) {
265 let a = Suit4::from(s1);
266 let b = Suit4::from(s2);
267
268 assert_eq!((a & b).is_empty(), s1 != s2);
269 }
270
271 #[quickcheck]
272 fn test_bit_or(s1: Suit, s2: Suit) {
273 let a = Suit4::from(s1);
274 let b = Suit4::from(s2);
275
276 assert!((a | b).contains_suit(s1));
277 assert!((a | b).contains_suit(s2));
278 }
279
280 #[quickcheck]
281 fn test_count(s1: Suit, s2: Suit) {
282 let suits = Suit4::from([s1, s2].as_ref());
283
284 let count = if s1 == s2 { 1 } else { 2 };
285
286 assert_eq!(count, suits.count());
287 }
288
289 #[quickcheck]
290 fn test_from_card64(cards: Vec<Card>) -> TestResult {
291 let mut suits = Suit4::default();
292
293 for i in 0..cards.len() {
294 suits.set(cards[i].suit);
295
296 let c64: Card64 = cards[0..=i].into();
297
298 assert_eq!(Suit4::from(c64), suits);
299 }
300
301 TestResult::passed()
302 }
303
304 #[test]
305 fn test_debug() {
306 let s = format!("{:?}", s4!("s") | s4!("d"));
307 assert_eq!(s, "Suit4(sd)");
308 }
309
310 #[test]
311 #[cfg(debug_assertions)]
312 fn test_suit_from_u8_debug_assert() {
313 use std::panic;
314
315 for i in 0..N_SUITS {
316 let result = panic::catch_unwind(|| {
317 Suit::from_u8(i);
318 });
319 assert!(result.is_ok(), "Suit::from_u8({i}) should not panic");
320 }
321
322 let result = panic::catch_unwind(|| {
323 Suit::from_u8(N_SUITS);
324 });
325 assert!(result.is_err(), "Suit::from_u8({N_SUITS}) should panic");
326
327 let result = panic::catch_unwind(|| {
328 Suit::from_u8(100);
329 });
330 assert!(result.is_err(), "Suit::from_u8(100) should panic");
331 }
332}