improve gamestate; misc changes
authorGeoffrey Allott <geoffrey@allott.email>
Thu, 11 Feb 2021 19:11:10 +0000 (19:11 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Thu, 11 Feb 2021 19:11:10 +0000 (19:11 +0000)
src/card.rs
src/client.rs
src/game.rs
src/gamestate.rs
src/main.rs
src/seats.rs [new file with mode: 0644]

index 81fe7438fc706812605cf1476ec4affa0e841467..f7d061e9d5e9e02d0d0ee577186f62198d3ec615 100644 (file)
@@ -1,5 +1,3 @@
-#![allow(non_upper_case_globals)]
-
 use std::fmt::{self, Display, Formatter};
 
 use self::Rank::*;
@@ -73,113 +71,113 @@ impl Display for Card {
     }
 }
 
-pub const TwoOfClubs: Card = Card{rank: Two, suit: Clubs};
-pub const ThreeOfClubs: Card = Card{rank: Three, suit: Clubs};
-pub const FourOfClubs: Card = Card{rank: Four, suit: Clubs};
-pub const FiveOfClubs: Card = Card{rank: Five, suit: Clubs};
-pub const SixOfClubs: Card = Card{rank: Six, suit: Clubs};
-pub const SevenOfClubs: Card = Card{rank: Seven, suit: Clubs};
-pub const EightOfClubs: Card = Card{rank: Eight, suit: Clubs};
-pub const NineOfClubs: Card = Card{rank: Nine, suit: Clubs};
-pub const TenOfClubs: Card = Card{rank: Ten, suit: Clubs};
-pub const JackOfClubs: Card = Card{rank: Jack, suit: Clubs};
-pub const QueenOfClubs: Card = Card{rank: Queen, suit: Clubs};
-pub const KingOfClubs: Card = Card{rank: King, suit: Clubs};
-pub const AceOfClubs: Card = Card{rank: Ace, suit: Clubs};
+pub const TWO_OF_CLUBS: Card = Card{rank: Two, suit: Clubs};
+pub const THREE_OF_CLUBS: Card = Card{rank: Three, suit: Clubs};
+pub const FOUR_OF_CLUBS: Card = Card{rank: Four, suit: Clubs};
+pub const FIVE_OF_CLUBS: Card = Card{rank: Five, suit: Clubs};
+pub const SIX_OF_CLUBS: Card = Card{rank: Six, suit: Clubs};
+pub const SEVEN_OF_CLUBS: Card = Card{rank: Seven, suit: Clubs};
+pub const EIGHT_OF_CLUBS: Card = Card{rank: Eight, suit: Clubs};
+pub const NINE_OF_CLUBS: Card = Card{rank: Nine, suit: Clubs};
+pub const TEN_OF_CLUBS: Card = Card{rank: Ten, suit: Clubs};
+pub const JACK_OF_CLUBS: Card = Card{rank: Jack, suit: Clubs};
+pub const QUEEN_OF_CLUBS: Card = Card{rank: Queen, suit: Clubs};
+pub const KING_OF_CLUBS: Card = Card{rank: King, suit: Clubs};
+pub const ACE_OF_CLUBS: Card = Card{rank: Ace, suit: Clubs};
 
