finish initial implementation of cribbage
authorGeoffrey Allott <geoffrey@allott.email>
Sun, 28 May 2023 20:35:01 +0000 (21:35 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Sun, 28 May 2023 20:35:01 +0000 (21:35 +0100)
src/game/cribbage/mod.rs
src/game/cribbage/score.rs

index 279640e9d4949b6d2fa228fb0a7634926e99a1ee..07fcc0480ca72da753cb6dbfa4a83d41f2e9d33b 100644 (file)
@@ -1,6 +1,16 @@
+use std::collections::{HashMap, HashSet};
+
+use crate::card::{Card, FIFTY_TWO_CARD_DECK};
+use crate::rng::{Seed, WaveRng};
+use crate::seats::Seats;
+use crate::username::Username;
+use crate::util::timestamp::Timestamp;
+
+use super::{Action, ActionError, DealerAction, Game, StartCondition, UserAction, ValidatedUserAction};
+
 mod score;
 
-/*
+use self::score::{PeggingScore, score_4_card_cribbage_hand, score_pegging, value};
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum State {
@@ -9,7 +19,9 @@ enum State {
     Choosing,
     TurnUp,
     Pegging,
+    ScoringPegging,
     Scoring,
+    ScoringBox,
     Completed,
 }
 
@@ -71,6 +83,69 @@ impl Cribbage {
             points: HashMap::new(),
         }
     }
+
+    fn hand_contains_card(&self, username: Username, card: Card) -> bool {
+        self.hands.get(&username).map_or(false, |hand| hand.contains(&card))
+    }
+
+    fn hand_size(&self, username: Username) -> usize {
+        self.hands.get(&username).map_or(0, HashSet::len)
+    }
+
+    fn all_hands_are_empty(&self) -> bool {
+        self.hands.values().all(HashSet::is_empty)
+    }
+
+    fn all_hands_dealt(&self) -> bool {
+        self.hands.values().map(HashSet::len).all(|len| len == 6)
+    }
+
+    fn pegging_total(&self) -> u32 {
+        self.pegging_cards.iter().map(|(_, card)| value(card.rank)).sum()
+    }
+
+    fn hand_has_playable_pegging_card(&self, username: Username) -> bool {
+        let total = self.pegging_total();
+        self.hands.get(&username).map_or(false, |hand| hand.iter().any(|card| total + value(card.rank) <= 31))
+    }
+
+    fn next_player_still_in(&self) -> Option<Username> {
+        self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)))
+    }
+
+    fn last_pegging_score(&self) -> Option<(Username, PeggingScore)> {
+        match self.pegging_cards.last() {
+            None => self.used_pegging_cards.last().map(|(username, _)| (*username, PeggingScore::one_for_a_go())),
+            Some((username, _)) => {
+                let cards: Vec<_> = self.pegging_cards.iter().map(|(_, card)| *card).collect();
+                let score = score_pegging(&cards, false);
+                if score.points() > 0 {
+                    Some((*username, score))
+                } else {
+                    None
+                }
+            }
+        }
+    }
+
+    fn four_card_hand(&self, username: Username) -> Option<[Card; 4]> {
+        self.hands.get(&username).and_then(|hand| {
+            let hand: Vec<_> = hand.into_iter().collect();
+            if let [a, b, c, d] = hand[..] {
+                Some([*a, *b, *c, *d])
+            } else {
+                None
+            }
+        })
+    }
+
+    fn player_has_won(&self, username: Username) -> bool {
+        self.points.get(&username).map_or(false, |points| *points >= 121)
+    }
+
+    fn winner(&self) -> Option<Username> {
+        self.points.iter().filter(|&(_, points)| *points >= 121).next().map(|(username, _)| *username)
+    }
 }
 
 impl Game for Cribbage {
@@ -126,7 +201,7 @@ impl Game for Cribbage {
                     Err(ActionError::OutOfTurn)
                 } else if !self.hand_contains_card(username, card) {
                     Err(ActionError::CardNotAvailable)
-                } else if self.current_total() + value(card.rank) > 31 {
+                } else if self.pegging_total() + value(card.rank) > 31 {
                     Err(ActionError::CardNotPlayable)
                 } else {
                     Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::PlayCard { card } }))
@@ -135,7 +210,7 @@ impl Game for Cribbage {
             (State::Pegging, Action::Pass) => {
                 if Some(username) != self.active {
                     Err(ActionError::OutOfTurn)
-                } else if self.hand_has_playable_card(username) {
+                } else if self.hand_has_playable_pegging_card(username) {
                     Err(ActionError::CannotPass)
                 } else {
                     Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Pass }))
@@ -143,6 +218,7 @@ impl Game for Cribbage {
             }
             (State::TurnUp, _) => Err(ActionError::Dealing),
             (State::Scoring, _) => Err(ActionError::Dealing),
+            (State::ScoringBox, _) => Err(ActionError::Dealing),
             (State::Completed, _) => Err(ActionError::GameHasEnded),
             (_, _) => Err(ActionError::InvalidActionForGameType),
         }
@@ -171,6 +247,10 @@ impl Game for Cribbage {
                 self.state = State::Dealing;
                 Ok(())
             }
+            (_, Action::WinGame) => {
+                self.state = State::Completed;
+                Ok(())
+            }
             (State::Dealing, Action::ReceiveCard { card: Some(card) }) => {
                 self.deck.remove(&card);
                 self.hands.entry(username).or_default().insert(card);
@@ -185,7 +265,7 @@ impl Game for Cribbage {
                 self.state = State::Choosing;
                 Ok(())
             }
-            (State::Choosing, Action::PutInBox { card: Some(Card) }) => {
+            (State::Choosing, Action::PutInBox { card: Some(card) }) => {
                 if let Some(hand) = self.hands.get_mut(&username) {
                     hand.remove(&card);
                 }
@@ -210,45 +290,67 @@ impl Game for Cribbage {
                     }
                 }
                 self.pegging_cards.push((username, card));
+                if self.last_pegging_score().is_some() {
+                    self.state = State::ScoringPegging;
+                }
                 Ok(())
             }
             (State::Pegging, Action::Pass) => {
                 self.players_still_in.remove(&username);
-                match self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player))) {
+                match self.next_player_still_in() {
                     None => {
+                        self.state = State::ScoringPegging;
                         self.used_pegging_cards.extend(self.pegging_cards.drain(..));
                         self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
-                        self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)));
-                    }
-                    Some(active) => self.active = active,
+                        self.active = self.next_player_still_in();
+                    },
+                    active => self.active = active,
                 }
                 if self.all_hands_are_empty() {
                     for (username, card) in self.used_pegging_cards.drain(..) {
                         self.hands.entry(username).or_default().insert(card);
                     }
+                    self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
                     self.state = State::Scoring;
                 }
                 Ok(())
             }
-            (State::Pegging, Action::Score { points, .. }) => {
-                *self.points.entry(username).or_default() += points;
-                match self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player))) {
+            (State::ScoringPegging, Action::Score { points, .. }) => {
+                *self.points.entry(username).or_default() += points as u32;
+                match self.next_player_still_in() {
                     None => {
                         self.used_pegging_cards.extend(self.pegging_cards.drain(..));
                         self.players_still_in = self.hands.iter().filter(|(_, cards)| !cards.is_empty()).map(|(&username, _)| username).collect();
-                        self.active = self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player)));
+                        self.active = self.next_player_still_in();
                     }
-                    Some(active) => self.active = active,
+                    active => self.active = active,
                 }
-                if self.all_hands_are_empty() {
+                if self.player_has_won(username) {
+                    self.state = State::ScoringPegging;
+                } else if self.all_hands_are_empty() {
                     for (username, card) in self.used_pegging_cards.drain(..) {
                         self.hands.entry(username).or_default().insert(card);
                     }
+                    self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
                     self.state = State::Scoring;
+                } else {
+                    self.state = State::Pegging;
                 }
+                Ok(())
             }
             (State::Scoring, Action::Score { points, .. }) => {
-                *self.points.entry(username).or_default() += points;
+                *self.points.entry(username).or_default() += points as u32;
+                if self.dealer == Some(username) {
+                    self.state = State::ScoringBox;
+                } else {
+                    self.active = self.seats.player_after(username);
+                }
+                Ok(())
+            }
+            (State::ScoringBox, Action::Score { points, .. }) => {
+                *self.points.entry(username).or_default() += points as u32;
+                self.active = None;
+                Ok(())
             }
             (State::Completed, _) => Err(ActionError::GameHasEnded),
             (_, _) => Err(ActionError::InvalidActionForGameType),
@@ -299,8 +401,50 @@ impl Game for Cribbage {
                     DealerAction::Leave
                 }
             }
+            State::Choosing => DealerAction::WaitForPlayer,
+            State::TurnUp => {
+                if let Some(username) = self.dealer {
+                    if let Some(&card) = rng.choose_from(&self.deck) {
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CommunityCard { card } }))
+                    } else {
+                        error!("Expected to deal a card but none were left in deck");
+                        DealerAction::Leave
+                    }
+                } else {
+                    error!("Expected to deal a card but there was no dealer");
+                    DealerAction::Leave
+                }
+            }
+            State::Pegging => DealerAction::WaitForPlayer,
+            State::ScoringPegging => {
+                if let Some(username) = self.winner() {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinGame }))
+                } else if let Some((username, score)) = self.last_pegging_score() {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::Score { points: score.points() as u64, reason: format!("{}", score) } }))
+                } else {
+                    error!("Expected a pegging score");
+                    DealerAction::Leave
+                }
+            }
+            State::Scoring | State::ScoringBox => {
+                if let Some(username) = self.winner() {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinGame }))
+                } else if let Some(username) = self.active {
+                    if let (Some(hand), Some(turn_up)) = (self.four_card_hand(username), self.turn_up) {
+                        let score = score_4_card_cribbage_hand(hand, turn_up, self.state == State::ScoringBox);
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::Score { points: score.points() as u64, reason: format!("{}", score) } }))
+                    } else {
+                        error!("Found no 4-card hand for scoring user");
+                        DealerAction::Leave
+                    }
+                } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal }))
+                } else {
+                    error!("Could not find next dealer");
+                    DealerAction::Leave
+                }
+            }
+            State::Completed => DealerAction::Leave,
         }
     }
 }
-
-*/
index f2f21545d0cf861ea949f7a84674cf56ad2e27f6..5ab2a6c84bf795c5a1b0230e26c52230174fe0c9 100644 (file)
@@ -108,48 +108,69 @@ pub struct PeggingScore {
     thirty_one: u8,
     pair: u8,
     run: u8,
+    go: bool,
 }
 
 impl PeggingScore {
+    pub fn one_for_a_go() -> Self {
+        PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: true }
+    }
+
     pub fn points(&self) -> u8 {
-        self.fifteen + self.thirty_one + self.pair + self.run
+        self.fifteen + self.thirty_one + self.pair + self.run + self.go as u8
     }
 }
 
 impl Display for PeggingScore {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0 } => Ok(()),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0 } => f.write_str("Fifteen for two"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0 } => f.write_str("Fifteen two and two is four"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3 } => f.write_str("Fifteen two and three is five"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4 } => f.write_str("Fifteen two and four is six"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5 } => f.write_str("Fifteen two and five is seven"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0 } => f.write_str("Fifteen two and six is eight"),
