From 89539aa5cfaebdb1e055ec9c086cb747d97bbfef Mon Sep 17 00:00:00 2001 From: Geoffrey Allott Date: Tue, 2 Mar 2021 22:12:18 +0000 Subject: [PATCH] return a DealerAction that can instruct the dealer to wait or to leave --- src/dealer.rs | 8 +- src/game/action.rs | 8 ++ src/game/chatroom.rs | 14 ++- src/game/mod.rs | 4 +- src/game/poker/holdem.rs | 190 ++++++++++++++++++++++++++------------- src/game/whist.rs | 83 +++++++++++------ 6 files changed, 212 insertions(+), 95 deletions(-) diff --git a/src/dealer.rs b/src/dealer.rs index fb56f65..55c9bc6 100644 --- a/src/dealer.rs +++ b/src/dealer.rs @@ -6,7 +6,7 @@ use futures::channel::mpsc::Receiver; use redis::{ErrorKind, RedisError, RedisResult}; use crate::client::ClientInterest; -use crate::game::{Game, ValidatedUserAction}; +use crate::game::{Game, DealerAction, ValidatedUserAction}; use crate::server::{ActionStatus, ServerState}; pub struct Dealer { @@ -59,7 +59,7 @@ impl Dealer { 'take_action: loop { let timestamp = SystemTime::now(); match self.dealer.game.next_dealer_action(timestamp) { - Some(action) => match self.take_action(action).await { + DealerAction::TakeAction(action) => match self.take_action(action).await { Ok(ActionStatus::Committed) => { debug!("Dealer: Game state: {:#?}", self.dealer.game); continue 'take_action; @@ -67,7 +67,9 @@ impl Dealer { Ok(ActionStatus::Interrupted) => continue 'retrieve_updates, Err(err) => return Err(err), }, - None => return Ok(()), + DealerAction::WaitUntil(_) => todo!(), + DealerAction::WaitForPlayer => return Ok(()), + DealerAction::Leave => return Ok(()), // TODO } } } diff --git a/src/game/action.rs b/src/game/action.rs index 68d6be8..05a6669 100644 --- a/src/game/action.rs +++ b/src/game/action.rs @@ -27,6 +27,14 @@ impl ValidatedUserAction { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DealerAction { + TakeAction(ValidatedUserAction), + WaitUntil(SystemTime), + WaitForPlayer, + Leave, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "action")] pub enum Action { diff --git a/src/game/chatroom.rs b/src/game/chatroom.rs index fbb8f98..66934a8 100644 --- a/src/game/chatroom.rs +++ b/src/game/chatroom.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::time::SystemTime; use crate::username::{DEALER, Username}; -use crate::game::{Action, ActionError}; +use crate::game::{Action, ActionError, DealerAction}; use super::{Game, UserAction}; use super::ValidatedUserAction; @@ -90,10 +90,16 @@ impl Game for Chatroom { } } - fn next_dealer_action(&self, timestamp: SystemTime) -> Option { + fn next_dealer_action(&self, timestamp: SystemTime) -> DealerAction { match self.messages.len() { - n if n % 10 == 0 => Some(ValidatedUserAction(UserAction{timestamp, username: DEALER, action: Action::Message{message: format!("{} messages posted so far", n)}})), - _ => None, + n if n % 10 == 0 => DealerAction::TakeAction( + ValidatedUserAction(UserAction{ + timestamp, + username: DEALER, + action: Action::Message{message: format!("{} messages posted so far", n)} + }) + ), + _ => DealerAction::WaitForPlayer, } } } diff --git a/src/game/mod.rs b/src/game/mod.rs index 690dea1..e4ad5b1 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -14,7 +14,7 @@ use self::chatroom::{Chatroom, ChatroomSettings}; use self::whist::{KnockOutWhist, KnockOutWhistSettings}; use self::poker::{TexasHoldEm, TexasHoldEmSettings}; -pub use self::action::{Action, ActionError, UserAction, ValidatedUserAction}; +pub use self::action::{Action, ActionError, DealerAction, UserAction, ValidatedUserAction}; pub trait Game : Debug + CloneBoxGame + Send + Sync { fn id(&self) -> i64; @@ -22,7 +22,7 @@ pub trait Game : Debug + CloneBoxGame + Send + Sync { fn actions_len(&self) -> usize; fn validate_action(&self, action: UserAction) -> Result; fn take_action(&mut self, action: ValidatedUserAction) -> Result<(), ActionError>; - fn next_dealer_action(&self, timestamp: SystemTime) -> Option; + fn next_dealer_action(&self, timestamp: SystemTime) -> DealerAction; } pub trait CloneBoxGame { diff --git a/src/game/poker/holdem.rs b/src/game/poker/holdem.rs index 505bd3f..2ed0f17 100644 --- a/src/game/poker/holdem.rs +++ b/src/game/poker/holdem.rs @@ -10,7 +10,7 @@ use crate::username::Username; use crate::util::max::IteratorMaxItems; use crate::rng::{Seed, WaveRng}; -use super::super::{Action, ActionError, Game, UserAction, ValidatedUserAction}; +use super::super::{Action, ActionError, DealerAction, Game, UserAction, ValidatedUserAction}; use super::classify::rank_7_card_hand; @@ -277,7 +277,7 @@ impl Game for TexasHoldEm { State::TurnBetting => State::DealingRiver, State::RiverBetting => State::Showdown, state => { - error!("In unexpected state while bet of {} received: {:?}: {:?}", chips, state, self); + error!("In unexpected state while bet of {} received: {:?}: {:#?}", chips, state, self); state } }; @@ -348,99 +348,159 @@ impl Game for TexasHoldEm { } } - fn next_dealer_action(&self, timestamp: SystemTime) -> Option { + fn next_dealer_action(&self, timestamp: SystemTime) -> DealerAction { let mut rng = self.rng.clone(); match self.state { State::NotStarted => { if self.seats.players_len() == self.settings.max_players as usize { // TODO if let Some(username) = rng.choose_from(self.seats.player_set()) { - return Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal})); + return DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) + ); } } - None + DealerAction::WaitForPlayer } State::Dealing => { if let Some(username) = self.receiver { let card = rng.choose_from(&self.deck).cloned(); - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::ReceiveCard{card}})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::ReceiveCard{card}}) + ) } else if let Some(username) = self.dealer { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal}) + ) } else { - None + DealerAction::WaitForPlayer } } State::PostingSmallBlind if self.seats.players_len() == 2 => { - self.dealer - .map(|username| { - let chips = self.stack(username).min(self.small_blind); + if let Some(username) = self.dealer { + let chips = self.stack(username).min(self.small_blind); + DealerAction::TakeAction( ValidatedUserAction(UserAction{timestamp, username, action: Action::PostBlind{chips}}) - }) + ) + } else { + error!("There is no player to post the small blind: {:#?}", self); + DealerAction::Leave + } } State::PostingSmallBlind => { - self.dealer.and_then(|dealer| self.seats.player_after(dealer)) - .map(|username| { - let chips = self.stack(username).min(self.small_blind); + if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { + let chips = self.stack(username).min(self.small_blind); + DealerAction::TakeAction( ValidatedUserAction(UserAction{timestamp, username, action: Action::PostBlind{chips}}) - }) + ) + } else { + error!("There is no player to post the small blind: {:#?}", self); + DealerAction::WaitForPlayer + } } State::PostingBigBlind if self.seats.players_len() == 2 => { - self.dealer.and_then(|dealer| self.seats.player_after(dealer)) - .map(|username| { - let chips = self.stack(username).min(self.small_blind * 2); + if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { + let chips = self.stack(username).min(self.small_blind * 2); + DealerAction::TakeAction( ValidatedUserAction(UserAction{timestamp, username, action: Action::PostBlind{chips}}) - }) + ) + } else { + error!("There is no player to post the big blind: {:#?}", self); + DealerAction::WaitForPlayer + } } State::PostingBigBlind => { - self.dealer.and_then(|dealer| self.seats.player_after(dealer)) + if let Some(username) = self.dealer + .and_then(|dealer| self.seats.player_after(dealer)) .and_then(|small_blind| self.seats.player_after(small_blind)) - .map(|username| { - let chips = self.stack(username).min(self.small_blind * 2); + { + let chips = self.stack(username).min(self.small_blind * 2); + DealerAction::TakeAction( ValidatedUserAction(UserAction{timestamp, username, action: Action::PostBlind{chips}}) - }) + ) + } else { + error!("There is no player to post the big blind: {:#?}", self); + DealerAction::WaitForPlayer + } } State::PreFlopBetting | State::PostFlopBetting | State::TurnBetting | State::RiverBetting => { if self.players.len() <= 1 { if self.pot > 0 { - self.players.iter().next() - .map(|&username| ValidatedUserAction(UserAction { - timestamp, - username, - action: Action::WinHand { - chips: self.pot, - hand: None, - } - })) + if let Some(&username) = self.players.iter().next() { + DealerAction::TakeAction( + ValidatedUserAction(UserAction { + timestamp, + username, + action: Action::WinHand { + chips: self.pot, + hand: None, + } + }) + ) + } else { + error!("There is no player to win the pot: {:#?}", self); + DealerAction::Leave + } } else if self.seats.players_len() == 1 { - self.seats.player_set().iter().next() - .map(|&username| ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame})) + if let Some(&username) = self.seats.player_set().iter().next() { + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) + ) + } else { + error!("There is no player to win the game {:#?}", self); + DealerAction::Leave + } } else if let Some((&username, _)) = self.stacks.iter().find(|&(_, &stack)| stack == 0) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut}) + ) } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) + ) } else { error!("Logic error: no dealer could be chosen: {:#?}", self); - None + DealerAction::Leave } } else { - None + DealerAction::WaitForPlayer } } State::DealingFlop | State::DealingTurn | State::DealingRiver => { - self.dealer.and_then(|username| - rng.choose_from(&self.deck).map(|&card| - ValidatedUserAction(UserAction{timestamp, username, action: Action::CommunityCard{card}}))) + 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 { + error!("Dealing community card but there are no cards left in deck: {:#?}", self); + DealerAction::Leave + } + } else { + error!("Dealing community card but there is no dealer: {:#?}", self); + DealerAction::Leave + } } State::Showdown if self.pot == 0 => { if let Some((&username, _)) = self.stacks.iter().find(|&(_, &stack)| stack == 0) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut}) + ) } else if self.seats.players_len() == 1 { - self.seats.player_set().iter().next() - .map(|&username| ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame})) + if let Some(&username) = self.seats.player_set().iter().next() { + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) + ) + } else { + error!("There was no player to win the game: {:#?}", self); + DealerAction::Leave + } } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) + ) } else { error!("Logic error: no dealer could be chosen: {:#?}", self); - None + DealerAction::Leave } } State::Showdown => { @@ -451,17 +511,23 @@ impl Game for TexasHoldEm { info!("Showdown: community: {:?}", self.community); info!("Showdown: all hands: {:?}", self.hands); info!("Showdown: winning hands: {:?}", winning_hands); - winning_hands.first() - .map(|&(username, hand)| 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, 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 => None, + State::Completed => DealerAction::Leave, } } } @@ -482,9 +548,13 @@ mod tests { game.take_action(validated).unwrap(); } _ => { - let dealer_action = game.next_dealer_action(SystemTime::UNIX_EPOCH).unwrap(); - assert_eq!(ValidatedUserAction(action), dealer_action); - game.take_action(dealer_action).unwrap(); + let dealer_action = game.next_dealer_action(SystemTime::UNIX_EPOCH); + if let DealerAction::TakeAction(ValidatedUserAction(dealer_action)) = dealer_action { + assert_eq!(action, dealer_action); + game.take_action(ValidatedUserAction(action)).unwrap(); + } else { + panic!("Expected DealerAction::TakeAction, got {:?}", dealer_action); + } } } } diff --git a/src/game/whist.rs b/src/game/whist.rs index 999fd98..be75887 100644 --- a/src/game/whist.rs +++ b/src/game/whist.rs @@ -7,7 +7,7 @@ use crate::seats::Seats; use crate::username::Username; use crate::util::max::IteratorMaxItems; -use super::{Action, ActionError, Game, UserAction, ValidatedUserAction}; +use super::{Action, ActionError, DealerAction, Game, UserAction, ValidatedUserAction}; #[derive(Copy, Clone, Debug)] enum State { @@ -303,83 +303,110 @@ impl Game for KnockOutWhist { } } - fn next_dealer_action(&self, timestamp: SystemTime) -> Option { + fn next_dealer_action(&self, timestamp: SystemTime) -> DealerAction { let mut rng = self.rng.clone(); match self.state { State::NotStarted => { if self.seats.players_len() == self.settings.max_players as usize { // TODO if let Some(username) = rng.choose_from(self.seats.player_set()) { - return Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal})); + return DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) + ); } } - None + DealerAction::WaitForPlayer } State::Dealing => { if let Some(username) = self.receiver { let card = rng.choose_from(&self.deck).cloned(); - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::ReceiveCard{card}})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::ReceiveCard{card}}) + ) } else if let Some(username) = self.dealer { match (self.call, self.trump_card) { (None, None) => { if let Some(&card) = rng.choose_from(&self.deck) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::CommunityCard{card}})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::CommunityCard{card}}) + ) } else { - None + error!("Expected to deal a card but none were left in deck"); + DealerAction::Leave } } - (Some(_), _) | (None, Some(_)) => Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal})), + (Some(_), _) | (None, Some(_)) => DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal}) + ), } } else { - None + error!("Expected to deal a card but there was no dealer"); + DealerAction::Leave } } State::ChoosingTrumps => { - None + DealerAction::WaitForPlayer } State::Playing => { if !self.winners.is_empty() { for username in self.seats.player_set() { if matches!(self.tricks_won.get(&username), Some(0) | None) { - return Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut})); + return DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut}) + ); } } if self.seats.players_len() == 1 { if let Some(&username) = self.winners.iter().next() { - return Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame})); + return DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) + ); } } if let Some(username) = self.call { - return Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall})); + return DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall}) + ); } - None + DealerAction::WaitForPlayer } else if let Some(username) = self.trick_winner() { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::WinTrick})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinTrick}) + ) } else { - None + DealerAction::WaitForPlayer } } State::CutForCall => { if let Some(username) = self.receiver { if let Some(card) = rng.choose_from(&self.deck).cloned() { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::RevealCard{card}})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::RevealCard{card}}) + ) } else { - None + error!("Expected to cut for call but there were no cards left in the deck"); + DealerAction::Leave } } else if let Some(username) = self.call { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall}) + ) } else { - None + error!("Cutting for call but there is nobody to receive a card"); + DealerAction::Leave } } State::RoundCompleted => { if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { - Some(ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal})) + DealerAction::TakeAction( + ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) + ) } else { - None + error!("Round completed but there is nobody to deal"); + DealerAction::Leave } } State::Completed => { - None + DealerAction::Leave } } } @@ -401,9 +428,13 @@ mod tests { game.take_action(validated).unwrap(); } _ => { - let dealer_action = game.next_dealer_action(SystemTime::UNIX_EPOCH).unwrap(); - assert_eq!(ValidatedUserAction(action), dealer_action); - game.take_action(dealer_action).unwrap(); + let dealer_action = game.next_dealer_action(SystemTime::UNIX_EPOCH); + if let DealerAction::TakeAction(ValidatedUserAction(dealer_action)) = dealer_action { + assert_eq!(action, dealer_action); + game.take_action(ValidatedUserAction(action)).unwrap(); + } else { + panic!("Expected DealerAction::TakeAction, got {:?}", dealer_action); + } } } } -- 2.34.1