-pub const TwoOfDiamonds: Card = Card{rank: Two, suit: Diamonds};
-pub const ThreeOfDiamonds: Card = Card{rank: Three, suit: Diamonds};
-pub const FourOfDiamonds: Card = Card{rank: Four, suit: Diamonds};
-pub const FiveOfDiamonds: Card = Card{rank: Five, suit: Diamonds};
-pub const SixOfDiamonds: Card = Card{rank: Six, suit: Diamonds};
-pub const SevenOfDiamonds: Card = Card{rank: Seven, suit: Diamonds};
-pub const EightOfDiamonds: Card = Card{rank: Eight, suit: Diamonds};
-pub const NineOfDiamonds: Card = Card{rank: Nine, suit: Diamonds};
-pub const TenOfDiamonds: Card = Card{rank: Ten, suit: Diamonds};
-pub const JackOfDiamonds: Card = Card{rank: Jack, suit: Diamonds};
-pub const QueenOfDiamonds: Card = Card{rank: Queen, suit: Diamonds};
-pub const KingOfDiamonds: Card = Card{rank: King, suit: Diamonds};
-pub const AceOfDiamonds: Card = Card{rank: Ace, suit: Diamonds};
+pub const TWO_OF_DIAMONDS: Card = Card{rank: Two, suit: Diamonds};
+pub const THREE_OF_DIAMONDS: Card = Card{rank: Three, suit: Diamonds};
+pub const FOUR_OF_DIAMONDS: Card = Card{rank: Four, suit: Diamonds};
+pub const FIVE_OF_DIAMONDS: Card = Card{rank: Five, suit: Diamonds};
+pub const SIX_OF_DIAMONDS: Card = Card{rank: Six, suit: Diamonds};
+pub const SEVEN_OF_DIAMONDS: Card = Card{rank: Seven, suit: Diamonds};
+pub const EIGHT_OF_DIAMONDS: Card = Card{rank: Eight, suit: Diamonds};
+pub const NINE_OF_DIAMONDS: Card = Card{rank: Nine, suit: Diamonds};
+pub const TEN_OF_DIAMONDS: Card = Card{rank: Ten, suit: Diamonds};
+pub const JACK_OF_DIAMONDS: Card = Card{rank: Jack, suit: Diamonds};
+pub const QUEEN_OF_DIAMONDS: Card = Card{rank: Queen, suit: Diamonds};
+pub const KING_OF_DIAMONDS: Card = Card{rank: King, suit: Diamonds};
+pub const ACE_OF_DIAMONDS: Card = Card{rank: Ace, suit: Diamonds};
 
-pub const TwoOfHearts: Card = Card{rank: Two, suit: Hearts};
-pub const ThreeOfHearts: Card = Card{rank: Three, suit: Hearts};
-pub const FourOfHearts: Card = Card{rank: Four, suit: Hearts};
-pub const FiveOfHearts: Card = Card{rank: Five, suit: Hearts};
-pub const SixOfHearts: Card = Card{rank: Six, suit: Hearts};
-pub const SevenOfHearts: Card = Card{rank: Seven, suit: Hearts};
-pub const EightOfHearts: Card = Card{rank: Eight, suit: Hearts};
-pub const NineOfHearts: Card = Card{rank: Nine, suit: Hearts};
-pub const TenOfHearts: Card = Card{rank: Ten, suit: Hearts};
-pub const JackOfHearts: Card = Card{rank: Jack, suit: Hearts};
-pub const QueenOfHearts: Card = Card{rank: Queen, suit: Hearts};
-pub const KingOfHearts: Card = Card{rank: King, suit: Hearts};
-pub const AceOfHearts: Card = Card{rank: Ace, suit: Hearts};
+pub const TWO_OF_HEARTS: Card = Card{rank: Two, suit: Hearts};
+pub const THREE_OF_HEARTS: Card = Card{rank: Three, suit: Hearts};
+pub const FOUR_OF_HEARTS: Card = Card{rank: Four, suit: Hearts};
+pub const FIVE_OF_HEARTS: Card = Card{rank: Five, suit: Hearts};
+pub const SIX_OF_HEARTS: Card = Card{rank: Six, suit: Hearts};
+pub const SEVEN_OF_HEARTS: Card = Card{rank: Seven, suit: Hearts};
+pub const EIGHT_OF_HEARTS: Card = Card{rank: Eight, suit: Hearts};
+pub const NINE_OF_HEARTS: Card = Card{rank: Nine, suit: Hearts};
+pub const TEN_OF_HEARTS: Card = Card{rank: Ten, suit: Hearts};
+pub const JACK_OF_HEARTS: Card = Card{rank: Jack, suit: Hearts};
+pub const QUEEN_OF_HEARTS: Card = Card{rank: Queen, suit: Hearts};
+pub const KING_OF_HEARTS: Card = Card{rank: King, suit: Hearts};
+pub const ACE_OF_HEARTS: Card = Card{rank: Ace, suit: Hearts};
 