-            PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0 } => f.write_str("Fifteen two and twelve is fourteen"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 0 } => f.write_str("Thirty-one for two"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 2, run: 0 } => f.write_str("Thirty-one for two and two is four"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 3 } => f.write_str("Thirty-one for two and three is five"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 4 } => f.write_str("Thirty-one for two and four is six"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 5 } => f.write_str("Thirty-one for two and five is seven"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 6 } => f.write_str("Thirty-one for two and six is eight"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 6, run: 0 } => f.write_str("Thirty-one for two and six is eight"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 7 } => f.write_str("Thirty-one for two and seven is nine"),
-            PeggingScore { fifteen: 0, thirty_one: 2, pair: 12, run: 0 } => f.write_str("Thirty-one for two and twelve is fourteen"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0 } => f.write_str("Two"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3 } => f.write_str("Three"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4 } => f.write_str("Four"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5 } => f.write_str("Five"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6 } => f.write_str("Six"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0 } => f.write_str("Six"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7 } => f.write_str("Seven"),
-            PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0 } => f.write_str("Twelve"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: false } => Ok(()),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go: true } => f.write_str("One for a go"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0, go: false } => f.write_str("Fifteen for two"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 0, go: true } => f.write_str("Fifteen for two and a go is three"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0, go: false } => f.write_str("Fifteen two and two is four"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 2, run: 0, go: true } => f.write_str("Fifteen two and two and a go is five"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3, go: false } => f.write_str("Fifteen two and three is five"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 3, go: true } => f.write_str("Fifteen two and three and a go is six"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4, go: false } => f.write_str("Fifteen two and four is six"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 4, go: true } => f.write_str("Fifteen two and four and a go is seven"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5, go: false } => f.write_str("Fifteen two and five is seven"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 0, run: 5, go: true } => f.write_str("Fifteen two and five and a go is eight"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0, go: false } => f.write_str("Fifteen two and six is eight"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 6, run: 0, go: true } => f.write_str("Fifteen two and six and a go is nine"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0, go: false } => f.write_str("Fifteen two and twelve is fourteen"),
+            PeggingScore { fifteen: 2, thirty_one: 0, pair: 12, run: 0, go: true } => f.write_str("Fifteen two and twelve and a go is fifteen"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 0, go: false } => f.write_str("Thirty-one for two"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 2, run: 0, go: false } => f.write_str("Thirty-one for two and two is four"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 3, go: false } => f.write_str("Thirty-one for two and three is five"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 4, go: false } => f.write_str("Thirty-one for two and four is six"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 5, go: false } => f.write_str("Thirty-one for two and five is seven"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 6, go: false } => f.write_str("Thirty-one for two and six is eight"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 6, run: 0, go: false } => f.write_str("Thirty-one for two and six is eight"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 0, run: 7, go: false } => f.write_str("Thirty-one for two and seven is nine"),
+            PeggingScore { fifteen: 0, thirty_one: 2, pair: 12, run: 0, go: false } => f.write_str("Thirty-one for two and twelve is fourteen"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0, go: false } => f.write_str("Two"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 2, run: 0, go: true } => f.write_str("Two and a go is three"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3, go: false } => f.write_str("Three"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 3, go: true } => f.write_str("Three and a go is four"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4, go: false } => f.write_str("Four"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 4, go: true } => f.write_str("Four and a go is five"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5, go: false } => f.write_str("Five"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 5, go: true } => f.write_str("Five and a go is six"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6, go: false } => f.write_str("Six"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 6, go: true } => f.write_str("Six and a go is seven"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0, go: false } => f.write_str("Six"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 6, run: 0, go: true } => f.write_str("Six and a go is seven"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7, go: false } => f.write_str("Seven"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 7, go: true } => f.write_str("Seven and a go is eight"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0, go: false } => f.write_str("Twelve"),
+            PeggingScore { fifteen: 0, thirty_one: 0, pair: 12, run: 0, go: true } => f.write_str("Twelve and a go is thirteen"),
             _ => write!(f, "[ERROR] {:?}", self),
         }
     }
 }
 
