1#[cfg(test)]
2mod test;
3
4use crate::contract::Strain;
5use core::fmt::{self, Write as _};
6use core::num::{NonZeroU8, Wrapping};
7use core::ops::{Add, AddAssign, BitAnd, BitOr, BitXor, Index, IndexMut, Not, Sub, SubAssign};
8use core::str::FromStr;
9use once_cell::sync::Lazy;
10use rand::prelude::SliceRandom as _;
11use thiserror::Error;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(u8)]
18pub enum Suit {
19 Clubs,
21 Diamonds,
23 Hearts,
25 Spades,
27}
28
29impl Suit {
30 pub const ASC: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
32
33 pub const DESC: [Self; 4] = [Self::Spades, Self::Hearts, Self::Diamonds, Self::Clubs];
35}
36
37impl From<Suit> for Strain {
38 #[inline]
39 fn from(suit: Suit) -> Self {
40 match suit {
41 Suit::Clubs => Self::Clubs,
42 Suit::Diamonds => Self::Diamonds,
43 Suit::Hearts => Self::Hearts,
44 Suit::Spades => Self::Spades,
45 }
46 }
47}
48
49#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
51#[error("Notrump is not a suit")]
52pub struct SuitFromNotrumpError;
53
54impl TryFrom<Strain> for Suit {
55 type Error = SuitFromNotrumpError;
56
57 #[inline]
58 fn try_from(strain: Strain) -> Result<Self, Self::Error> {
59 match strain {
60 Strain::Clubs => Ok(Self::Clubs),
61 Strain::Diamonds => Ok(Self::Diamonds),
62 Strain::Hearts => Ok(Self::Hearts),
63 Strain::Spades => Ok(Self::Spades),
64 Strain::Notrump => Err(SuitFromNotrumpError),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71#[repr(u8)]
72pub enum Seat {
73 North,
75 East,
77 South,
79 West,
81}
82
83impl Seat {
84 pub const ALL: [Self; 4] = [Self::North, Self::East, Self::South, Self::West];
86}
87
88impl Add<Wrapping<u8>> for Seat {
89 type Output = Self;
90
91 #[inline]
92 fn add(self, rhs: Wrapping<u8>) -> Self {
93 unsafe { core::mem::transmute((Wrapping(self as u8) + rhs).0 & 3) }
95 }
96}
97
98impl Add<Seat> for Wrapping<u8> {
99 type Output = Seat;
100
101 #[inline]
102 fn add(self, rhs: Seat) -> Seat {
103 rhs + self
104 }
105}
106
107impl AddAssign<Wrapping<u8>> for Seat {
108 #[inline]
109 fn add_assign(&mut self, rhs: Wrapping<u8>) {
110 *self = *self + rhs;
111 }
112}
113
114impl Sub<Wrapping<u8>> for Seat {
115 type Output = Self;
116
117 #[inline]
118 fn sub(self, rhs: Wrapping<u8>) -> Self {
119 unsafe { core::mem::transmute((Wrapping(self as u8) - rhs).0 & 3) }
121 }
122}
123
124impl SubAssign<Wrapping<u8>> for Seat {
125 #[inline]
126 fn sub_assign(&mut self, rhs: Wrapping<u8>) {
127 *self = *self - rhs;
128 }
129}
130
131impl Sub<Self> for Seat {
132 type Output = Wrapping<u8>;
133
134 #[inline]
135 fn sub(self, rhs: Self) -> Wrapping<u8> {
136 (Wrapping(self as u8) - Wrapping(rhs as u8)) & Wrapping(3)
137 }
138}
139
140impl From<Seat> for char {
141 #[inline]
142 fn from(seat: Seat) -> Self {
143 match seat {
144 Seat::North => 'N',
145 Seat::East => 'E',
146 Seat::South => 'S',
147 Seat::West => 'W',
148 }
149 }
150}
151
152bitflags::bitflags! {
153 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
155 pub struct SeatFlags: u8 {
156 const NORTH = 0b0001;
158 const EAST = 0b0010;
160 const SOUTH = 0b0100;
162 const WEST = 0b1000;
164 }
165}
166
167impl SeatFlags {
168 pub const EMPTY: Self = Self::empty();
170
171 pub const ALL: Self = Self::all();
173
174 pub const NS: Self = Self::NORTH.union(Self::SOUTH);
176
177 pub const EW: Self = Self::EAST.union(Self::WEST);
179}
180
181impl From<Seat> for SeatFlags {
182 #[inline]
183 fn from(seat: Seat) -> Self {
184 match seat {
185 Seat::North => Self::NORTH,
186 Seat::East => Self::EAST,
187 Seat::South => Self::SOUTH,
188 Seat::West => Self::WEST,
189 }
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
195pub struct Card(NonZeroU8);
196
197impl Card {
198 #[must_use]
206 #[inline]
207 pub const fn new(suit: Suit, rank: u8) -> Self {
208 assert!(rank >= 2 && rank <= 14);
209 Self(unsafe { NonZeroU8::new_unchecked(rank << 2 | suit as u8) })
211 }
212
213 #[must_use]
215 #[inline]
216 pub const fn suit(self) -> Suit {
217 unsafe { core::mem::transmute(self.0.get() & 3) }
219 }
220
221 #[must_use]
226 #[inline]
227 pub const fn rank(self) -> u8 {
228 self.0.get() >> 2
229 }
230}
231
232pub trait SmallSet<T>: Copy + Eq + BitAnd + BitOr + BitXor + Not + Sub {
234 const EMPTY: Self;
236
237 const ALL: Self;
239
240 #[must_use]
242 fn len(self) -> usize;
243
244 #[must_use]
246 #[inline]
247 fn is_empty(self) -> bool {
248 self == Self::EMPTY
249 }
250
251 fn contains(self, value: T) -> bool;
253
254 fn insert(&mut self, value: T) -> bool;
256
257 fn remove(&mut self, value: T) -> bool;
259
260 fn toggle(&mut self, value: T) -> bool;
262
263 fn set(&mut self, value: T, condition: bool) {
265 if condition {
266 self.insert(value);
267 } else {
268 self.remove(value);
269 }
270 }
271
272 fn iter(self) -> impl Iterator<Item = T>;
274
275 #[must_use]
277 #[inline]
278 fn intersection(self, rhs: Self) -> <Self as BitAnd>::Output {
279 self & rhs
280 }
281
282 #[must_use]
284 #[inline]
285 fn union(self, rhs: Self) -> <Self as BitOr>::Output {
286 self | rhs
287 }
288
289 #[must_use]
291 #[inline]
292 fn difference(self, rhs: Self) -> <Self as Sub>::Output {
293 self - rhs
294 }
295
296 #[must_use]
298 #[inline]
299 fn symmetric_difference(self, rhs: Self) -> <Self as BitXor>::Output {
300 self ^ rhs
301 }
302
303 #[must_use]
305 #[inline]
306 fn complement(self) -> <Self as Not>::Output {
307 !self
308 }
309}
310
311#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
313pub struct Holding(u16);
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316struct HoldingIter {
317 rest: u16,
318 cursor: u8,
319}
320
321impl Iterator for HoldingIter {
322 type Item = u8;
323
324 #[inline]
325 fn next(&mut self) -> Option<Self::Item> {
326 if self.rest == 0 {
327 return None;
328 }
329 #[allow(clippy::cast_possible_truncation)]
332 let step = self.rest.trailing_zeros() as u8 + 1;
333 self.rest >>= step;
334 self.cursor += step;
335 Some(self.cursor - 1)
336 }
337
338 #[inline]
339 fn size_hint(&self) -> (usize, Option<usize>) {
340 let count = self.rest.count_ones() as usize;
341 (count, Some(count))
342 }
343
344 #[inline]
345 fn count(self) -> usize {
346 self.rest.count_ones() as usize
347 }
348}
349
350impl SmallSet<u8> for Holding {
351 const EMPTY: Self = Self(0);
352 const ALL: Self = Self(0x7FFC);
353
354 #[inline]
355 fn len(self) -> usize {
356 self.0.count_ones() as usize
357 }
358
359 #[inline]
360 fn contains(self, rank: u8) -> bool {
361 self.0 & 1 << rank != 0
362 }
363
364 #[inline]
365 fn insert(&mut self, rank: u8) -> bool {
366 let insertion = 1 << rank & Self::ALL.0;
367 let inserted = insertion & !self.0 != 0;
368 self.0 |= insertion;
369 inserted
370 }
371
372 #[inline]
373 fn remove(&mut self, rank: u8) -> bool {
374 let removed = self.contains(rank);
375 self.0 &= !(1 << rank);
376 removed
377 }
378
379 #[inline]
380 fn toggle(&mut self, rank: u8) -> bool {
381 self.0 ^= 1 << rank & Self::ALL.0;
382 self.contains(rank)
383 }
384
385 #[inline]
386 fn set(&mut self, rank: u8, condition: bool) {
387 let flag = 1 << rank;
388 let mask = u16::from(condition).wrapping_neg();
389 self.0 = (self.0 & !flag) | (mask & flag);
390 }
391
392 #[inline]
393 fn iter(self) -> impl Iterator<Item = u8> {
394 HoldingIter {
395 rest: self.0,
396 cursor: 0,
397 }
398 }
399}
400
401impl Holding {
402 #[must_use]
404 #[inline]
405 pub const fn to_bits(self) -> u16 {
406 self.0
407 }
408
409 #[must_use]
411 #[inline]
412 pub const fn from_bits_retain(bits: u16) -> Self {
413 Self(bits)
414 }
415
416 #[must_use]
418 #[inline]
419 pub const fn contains_unknown_bits(self) -> bool {
420 self.0 & Self::ALL.0 != self.0
421 }
422
423 #[must_use]
425 #[inline]
426 pub const fn from_bits(bits: u16) -> Option<Self> {
427 if bits & Self::ALL.0 == bits {
428 Some(Self(bits))
429 } else {
430 None
431 }
432 }
433
434 #[must_use]
436 #[inline]
437 pub const fn from_bits_truncate(bits: u16) -> Self {
438 Self(bits & Self::ALL.0)
439 }
440
441 #[must_use]
443 #[inline]
444 pub const fn from_rank(rank: u8) -> Self {
445 Self(1 << rank)
446 }
447}
448
449impl BitAnd for Holding {
450 type Output = Self;
451
452 #[inline]
453 fn bitand(self, rhs: Self) -> Self {
454 Self(self.0 & rhs.0)
455 }
456}
457
458impl BitOr for Holding {
459 type Output = Self;
460
461 #[inline]
462 fn bitor(self, rhs: Self) -> Self {
463 Self(self.0 | rhs.0)
464 }
465}
466
467impl BitXor for Holding {
468 type Output = Self;
469
470 #[inline]
471 fn bitxor(self, rhs: Self) -> Self {
472 Self(self.0 ^ rhs.0)
473 }
474}
475
476impl Not for Holding {
477 type Output = Self;
478
479 #[inline]
480 fn not(self) -> Self {
481 Self::from_bits_truncate(!self.0)
482 }
483}
484
485impl Sub for Holding {
486 type Output = Self;
487
488 #[inline]
489 fn sub(self, rhs: Self) -> Self {
490 Self(self.0 & !rhs.0)
491 }
492}
493
494impl fmt::Display for Holding {
500 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
501 for rank in (2..15).rev() {
502 if self.contains(rank) {
503 f.write_char(b"23456789TJQKA"[rank as usize - 2] as char)?;
504 }
505 }
506 Ok(())
507 }
508}
509
510#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
512pub enum ParseHandError {
513 #[error("Ranks are not all valid or in descending order")]
515 InvalidHolding,
516
517 #[error("The same rank appears more than once")]
519 RepeatedRank,
520
521 #[error("A suit contains more than 13 cards")]
523 TooManyCards,
524
525 #[error("The hand does not contain 4 suits")]
527 NotFourSuits,
528
529 #[error("Invalid dealer tag for a deal")]
531 InvalidDealer,
532
533 #[error("The deal does not contain 4 hands")]
535 NotFourHands,
536}
537
538impl FromStr for Holding {
539 type Err = ParseHandError;
540
541 fn from_str(s: &str) -> Result<Self, Self::Err> {
542 static RE: Lazy<regex::Regex> = Lazy::new(|| {
543 regex::RegexBuilder::new("^(A?K?Q?J?(?:T|10)?9?8?7?6?5?4?3?2?)(x*)$")
544 .case_insensitive(true)
545 .build()
546 .expect("Invalid regex")
547 });
548
549 if s.len() > 13 + 1 {
551 return Err(ParseHandError::TooManyCards);
552 }
553
554 let Some((_, [explicit, spots])) = RE.captures(s).map(|x| x.extract()) else {
555 return Err(ParseHandError::InvalidHolding);
556 };
557
558 let explicit = explicit.bytes().try_fold(Self::EMPTY, |mut holding, c| {
559 let rank = match c.to_ascii_uppercase() {
560 b'1' => return Ok(holding),
561 b'2' => 2,
562 b'3' => 3,
563 b'4' => 4,
564 b'5' => 5,
565 b'6' => 6,
566 b'7' => 7,
567 b'8' => 8,
568 b'9' => 9,
569 b'T' | b'0' => 10,
570 b'J' => 11,
571 b'Q' => 12,
572 b'K' => 13,
573 b'A' => 14,
574 _ => unreachable!("Invalid ranks should have been caught by the regex"),
575 };
576
577 if holding.insert(rank) {
578 Ok(holding)
579 } else {
580 Err(ParseHandError::RepeatedRank)
581 }
582 })?;
583
584 let spots = Self::from_bits_truncate((4 << spots.len()) - 4);
585
586 if explicit & spots == Self::EMPTY {
587 Ok(explicit | spots)
588 } else {
589 Err(ParseHandError::RepeatedRank)
590 }
591 }
592}
593
594#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
596pub struct Hand(pub [Holding; 4]);
597
598impl Index<Suit> for Hand {
599 type Output = Holding;
600
601 #[inline]
602 fn index(&self, suit: Suit) -> &Holding {
603 &self.0[suit as usize]
604 }
605}
606
607impl IndexMut<Suit> for Hand {
608 #[inline]
609 fn index_mut(&mut self, suit: Suit) -> &mut Holding {
610 &mut self.0[suit as usize]
611 }
612}
613
614impl Hand {
615 #[must_use]
617 #[inline]
618 pub const fn to_bits(self) -> u64 {
619 unsafe { core::mem::transmute(self.0) }
621 }
622
623 #[must_use]
625 #[inline]
626 pub const fn from_bits_retain(bits: u64) -> Self {
627 unsafe { core::mem::transmute(bits) }
629 }
630
631 #[must_use]
633 #[inline]
634 pub const fn contains_unknown_bits(self) -> bool {
635 self.to_bits() & Self::ALL.to_bits() != self.to_bits()
636 }
637
638 #[must_use]
640 #[inline]
641 pub const fn from_bits(bits: u64) -> Option<Self> {
642 if bits & Self::ALL.to_bits() == bits {
643 Some(Self::from_bits_retain(bits))
644 } else {
645 None
646 }
647 }
648
649 #[must_use]
651 #[inline]
652 pub const fn from_bits_truncate(bits: u64) -> Self {
653 Self::from_bits_retain(bits & Self::ALL.to_bits())
654 }
655}
656
657impl SmallSet<Card> for Hand {
658 const EMPTY: Self = Self([Holding::EMPTY; 4]);
659 const ALL: Self = Self([Holding::ALL; 4]);
660
661 #[inline]
662 fn len(self) -> usize {
663 self.to_bits().count_ones() as usize
664 }
665
666 #[inline]
667 fn contains(self, card: Card) -> bool {
668 self[card.suit()].contains(card.rank())
669 }
670
671 #[inline]
672 fn insert(&mut self, card: Card) -> bool {
673 self[card.suit()].insert(card.rank())
674 }
675
676 #[inline]
677 fn remove(&mut self, card: Card) -> bool {
678 self[card.suit()].remove(card.rank())
679 }
680
681 #[inline]
682 fn toggle(&mut self, card: Card) -> bool {
683 self[card.suit()].toggle(card.rank())
684 }
685
686 #[inline]
687 fn set(&mut self, card: Card, condition: bool) {
688 self[card.suit()].set(card.rank(), condition);
689 }
690
691 #[inline]
692 fn iter(self) -> impl Iterator<Item = Card> {
693 Suit::ASC
694 .into_iter()
695 .flat_map(move |suit| self[suit].iter().map(move |rank| Card::new(suit, rank)))
696 }
697}
698
699impl fmt::Display for Hand {
703 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
704 if self.is_empty() {
705 return f.write_char('-');
706 }
707
708 self[Suit::Spades].fmt(f)?;
709 f.write_char('.')?;
710
711 self[Suit::Hearts].fmt(f)?;
712 f.write_char('.')?;
713
714 self[Suit::Diamonds].fmt(f)?;
715 f.write_char('.')?;
716
717 self[Suit::Clubs].fmt(f)
718 }
719}
720
721impl FromStr for Hand {
722 type Err = ParseHandError;
723
724 fn from_str(s: &str) -> Result<Self, Self::Err> {
725 if s.len() > 52 + 4 + 3 {
727 return Err(ParseHandError::TooManyCards);
728 }
729
730 if s == "-" {
731 return Ok(Self::EMPTY);
732 }
733
734 let holdings: Result<Vec<_>, _> = s.split('.').map(Holding::from_str).rev().collect();
735
736 Ok(Self(
737 holdings?
738 .try_into()
739 .map_err(|_| ParseHandError::NotFourSuits)?,
740 ))
741 }
742}
743
744impl BitAnd for Hand {
745 type Output = Self;
746
747 #[inline]
748 fn bitand(self, rhs: Self) -> Self {
749 Self::from_bits_retain(self.to_bits() & rhs.to_bits())
750 }
751}
752
753impl BitOr for Hand {
754 type Output = Self;
755
756 #[inline]
757 fn bitor(self, rhs: Self) -> Self {
758 Self::from_bits_retain(self.to_bits() | rhs.to_bits())
759 }
760}
761
762impl BitXor for Hand {
763 type Output = Self;
764
765 #[inline]
766 fn bitxor(self, rhs: Self) -> Self {
767 Self::from_bits_retain(self.to_bits() ^ rhs.to_bits())
768 }
769}
770
771impl Not for Hand {
772 type Output = Self;
773
774 #[inline]
775 fn not(self) -> Self {
776 Self::from_bits_truncate(!self.to_bits())
777 }
778}
779
780impl Sub for Hand {
781 type Output = Self;
782
783 #[inline]
784 fn sub(self, rhs: Self) -> Self {
785 Self::from_bits_retain(self.to_bits() & !rhs.to_bits())
786 }
787}
788
789#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
791pub struct Deal(pub [Hand; 4]);
792
793impl Index<Seat> for Deal {
794 type Output = Hand;
795
796 #[inline]
797 fn index(&self, seat: Seat) -> &Hand {
798 &self.0[seat as usize]
799 }
800}
801
802impl IndexMut<Seat> for Deal {
803 #[inline]
804 fn index_mut(&mut self, seat: Seat) -> &mut Hand {
805 &mut self.0[seat as usize]
806 }
807}
808
809struct DealDisplay {
810 deal: Deal,
811 seat: Seat,
812}
813
814impl fmt::Display for DealDisplay {
815 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
816 f.write_char(char::from(self.seat))?;
817 f.write_char(':')?;
818
819 self.deal[self.seat].fmt(f)?;
820 f.write_char(' ')?;
821
822 self.deal[self.seat + Wrapping(1)].fmt(f)?;
823 f.write_char(' ')?;
824
825 self.deal[self.seat + Wrapping(2)].fmt(f)?;
826 f.write_char(' ')?;
827
828 self.deal[self.seat + Wrapping(3)].fmt(f)
829 }
830}
831
832impl Deal {
833 pub fn new(rng: &mut (impl rand::Rng + ?Sized)) -> Self {
835 let mut deck: [_; 52] = core::array::from_fn(|i| {
836 #[allow(clippy::cast_possible_truncation)]
838 let i = i as u8;
839 let suit: Suit = unsafe { core::mem::transmute(i & 3) };
840 Card::new(suit, (i >> 2) + 2)
841 });
842 deck.shuffle(rng);
843
844 deck.into_iter()
845 .enumerate()
846 .fold(Self::default(), |mut deal, (i, card)| {
847 #[allow(clippy::cast_possible_truncation)]
849 let seat: Seat = unsafe { core::mem::transmute((i & 3) as u8) };
850 deal[seat].insert(card);
851 deal
852 })
853 }
854
855 #[must_use]
857 #[inline]
858 pub fn display(self, seat: Seat) -> impl fmt::Display {
859 DealDisplay { deal: self, seat }
860 }
861
862 #[must_use]
864 pub fn shuffled(self, rng: &mut (impl rand::Rng + ?Sized), seats: SeatFlags) -> Self {
865 let mut deal = Self::default();
866 let mut deck = Vec::with_capacity(52);
867 let mut lengths = [0; 4];
868
869 for seat in Seat::ALL {
870 if seats.contains(seat.into()) {
871 deck.extend(self[seat].iter());
872 lengths[seat as usize] = self[seat].len();
873 } else {
874 deal[seat] = self[seat];
875 }
876 }
877
878 deck.shuffle(rng);
879 let mut seat = Seat::North;
880
881 loop {
882 let Some(card) = deck.pop() else { break };
883
884 while lengths[seat as usize] == 0 {
885 seat += Wrapping(1);
886 }
887
888 lengths[seat as usize] -= 1;
889 deal[seat].insert(card);
890 }
891 deal
892 }
893}
894
895impl FromStr for Deal {
896 type Err = ParseHandError;
897
898 fn from_str(s: &str) -> Result<Self, Self::Err> {
899 static DEALER: Lazy<regex::Regex> = Lazy::new(|| {
900 regex::RegexBuilder::new(r"^[NESW]:\s*")
901 .case_insensitive(true)
902 .build()
903 .expect("Invalid regex")
904 });
905
906 let Some(tag) = DEALER.find(s) else {
907 return Err(ParseHandError::InvalidDealer);
908 };
909
910 let dealer = match s.as_bytes()[0].to_ascii_uppercase() {
911 b'N' => Seat::North,
912 b'E' => Seat::East,
913 b'S' => Seat::South,
914 b'W' => Seat::West,
915 _ => unreachable!("Invalid dealer should have been caught by the regex"),
916 };
917
918 let hands: Result<Vec<_>, _> = s[tag.end()..]
919 .split_whitespace()
920 .map(Hand::from_str)
921 .collect();
922
923 let mut deal = Self(
924 hands?
925 .try_into()
926 .map_err(|_| ParseHandError::NotFourHands)?,
927 );
928 deal.0.rotate_right(dealer as usize);
929 Ok(deal)
930 }
931}