-pub const TwoOfSpades: Card = Card{rank: Two, suit: Spades};
-pub const ThreeOfSpades: Card = Card{rank: Three, suit: Spades};
-pub const FourOfSpades: Card = Card{rank: Four, suit: Spades};
-pub const FiveOfSpades: Card = Card{rank: Five, suit: Spades};
-pub const SixOfSpades: Card = Card{rank: Six, suit: Spades};
-pub const SevenOfSpades: Card = Card{rank: Seven, suit: Spades};
-pub const EightOfSpades: Card = Card{rank: Eight, suit: Spades};
-pub const NineOfSpades: Card = Card{rank: Nine, suit: Spades};
-pub const TenOfSpades: Card = Card{rank: Ten, suit: Spades};
-pub const JackOfSpades: Card = Card{rank: Jack, suit: Spades};
-pub const QueenOfSpades: Card = Card{rank: Queen, suit: Spades};
-pub const KingOfSpades: Card = Card{rank: King, suit: Spades};
-pub const AceOfSpades: Card = Card{rank: Ace, suit: Spades};
+pub const TWO_OF_SPADES: Card = Card{rank: Two, suit: Spades};
+pub const THREE_OF_SPADES: Card = Card{rank: Three, suit: Spades};
+pub const FOUR_OF_SPADES: Card = Card{rank: Four, suit: Spades};
+pub const FIVE_OF_SPADES: Card = Card{rank: Five, suit: Spades};
+pub const SIX_OF_SPADES: Card = Card{rank: Six, suit: Spades};
+pub const SEVEN_OF_SPADES: Card = Card{rank: Seven, suit: Spades};
+pub const EIGHT_OF_SPADES: Card = Card{rank: Eight, suit: Spades};
+pub const NINE_OF_SPADES: Card = Card{rank: Nine, suit: Spades};
+pub const TEN_OF_SPADES: Card = Card{rank: Ten, suit: Spades};
+pub const JACK_OF_SPADES: Card = Card{rank: Jack, suit: Spades};
+pub const QUEEN_OF_SPADES: Card = Card{rank: Queen, suit: Spades};
+pub const KING_OF_SPADES: Card = Card{rank: King, suit: Spades};
+pub const ACE_OF_SPADES: Card = Card{rank: Ace, suit: Spades};
 
 pub const FIFTY_TWO_CARD_DECK: [Card; 52] = [
-    AceOfSpades,
-    AceOfHearts,
-    AceOfDiamonds,
-    AceOfClubs,
-    KingOfSpades,
-    KingOfHearts,
-    KingOfDiamonds,
-    KingOfClubs,
-    QueenOfSpades,
-    QueenOfHearts,
-    QueenOfDiamonds,
-    QueenOfClubs,
-    JackOfSpades,
-    JackOfHearts,
-    JackOfDiamonds,
-    JackOfClubs,
-    TenOfSpades,
-    TenOfHearts,
-    TenOfDiamonds,
-    TenOfClubs,
-    NineOfSpades,
-    NineOfHearts,
-    NineOfDiamonds,
-    NineOfClubs,
-    EightOfSpades,
-    EightOfHearts,
-    EightOfDiamonds,
-    EightOfClubs,
-    SevenOfSpades,
-    SevenOfHearts,
-    SevenOfDiamonds,
-    SevenOfClubs,
-    SixOfSpades,
-    SixOfHearts,
-    SixOfDiamonds,
-    SixOfClubs,
-    FiveOfSpades,
-    FiveOfHearts,
-    FiveOfDiamonds,
-    FiveOfClubs,
-    FourOfSpades,
-    FourOfHearts,
-    FourOfDiamonds,
-    FourOfClubs,
-    ThreeOfSpades,
-    ThreeOfHearts,
-    ThreeOfDiamonds,
-    ThreeOfClubs,
-    TwoOfSpades,
-    TwoOfHearts,
-    TwoOfDiamonds,
-    TwoOfClubs,
+    ACE_OF_SPADES,
+    ACE_OF_HEARTS,
+    ACE_OF_DIAMONDS,
+    ACE_OF_CLUBS,
+    KING_OF_SPADES,
+    KING_OF_HEARTS,
+    KING_OF_DIAMONDS,
+    KING_OF_CLUBS,
+    QUEEN_OF_SPADES,
+    QUEEN_OF_HEARTS,
+    QUEEN_OF_DIAMONDS,
+    QUEEN_OF_CLUBS,
+    JACK_OF_SPADES,
+    JACK_OF_HEARTS,
+    JACK_OF_DIAMONDS,
+    JACK_OF_CLUBS,
+    TEN_OF_SPADES,
+    TEN_OF_HEARTS,
+    TEN_OF_DIAMONDS,
+    TEN_OF_CLUBS,
+    NINE_OF_SPADES,
+    NINE_OF_HEARTS,
+    NINE_OF_DIAMONDS,
+    NINE_OF_CLUBS,
+    EIGHT_OF_SPADES,
+    EIGHT_OF_HEARTS,
+    EIGHT_OF_DIAMONDS,
+    EIGHT_OF_CLUBS,
+    SEVEN_OF_SPADES,
+    SEVEN_OF_HEARTS,
+    SEVEN_OF_DIAMONDS,
+    SEVEN_OF_CLUBS,
+    SIX_OF_SPADES,
+    SIX_OF_HEARTS,
+    SIX_OF_DIAMONDS,
+    SIX_OF_CLUBS,
+    FIVE_OF_SPADES,
+    FIVE_OF_HEARTS,
+    FIVE_OF_DIAMONDS,
+    FIVE_OF_CLUBS,
+    FOUR_OF_SPADES,
+    FOUR_OF_HEARTS,
+    FOUR_OF_DIAMONDS,
+    FOUR_OF_CLUBS,
+    THREE_OF_SPADES,
+    THREE_OF_HEARTS,
+    THREE_OF_DIAMONDS,
+    THREE_OF_CLUBS,
+    TWO_OF_SPADES,
+    TWO_OF_HEARTS,
+    TWO_OF_DIAMONDS,
+    TWO_OF_CLUBS,
 ];