-fn value(rank: Rank) -> u32 {
+pub fn value(rank: Rank) -> u32 {
     match rank {
         Ace => 1,
         Two => 2,
@@ -167,7 +188,7 @@ fn value(rank: Rank) -> u32 {
     }
 }
 
-fn sum_value(cards: &[Card]) -> u32 {
+pub fn sum_value(cards: &[Card]) -> u32 {
     cards.iter().map(|card| card.rank).map(value).sum()
 }
 
@@ -175,8 +196,8 @@ fn is_run(cards: &[Card]) -> bool {
     cards.iter().map(|card| card.rank.ace_low_rank()).sorted().tuple_windows().all(|(rank1, rank2)| rank2 == rank1 + 1)
 }
 
-pub fn score_pegging(cards: &[Card]) -> PeggingScore {
-    let mut score = PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0 };
+pub fn score_pegging(cards: &[Card], go: bool) -> PeggingScore {
+    let mut score = PeggingScore { fifteen: 0, thirty_one: 0, pair: 0, run: 0, go };
 
     if sum_value(cards) == 15 {
         score.fifteen += 2;
@@ -343,19 +364,19 @@ mod test {
 
     #[test]
     fn pegging_scores() {
-        let score = score_pegging(&[ACE_OF_SPADES, TWO_OF_SPADES, THREE_OF_SPADES, FOUR_OF_SPADES, FIVE_OF_SPADES]);
+        let score = score_pegging(&[ACE_OF_SPADES, TWO_OF_SPADES, THREE_OF_SPADES, FOUR_OF_SPADES, FIVE_OF_SPADES], false);
         assert_eq!(7, score.points());
         assert_eq!("Fifteen two and five is seven", format!("{}", score));
 
-        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS]);
+        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS], false);
         assert_eq!(2, score.points());
         assert_eq!("Fifteen for two", format!("{}", score));
 
-        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS]);
+        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS], false);
         assert_eq!(0, score.points());
         assert_eq!("", format!("{}", score));
 
-        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS, SIX_OF_DIAMONDS]);
+        let score = score_pegging(&[TEN_OF_HEARTS, FIVE_OF_CLUBS, TEN_OF_CLUBS, SIX_OF_DIAMONDS], false);
         assert_eq!(2, score.points());
         assert_eq!("Thirty-one for two", format!("{}", score));
     }