fix logic that detects when the betting round has ended
authorGeoffrey Allott <geoffrey@allott.email>
Thu, 4 Mar 2021 18:01:48 +0000 (18:01 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Thu, 4 Mar 2021 18:01:48 +0000 (18:01 +0000)
src/dealer.rs
src/game/poker/holdem.rs

index 20f3570e11b738470d4f0a7a48fed4121d3ca24d..95ca00481ae501529e41f62cc97aea5dc50c24db 100644 (file)
@@ -48,6 +48,7 @@ impl Dealer {
                 }
             }
         }
+        info!("Dealer for game {} is terminating", self.dealer.game.id());
     }
 
     async fn retrieve_updates(&mut self) -> RedisResult<Termination> {
index 4ccd4290e21e002267a69d3799b17d4d13b94064..6a014c025ff1cf6bcda6ee29df15879e2ea27877 100644 (file)
@@ -13,7 +13,7 @@ use super::super::{Action, ActionError, DealerAction, Game, UserAction, Validate
 
 use super::classify::rank_7_card_hand;
 
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum State {
     NotStarted,
     Dealing,
@@ -127,7 +127,50 @@ impl TexasHoldEm {
     }
 
     fn players_able_to_bet(&self) -> usize {
-        self.players.iter().map(|&username| self.is_able_to_bet(username)).count()
+        self.players.iter().filter(|&&username| self.is_able_to_bet(username)).count()
+    }
+
+    fn all_bets_are_in(&self) -> bool {
+        self.players.iter().filter(|&&username| self.is_able_to_bet(username))
+            .all(|username| self.bets.contains_key(username))
+    }
+
+    fn player_is_all_in(&self, username: Username) -> bool {
+        self.players.contains(&username) && matches!(self.stacks.get(&username), Some(&0) | None)
+    }
+
+    fn bets_of_players_who_are_not_all_in(&self) -> impl Iterator<Item=u64> + '_ {
+        self.bets.iter()
+            .filter(move |&(&username, _)| !self.player_is_all_in(username))
+            .map(|(_, &bet)| bet)
+    }
+
+    fn all_bets_are_equal(&self) -> bool {
+        self.players_able_to_bet() == 0
+            || self.bets_of_players_who_are_not_all_in().all_equal()
+                && self.bets.values().cloned().max() <= self.bets_of_players_who_are_not_all_in().max()
+    }
+
+    fn big_blind_player(&self) -> Option<Username> {
+        if self.seats.players_len() == 2 {
+            self.dealer
+                .and_then(|dealer| self.seats.player_after(dealer))
+        } else {
+            self.dealer
+                .and_then(|dealer| self.seats.player_after(dealer))
+                .and_then(|small_blind| self.seats.player_after(small_blind))
+        }
+    }
+
+    fn betting_round_completed(&self) -> bool {
+        if self.state == State::PreFlopBetting {
+            if let Some(big_blind) = self.big_blind_player() {
+                if self.active != Some(big_blind) && !self.player_is_all_in(big_blind) && self.bets.get(&big_blind) == Some(&self.big_blind) {
+                    return false;
+                }
+            }
+        }
+        self.all_bets_are_in() && self.all_bets_are_equal()
     }
 }
 
@@ -269,8 +312,7 @@ impl Game for TexasHoldEm {
                 *self.bets.entry(username).or_default() += chips;
                 *self.committed.entry(username).or_default() += chips;
                 *self.stacks.entry(username).or_default() -= chips;
-                if (chips == 0 || !matches!(self.state, State::PreFlopBetting) || self.bets.values().max() > Some(&self.big_blind)) && self.bets.len() == self.players_able_to_bet() && self.bets.values().all_equal()
-                {
+                if self.betting_round_completed() {
                     self.active = None;
                     self.pot += self.bets.values().sum::<u64>();
                     self.bets.clear();
@@ -739,4 +781,65 @@ mod tests {
 
         test_game(actions, settings, seed);
     }
+
+    #[test]
+    fn detects_all_in_correctly() {
+        let actions = r#"[
+            {"timestamp":0,"username":"geoff","action":{"action":"Join","seat":0,"chips":1000}},
+            {"timestamp":0,"username":"kat","action":{"action":"Join","seat":1,"chips":1000}},
+            {"timestamp":0,"username":"geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"Two","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"EndDeal"}},
+            {"timestamp":0,"username":"geoff","action":{"action":"PostBlind","chips":100}},
+            {"timestamp":0,"username":"kat","action":{"action":"PostBlind","chips":200}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Fold"}},
+            {"timestamp":0,"username":"kat","action":{"action":"WinHand","chips":300,"hand":null}},
+            {"timestamp":0,"username":"kat","action":{"action":"NextToDeal"}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"EndDeal"}},
+            {"timestamp":0,"username":"kat","action":{"action":"PostBlind","chips":100}},
+            {"timestamp":0,"username":"geoff","action":{"action":"PostBlind","chips":200}},
+            {"timestamp":0,"username":"kat","action":{"action":"Bet","chips":300}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":200}},
+            {"timestamp":0,"username":"kat","action":{"action":"CommunityCard","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"CommunityCard","card":{"rank":"Jack","suit":"Spades"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"CommunityCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"CommunityCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"CommunityCard","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"Bet","chips":0}},
+            {"timestamp":0,"username":"kat","action":{"action":"WinHand","chips":800,"hand":"Pair of 4s, AJT Kickers"}},
+            {"timestamp":0,"username":"geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":0,"username":"kat","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Hearts"}}},
+            {"timestamp":0,"username":"geoff","action":{"action":"EndDeal"}},
+            {"timestamp":0,"username":"geoff","action":{"action":"PostBlind","chips":100}},
+            {"timestamp":0,"username":"kat","action":{"action":"PostBlind","chips":200}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":300}},
+            {"timestamp":0,"username":"kat","action":{"action":"Bet","chips":400}},
+            {"timestamp":0,"username":"geoff","action":{"action":"Bet","chips":100}},
+            {"timestamp":0,"username":"geoff","action":{"action":"CommunityCard","card":{"rank":"Two","suit":"Hearts"}}}
+        ]"#;
+        let actions = serde_json::from_str(actions).unwrap();
+
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":100,"starting_stack":1000,"action_timeout":null}"#;
+        let settings = serde_json::from_str(settings).unwrap();
+
+        let seed = r#"{"rng":"ChaCha20","seed":"fd87ec4b51fcaf056ef53c0460322e1fa5261cf2801d005065c9add8ec541bb4"}"#;
+        let seed = serde_json::from_str(seed).unwrap();
+
+        test_game(actions, settings, seed);
+    }
 }