fix some issues with cribbage scoring
authorGeoffrey Allott <geoffrey@allott.email>
Wed, 31 May 2023 21:53:27 +0000 (22:53 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Wed, 31 May 2023 21:53:27 +0000 (22:53 +0100)
src/game/cribbage/mod.rs

index 1373ac3e2120b35a8ee07f3ba97732014ad2c5ee..f070bd43ca01e5034ed65a36195129bfd4f8bd19 100644 (file)
@@ -120,7 +120,7 @@ impl Cribbage {
             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);
+                let score = score_pegging(&cards, self.all_hands_are_empty());
                 if score.points() > 0 {
                     Some((*username, score))
                 } else {
@@ -131,7 +131,7 @@ impl Cribbage {
     }
 
     fn unrevealed_card(&self, username: Username) -> Option<Card> {
-        self.hands.get(&username).and_then(|hand| hand.iter().filter(|card| !self.revealed.contains(*card)).next().copied())
+        self.hands.get(&username).and_then(|hand| hand.iter().filter(|card| !self.revealed.contains(*card)).min().copied())
     }
 
     fn four_card_hand(&self, username: Username) -> Option<[Card; 4]> {
@@ -290,6 +290,10 @@ impl Game for Cribbage {
                 Ok(())
             }
             (State::Pegging, Action::PlayCard { card }) => {
+                if Some(username) != self.active {
+                    error!("Username taking action must be active");
+                    return Err(ActionError::OutOfTurn);
+                }
                 if let Some(hand) = self.hands.get_mut(&username) {
                     hand.remove(&card);
                     if hand.is_empty() {
@@ -299,8 +303,9 @@ impl Game for Cribbage {
                 self.pegging_cards.push((username, card));
                 if self.last_pegging_score().is_some() {
                     self.state = State::ScoringPegging;
+                } else {
+                    self.active = self.next_player_still_in();
                 }
-                self.active = self.next_player_still_in();
                 Ok(())
             }
             (State::Pegging, Action::Pass) => {
@@ -313,24 +318,13 @@ impl Game for Cribbage {
                     },
                     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::ScoringPegging, Action::Score { points, .. }) => {
                 *self.points.entry(username).or_default() += points as u32;
-                if self.next_player_still_in().is_none() {
+                if self.next_player_still_in().is_none() || self.pegging_total() == 31 {
                     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 = Some(username);
-                    if !self.players_still_in.contains(&username) {
-                        self.active = self.next_player_still_in();
-                    }
                 }
                 if self.player_has_won(username) {
                     self.state = State::ScoringPegging;
@@ -341,6 +335,7 @@ impl Game for Cribbage {
                     self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer));
                     self.state = State::Scoring;
                 } else {
+                    self.active = self.next_player_still_in();
                     self.state = State::Pegging;
                 }
                 Ok(())
@@ -474,7 +469,7 @@ mod tests {
         let mut game = Cribbage::new(0, settings, seed);
         for action in actions {
             match action.action {
-                Action::Join { .. } | Action::PutInBox { .. } | Action::PlayCard { .. } => {
+                Action::Join { .. } | Action::PutInBox { .. } | Action::PlayCard { .. } | Action::Pass => {
                     let validated = game.validate_action(action.clone()).unwrap();
                     assert_eq!(ValidatedUserAction(action), validated);
                     game.take_action(validated).unwrap();
@@ -519,12 +514,44 @@ mod tests {
             {"timestamp":1685308376248,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"King","suit":"Diamonds"}}},
             {"timestamp":1685308376248,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Two","suit":"Spades"}}},
             {"timestamp":1685308376248,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Hearts"}}},
-            {"timestamp":1685309372399,"username":"Aga","action":{"action":"Score","points":2,"reason":"Fifteen for two"}}
+            {"timestamp":1685309372399,"username":"Aga","action":{"action":"Score","points":2,"reason":"Fifteen for two"}},
+            {"timestamp":1685308376248,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1685308376248,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1685308376248,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1685567770541,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1685308376248,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1685308376248,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1685568658530,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1685568658532,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1685568658533,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1685568658531,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Diamonds"}}},
+            {"timestamp":1685568658532,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1685568658563,"username":"Aga","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1685568658567,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1685568658565,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1685568658568,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1685568658564,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1685568658569,"username":"Geoff","action":{"action":"Score","points":4,"reason":"Fifteen two, fifteen four, look all day, see no more"}},
+            {"timestamp":1685568658570,"username":"Geoff","action":{"action":"Score","points":4,"reason":"Fifteen two, fifteen four, look all day, see no more"}},
+            {"timestamp":1685568658571,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1685568658572,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1685568658572,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1685568658573,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1685568658574,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1685568658575,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1685568658576,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1685568658576,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1685568658577,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1685568658578,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1685568658579,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1685568658580,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1685568658581,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":1685568658582,"username":"Aga","action":{"action":"EndDeal"}}
         ]"#;
 
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"Cribbage","title":"Cribbage Testing","max_players":2,"target_score":181,"start_time":null}"#;
+        let settings = r#"{"format":"Cribbage","title":"Cribbage Testing","max_players":2,"target_score":121,"start_time":null}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"3789582e9d1a9229bd22d2a61b156bcf907e4cb44d613f97c08b16ec73ff0d90"}"#;