Skip to main content

gin_rummy/
meld.rs

1//! Melds, arrangements, and the exact deadwood solver.
2//!
3//! A [`Meld`] is a *set* (3-4 cards of one rank) or a *run* (3+ consecutive
4//! cards of one suit).  A [`Melds`] is one chosen arrangement of a hand into
5//! at most three disjoint melds plus deadwood.  [`best_melds`] and
6//! [`deadwood`] solve the optimization gin rummy revolves around: splitting
7//! a hand into disjoint melds that minimize the pip value of the leftovers.
8//!
9//! The solver follows the approach popularized by Todd Neller's EAAI gin
10//! rummy framework: all 329 possible melds (65 sets and 264 runs) are
11//! precomputed as card bitsets at compile time, applicability is one bitwise
12//! test per meld, and a branch-and-bound search over "lowest card is
13//! deadwood or starts one of its melds" explores every maximal disjoint
14//! packing.  Hands have at most 11 cards during play, so the search takes
15//! microseconds.
16//!
17//! # Panic policy
18//!
19//! [`Meld::run`] panics on runs shorter than three cards and has
20//! [`Meld::try_run`] for fallible construction.  [`best_melds`] panics on
21//! hands of more than 11 cards, for which an arrangement of at most three
22//! melds is not enough; [`deadwood`] and [`pip_sum`] accept any card set.
23
24use crate::hand::ParseCardError;
25use crate::{Card, Hand, Rank, Suit};
26use core::fmt::{self, Write as _};
27use core::str::FromStr;
28use thiserror::Error;
29
30/// The two shapes of a meld
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum MeldKind {
34    /// 3 or 4 cards of the same rank
35    Set,
36    /// 3 or more consecutive cards of the same suit
37    Run,
38}
39
40/// Error indicating that cards do not form a meld
41#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum InvalidMeld {
44    /// A meld requires at least 3 cards
45    #[error("A meld requires at least 3 cards")]
46    TooFewCards,
47
48    /// Cards of one suit do not form consecutive ranks
49    #[error("Cards of one suit do not form consecutive ranks")]
50    NotConsecutive,
51
52    /// Cards of several suits form neither a set nor a run
53    #[error("Cards of several suits form neither a set nor a run")]
54    MixedCards,
55
56    /// The card set contains bits outside the 52-card deck
57    #[error("The card set contains bits outside the 52-card deck")]
58    UnknownCards,
59}
60
61/// A validated meld: a card set that is entirely one set or one run
62///
63/// A meld is internally a [`Hand`], so meld membership tests are single
64/// bitwise operations on the shared `u64` layout.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66#[cfg_attr(
67    feature = "serde",
68    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
69)]
70#[repr(transparent)]
71pub struct Meld(Hand);
72
73impl Meld {
74    /// Create a set of the given rank, with all four suits (`None`) or the
75    /// three suits other than `missing`
76    #[must_use]
77    #[inline]
78    pub const fn set(rank: Rank, missing: Option<Suit>) -> Self {
79        let bit = 1u64 << rank.get();
80        let all = bit | bit << 16 | bit << 32 | bit << 48;
81        Self(Hand::from_bits_retain(match missing {
82            None => all,
83            Some(suit) => all & !(0xFFFF << (16 * suit as u64)),
84        }))
85    }
86
87    /// Create a run of the given suit spanning `low..=high`
88    ///
89    /// # Panics
90    ///
91    /// When the run would be shorter than 3 cards.  In const contexts, this
92    /// is a compile-time error.
93    #[must_use]
94    #[inline]
95    pub const fn run(suit: Suit, low: Rank, high: Rank) -> Self {
96        match Self::try_run(suit, low, high) {
97            Ok(run) => run,
98            Err(_) => panic!("a run spans at least 3 consecutive ranks"),
99        }
100    }
101
102    /// Try to create a run of the given suit spanning `low..=high`
103    ///
104    /// # Errors
105    ///
106    /// When the run would be shorter than 3 cards.
107    #[inline]
108    pub const fn try_run(suit: Suit, low: Rank, high: Rank) -> Result<Self, InvalidMeld> {
109        if high.get() < low.get() + 2 {
110            return Err(InvalidMeld::TooFewCards);
111        }
112        let holding = (1u64 << (high.get() + 1)) - (1 << low.get());
113        Ok(Self(Hand::from_bits_retain(holding << (16 * suit as u64))))
114    }
115
116    /// Try to create a meld from a card set
117    ///
118    /// # Errors
119    ///
120    /// When the cards do not form exactly one set or one run.
121    pub const fn try_from_cards(cards: Hand) -> Result<Self, InvalidMeld> {
122        if cards.contains_unknown_bits() {
123            return Err(InvalidMeld::UnknownCards);
124        }
125        if cards.len() < 3 {
126            return Err(InvalidMeld::TooFewCards);
127        }
128
129        let bits = cards.to_bits();
130        let lanes = [
131            bits as u16,
132            (bits >> 16) as u16,
133            (bits >> 32) as u16,
134            (bits >> 48) as u16,
135        ];
136        let union = lanes[0] | lanes[1] | lanes[2] | lanes[3];
137
138        if union.count_ones() as usize == cards.len() {
139            // The suits do not overlap in rank: one suit forms a run if its
140            // ranks are consecutive, and several suits never meld together.
141            if union != lanes[0] && union != lanes[1] && union != lanes[2] && union != lanes[3] {
142                return Err(InvalidMeld::MixedCards);
143            }
144            if (union >> union.trailing_zeros()) + 1 != 1 << union.count_ones() {
145                return Err(InvalidMeld::NotConsecutive);
146            }
147        } else if union.count_ones() != 1 {
148            // Overlapping ranks must all be the same single rank.
149            return Err(InvalidMeld::MixedCards);
150        }
151
152        Ok(Self(cards))
153    }
154
155    /// The shape of this meld
156    #[must_use]
157    #[inline]
158    pub const fn kind(self) -> MeldKind {
159        let bits = self.0.to_bits();
160        let lanes = (bits & 0xFFFF != 0) as u8
161            + (bits >> 16 & 0xFFFF != 0) as u8
162            + (bits >> 32 & 0xFFFF != 0) as u8
163            + (bits >> 48 != 0) as u8;
164        if lanes == 1 {
165            MeldKind::Run
166        } else {
167            MeldKind::Set
168        }
169    }
170
171    /// The cards of this meld
172    #[must_use]
173    #[inline]
174    pub const fn cards(self) -> Hand {
175        self.0
176    }
177
178    /// The number of cards in this meld, from 3 to 13
179    #[allow(clippy::len_without_is_empty)] // a meld is never empty
180    #[must_use]
181    #[inline]
182    pub const fn len(self) -> usize {
183        self.0.len()
184    }
185
186    /// The suit of a run, or `None` for a set
187    #[must_use]
188    pub const fn suit(self) -> Option<Suit> {
189        match self.kind() {
190            MeldKind::Set => None,
191            MeldKind::Run => Some(Suit::ASC[self.0.to_bits().trailing_zeros() as usize / 16]),
192        }
193    }
194
195    /// The rank of a set, or `None` for a run
196    #[must_use]
197    pub const fn rank(self) -> Option<Rank> {
198        match self.kind() {
199            // Truncation is exact: trailing_zeros % 16 is in 1..=13.
200            MeldKind::Set => Some(Rank::new(self.0.to_bits().trailing_zeros() as u8 % 16)),
201            MeldKind::Run => None,
202        }
203    }
204
205    /// The lowest rank of a run, or `None` for a set
206    #[must_use]
207    pub const fn low(self) -> Option<Rank> {
208        match self.kind() {
209            MeldKind::Set => None,
210            // Truncation is exact: trailing_zeros % 16 is in 1..=13.
211            MeldKind::Run => Some(Rank::new(self.0.to_bits().trailing_zeros() as u8 % 16)),
212        }
213    }
214
215    /// The highest rank of a run, or `None` for a set
216    #[must_use]
217    pub const fn high(self) -> Option<Rank> {
218        match self.kind() {
219            MeldKind::Set => None,
220            // Truncation is exact: 63 - leading_zeros % 16 is in 1..=13.
221            MeldKind::Run => Some(Rank::new(
222                (63 - self.0.to_bits().leading_zeros() as u8) % 16,
223            )),
224        }
225    }
226
227    /// The meld extended by a card, or `None` if the card does not fit
228    ///
229    /// This is the layoff primitive: a card extends a 3-card set of its rank
230    /// or prolongs a run of its suit at either end.  Chained layoffs work by
231    /// extending the returned meld again.
232    #[must_use]
233    pub const fn extended(self, card: Card) -> Option<Self> {
234        let bits = self.0.to_bits();
235        let card = 1u64 << (16 * card.suit as u64 + card.rank.get() as u64);
236        if bits & card != 0 {
237            return None;
238        }
239        match Self::try_from_cards(Hand::from_bits_retain(bits | card)) {
240            Ok(meld) => Some(meld),
241            Err(_) => None,
242        }
243    }
244}
245
246impl TryFrom<Hand> for Meld {
247    type Error = InvalidMeld;
248
249    #[inline]
250    fn try_from(cards: Hand) -> Result<Self, InvalidMeld> {
251        Self::try_from_cards(cards)
252    }
253}
254
255impl From<Meld> for Hand {
256    #[inline]
257    fn from(meld: Meld) -> Self {
258        meld.cards()
259    }
260}
261
262/// Concatenated cards in ascending order, e.g. `5♠6♠7♠`
263impl fmt::Display for Meld {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        self.0.iter().try_for_each(|card| write!(f, "{card}"))
266    }
267}
268
269/// Error returned when parsing a [`Meld`] fails
270#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
271#[non_exhaustive]
272pub enum ParseMeldError {
273    /// Error in a card
274    #[error(transparent)]
275    Card(#[from] ParseCardError),
276
277    /// The same card appears more than once
278    #[error("The same card appears more than once")]
279    RepeatedCard,
280
281    /// The cards do not form a meld
282    #[error(transparent)]
283    Invalid(#[from] InvalidMeld),
284}
285
286impl FromStr for Meld {
287    type Err = ParseMeldError;
288
289    fn from_str(s: &str) -> Result<Self, Self::Err> {
290        const fn is_suit_char(c: char) -> bool {
291            matches!(
292                c.to_ascii_uppercase(),
293                'C' | 'D' | 'H' | 'S' | '♣' | '♦' | '♥' | '♠' | '♧' | '♢' | '♡' | '♤'
294            )
295        }
296
297        const SELECTORS: [char; 2] = ['\u{FE0F}', '\u{FE0E}'];
298
299        let mut cards = Hand::EMPTY;
300        let mut rest = s.trim_ascii_start();
301
302        // The Display format is rank-first (`5♠6♠7♠`), so each card runs up to
303        // and including its suit glyph.  A string that instead opens with a
304        // suit glyph is legacy suit-first text (`♠5♠6♠7`), where a card runs up
305        // to the *next* suit glyph.
306        let suit_first = rest.chars().next().is_some_and(is_suit_char);
307
308        while !rest.is_empty() {
309            let end = if suit_first {
310                rest.char_indices()
311                    .skip(1)
312                    .find_map(|(i, c)| is_suit_char(c).then_some(i))
313                    .unwrap_or(rest.len())
314            } else {
315                let after_suit = rest
316                    .char_indices()
317                    .find_map(|(i, c)| is_suit_char(c).then_some(i + c.len_utf8()))
318                    .unwrap_or(rest.len());
319                // Include any variation selector trailing the suit glyph.
320                rest.len() - rest[after_suit..].trim_start_matches(SELECTORS).len()
321            };
322            let card: Card = rest[..end].trim_ascii().parse()?;
323            if !cards.insert(card) {
324                return Err(ParseMeldError::RepeatedCard);
325            }
326            rest = &rest[end..];
327        }
328
329        Ok(Self::try_from_cards(cards)?)
330    }
331}
332
333/// Error indicating an invalid arrangement of a hand into melds
334#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
335#[non_exhaustive]
336pub enum ArrangeError {
337    /// An arrangement covers a game hand of at most 11 cards
338    #[error("An arrangement covers a game hand of at most 11 cards")]
339    TooManyCards,
340
341    /// At most 3 disjoint melds fit in 11 cards
342    #[error("At most 3 disjoint melds fit in 11 cards")]
343    TooManyMelds,
344
345    /// Two melds share a card
346    #[error("Two melds share a card")]
347    OverlappingMelds,
348
349    /// A meld contains a card outside the hand
350    #[error("A meld contains a card outside the hand")]
351    MeldNotInHand,
352}
353
354/// One arrangement of a hand into disjoint melds plus deadwood
355///
356/// This is what a knocker spreads on the table.  The arrangement fixes which
357/// cards are melded — and therefore what the opponent may lay off — so a
358/// knocker may legitimately choose a non-optimal arrangement.
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
360pub struct Melds {
361    melds: [Option<Meld>; 3],
362    hand: Hand,
363}
364
365impl Melds {
366    /// Arrange a hand into the given melds
367    ///
368    /// # Errors
369    ///
370    /// When the hand exceeds 11 cards, the melds exceed 3, the melds
371    /// overlap, or a meld is not contained in the hand.
372    pub fn try_new(hand: Hand, melds: &[Meld]) -> Result<Self, ArrangeError> {
373        if hand.len() > 11 {
374            return Err(ArrangeError::TooManyCards);
375        }
376        if melds.len() > 3 {
377            return Err(ArrangeError::TooManyMelds);
378        }
379
380        let mut union = Hand::EMPTY;
381        let mut array = [None; 3];
382
383        for (slot, &meld) in array.iter_mut().zip(melds) {
384            if meld.cards() & hand != meld.cards() {
385                return Err(ArrangeError::MeldNotInHand);
386            }
387            if !(union & meld.cards()).is_empty() {
388                return Err(ArrangeError::OverlappingMelds);
389            }
390            union |= meld.cards();
391            *slot = Some(meld);
392        }
393
394        Ok(Self { melds: array, hand })
395    }
396
397    /// Iterate over the melds of this arrangement
398    #[inline]
399    pub fn iter(self) -> impl Iterator<Item = Meld> {
400        self.melds.into_iter().flatten()
401    }
402
403    /// The arranged hand
404    #[must_use]
405    #[inline]
406    pub const fn hand(self) -> Hand {
407        self.hand
408    }
409
410    /// The union of the melds
411    #[must_use]
412    pub fn melded(self) -> Hand {
413        self.iter()
414            .fold(Hand::EMPTY, |acc, meld| acc | meld.cards())
415    }
416
417    /// The unmelded cards
418    #[must_use]
419    pub fn deadwood_cards(self) -> Hand {
420        self.hand - self.melded()
421    }
422
423    /// The deadwood value of the unmelded cards
424    #[must_use]
425    pub fn deadwood(self) -> u8 {
426        // An arrangement holds at most 11 cards, worth at most 110 points.
427        pip_sum(self.deadwood_cards()) as u8
428    }
429
430    pub(crate) const fn into_array(self) -> [Option<Meld>; 3] {
431        self.melds
432    }
433}
434
435/// Melds separated by spaces, then `|` and the deadwood cards if any,
436/// e.g. `7♥8♥9♥ Q♣Q♦Q♠ | A♦5♦`
437///
438/// This human-oriented format is informal and not parseable.
439impl fmt::Display for Melds {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        let mut leading = true;
442
443        for meld in self.iter() {
444            if !leading {
445                f.write_char(' ')?;
446            }
447            write!(f, "{meld}")?;
448            leading = false;
449        }
450
451        let deadwood = self.deadwood_cards();
452        if !deadwood.is_empty() {
453            if !leading {
454                f.write_str(" | ")?;
455            }
456            deadwood.iter().try_for_each(|card| write!(f, "{card}"))?;
457        }
458        Ok(())
459    }
460}
461
462/// The number of possible melds: 65 sets (5 per rank) and 264 runs (66 per
463/// suit)
464const MELD_COUNT: usize = 329;
465
466/// All possible melds, precomputed at compile time
467static MELDS: [Meld; MELD_COUNT] = build_melds();
468
469const fn build_melds() -> [Meld; MELD_COUNT] {
470    let mut table = [Meld::set(Rank::A, None); MELD_COUNT];
471    let mut i = 0;
472
473    let mut rank = 1;
474    while rank <= 13 {
475        table[i] = Meld::set(Rank::new(rank), None);
476        i += 1;
477        let mut suit = 0;
478        while suit < 4 {
479            table[i] = Meld::set(Rank::new(rank), Some(Suit::ASC[suit]));
480            i += 1;
481            suit += 1;
482        }
483        rank += 1;
484    }
485
486    let mut suit = 0;
487    while suit < 4 {
488        let mut low = 1;
489        while low <= 11 {
490            let mut high = low + 2;
491            while high <= 13 {
492                table[i] = Meld::run(Suit::ASC[suit], Rank::new(low), Rank::new(high));
493                i += 1;
494                high += 1;
495            }
496            low += 1;
497        }
498        suit += 1;
499    }
500
501    assert!(i == MELD_COUNT);
502    table
503}
504
505/// The deadwood value of the lowest card of a non-empty bitset
506const fn card_value(card: u64) -> u16 {
507    let rank = card.trailing_zeros() as u16 % 16;
508    if rank > 10 { 10 } else { rank }
509}
510
511/// Whether a meld bitset spans more than one suit lane, i.e. is a set
512const fn is_set(meld: u64) -> bool {
513    let lanes = (meld & 0xFFFF != 0) as u8
514        + (meld >> 16 & 0xFFFF != 0) as u8
515        + (meld >> 32 & 0xFFFF != 0) as u8
516        + (meld >> 48 != 0) as u8;
517    lanes > 1
518}
519
520/// The total deadwood value of a card set, melded or not
521///
522/// Cards outside the 52-card deck (possible only via
523/// [`Hand::from_bits_retain`]) are ignored.
524#[must_use]
525pub const fn pip_sum(hand: Hand) -> u16 {
526    let mut bits = hand.to_bits() & Hand::ALL.to_bits();
527    let mut total = 0;
528    while bits != 0 {
529        total += card_value(bits);
530        bits &= bits - 1;
531    }
532    total
533}
534
535/// Collect the applicable melds of a hand into `buf`, returning the count
536fn applicable_melds(bits: u64, buf: &mut [u64; MELD_COUNT]) -> usize {
537    let mut count = 0;
538    for meld in &MELDS {
539        let meld = meld.cards().to_bits();
540        if meld & bits == meld {
541            buf[count] = meld;
542            count += 1;
543        }
544    }
545    count
546}
547
548/// Branch and bound over "the lowest card is deadwood or in one of its
549/// melds", the exhaustive search over maximal disjoint meld packings
550///
551/// `sets` counts the set melds chosen so far.  It only matters to the
552/// arrangement-recording variant, where `(deadwood, sets)` is minimized
553/// lexicographically so equal-deadwood ties favor runs; both components grow
554/// monotonically down the search, so the bound stays a sound lower bound.
555fn search(hand: u64, melds: &[u64], acc: u16, sets: u16, tracker: &mut Tracker<'_>) {
556    if !tracker.improves(acc, sets) {
557        return;
558    }
559    if hand == 0 {
560        tracker.record(acc, sets);
561        return;
562    }
563
564    let card = hand & hand.wrapping_neg();
565    if tracker.depth() < 3 {
566        for &meld in melds {
567            if meld & card != 0 && meld & hand == meld {
568                tracker.push(meld);
569                search(
570                    hand & !meld,
571                    melds,
572                    acc,
573                    sets + is_set(meld) as u16,
574                    tracker,
575                );
576                tracker.pop();
577            }
578        }
579    }
580    search(hand & !card, melds, acc + card_value(card), sets, tracker);
581}
582
583struct Tracker<'a> {
584    best: u16,
585    best_sets: u16,
586    chosen: [u64; 3],
587    count: usize,
588    best_melds: Option<&'a mut [u64; 3]>,
589}
590
591impl Tracker<'_> {
592    const fn depth(&self) -> usize {
593        // Without a consumer of the chosen melds, the depth cap is
594        // irrelevant: packings differing only in melds score alike.
595        if self.best_melds.is_some() {
596            self.count
597        } else {
598            0
599        }
600    }
601
602    /// Whether `(acc, sets)` could still beat the best recorded arrangement.
603    /// `deadwood()` ignores the set count and stays a pure-pip search; only
604    /// `best_melds()` applies the run-favoring tie-break.
605    const fn improves(&self, acc: u16, sets: u16) -> bool {
606        if self.best_melds.is_some() {
607            acc < self.best || (acc == self.best && sets < self.best_sets)
608        } else {
609            acc < self.best
610        }
611    }
612
613    const fn push(&mut self, meld: u64) {
614        if self.best_melds.is_some() {
615            self.chosen[self.count] = meld;
616        }
617        self.count += 1;
618    }
619
620    const fn pop(&mut self) {
621        self.count -= 1;
622    }
623
624    fn record(&mut self, acc: u16, sets: u16) {
625        self.best = acc;
626        self.best_sets = sets;
627        if let Some(best_melds) = &mut self.best_melds {
628            **best_melds = [0; 3];
629            best_melds[..self.count].copy_from_slice(&self.chosen[..self.count]);
630        }
631    }
632}
633
634/// The minimum deadwood value of a card set over all meld arrangements
635///
636/// Cards outside the 52-card deck (possible only via
637/// [`Hand::from_bits_retain`]) are ignored.  Any number of cards is
638/// accepted; hands beyond the 11 cards of gin rummy merely take longer.
639#[must_use]
640pub fn deadwood(hand: Hand) -> u8 {
641    let bits = hand.to_bits() & Hand::ALL.to_bits();
642    let mut buf = [0; MELD_COUNT];
643    let count = applicable_melds(bits, &mut buf);
644    let mut tracker = Tracker {
645        best: u16::MAX,
646        best_sets: u16::MAX,
647        chosen: [0; 3],
648        count: 0,
649        best_melds: None,
650    };
651    search(bits, &buf[..count], 0, 0, &mut tracker);
652
653    // An optimal remainder is meld-free — melding a leftover meld would only
654    // shrink the deadwood — and the most valuable meld-free card set is worth
655    // 170 points (two suits of A 3 4 6 7 9 T Q K), so the minimum fits u8.
656    tracker.best as u8
657}
658
659/// A best arrangement of a hand: disjoint melds minimizing deadwood
660///
661/// Among equal-deadwood arrangements the tie-break currently favors runs over
662/// sets — a run gins more readily because it extends at both ends — but the
663/// exact choice is unspecified and may change between releases.  Cards outside
664/// the 52-card deck (possible only via [`Hand::from_bits_retain`]) are
665/// ignored.
666///
667/// # Panics
668///
669/// When the hand has more than 11 cards, for which an arrangement of at most
670/// three melds is not enough.  Use [`deadwood`] for arbitrary card sets.
671#[must_use]
672pub fn best_melds(hand: Hand) -> Melds {
673    let hand = Hand::from_bits_truncate(hand.to_bits());
674    assert!(
675        hand.len() <= 11,
676        "best_melds arranges game hands of at most 11 cards"
677    );
678
679    let mut buf = [0; MELD_COUNT];
680    let count = applicable_melds(hand.to_bits(), &mut buf);
681    let mut best_melds = [0; 3];
682    let mut tracker = Tracker {
683        best: u16::MAX,
684        best_sets: u16::MAX,
685        chosen: [0; 3],
686        count: 0,
687        best_melds: Some(&mut best_melds),
688    };
689    search(hand.to_bits(), &buf[..count], 0, 0, &mut tracker);
690
691    let mut melds = [None; 3];
692    for (slot, &bits) in melds.iter_mut().zip(&best_melds) {
693        if bits != 0 {
694            *slot = Some(Meld(Hand::from_bits_retain(bits)));
695        }
696    }
697    Melds { melds, hand }
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703
704    #[test]
705    fn table_is_sound() {
706        assert_eq!(MELDS.len(), 329);
707
708        for (i, meld) in MELDS.iter().enumerate() {
709            assert_eq!(Meld::try_from_cards(meld.cards()), Ok(*meld));
710            for other in &MELDS[..i] {
711                assert_ne!(meld, other);
712            }
713        }
714
715        let sets = MELDS.iter().filter(|m| m.kind() == MeldKind::Set).count();
716        assert_eq!(sets, 65);
717    }
718
719    #[test]
720    fn meld_constructors() {
721        let set = Meld::set(Rank::Q, None);
722        assert_eq!(set.kind(), MeldKind::Set);
723        assert_eq!(set.len(), 4);
724        assert_eq!(set.rank(), Some(Rank::Q));
725        assert_eq!((set.suit(), set.low(), set.high()), (None, None, None));
726
727        let set = Meld::set(Rank::A, Some(Suit::Hearts));
728        assert_eq!(set.len(), 3);
729        assert!(!set.cards().contains("♥A".parse().unwrap()));
730
731        let run = Meld::run(Suit::Spades, Rank::A, Rank::new(3));
732        assert_eq!(run.kind(), MeldKind::Run);
733        assert_eq!(run.len(), 3);
734        assert_eq!(run.suit(), Some(Suit::Spades));
735        assert_eq!(run.low(), Some(Rank::A));
736        assert_eq!(run.high(), Some(Rank::new(3)));
737        assert_eq!(run.rank(), None);
738
739        assert_eq!(
740            Meld::try_run(Suit::Clubs, Rank::A, Rank::new(2)),
741            Err(InvalidMeld::TooFewCards),
742        );
743
744        let all_spades = Meld::run(Suit::Spades, Rank::A, Rank::K);
745        assert_eq!(all_spades.len(), 13);
746    }
747
748    #[test]
749    fn try_from_cards_rejects_non_melds() {
750        let parse = |s: &str| Meld::try_from_cards(s.parse::<Hand>().unwrap());
751
752        assert!(parse("567...").is_ok());
753        assert!(parse("7.7.7.").is_ok());
754        assert!(parse("7.7.7.7").is_ok());
755        assert_eq!(parse("57.5.."), Err(InvalidMeld::MixedCards));
756        assert_eq!(parse("567.8.."), Err(InvalidMeld::MixedCards));
757        assert_eq!(parse("579..."), Err(InvalidMeld::NotConsecutive));
758        assert_eq!(parse("56..."), Err(InvalidMeld::TooFewCards));
759        assert_eq!(parse("..."), Err(InvalidMeld::TooFewCards));
760        assert_eq!(
761            Meld::try_from_cards(Hand::from_bits_retain(7 << 14)),
762            Err(InvalidMeld::UnknownCards),
763        );
764    }
765
766    #[test]
767    fn extension() {
768        let run = Meld::run(Suit::Spades, Rank::new(5), Rank::new(7));
769        let extended = run.extended("♠8".parse().unwrap()).unwrap();
770        assert_eq!(extended.high(), Some(Rank::new(8)));
771        let chained = extended.extended("♠9".parse().unwrap()).unwrap();
772        assert_eq!(chained.high(), Some(Rank::new(9)));
773        let low_end = run.extended("♠4".parse().unwrap()).unwrap();
774        assert_eq!(low_end.low(), Some(Rank::new(4)));
775
776        assert_eq!(run.extended("♠9".parse().unwrap()), None);
777        assert_eq!(run.extended("♥8".parse().unwrap()), None);
778        assert_eq!(run.extended("♠6".parse().unwrap()), None);
779
780        let low_run = Meld::run(Suit::Clubs, Rank::A, Rank::new(3));
781        let high_run = Meld::run(Suit::Clubs, Rank::J, Rank::K);
782        assert_eq!(low_run.extended("♣K".parse().unwrap()), None);
783        assert_eq!(high_run.extended("♣A".parse().unwrap()), None);
784
785        let set = Meld::set(Rank::Q, Some(Suit::Diamonds));
786        let full = set.extended("♦Q".parse().unwrap()).unwrap();
787        assert_eq!(full.len(), 4);
788        assert_eq!(full.extended("♦Q".parse().unwrap()), None);
789        assert_eq!(set.extended("♦J".parse().unwrap()), None);
790    }
791
792    #[test]
793    fn melds_arrangement() {
794        let hand: Hand = "A23.456.789.T".parse().unwrap();
795        let runs = [
796            Meld::run(Suit::Clubs, Rank::A, Rank::new(3)),
797            Meld::run(Suit::Diamonds, Rank::new(4), Rank::new(6)),
798            Meld::run(Suit::Hearts, Rank::new(7), Rank::new(9)),
799        ];
800
801        let melds = Melds::try_new(hand, &runs).unwrap();
802        assert_eq!(melds.iter().count(), 3);
803        assert_eq!(melds.deadwood_cards(), "...T".parse().unwrap());
804        assert_eq!(melds.deadwood(), 10);
805        assert_eq!(melds.to_string(), "A♣2♣3♣ 4♦5♦6♦ 7♥8♥9♥ | T♠");
806
807        assert_eq!(
808            Melds::try_new(hand, &runs[1..]).map(|m| m.deadwood()),
809            Ok(16),
810        );
811        assert_eq!(
812            Melds::try_new("A23...".parse().unwrap(), &runs[..1]).map(|m| m.deadwood()),
813            Ok(0),
814        );
815
816        assert_eq!(
817            Melds::try_new(hand, &[runs[0], runs[0]]),
818            Err(ArrangeError::OverlappingMelds),
819        );
820        assert_eq!(
821            Melds::try_new(Hand::EMPTY, &runs[..1]),
822            Err(ArrangeError::MeldNotInHand),
823        );
824        assert_eq!(
825            Melds::try_new(Hand::ALL, &runs),
826            Err(ArrangeError::TooManyCards),
827        );
828
829        let two_runs: Hand = "A23456...".parse().unwrap();
830        let split = [
831            Meld::run(Suit::Clubs, Rank::A, Rank::new(3)),
832            Meld::run(Suit::Clubs, Rank::new(4), Rank::new(6)),
833        ];
834        assert_eq!(
835            Melds::try_new(two_runs, &split).map(|m| m.deadwood()),
836            Ok(0)
837        );
838    }
839
840    #[test]
841    fn meld_parsing() {
842        let run: Meld = "♠5♠6♠7".parse().unwrap();
843        assert_eq!(run, Meld::run(Suit::Spades, Rank::new(5), Rank::new(7)));
844        assert_eq!(run.to_string(), "5♠6♠7♠");
845        assert_eq!("S5 S6 S7".parse(), Ok(run));
846        assert_eq!("s5s6s7".parse(), Ok(run));
847
848        let set: Meld = "♣7♦7♠7".parse().unwrap();
849        assert_eq!(set, Meld::set(Rank::new(7), Some(Suit::Hearts)));
850
851        let tens: Meld = "♣10♦10♥10♠10".parse().unwrap();
852        assert_eq!(tens, Meld::set(Rank::T, None));
853
854        assert_eq!(
855            "♠5♠6".parse::<Meld>(),
856            Err(ParseMeldError::Invalid(InvalidMeld::TooFewCards)),
857        );
858        assert_eq!(
859            "♠5♠5♠6♠7".parse::<Meld>(),
860            Err(ParseMeldError::RepeatedCard),
861        );
862        assert_eq!(
863            "♠5♠6♥7".parse::<Meld>(),
864            Err(ParseMeldError::Invalid(InvalidMeld::MixedCards)),
865        );
866        assert!(matches!(
867            "5♠6♠7".parse::<Meld>(),
868            Err(ParseMeldError::Card(_)),
869        ));
870        assert!(matches!(
871            "".parse::<Meld>(),
872            Err(ParseMeldError::Invalid(_))
873        ));
874    }
875
876    #[test]
877    fn deadwood_basics() {
878        let gin: Hand = "A23.456.789.T".parse().unwrap();
879        assert_eq!(deadwood(gin), 10);
880        assert_eq!(best_melds(gin).deadwood(), 10);
881
882        assert_eq!(deadwood(Hand::EMPTY), 0);
883        assert_eq!(deadwood(Hand::ALL), 0);
884        assert_eq!(pip_sum(Hand::ALL), 340);
885        assert_eq!(pip_sum(gin), 55);
886    }
887
888    #[test]
889    fn tie_break_favors_runs() {
890        // A middle-of-run card shared with a set of its rank (e.g. 5-6-7 vs
891        // three 6s) leaves 12 deadwood either way.  The arrangement is picked
892        // regardless of which suit lane sorts lowest, so the run must win in
893        // all rotations, not just when its cards happen to sort first.
894        for hand in ["567.6.6.", "6.6.567.", "6.567.6."] {
895            let melds = best_melds(hand.parse().unwrap());
896            assert_eq!(melds.deadwood(), 12, "{hand}");
897            assert!(
898                melds.iter().all(|m| m.kind() == MeldKind::Run),
899                "{hand} chose a set: {melds}",
900            );
901        }
902    }
903}