Skip to main content

dds_bridge/
deal.rs

1use crate::Suit;
2use core::fmt::{self, Write as _};
3use core::iter::FusedIterator;
4use core::num::NonZero;
5use core::ops;
6use core::str::FromStr;
7use thiserror::Error;
8
9/// Position at the table
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Seat {
12    /// Dealer of Board 1, partner of [`Seat::South`]
13    North,
14    /// Dealer of Board 2, partner of [`Seat::West`]
15    East,
16    /// Dealer of Board 3, partner of [`Seat::North`]
17    South,
18    /// Dealer of Board 4, partner of [`Seat::East`]
19    West,
20}
21
22impl Seat {
23    /// Seats in the order of dealing
24    pub const ALL: [Self; 4] = [Self::North, Self::East, Self::South, Self::West];
25
26    /// The partner of the seat
27    #[must_use]
28    pub const fn partner(self) -> Self {
29        match self {
30            Self::North => Self::South,
31            Self::East => Self::West,
32            Self::South => Self::North,
33            Self::West => Self::East,
34        }
35    }
36
37    /// The opponent on the left of the seat
38    #[must_use]
39    pub const fn lho(self) -> Self {
40        match self {
41            Self::North => Self::East,
42            Self::East => Self::South,
43            Self::South => Self::West,
44            Self::West => Self::North,
45        }
46    }
47
48    /// The opponent on the right of the seat
49    #[must_use]
50    pub const fn rho(self) -> Self {
51        match self {
52            Self::North => Self::West,
53            Self::East => Self::North,
54            Self::South => Self::East,
55            Self::West => Self::South,
56        }
57    }
58
59    /// Display character for this seat
60    #[must_use]
61    #[inline]
62    pub const fn letter(self) -> char {
63        match self {
64            Self::North => 'N',
65            Self::East => 'E',
66            Self::South => 'S',
67            Self::West => 'W',
68        }
69    }
70}
71
72const _: () = assert!(Seat::ALL[0] as u8 == 0);
73const _: () = assert!(Seat::ALL[1] as u8 == 1);
74const _: () = assert!(Seat::ALL[2] as u8 == 2);
75const _: () = assert!(Seat::ALL[3] as u8 == 3);
76
77impl fmt::Display for Seat {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.write_char(self.letter())
80    }
81}
82
83/// Error returned when parsing a [`Seat`] fails
84#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
85#[error("Invalid seat: expected one of N, E, S, W (or full names)")]
86pub struct ParseSeatError;
87
88impl FromStr for Seat {
89    type Err = ParseSeatError;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        match s.to_ascii_uppercase().as_str() {
93            "N" | "NORTH" => Ok(Self::North),
94            "E" | "EAST" => Ok(Self::East),
95            "S" | "SOUTH" => Ok(Self::South),
96            "W" | "WEST" => Ok(Self::West),
97            _ => Err(ParseSeatError),
98        }
99    }
100}
101
102bitflags::bitflags! {
103    /// A set of seats
104    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105    pub struct SeatFlags: u8 {
106        /// The set containing [`Seat::North`]
107        const NORTH = 0b0001;
108        /// The set containing [`Seat::East`]
109        const EAST = 0b0010;
110        /// The set containing [`Seat::South`]
111        const SOUTH = 0b0100;
112        /// The set containing [`Seat::West`]
113        const WEST = 0b1000;
114    }
115}
116
117impl SeatFlags {
118    /// The empty set
119    pub const EMPTY: Self = Self::empty();
120
121    /// The set containing all seats
122    pub const ALL: Self = Self::all();
123
124    /// The set containing [`Seat::North`] and [`Seat::South`]
125    pub const NS: Self = Self::NORTH.union(Self::SOUTH);
126
127    /// The set containing [`Seat::East`] and [`Seat::West`]
128    pub const EW: Self = Self::EAST.union(Self::WEST);
129}
130
131impl From<Seat> for SeatFlags {
132    fn from(seat: Seat) -> Self {
133        match seat {
134            Seat::North => Self::NORTH,
135            Seat::East => Self::EAST,
136            Seat::South => Self::SOUTH,
137            Seat::West => Self::WEST,
138        }
139    }
140}
141
142/// Error indicating an invalid rank
143///
144/// The rank of a card must be in `2..=14`, where J, Q, K, A are denoted as 11,
145/// 12, 13, 14 respectively.
146#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
147#[error("{0} is not a valid rank (2..=14)")]
148pub struct InvalidRank(u8);
149
150/// The rank of a card, from 2 to 14, where J, Q, K, A are internally denoted as
151/// 11, 12, 13, 14 respectively.
152#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
153pub struct Rank(NonZero<u8>);
154
155impl Rank {
156    /// Ace
157    pub const A: Self = Self(NonZero::new(14).unwrap());
158
159    /// King
160    pub const K: Self = Self(NonZero::new(13).unwrap());
161
162    /// Queen
163    pub const Q: Self = Self(NonZero::new(12).unwrap());
164
165    /// Jack
166    pub const J: Self = Self(NonZero::new(11).unwrap());
167
168    /// Ten
169    pub const T: Self = Self(NonZero::new(10).unwrap());
170
171    /// Create a rank from a number
172    ///
173    /// # Panics
174    ///
175    /// When the rank is not in `2..=14`.  In const contexts, this is a
176    /// compile-time error.
177    #[must_use]
178    #[inline]
179    pub const fn new(rank: u8) -> Self {
180        match Self::try_new(rank) {
181            Ok(r) => r,
182            Err(_) => panic!("rank must be in 2..=14"),
183        }
184    }
185
186    /// Try to create a rank from a number
187    ///
188    /// # Errors
189    ///
190    /// When the rank is not in `2..=14`.
191    #[inline]
192    pub const fn try_new(rank: u8) -> Result<Self, InvalidRank> {
193        match NonZero::new(rank) {
194            Some(nonzero) if rank >= 2 && rank <= 14 => Ok(Self(nonzero)),
195            _ => Err(InvalidRank(rank)),
196        }
197    }
198
199    /// Get the stored rank as [`u8`]
200    #[must_use]
201    #[inline]
202    pub const fn get(self) -> u8 {
203        self.0.get()
204    }
205
206    /// Display character for this rank
207    #[must_use]
208    #[inline]
209    pub const fn letter(self) -> char {
210        b"23456789TJQKA"[self.get() as usize - 2] as char
211    }
212}
213
214impl fmt::Display for Rank {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        f.write_char(self.letter())
217    }
218}
219
220/// A playing card
221///
222/// Internally packed as `(rank << 2) | suit` in a single byte.
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
224pub struct Card(NonZero<u8>);
225
226impl Card {
227    /// Create a card from suit and rank
228    #[must_use]
229    #[inline]
230    pub const fn new(suit: Suit, rank: Rank) -> Self {
231        // SAFETY: rank is in 2..=14, so (rank << 2 | suit) is always nonzero
232        Self(unsafe { NonZero::new_unchecked(rank.get() << 2 | suit as u8) })
233    }
234
235    /// The suit of the card
236    #[must_use]
237    #[inline]
238    pub const fn suit(self) -> Suit {
239        // SAFETY: the low 2 bits are always a valid Suit (0..=3) by construction
240        unsafe { core::mem::transmute(self.0.get() & 3) }
241    }
242
243    /// The rank of the card
244    #[must_use]
245    #[inline]
246    pub const fn rank(self) -> Rank {
247        // SAFETY: the stored rank is always in 2..=14 by construction
248        unsafe { Rank(core::num::NonZero::new_unchecked(self.0.get() >> 2)) }
249    }
250}
251
252impl fmt::Display for Card {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        write!(f, "{}{}", self.suit(), self.rank())
255    }
256}
257
258/// A set of cards of the same suit
259#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
260pub struct Holding(u16);
261
262/// Iterator over the ranks in a [`Holding`], yielding [`Rank`]s in descending order
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct HoldingIter {
265    rest: u16,
266    cursor: u8,
267}
268
269impl Iterator for HoldingIter {
270    type Item = Rank;
271
272    fn next(&mut self) -> Option<Self::Item> {
273        if self.rest == 0 {
274            return None;
275        }
276
277        // For non-zero u16, leading_zeros is in 0..=15, fitting in u8.
278        // Bit 15 is never set in a valid Holding (max rank is 14), so pos <= 14.
279        #[allow(clippy::cast_possible_truncation)]
280        let pos = 15 - self.rest.leading_zeros() as u8;
281        self.rest &= !(1u16 << pos);
282        let rank = self.cursor + pos;
283
284        // SAFETY: rank is in 2..=14 by construction from a valid Holding
285        Some(Rank(unsafe { core::num::NonZero::new_unchecked(rank) }))
286    }
287
288    fn size_hint(&self) -> (usize, Option<usize>) {
289        let count = self.rest.count_ones() as usize;
290        (count, Some(count))
291    }
292
293    fn count(self) -> usize {
294        self.rest.count_ones() as usize
295    }
296}
297
298impl DoubleEndedIterator for HoldingIter {
299    fn next_back(&mut self) -> Option<Self::Item> {
300        if self.rest == 0 {
301            return None;
302        }
303
304        // 1. Trailing zeros are in the range of 0..=15, which fits in `u8`
305        // 2. Trailing zeros cannot be 15 since the bitset is from a `Holding`
306        #[allow(clippy::cast_possible_truncation)]
307        let step = self.rest.trailing_zeros() as u8 + 1;
308        self.rest >>= step;
309        self.cursor += step;
310
311        // SAFETY: cursor is in 2..=14 by construction from a valid Holding
312        Some(Rank(unsafe {
313            core::num::NonZero::new_unchecked(self.cursor - 1)
314        }))
315    }
316}
317
318impl ExactSizeIterator for HoldingIter {
319    fn len(&self) -> usize {
320        self.rest.count_ones() as usize
321    }
322}
323
324impl FusedIterator for HoldingIter {}
325
326impl Holding {
327    /// The empty set
328    pub const EMPTY: Self = Self(0);
329
330    /// The set containing all possible ranks (2..=14)
331    pub const ALL: Self = Self(0x7FFC);
332
333    /// The number of cards in the holding
334    #[must_use]
335    #[inline]
336    pub const fn len(self) -> usize {
337        self.0.count_ones() as usize
338    }
339
340    /// Whether the holding is empty
341    #[must_use]
342    #[inline]
343    pub const fn is_empty(self) -> bool {
344        self.0 == 0
345    }
346
347    /// Whether the holding contains a rank
348    #[must_use]
349    #[inline]
350    pub const fn contains(self, rank: Rank) -> bool {
351        self.0 & 1 << rank.get() != 0
352    }
353
354    /// Insert a rank into the holding, returning whether it was newly inserted
355    #[inline]
356    pub const fn insert(&mut self, rank: Rank) -> bool {
357        let insertion = 1 << rank.get() & Self::ALL.0;
358        let inserted = insertion & !self.0 != 0;
359        self.0 |= insertion;
360        inserted
361    }
362
363    /// Remove a rank from the holding, returning whether it was present
364    #[inline]
365    pub const fn remove(&mut self, rank: Rank) -> bool {
366        let removed = self.contains(rank);
367        self.0 &= !(1 << rank.get());
368        removed
369    }
370
371    /// Toggle a rank in the holding, returning whether it is now present
372    #[inline]
373    pub const fn toggle(&mut self, rank: Rank) -> bool {
374        self.0 ^= 1 << rank.get() & Self::ALL.0;
375        self.contains(rank)
376    }
377
378    /// Conditionally insert/remove a rank from the holding
379    #[inline]
380    pub fn set(&mut self, rank: Rank, condition: bool) {
381        let flag = 1 << rank.get();
382        let mask = u16::from(condition).wrapping_neg();
383        self.0 = (self.0 & !flag) | (mask & flag);
384    }
385
386    /// Iterate over the ranks in the holding
387    #[inline]
388    #[must_use]
389    pub const fn iter(self) -> HoldingIter {
390        HoldingIter {
391            rest: self.0,
392            cursor: 0,
393        }
394    }
395
396    /// As a bitset of ranks
397    #[must_use]
398    #[inline]
399    pub const fn to_bits(self) -> u16 {
400        self.0
401    }
402
403    /// Create a holding from a bitset of ranks, retaining invalid ranks
404    #[must_use]
405    #[inline]
406    pub const fn from_bits_retain(bits: u16) -> Self {
407        Self(bits)
408    }
409
410    /// Whether the holding contains an invalid rank
411    #[must_use]
412    #[inline]
413    pub const fn contains_unknown_bits(self) -> bool {
414        self.0 & Self::ALL.0 != self.0
415    }
416
417    /// Create a holding from a bitset of ranks, checking for invalid ranks
418    #[must_use]
419    #[inline]
420    pub const fn from_bits(bits: u16) -> Option<Self> {
421        if bits & Self::ALL.0 == bits {
422            Some(Self(bits))
423        } else {
424            None
425        }
426    }
427
428    /// Create a holding from a bitset of ranks, removing invalid ranks
429    #[must_use]
430    #[inline]
431    pub const fn from_bits_truncate(bits: u16) -> Self {
432        Self(bits & Self::ALL.0)
433    }
434
435    /// Create a holding from a rank
436    #[must_use]
437    #[inline]
438    pub const fn from_rank(rank: Rank) -> Self {
439        Self(1 << rank.get())
440    }
441}
442
443impl IntoIterator for Holding {
444    type Item = Rank;
445    type IntoIter = HoldingIter;
446
447    fn into_iter(self) -> HoldingIter {
448        self.iter()
449    }
450}
451
452impl ops::BitAnd for Holding {
453    type Output = Self;
454
455    fn bitand(self, rhs: Self) -> Self {
456        Self(self.0 & rhs.0)
457    }
458}
459
460impl ops::BitOr for Holding {
461    type Output = Self;
462
463    fn bitor(self, rhs: Self) -> Self {
464        Self(self.0 | rhs.0)
465    }
466}
467
468impl ops::BitXor for Holding {
469    type Output = Self;
470
471    fn bitxor(self, rhs: Self) -> Self {
472        Self(self.0 ^ rhs.0)
473    }
474}
475
476impl ops::Not for Holding {
477    type Output = Self;
478
479    fn not(self) -> Self {
480        Self::from_bits_truncate(!self.0)
481    }
482}
483
484impl ops::Sub for Holding {
485    type Output = Self;
486
487    fn sub(self, rhs: Self) -> Self {
488        Self(self.0 & !rhs.0)
489    }
490}
491
492impl ops::BitAndAssign for Holding {
493    fn bitand_assign(&mut self, rhs: Self) {
494        *self = *self & rhs;
495    }
496}
497
498impl ops::BitOrAssign for Holding {
499    fn bitor_assign(&mut self, rhs: Self) {
500        *self = *self | rhs;
501    }
502}
503
504impl ops::BitXorAssign for Holding {
505    fn bitxor_assign(&mut self, rhs: Self) {
506        *self = *self ^ rhs;
507    }
508}
509
510impl ops::SubAssign for Holding {
511    fn sub_assign(&mut self, rhs: Self) {
512        *self = *self - rhs;
513    }
514}
515
516/// Show cards in descending order
517///
518/// 1. The ten is shown as `T` for PBN compatibility.
519/// 2. This implementation ignores formatting flags for simplicity and speed.
520///    If you want to pad or align the output, use [`fmt::Formatter::pad`].
521impl fmt::Display for Holding {
522    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
523        for rank in (2u8..15).rev() {
524            if self.0 & 1 << rank != 0 {
525                f.write_char(b"23456789TJQKA"[rank as usize - 2] as char)?;
526            }
527        }
528        Ok(())
529    }
530}
531
532/// An error which can be returned when parsing a [`Holding`]
533#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
534pub enum ParseHoldingError {
535    /// Ranks are not all valid or in descending order
536    #[error("Ranks are not all valid or in descending order")]
537    InvalidRanks,
538
539    /// The same rank appears more than once
540    #[error("The same rank appears more than once")]
541    RepeatedRank,
542
543    /// A suit contains more than 13 cards
544    #[error("A suit contains more than 13 cards")]
545    TooManyCards,
546}
547
548/// An error which can be returned when parsing a [`Hand`]
549#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
550pub enum ParseHandError {
551    /// Error in a holding
552    #[error(transparent)]
553    Holding(#[from] ParseHoldingError),
554
555    /// The hand does not contain 4 suits
556    #[error("The hand does not contain 4 suits")]
557    NotFourSuits,
558}
559
560/// An error which can be returned when parsing a [`Deal`]
561#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
562pub enum ParseDealError {
563    /// Invalid dealer tag
564    #[error("Invalid dealer tag for a deal")]
565    InvalidDealer,
566
567    /// Error in a hand
568    #[error(transparent)]
569    Hand(#[from] ParseHandError),
570
571    /// The deal does not contain 4 hands
572    #[error("The deal does not contain 4 hands")]
573    NotFourHands,
574}
575
576impl FromStr for Holding {
577    type Err = ParseHoldingError;
578
579    fn from_str(s: &str) -> Result<Self, Self::Err> {
580        // 13 cards + 1 extra char for "10"
581        if s.len() > 14 {
582            return Err(ParseHoldingError::TooManyCards);
583        }
584
585        let bytes = s.as_bytes();
586        let mut i = 0;
587        let mut prev_rank: u8 = 15;
588        let mut explicit = Self::EMPTY;
589
590        while i < bytes.len() {
591            let c = bytes[i].to_ascii_uppercase();
592            let rank: u8 = match c {
593                b'A' => 14,
594                b'K' => 13,
595                b'Q' => 12,
596                b'J' => 11,
597                b'T' => 10,
598                b'1' => {
599                    if bytes.get(i + 1) != Some(&b'0') {
600                        return Err(ParseHoldingError::InvalidRanks);
601                    }
602                    i += 1;
603                    10
604                }
605                b'2'..=b'9' => c - b'0',
606                b'X' => break,
607                _ => return Err(ParseHoldingError::InvalidRanks),
608            };
609
610            if rank >= prev_rank {
611                return Err(ParseHoldingError::InvalidRanks);
612            }
613            prev_rank = rank;
614
615            // SAFETY: rank is in 2..=14 by construction above
616            let r = Rank(unsafe { core::num::NonZero::new_unchecked(rank) });
617            explicit.insert(r);
618            i += 1;
619        }
620
621        let spot_count = bytes.len() - i;
622        if bytes[i..].iter().any(|&b| !b.eq_ignore_ascii_case(&b'x')) {
623            return Err(ParseHoldingError::InvalidRanks);
624        }
625        if spot_count > 13 {
626            return Err(ParseHoldingError::TooManyCards);
627        }
628
629        let spots = Self::from_bits_truncate((4u16 << spot_count) - 4);
630
631        if explicit & spots == Self::EMPTY {
632            Ok(explicit | spots)
633        } else {
634            Err(ParseHoldingError::RepeatedRank)
635        }
636    }
637}
638
639/// A hand of playing cards
640#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
641pub struct Hand([Holding; 4]);
642
643/// Iterator over the cards in a [`Hand`], yielding [`Card`]s in descending
644/// suit order and descending rank order within each suit
645#[derive(Debug, Clone, PartialEq, Eq)]
646pub struct HandIter {
647    suits: [HoldingIter; 4],
648    fwd: u8,
649    bwd: u8,
650}
651
652impl Iterator for HandIter {
653    type Item = Card;
654
655    fn next(&mut self) -> Option<Self::Item> {
656        loop {
657            if self.fwd > 3 {
658                return None;
659            }
660            let suit = Suit::ASC[self.fwd as usize];
661            if let Some(rank) = self.suits[self.fwd as usize].next() {
662                return Some(Card::new(suit, rank));
663            }
664            if self.fwd == self.bwd {
665                self.fwd = 4;
666                return None;
667            }
668            self.fwd -= 1;
669        }
670    }
671
672    fn size_hint(&self) -> (usize, Option<usize>) {
673        let count = self.len();
674        (count, Some(count))
675    }
676
677    fn count(self) -> usize {
678        self.len()
679    }
680}
681
682impl DoubleEndedIterator for HandIter {
683    fn next_back(&mut self) -> Option<Self::Item> {
684        loop {
685            if self.fwd > 3 {
686                return None;
687            }
688            let suit = Suit::ASC[self.bwd as usize];
689            if let Some(rank) = self.suits[self.bwd as usize].next_back() {
690                return Some(Card::new(suit, rank));
691            }
692            if self.fwd == self.bwd {
693                self.fwd = 4;
694                return None;
695            }
696            self.bwd += 1;
697        }
698    }
699}
700
701impl ExactSizeIterator for HandIter {
702    fn len(&self) -> usize {
703        if self.fwd > 3 {
704            return 0;
705        }
706        (self.bwd as usize..=self.fwd as usize)
707            .map(|i| self.suits[i].len())
708            .sum()
709    }
710}
711
712impl FusedIterator for HandIter {}
713
714impl ops::Index<Suit> for Hand {
715    type Output = Holding;
716
717    #[inline]
718    fn index(&self, suit: Suit) -> &Holding {
719        &self.0[suit as usize]
720    }
721}
722
723impl ops::IndexMut<Suit> for Hand {
724    #[inline]
725    fn index_mut(&mut self, suit: Suit) -> &mut Holding {
726        &mut self.0[suit as usize]
727    }
728}
729
730impl Hand {
731    /// As a bitset of cards
732    #[must_use]
733    #[inline]
734    pub const fn to_bits(self) -> u64 {
735        unsafe { core::mem::transmute(self.0) }
736    }
737
738    /// Create a hand from a bitset of cards, retaining invalid cards
739    #[must_use]
740    #[inline]
741    pub const fn from_bits_retain(bits: u64) -> Self {
742        unsafe { core::mem::transmute(bits) }
743    }
744
745    /// Whether the hand contains an invalid card
746    #[must_use]
747    #[inline]
748    pub const fn contains_unknown_bits(self) -> bool {
749        self.to_bits() & Self::ALL.to_bits() != self.to_bits()
750    }
751
752    /// Create a hand from a bitset of cards, checking for invalid cards
753    #[must_use]
754    #[inline]
755    pub const fn from_bits(bits: u64) -> Option<Self> {
756        if bits & Self::ALL.to_bits() == bits {
757            Some(Self::from_bits_retain(bits))
758        } else {
759            None
760        }
761    }
762
763    /// Create a hand from a bitset of cards, removing invalid cards
764    #[must_use]
765    #[inline]
766    pub const fn from_bits_truncate(bits: u64) -> Self {
767        Self::from_bits_retain(bits & Self::ALL.to_bits())
768    }
769
770    /// Create a hand from four holdings in suit order (clubs, diamonds, hearts, spades)
771    #[must_use]
772    #[inline]
773    pub const fn new(clubs: Holding, diamonds: Holding, hearts: Holding, spades: Holding) -> Self {
774        Self([clubs, diamonds, hearts, spades])
775    }
776
777    /// The empty hand
778    pub const EMPTY: Self = Self([Holding::EMPTY; 4]);
779
780    /// The hand containing all 52 cards
781    pub const ALL: Self = Self([Holding::ALL; 4]);
782
783    /// The number of cards in the hand
784    #[must_use]
785    #[inline]
786    pub const fn len(self) -> usize {
787        self.to_bits().count_ones() as usize
788    }
789
790    /// Whether the hand is empty
791    #[must_use]
792    #[inline]
793    pub const fn is_empty(self) -> bool {
794        self.to_bits() == 0
795    }
796
797    /// Whether the hand contains a card
798    #[must_use]
799    #[inline]
800    pub fn contains(self, card: Card) -> bool {
801        self[card.suit()].contains(card.rank())
802    }
803
804    /// Insert a card into the hand, returning whether it was newly inserted
805    #[inline]
806    pub fn insert(&mut self, card: Card) -> bool {
807        self[card.suit()].insert(card.rank())
808    }
809
810    /// Remove a card from the hand, returning whether it was present
811    #[inline]
812    pub fn remove(&mut self, card: Card) -> bool {
813        self[card.suit()].remove(card.rank())
814    }
815
816    /// Toggle a card in the hand, returning whether it is now present
817    #[inline]
818    pub fn toggle(&mut self, card: Card) -> bool {
819        self[card.suit()].toggle(card.rank())
820    }
821
822    /// Conditionally insert/remove a card from the hand
823    #[inline]
824    pub fn set(&mut self, card: Card, condition: bool) {
825        self[card.suit()].set(card.rank(), condition);
826    }
827
828    /// Iterate over the cards in the hand
829    #[inline]
830    #[must_use]
831    pub const fn iter(self) -> HandIter {
832        HandIter {
833            suits: [
834                self.0[0].iter(),
835                self.0[1].iter(),
836                self.0[2].iter(),
837                self.0[3].iter(),
838            ],
839            fwd: 3,
840            bwd: 0,
841        }
842    }
843}
844
845impl IntoIterator for Hand {
846    type Item = Card;
847    type IntoIter = HandIter;
848
849    #[inline]
850    fn into_iter(self) -> HandIter {
851        self.iter()
852    }
853}
854
855impl FromIterator<Card> for Hand {
856    fn from_iter<I: IntoIterator<Item = Card>>(iter: I) -> Self {
857        iter.into_iter().fold(Self::EMPTY, |mut hand, card| {
858            hand.insert(card);
859            hand
860        })
861    }
862}
863
864/// PBN-compatible display of a hand
865///
866/// This implementation ignores formatting flags for simplicity and speed.
867impl fmt::Display for Hand {
868    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
869        if self.is_empty() {
870            return f.write_char('-');
871        }
872
873        self[Suit::Spades].fmt(f)?;
874        f.write_char('.')?;
875
876        self[Suit::Hearts].fmt(f)?;
877        f.write_char('.')?;
878
879        self[Suit::Diamonds].fmt(f)?;
880        f.write_char('.')?;
881
882        self[Suit::Clubs].fmt(f)
883    }
884}
885
886impl FromStr for Hand {
887    type Err = ParseHandError;
888
889    fn from_str(s: &str) -> Result<Self, Self::Err> {
890        // 52 cards + 4 tens + 3 dots
891        if s.len() > 52 + 4 + 3 {
892            return Err(ParseHoldingError::TooManyCards.into());
893        }
894
895        if s == "-" {
896            return Ok(Self::EMPTY);
897        }
898
899        let holdings: Result<Vec<_>, _> = s.split('.').map(Holding::from_str).rev().collect();
900
901        Ok(Self(
902            holdings?
903                .try_into()
904                .map_err(|_| ParseHandError::NotFourSuits)?,
905        ))
906    }
907}
908
909impl ops::BitAnd for Hand {
910    type Output = Self;
911
912    #[inline]
913    fn bitand(self, rhs: Self) -> Self {
914        Self::from_bits_retain(self.to_bits() & rhs.to_bits())
915    }
916}
917
918impl ops::BitOr for Hand {
919    type Output = Self;
920
921    #[inline]
922    fn bitor(self, rhs: Self) -> Self {
923        Self::from_bits_retain(self.to_bits() | rhs.to_bits())
924    }
925}
926
927impl ops::BitXor for Hand {
928    type Output = Self;
929
930    #[inline]
931    fn bitxor(self, rhs: Self) -> Self {
932        Self::from_bits_retain(self.to_bits() ^ rhs.to_bits())
933    }
934}
935
936impl ops::Not for Hand {
937    type Output = Self;
938
939    #[inline]
940    fn not(self) -> Self {
941        Self::from_bits_truncate(!self.to_bits())
942    }
943}
944
945impl ops::Sub for Hand {
946    type Output = Self;
947
948    #[inline]
949    fn sub(self, rhs: Self) -> Self {
950        Self::from_bits_retain(self.to_bits() & !rhs.to_bits())
951    }
952}
953
954impl ops::BitAndAssign for Hand {
955    #[inline]
956    fn bitand_assign(&mut self, rhs: Self) {
957        *self = *self & rhs;
958    }
959}
960
961impl ops::BitOrAssign for Hand {
962    #[inline]
963    fn bitor_assign(&mut self, rhs: Self) {
964        *self = *self | rhs;
965    }
966}
967
968impl ops::BitXorAssign for Hand {
969    #[inline]
970    fn bitxor_assign(&mut self, rhs: Self) {
971        *self = *self ^ rhs;
972    }
973}
974
975impl ops::SubAssign for Hand {
976    #[inline]
977    fn sub_assign(&mut self, rhs: Self) {
978        *self = *self - rhs;
979    }
980}
981
982/// A deal of four hands
983#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
984pub struct Deal([Hand; 4]);
985
986impl IntoIterator for Deal {
987    type Item = Hand;
988    type IntoIter = core::array::IntoIter<Hand, 4>;
989
990    #[inline]
991    fn into_iter(self) -> Self::IntoIter {
992        self.0.into_iter()
993    }
994}
995
996impl ops::Index<Seat> for Deal {
997    type Output = Hand;
998
999    #[inline]
1000    fn index(&self, seat: Seat) -> &Hand {
1001        &self.0[seat as usize]
1002    }
1003}
1004
1005impl ops::IndexMut<Seat> for Deal {
1006    #[inline]
1007    fn index_mut(&mut self, seat: Seat) -> &mut Hand {
1008        &mut self.0[seat as usize]
1009    }
1010}
1011
1012impl Deal {
1013    /// Empty deal
1014    pub const EMPTY: Self = Self([Hand::EMPTY; 4]);
1015
1016    /// Construct a deal from four hands
1017    #[must_use]
1018    pub const fn new(north: Hand, east: Hand, south: Hand, west: Hand) -> Self {
1019        Self([north, east, south, west])
1020    }
1021
1022    /// If the deal is a subset of a bridge deal, collect all the cards into a
1023    /// single hand.  Otherwise, return `None`.  This function checks the
1024    /// validity of the deal and also returns potentially useful information.
1025    ///
1026    /// A deal is a subset of a bridge deal if it satisfies the following
1027    /// conditions:
1028    ///
1029    /// 1. Each hand contains at most 13 cards.
1030    /// 2. The hands are pairwise disjoint.
1031    #[must_use]
1032    pub fn validate_and_collect(self) -> Option<Hand> {
1033        let mut seen = Hand::EMPTY;
1034        for hand in self.0 {
1035            if hand.len() > 13 || hand & seen != Hand::EMPTY {
1036                return None;
1037            }
1038            seen |= hand;
1039        }
1040        Some(seen)
1041    }
1042
1043    /// PBN-compatible display from a seat's perspective
1044    #[must_use]
1045    pub fn display(self, seat: Seat) -> impl fmt::Display {
1046        struct DisplayAt {
1047            deal: Deal,
1048            seat: Seat,
1049        }
1050        impl fmt::Display for DisplayAt {
1051            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1052                f.write_char(self.seat.letter())?;
1053                f.write_char(':')?;
1054
1055                self.deal[self.seat].fmt(f)?;
1056                f.write_char(' ')?;
1057
1058                self.deal[self.seat.lho()].fmt(f)?;
1059                f.write_char(' ')?;
1060
1061                self.deal[self.seat.partner()].fmt(f)?;
1062                f.write_char(' ')?;
1063
1064                self.deal[self.seat.rho()].fmt(f)
1065            }
1066        }
1067        DisplayAt { deal: self, seat }
1068    }
1069}
1070
1071impl FromStr for Deal {
1072    type Err = ParseDealError;
1073
1074    fn from_str(s: &str) -> Result<Self, Self::Err> {
1075        let bytes = s.as_bytes();
1076
1077        let dealer = match bytes.first().map(u8::to_ascii_uppercase) {
1078            Some(b'N') => Seat::North,
1079            Some(b'E') => Seat::East,
1080            Some(b'S') => Seat::South,
1081            Some(b'W') => Seat::West,
1082            _ => return Err(ParseDealError::InvalidDealer),
1083        };
1084
1085        if bytes.get(1) != Some(&b':') {
1086            return Err(ParseDealError::InvalidDealer);
1087        }
1088
1089        let hands: Result<Vec<_>, _> = s[2..].split_whitespace().map(Hand::from_str).collect();
1090
1091        let mut deal = Self(
1092            hands?
1093                .try_into()
1094                .map_err(|_| ParseDealError::NotFourHands)?,
1095        );
1096        deal.0.rotate_right(dealer as usize);
1097        Ok(deal)
1098    }
1099}