1use crate::hand::ParseCardError;
25use crate::{Card, Hand, Rank, Suit};
26use core::fmt::{self, Write as _};
27use core::str::FromStr;
28use thiserror::Error;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum MeldKind {
34 Set,
36 Run,
38}
39
40#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum InvalidMeld {
44 #[error("A meld requires at least 3 cards")]
46 TooFewCards,
47
48 #[error("Cards of one suit do not form consecutive ranks")]
50 NotConsecutive,
51
52 #[error("Cards of several suits form neither a set nor a run")]
54 MixedCards,
55
56 #[error("The card set contains bits outside the 52-card deck")]
58 UnknownCards,
59}
60
61#[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 #[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 #[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 #[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 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 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 return Err(InvalidMeld::MixedCards);
150 }
151
152 Ok(Self(cards))
153 }
154
155 #[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 #[must_use]
173 #[inline]
174 pub const fn cards(self) -> Hand {
175 self.0
176 }
177
178 #[allow(clippy::len_without_is_empty)] #[must_use]
181 #[inline]
182 pub const fn len(self) -> usize {
183 self.0.len()
184 }
185
186 #[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 #[must_use]
197 pub const fn rank(self) -> Option<Rank> {
198 match self.kind() {
199 MeldKind::Set => Some(Rank::new(self.0.to_bits().trailing_zeros() as u8 % 16)),
201 MeldKind::Run => None,
202 }
203 }
204
205 #[must_use]
207 pub const fn low(self) -> Option<Rank> {
208 match self.kind() {
209 MeldKind::Set => None,
210 MeldKind::Run => Some(Rank::new(self.0.to_bits().trailing_zeros() as u8 % 16)),
212 }
213 }
214
215 #[must_use]
217 pub const fn high(self) -> Option<Rank> {
218 match self.kind() {
219 MeldKind::Set => None,
220 MeldKind::Run => Some(Rank::new(
222 (63 - self.0.to_bits().leading_zeros() as u8) % 16,
223 )),
224 }
225 }
226
227 #[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
262impl 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#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
271#[non_exhaustive]
272pub enum ParseMeldError {
273 #[error(transparent)]
275 Card(#[from] ParseCardError),
276
277 #[error("The same card appears more than once")]
279 RepeatedCard,
280
281 #[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 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 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#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
335#[non_exhaustive]
336pub enum ArrangeError {
337 #[error("An arrangement covers a game hand of at most 11 cards")]
339 TooManyCards,
340
341 #[error("At most 3 disjoint melds fit in 11 cards")]
343 TooManyMelds,
344
345 #[error("Two melds share a card")]
347 OverlappingMelds,
348
349 #[error("A meld contains a card outside the hand")]
351 MeldNotInHand,
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
360pub struct Melds {
361 melds: [Option<Meld>; 3],
362 hand: Hand,
363}
364
365impl Melds {
366 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 #[inline]
399 pub fn iter(self) -> impl Iterator<Item = Meld> {
400 self.melds.into_iter().flatten()
401 }
402
403 #[must_use]
405 #[inline]
406 pub const fn hand(self) -> Hand {
407 self.hand
408 }
409
410 #[must_use]
412 pub fn melded(self) -> Hand {
413 self.iter()
414 .fold(Hand::EMPTY, |acc, meld| acc | meld.cards())
415 }
416
417 #[must_use]
419 pub fn deadwood_cards(self) -> Hand {
420 self.hand - self.melded()
421 }
422
423 #[must_use]
425 pub fn deadwood(self) -> u8 {
426 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
435impl 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
462const MELD_COUNT: usize = 329;
465
466static 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
505const fn card_value(card: u64) -> u16 {
507 let rank = card.trailing_zeros() as u16 % 16;
508 if rank > 10 { 10 } else { rank }
509}
510
511const 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#[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
535fn 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
548fn 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 if self.best_melds.is_some() {
596 self.count
597 } else {
598 0
599 }
600 }
601
602 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#[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 tracker.best as u8
657}
658
659#[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 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}