index 1f066354ff61414375d440c48b0482e8242e6511..9fb6a24cf82a8fadceeccb985b18b3f79c962b8a 100644 (file)
@@ -152,6 +152,9 @@ impl ConnectionState {
                 }
             }
             (ClientState::LoggedIn{ref username, state: LoggedInState::InGame{ref mut game}}, ClientMessage::TakeAction{action}) => {
+                if action.is_initiated_server_side_only() {
+                    return ServerMessage::TakeActionFailure{reason: "Not authorised".to_string()};
+                }
                 let action = UserAction{username: username.clone(), action};
                 loop {
                     let len = game.actions_len();
index 27fb131077de3a1c046be3f2e9eb7bceab58ec31..3e93b9ea9ebcab5e3a3b7c6a67ab066e83a0073c 100644 (file)
@@ -1,7 +1,7 @@
 use std::collections::HashSet;
 use std::fmt::Debug;
 
-use crate::card::Card;
+use crate::card::{Card, Suit};
 use crate::gamestate::GameState;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -9,10 +9,11 @@ use crate::gamestate::GameState;
 pub enum GameSettings {
     Chatroom {
         title: String,
-        max_users: u32,
+        max_players: u32,
     },
-    TexasHoldEm {
-        // TODO
+    KnockOutWhist {
+        title: String,
+        max_players: u32,
     },
 }
 
@@ -49,13 +50,18 @@ impl GameList {
 pub enum Action {
     Join { seat: u32, chips: u64 },
     AddOn { chips: u64 },
-    NextToAct,
+    NextToDeal,
     CommunityCard { card: Card },
     ReceiveCard { card: Option<Card> },
+    EndDeal,
     RevealCard { card: Card },
+    PlayCard { card: Card },
+    ChooseTrumps { suit: Suit },
     Fold,
     Bet { chips: u64 },
-    WinPot { chips: u64 },
+    WinTrick,
+    WinHand { chips: u64 },
+    WinGame,
     Message { message: String },
     Leave,
 }
@@ -67,6 +73,15 @@ impl Action {
             action => action.clone(),
         }
     }
+
+    pub fn is_initiated_server_side_only(&self) -> bool {
+        match self {
+            Action::Join{..} | Action::AddOn{..} | Action::RevealCard{..} | Action::PlayCard{..} |
+            Action::ChooseTrumps{..} | Action::Fold | Action::Bet{..} | Action::Message{..} | Action::Leave => false,
+            Action::NextToDeal | Action::CommunityCard{..} | Action::ReceiveCard{..} | Action::EndDeal |
+            Action::WinTrick | Action::WinHand{..} | Action::WinGame => true,
+        }
+    }
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -88,8 +103,10 @@ impl UserAction {
 pub enum ActionError {
     NotAuthorised,
     AlreadyJoined,
+    GameHasStarted,
     NoSeatAvailable,
     OutOfTurn,
+    CardNotPlayable,
     InvalidActionForGameType,
 }
 
@@ -100,8 +117,10 @@ impl Display for ActionError {
         match self {
             ActionError::NotAuthorised => f.write_str("NotAuthorised"),
             ActionError::AlreadyJoined => f.write_str("AlreadyJoined"),
+            ActionError::GameHasStarted => f.write_str("GameHasStarted"),
             ActionError::NoSeatAvailable => f.write_str("NoSeatAvailable"),
             ActionError::OutOfTurn => f.write_str("OutOfTurn"),
+            ActionError::CardNotPlayable => f.write_str("CardNotPlayable"),
             ActionError::InvalidActionForGameType => f.write_str("InvalidActionForGameType"),
         }
     }
@@ -159,9 +178,9 @@ impl Game {
     pub fn verify(&self, UserAction{ref username, ref action}: &UserAction) -> Result<(), ActionError> {
         debug!("Verifying action: UserAction {{ username: {:?}, action: {:?} }}", username, action);
         match self.summary.settings {
-            GameSettings::Chatroom{max_users, ..} => match action {
+            GameSettings::Chatroom{max_players, ..} => match action {
                 Action::Join{seat: 0, chips: 0} if self.state.user_has_joined(username) => Err(ActionError::AlreadyJoined),
-                Action::Join{seat: 0, chips: 0} if self.state.num_players() + 1 > max_users => Err(ActionError::NoSeatAvailable),
+                Action::Join{seat: 0, chips: 0} if self.state.num_players() + 1 > max_players => Err(ActionError::NoSeatAvailable),
                 Action::Join{seat: 0, chips: 0} => Ok(()),
                 Action::Message{..} if self.state.user_has_joined(username) => Ok(()),
                 Action::Message{..} => Err(ActionError::NotAuthorised),
@@ -169,9 +188,26 @@ impl Game {
                 Action::Leave => Err(ActionError::NotAuthorised),
                 _ => Err(ActionError::InvalidActionForGameType),
             },
-            GameSettings::TexasHoldEm{..} => {
-                // TODO
-                Err(ActionError::NotAuthorised)
+            GameSettings::KnockOutWhist{max_players, ..} => match action {
+                Action::Join{seat, chips: 0} if self.state.user_has_joined(username) => Err(ActionError::AlreadyJoined),
+                Action::Join{seat, chips: 0} if self.state.has_started() => Err(ActionError::GameHasStarted),
+                Action::Join{seat, chips: 0} if !self.state.seat_is_available(*seat) => Err(ActionError::NoSeatAvailable),
+                Action::Join{seat, chips: 0} => Ok(()),
+                Action::Leave if self.state.user_has_joined(username) && !self.state.has_started() => Ok(()),
+                Action::Leave if self.state.user_has_joined(username) => Err(ActionError::GameHasStarted),
+                Action::Leave => Err(ActionError::NotAuthorised),
+                Action::ReceiveCard{card: Some(card)} if self.state.is_dealing_to(username) => Ok(()),
+                Action::ReceiveCard{..} => Err(ActionError::OutOfTurn),
+                Action::NextToDeal => Ok(()),
+                Action::CommunityCard{..} if self.state.is_dealing_community_cards() => Ok(()),
+                Action::CommunityCard{..} => Err(ActionError::OutOfTurn),
+                Action::PlayCard{card} if self.state.player_is_active(username) && self.state.player_has_card(username, *card) => Ok(()),
+                Action::PlayCard{card} if !self.state.player_is_active(username) => Err(ActionError::OutOfTurn),
+                Action::PlayCard{..} => Err(ActionError::CardNotPlayable),
+                Action::WinTrick{..} => Ok(()),
+                Action::WinHand{..} => Ok(()),
+                Action::WinGame => Ok(()),
+                _ => Err(ActionError::InvalidActionForGameType),
             }
         }
     }
index b76761193b0b7c227e3e85aa0487ca4002babd40..3aeb8318f044d9c8a3f0374f6327762e54bd25de 100644 (file)
@@ -1,6 +1,8 @@
-use std::collections::HashSet;
+use std::collections::{BTreeMap, HashSet};
 
+use crate::card::*;
 use crate::game::{Action, UserAction};
+use crate::seats::Seats;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct GameState {
@@ -64,6 +66,98 @@ impl GameState {
         num_players
     }
 
+    pub fn is_dealing_to(&self, username: &str) -> bool {
+        let mut is_dealing_to = None;
+        let mut seats = Seats::new();
+        for action in &self.actions {
+            match &action.action {
+                Action::Join{seat, ..} => seats.add_player(*seat, &action.username),
+                Action::Leave => seats.remove_player(&action.username),
+                Action::NextToDeal => is_dealing_to = seats.player_after(&action.username),
+                Action::ReceiveCard{..} if is_dealing_to.as_ref().map(String::as_str) == Some(&action.username) => is_dealing_to = seats.player_after(&action.username),
+                Action::ReceiveCard{..} => error!("Expected {:?} to be dealt a card, but {:?} received one", is_dealing_to, username),
+                Action::WinHand{..} => is_dealing_to = seats.player_after(&action.username),
+                Action::EndDeal | Action::WinGame => is_dealing_to = None,
+                _ => {},
+            }
+        }
+        is_dealing_to.as_ref().map(String::as_str) == Some(username)
+    }
+
+    pub fn seat_is_available(&self, seat: u32) -> bool {
+        let mut seats = Seats::new();
+        for action in &self.actions {
+            match &action.action {
+                Action::Join{seat, ..} => seats.add_player(*seat, &action.username),
+                Action::Leave => seats.remove_player(&action.username),
+                _ => {},
+            }
+        }
+        seats.seat_is_available(seat)
+    }
+
+    pub fn is_choosing_next_to_deal(&self) -> bool {
+        for action in &self.actions {
+            if let Action::NextToDeal = &action.action {
+                return false;
+            }
+        }
+        true
+    }
+
+    pub fn has_started(&self) -> bool {
+        for action in &self.actions {
+            if let Action::NextToDeal = &action.action {
+                return true;
+            }
+        }
+        false
+    }
+
+    pub fn player_is_active(&self, username: &str) -> bool {
+        let mut dealer = None;
+        let mut active_player = None;
+        let mut next_active_player = None;
+        let mut seats = Seats::new();
+        let mut players_in_hand = Seats::new();
+        for action in &self.actions {
+            println!("processing action: {:?}", action);
+            match &action.action {
+                Action::Join{seat, ..} => seats.add_player(*seat, &action.username),
+                Action::Leave => seats.remove_player(&action.username),
+                Action::NextToDeal => {
+                    players_in_hand = seats.clone();
+                    next_active_player = players_in_hand.player_after(&action.username);
+                    dealer = Some(action.username.clone());
+                }
+                Action::PlayCard{..} | Action::Bet{..} => {
+                    active_player = players_in_hand.player_after(&action.username);
+                }
+                Action::Fold => {
+                    active_player = players_in_hand.player_after(&action.username);
+                    players_in_hand.remove_player(&action.username);
+                }
+                Action::WinHand{..} => active_player = None,
+                Action::EndDeal => active_player = next_active_player.clone(),
+                Action::WinGame => active_player = None,
+                _ => {},
+            }
+            println!("dealer: {:?}", dealer);
+            println!("active_player: {:?}", active_player);
+            println!("next_active_player: {:?}", next_active_player);
+            println!("seats: {:?}", seats);
+        }
+        active_player.as_ref().map(String::as_str) == Some(username)
+    }
+
+    pub fn player_has_card(&self, username: &str, card: Card) -> bool {
+        false // TODO
+    }
+
+    pub fn is_dealing_community_cards(&self) -> bool {
+        false // TODO
+    }
+
     pub fn take_action(&mut self, action: UserAction) {
         self.actions.push(action);
     }
@@ -90,4 +184,75 @@ mod tests {
         state.take_action(UserAction{username: "user".to_string(), action: Action::Leave});
         assert_eq!(1, state.num_players());
     }
+
+    #[test]
+    fn players() {
+        let mut state = GameState::new();
+        state.take_action(UserAction{username: "user".to_string(), action: Action::Join{seat: 0, chips: 10000}});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Join{seat: 1, chips: 10000}});
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::Join{seat: 2, chips: 10000}});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Leave});
+        assert_eq!(vec!["user", "user3"].into_iter().collect::<HashSet<_>>(), state.players());
+    }
+
+    #[test]
+    fn is_dealing_to() {
+        let mut state = GameState::new();
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::Join{seat: 0, chips: 10000}});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Join{seat: 1, chips: 10000}});
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::Join{seat: 2, chips: 10000}});
+        assert!(!state.is_dealing_to("user1"));
+        assert!(!state.is_dealing_to("user2"));
+        assert!(!state.is_dealing_to("user3"));
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::NextToDeal});
+        assert!(state.is_dealing_to("user2"));
+        state.take_action(UserAction{
+            username: "user2".to_string(),
+            action: Action::ReceiveCard{card: Some(THREE_OF_SPADES)},
+        });
+        assert!(state.is_dealing_to("user3"));
+        state.take_action(UserAction{
+            username: "user3".to_string(),
+            action: Action::ReceiveCard{card: Some(ACE_OF_HEARTS)},
+        });
+        assert!(state.is_dealing_to("user1"));
+        state.take_action(UserAction{
+            username: "user1".to_string(),
+            action: Action::ReceiveCard{card: Some(KING_OF_CLUBS)},
+        });
+        assert!(state.is_dealing_to("user2"));
+        state.take_action(UserAction{username: "dealer".to_string(), action: Action::EndDeal});
+        assert!(!state.is_dealing_to("user2"));
+    }
+
+    #[test]
+    fn player_is_active() {
+        let mut state = GameState::new();
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::Join{seat: 0, chips: 10000}});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Join{seat: 1, chips: 10000}});
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::Join{seat: 2, chips: 10000}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::NextToDeal});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::ReceiveCard{card: Some(ACE_OF_SPADES)}});
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::ReceiveCard{card: Some(KING_OF_HEARTS)}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::ReceiveCard{card: Some(TWO_OF_DIAMONDS)}});
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::ReceiveCard{card: Some(QUEEN_OF_SPADES)}});
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::ReceiveCard{card: Some(THREE_OF_SPADES)}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::ReceiveCard{card: Some(FOUR_OF_CLUBS)}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::EndDeal});
+        assert!(state.player_is_active("user2"));
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Bet{chips: 50}});
+        assert!(state.player_is_active("user3"));
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::Bet{chips: 100}});
+        assert!(state.player_is_active("user1"));
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::Fold});
+        assert!(state.player_is_active("user2"));
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Bet{chips: 250}});
+        assert!(state.player_is_active("user3"));
+        state.take_action(UserAction{username: "user3".to_string(), action: Action::Bet{chips: 300}});
+        assert!(state.player_is_active("user2"));
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Bet{chips: 300}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::CommunityCard{card: ACE_OF_HEARTS}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::CommunityCard{card: KING_OF_CLUBS}});
+        state.take_action(UserAction{username: "user1".to_string(), action: Action::CommunityCard{card: THREE_OF_HEARTS}});
+    }
 }
