fix revealing of box cards
authorGeoffrey Allott <geoffrey@allott.email>
Thu, 1 Jun 2023 21:26:01 +0000 (22:26 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Thu, 1 Jun 2023 21:26:01 +0000 (22:26 +0100)
src/game/cribbage/mod.rs
src/game/cribbage/score.rs

index f070bd43ca01e5034ed65a36195129bfd4f8bd19..ebf172481100b5a5f24f159cd795251c490c34b1 100644 (file)
@@ -112,7 +112,8 @@ impl Cribbage {
     }
 
     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)))
+        self.active.and_then(|player| self.seats.player_after_where(player, |player| self.players_still_in.contains(&player))
+            .or_else(|| self.players_still_in.get(&player).copied()))
     }
 
     fn last_pegging_score(&self) -> Option<(Username, PeggingScore)> {
@@ -145,6 +146,19 @@ impl Cribbage {
         })
     }
 
+    fn unrevealed_box_card(&self) -> Option<Card> {
+        self.box_cards.iter().filter(|card| !self.revealed.contains(*card)).min().copied()
+    }
+
+    fn four_card_box(&self) -> Option<[Card; 4]> {
+        let hand: Vec<_> = self.box_cards.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 >= self.settings.target_score)
     }
@@ -309,6 +323,10 @@ impl Game for Cribbage {
                 Ok(())
             }
             (State::Pegging, Action::Pass) => {
+                if Some(username) != self.active {
+                    error!("Username taking action must be active");
+                    return Err(ActionError::OutOfTurn);
+                }
                 self.players_still_in.remove(&username);
                 match self.next_player_still_in() {
                     None => {
@@ -436,14 +454,14 @@ impl Game for Cribbage {
                     DealerAction::Leave
                 }
             }
-            State::Scoring | State::ScoringBox => {
+            State::Scoring => {
                 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(card) = self.unrevealed_card(username) {
                         DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } }))
                     } else 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);
+                        let score = score_4_card_cribbage_hand(hand, turn_up, false);
                         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");
@@ -456,6 +474,26 @@ impl Game for Cribbage {
                     DealerAction::Leave
                 }
             }
+            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(card) = self.unrevealed_box_card() {
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } }))
+                    } else if let (Some(hand), Some(turn_up)) = (self.four_card_box(), self.turn_up) {
+                        let score = score_4_card_cribbage_hand(hand, turn_up, true);
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::Score { points: score.points() as u64, reason: format!("{}", score) } }))
+                    } else {
+                        error!("Found no 4-card box 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,
         }
     }
@@ -532,26 +570,63 @@ mod tests {
             {"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":1685568658569,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1685568658569,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1685568658569,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1685568658569,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1685568658570,"username":"Geoff","action":{"action":"Score","points":7,"reason":"Fifteen two, fifteen four, fifteen six and one for his nob is 7"}},
             {"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"}}
+            {"timestamp":1685653386239,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1685653386240,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1685653386240,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1685653386241,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1685653386241,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1685653386242,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1685653386242,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1685653386243,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1685653386243,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1685653386244,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1685653386244,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1685653386244,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1685653386245,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1685653386245,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1685653386245,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1685653386245,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1685653386245,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1685653386245,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"Score","points":2,"reason":"Fifteen for two"}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"Score","points":3,"reason":"Three"}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"Score","points":4,"reason":"Thirty-one for two and two is four"}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"Score","points":7,"reason":"Fifteen two, fifteen four and three is 7"}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"Score","points":9,"reason":"Fifteen two, fifteen four, fifteen six and three is 9"}},
+            {"timestamp":1685653387245,"username":"Aga","action":{"action":"WinGame"}}
         ]"#;
 
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"Cribbage","title":"Cribbage Testing","max_players":2,"target_score":121,"start_time":null}"#;
+        let settings = r#"{"format":"Cribbage","title":"Cribbage Testing","max_players":2,"target_score":21,"start_time":null}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"3789582e9d1a9229bd22d2a61b156bcf907e4cb44d613f97c08b16ec73ff0d90"}"#;
index 5ab2a6c84bf795c5a1b0230e26c52230174fe0c9..ec7379ce63e31bbfe851d4b54d2c004fe66b3bc8 100644 (file)
@@ -223,7 +223,7 @@ pub fn score_pegging(cards: &[Card], go: bool) -> PeggingScore {
         if cards.len() < 2 {
             break;
         }
-        if cards.iter().all_equal() {
+        if cards.iter().map(|card| card.rank).all_equal() {
             score.pair += (cards.len() * (cards.len() - 1)) as u8;
             break;
         }
@@ -380,4 +380,11 @@ mod test {
         assert_eq!(2, score.points());
         assert_eq!("Thirty-one for two", format!("{}", score));
     }
+
+    #[test]
+    fn pegging_pair_with_a_go() {
+        let score = score_pegging(&[TEN_OF_DIAMONDS, THREE_OF_DIAMONDS, THREE_OF_HEARTS], true);
+        assert_eq!(3, score.points());
+        assert_eq!("Two and a go is three", format!("{}", score));
+    }
 }