Skip to main content

gin_rummy/
hand.rs

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