index 77da1d7d653728e617267f0b990dd077925f9f47..4eb2784e21ba23ba3dd6e73a919a5565935233d5 100644 (file)
@@ -20,6 +20,7 @@ mod card;
 mod client;
 mod game;
 mod gamestate;
+mod seats;
 mod server;
 
 use crate::api::ServerMessage;
@@ -226,12 +227,13 @@ async fn main() -> Result<(), Error> {
 
     let handle_client_interest = handle_client_interest(pubsub, register_update_stream_rx);
 
-    let listener = TlsListener::build()
+    /*let listener = TlsListener::build()
         .addrs("localhost:4433")
         .cert("cert/cert.pem")
         .key("cert/key.pem");
 
-    let app = app.listen(listener);
+    let app = app.listen(listener);*/
+    let app = app.listen("0.0.0.0:8080");
     pin_mut!(app, handle_client_interest, signal_handler);
 
     select(select(app, handle_client_interest).map(|f| f.factor_first().0), signal_handler).await.factor_first().0?;
diff --git a/src/seats.rs b/src/seats.rs
new file mode 100644 (file)
index 0000000..d32cfa8
--- /dev/null
@@ -0,0 +1,125 @@
+use std::collections::BTreeMap;
+
+#[derive(Debug, Clone)]
+pub struct Seats {
+    players: BTreeMap<u32, String>,
+}
+
+impl Seats {
+    pub fn new() -> Self {
+        Self {
+            players: BTreeMap::new(),
+        }
+    }
+
+    pub fn add_player(&mut self, seat: u32, username: &str) {
+        self.players.insert(seat, username.to_owned());
+    }
+
+    pub fn remove_player(&mut self, username: &str) {
+        if let Some(seat) = self.players.iter().find(|(_, player)| &**player == username).map(|(&seat, _)| seat) {
+            self.players.remove(&seat);
+        }
+    }
+
+    fn player_after_seat(&self, seat: u32) -> Option<String> {
+        if let Some((_, name)) = self.players.range(seat+1..).next() {
+            Some(name.to_owned())
+        } else if let Some((_, name)) = self.players.range(..seat).next() {
+            Some(name.to_owned())
+        } else {
+            None
+        }
+    }
+
+    pub fn player_after(&self, username: &str) -> Option<String> {
+        for (&seat, player) in &self.players {
+            if player == username {
+                return self.player_after_seat(seat);
+            }
+        }
+        None
+    }
+
+    pub fn seat_is_available(&self, seat: u32) -> bool {
+        self.players.get(&seat).is_none()
+    }
+
+    pub fn players_len(&self) -> usize {
+        self.players.len()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn player_after_absent_player_is_none() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        seats.add_player(2, "Player 2");
+        seats.add_player(3, "Player 3");
+        assert_eq!(None, seats.player_after("Player 4"));
+        assert!(seats.seat_is_available(0));
+        assert!(!seats.seat_is_available(1));
+        assert!(!seats.seat_is_available(2));
+        assert!(!seats.seat_is_available(3));
+        assert!(seats.seat_is_available(4));
+    }
+
+    #[test]
+    fn player_after_single_player_is_none() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        assert_eq!(None, seats.player_after("Player 1"));
+        assert!(seats.seat_is_available(0));
+        assert!(!seats.seat_is_available(1));
+        assert!(seats.seat_is_available(2));
+    }
+
+    #[test]
+    fn player_after_simple() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        seats.add_player(2, "Player 2");
+        seats.add_player(3, "Player 3");
+        assert_eq!("Player 2", seats.player_after("Player 1").unwrap());
+        assert_eq!("Player 3", seats.player_after("Player 2").unwrap());
+    }
+
+    #[test]
+    fn player_after_wrap_around() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        seats.add_player(5, "Player 5");
+        seats.add_player(8, "Player 8");
+        assert_eq!("Player 1", seats.player_after("Player 8").unwrap());
+    }
+
+    #[test]
+    fn player_after_two_player() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        seats.add_player(4, "Player 4");
+        assert_eq!("Player 4", seats.player_after("Player 1").unwrap());
+        assert_eq!("Player 1", seats.player_after("Player 4").unwrap());
+    }
+
+    #[test]
+    fn remove_player() {
+        let mut seats = Seats::new();
+        seats.add_player(1, "Player 1");
+        seats.add_player(2, "Player 2");
+        seats.add_player(3, "Player 3");
+        seats.remove_player("Player 2");
+        assert_eq!("Player 3", seats.player_after("Player 1").unwrap());
+        assert_eq!(None, seats.player_after("Player 2"));
+        assert_eq!("Player 1", seats.player_after("Player 3").unwrap());
+        assert!(seats.seat_is_available(0));
+        assert!(!seats.seat_is_available(1));
+        assert!(seats.seat_is_available(2));
+        assert!(!seats.seat_is_available(3));
+        assert!(seats.seat_is_available(4));
+    }
+}