implement reveal card logic
authorGeoffrey Allott <geoffrey@allott.email>
Wed, 31 Mar 2021 19:18:16 +0000 (20:18 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Wed, 31 Mar 2021 19:18:16 +0000 (20:18 +0100)
site/modules/chatroom.js
site/modules/poker.js
site/modules/socket.js
src/game/poker/holdem.rs

index 0d5c31b0b9843092ba7eda0944577098fef2e1de..a111e1f934acb4ada3b11f356d6f778ecf6398ca 100644 (file)
@@ -94,6 +94,7 @@ export class Chatroom {
                 break;
             case "RevealCard":
                 this.chat.append(this.info_element(user_action.username, "Reveals " + card_name(user_action.action.card)));
+                break;
             case "PlayCard":
                 this.chat.append(this.info_element(user_action.username, "Plays " + card_name(user_action.action.card)));
                 break;
index 48750ecc76ab5678261e18191751f36d7b4137e2..b83354e56a144d8fc8cd71751723060c5c9d3d9f 100644 (file)
@@ -483,6 +483,15 @@ export class TexasHoldEm {
         }
     }
 
+    user_has_card(username, card_to_find) {
+        for (const {card} of this.hands.get(username)) {
+            if (card == card_to_find) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     take_action(user_action) {
         this.chatroom.take_action(user_action);
         switch (user_action.action.action) {
@@ -583,6 +592,19 @@ export class TexasHoldEm {
                 this.hands.get(user_action.username).push(card);
                 this.redraw_cards();
                 break;
+            case "RevealCard":
+                if (!this.user_has_card(user_action.username, user_action.action.card)) {
+                    for (const card of this.hands.get(user_action.username)) {
+                        if (card.card === null) {
+                            card.card = user_action.action.card;
+                            this.svg.removeChild(card.image);
+                            card.image = this.card_image(user_action.username, user_action.action.card);
+                            this.redraw_cards();
+                            break;
+                        }
+                    }
+                }
+                break;
             case "CommunityCard":
                 const community_card = {
                     card: user_action.action.card,
index 68147ed3cf1faddbc99b453c9e1c3edaf643250b..3a77a09cb65d427129a8fcad95e2e6575c12a653 100644 (file)
@@ -137,16 +137,20 @@ export class Socket {
             case "WinGame":
                 return 0;
             case "ReceiveCard":
-            case "CommunityCard":
                 return 200;
+            case "CommunityCard":
+                return 1000;
+            case "RevealCard":
+                return 1000;
             case "CutCard":
                 return 3000;
             case "PlayCard":
                 return 500;
             case "WinTrick":
             case "WinCall":
-            case "WinHand":
                 return 2000;
+            case "WinHand":
+                return 5000;
             case "NewBlinds":
                 return 5000;
             case "PostBlind":
@@ -162,7 +166,7 @@ export class Socket {
 
     set_schedule_timeout(timeout) {
         this.schedule_timeout = setTimeout(() => {
-            if (this.scheduled_actions.length == 0) {
+            if (this.scheduled_actions.length === 0) {
                 this.schedule_timeout = null;
             } else {
                 const {game, user_action} = this.scheduled_actions.shift();
index 5069844062f1795283e19a79cfb03e64266c89b2..d970870d434981728ec59f7ef2916ecb7a85b64b 100644 (file)
@@ -49,6 +49,8 @@ pub struct TexasHoldEmSettings {
     action_timeout: Option<i64>,
     #[serde(default)]
     start_time: StartCondition,
+    #[serde(default)]
+    hide_cards: bool,
 }
 
 impl TexasHoldEmSettings {
@@ -112,6 +114,7 @@ pub struct TexasHoldEm {
     pot: u64,
     level: Level,
     ghosts: HashMap<Username, u8>,
+    revealed: HashSet<(Username, Card)>,
 }
 
 impl TexasHoldEm {
@@ -139,6 +142,7 @@ impl TexasHoldEm {
             pot: 0,
             level,
             ghosts: HashMap::new(),
+            revealed: HashSet::new(),
         }
     }
 
@@ -251,6 +255,21 @@ impl TexasHoldEm {
     fn only_player_left(&self) -> Option<Username> {
         self.seats.player_set().into_iter().filter(|username| !self.ghosts.contains_key(username)).exactly_one().ok()
     }
+
+    fn next_card_to_reveal(&self) -> Option<(Username, Card)> {
+        if !self.settings.hide_cards && (self.players_able_to_bet() <= 1 || matches!(self.state, State::Showdown)) {
+            for &username in self.in_hand.iter().sorted() {
+                if let Some(hand) = self.hands.get(&username) {
+                    for &card in hand.iter().sorted() {
+                        if !self.revealed.contains(&(username, card)) {
+                            return Some((username, card));
+                        }
+                    }
+                }
+            }
+        }
+        None
+    }
 }
 
 impl Game for TexasHoldEm {
@@ -367,6 +386,7 @@ impl Game for TexasHoldEm {
                 self.receiver = self.seats.player_after_where(username, |username| self.in_hand.contains(&username));
                 self.bets.clear();
                 self.committed.clear();
+                self.revealed.clear();
                 if self.pot != 0 {
                     error!("Logic error: pot was {} upon dealing: {:#?}", self.pot, self);
                     self.pot = 0;
@@ -384,6 +404,10 @@ impl Game for TexasHoldEm {
                 }
                 Ok(())
             }
+            (_, Action::RevealCard { card }) => {
+                self.revealed.insert((username, card));
+                Ok(())
+            }
             (State::Dealing, Action::EndDeal) => {
                 self.state = State::PostingSmallBlind;
                 self.receiver = None;
@@ -599,7 +623,9 @@ impl Game for TexasHoldEm {
                 }
             }
             State::DealingFlop | State::DealingTurn | State::DealingRiver => {
-                if let Some(username) = self.dealer {
+                if let Some((username, card)) = self.next_card_to_reveal() {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } }))
+                } else 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 {
@@ -633,28 +659,32 @@ impl Game for TexasHoldEm {
                 }
             }
             State::Showdown => {
-                let winning_hands: Vec<_> = self
-                    .hands
-                    .iter()
-                    .sorted_by_key(|&(username, _)| username)
-                    .map(|(&username, hand)| (username, hand.iter().chain(self.community.iter()).cloned().collect::<Vec<_>>()))
-                    .filter_map(|(username, cards)| cards.try_into().ok().map(rank_7_card_hand).map(|hand| (username, hand)))
-                    .max_items_by_key(|(_, hand)| *hand);
-                info!("Showdown: community: {:?}", self.community);
-                info!("Showdown: all hands: {:?}", self.hands);
-                info!("Showdown: winning hands: {:?}", winning_hands);
-                if let Some(&(username, hand)) = winning_hands.first() {
-                    DealerAction::TakeAction(ValidatedUserAction(UserAction {
-                        timestamp,
-                        username,
-                        action: Action::WinHand {
-                            chips: (self.pot / winning_hands.len() as u64).min(self.max_winnings(username)),
-                            hand: Some(hand.to_string()),
-                        },
-                    }))
+                if let Some((username, card)) = self.next_card_to_reveal() {
+                    DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } }))
                 } else {
-                    error!("There were no winning hands in the showdown: {:#?}", self);
-                    DealerAction::Leave
+                    let winning_hands: Vec<_> = self
+                        .hands
+                        .iter()
+                        .sorted_by_key(|&(username, _)| username)
+                        .map(|(&username, hand)| (username, hand.iter().chain(self.community.iter()).cloned().collect::<Vec<_>>()))
+                        .filter_map(|(username, cards)| cards.try_into().ok().map(rank_7_card_hand).map(|hand| (username, hand)))
+                        .max_items_by_key(|(_, hand)| *hand);
+                    info!("Showdown: community: {:?}", self.community);
+                    info!("Showdown: all hands: {:?}", self.hands);
+                    info!("Showdown: winning hands: {:?}", winning_hands);
+                    if let Some(&(username, hand)) = winning_hands.first() {
+                        DealerAction::TakeAction(ValidatedUserAction(UserAction {
+                            timestamp,
+                            username,
+                            action: Action::WinHand {
+                                chips: (self.pot / winning_hands.len() as u64).min(self.max_winnings(username)),
+                                hand: Some(hand.to_string()),
+                            },
+                        }))
+                    } else {
+                        error!("There were no winning hands in the showdown: {:#?}", self);
+                        DealerAction::Leave
+                    }
                 }
             }
             State::Completed => DealerAction::Leave,
@@ -677,6 +707,7 @@ mod tests {
             tournament_length: None,
             action_timeout: None,
             start_time: StartCondition::WhenFull,
+            hide_cards: false,
         };
 
         assert_eq!(50, settings.level(0).small_blind);
@@ -697,6 +728,7 @@ mod tests {
             tournament_length: Some(4 * 60 * 60 * 1000),
             action_timeout: None,
             start_time: StartCondition::WhenFull,
+            hide_cards: false,
         };
 
         assert_eq!(25, settings.level(0).small_blind);
@@ -765,7 +797,7 @@ mod tests {
         ]"#;
         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}"#;
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":100,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"e0355d5c6c63ef757d1b874b0392a3deec73cadfb0a2aa7947a04db651bf9269"}"#;
@@ -811,7 +843,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"f05dc83bdce966e72a3a81b19ccded2e70387eb68deacf60ed8de1ee78b9ff0e"}"#;
@@ -894,7 +926,7 @@ mod tests {
         ]"#;
         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}"#;
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":100,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"fd87ec4b51fcaf056ef53c0460322e1fa5261cf2801d005065c9add8ec541bb4"}"#;
@@ -955,7 +987,7 @@ mod tests {
         ]"#;
         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}"#;
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":100,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"fd87ec4b51fcaf056ef53c0460322e1fa5261cf2801d005065c9add8ec541bb4"}"#;
@@ -1001,7 +1033,7 @@ mod tests {
         ]"#;
         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}"#;
+        let settings = r#"{"title":"2-Player TexasHoldEm Test","max_players":2,"small_blind":100,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"8de0ac3be302e26cbc0a371044c8b349107108abb1f94a10fe84ba04a59d7f31"}"#;
@@ -1150,7 +1182,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"48e2f45eb4a1ac6bc4ab4f2368ba2d9b0d7c1f132d7fc7f51036e92112dae136"}"#;
@@ -1210,7 +1242,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"48e2f45eb4a1ac6bc4ab4f2368ba2d9b0d7c1f132d7fc7f51036e92112dae136"}"#;
@@ -1314,7 +1346,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"092b99f45313fff167029dc7420ed69a92becae492e09b65bc06ddcaae3c9e9c"}"#;
@@ -1424,7 +1456,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"092b99f45313fff167029dc7420ed69a92becae492e09b65bc06ddcaae3c9e9c"}"#;
@@ -1469,7 +1501,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"092b99f45313fff167029dc7420ed69a92becae492e09b65bc06ddcaae3c9e9c"}"#;
@@ -1683,7 +1715,7 @@ mod tests {
         ]"#;
         let actions = serde_json::from_str(actions).unwrap();
 
-        let settings = r#"{"format":"TexasHoldEm","title":"Ultimate Merriment","max_players":4,"small_blind":200,"starting_stack":5000,"round_length":null,"tournament_length":null,"action_timeout":null,"start_time":null}"#;
+        let settings = r#"{"format":"TexasHoldEm","title":"Ultimate Merriment","max_players":4,"small_blind":200,"starting_stack":5000,"round_length":null,"tournament_length":null,"action_timeout":null,"start_time":null,"hide_cards":true}"#;
         let settings = serde_json::from_str(settings).unwrap();
 
         let seed = r#"{"rng":"ChaCha20","seed":"d9d9cf40cd30cc5da249bf1c0c710a34f96d24590ed1c6668d369ae290276222"}"#;