return a DealerAction that can instruct the dealer to wait or to leave
authorGeoffrey Allott <geoffrey@allott.email>
Tue, 2 Mar 2021 22:12:18 +0000 (22:12 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Tue, 2 Mar 2021 22:12:18 +0000 (22:12 +0000)
src/dealer.rs
src/game/action.rs
src/game/chatroom.rs
src/game/mod.rs
src/game/poker/holdem.rs
src/game/whist.rs

index fb56f65b4f2ff8202b5d28809e0f343f2a92f22a..55c9bc6bb4862764c169b16442c9a972d30f8ed9 100644 (file)
@@ -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
                 }
             }
         }
index 68d6be8da7f3de73e5f16480ba305062cec2945b..05a666964c92464f214e53120726587ad2b1ab12 100644 (file)
@@ -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 {
index fbb8f98db87d569281da335b507f2ef928960d69..66934a86b5c0c64074b9aaaf41fd38c564df5681 100644 (file)
@@ -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<ValidatedUserAction> {
+    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,
         }
     }
 }
index 690dea1c427a2f5a85c49338a28828ed350c7401..e4ad5b1b50b15025f3ffd874b72a8aa3f3b4bc1a 100644 (file)
@@ -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<ValidatedUserAction, ActionError>;
     fn take_action(&mut self, action: ValidatedUserAction) -> Result<(), ActionError>;
-    fn next_dealer_action(&self, timestamp: SystemTime) -> Option<ValidatedUserAction>;
+    fn next_dealer_action(&self, timestamp: SystemTime) -> DealerAction;
 }
 
 pub trait CloneBoxGame {
index 505bd3fb654071ea699138f716827d4f76dee6a4..2ed0f179d1c677e300cc917f78fc1d7093a37156 100644 (file)
@@ -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<ValidatedUserAction> {
+    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);
+                    }
                 }
             }
         }
index 999fd98ed0b0ce0577834264a6d2ee51ff536602..be75887cd0fc707fc43b47a13d9c4044856d787f 100644 (file)
@@ -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<ValidatedUserAction> {
+    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);
+                    }
                 }
             }
         }