From 34de95fd5b2fa8bfd799336117f3cf3efad27c79 Mon Sep 17 00:00:00 2001 From: Geoffrey Allott Date: Wed, 31 Mar 2021 20:18:16 +0100 Subject: [PATCH] implement reveal card logic --- site/modules/chatroom.js | 1 + site/modules/poker.js | 22 +++++++++ site/modules/socket.js | 10 ++-- src/game/poker/holdem.rs | 98 ++++++++++++++++++++++++++-------------- 4 files changed, 95 insertions(+), 36 deletions(-) diff --git a/site/modules/chatroom.js b/site/modules/chatroom.js index 0d5c31b..a111e1f 100644 --- a/site/modules/chatroom.js +++ b/site/modules/chatroom.js @@ -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; diff --git a/site/modules/poker.js b/site/modules/poker.js index 48750ec..b83354e 100644 --- a/site/modules/poker.js +++ b/site/modules/poker.js @@ -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, diff --git a/site/modules/socket.js b/site/modules/socket.js index 68147ed..3a77a09 100644 --- a/site/modules/socket.js +++ b/site/modules/socket.js @@ -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(); diff --git a/src/game/poker/holdem.rs b/src/game/poker/holdem.rs index 5069844..d970870 100644 --- a/src/game/poker/holdem.rs +++ b/src/game/poker/holdem.rs @@ -49,6 +49,8 @@ pub struct TexasHoldEmSettings { action_timeout: Option, #[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, + 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 { 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::>())) - .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::>())) + .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"}"#; -- 2.34.1