From 2b3f72f5142c009cdddf66ebb1d442458c1b5738 Mon Sep 17 00:00:00 2001 From: Geoffrey Allott Date: Thu, 11 Feb 2021 19:11:10 +0000 Subject: [PATCH] improve gamestate; misc changes --- src/card.rs | 210 +++++++++++++++++++++++------------------------ src/client.rs | 3 + src/game.rs | 58 ++++++++++--- src/gamestate.rs | 167 ++++++++++++++++++++++++++++++++++++- src/main.rs | 6 +- src/seats.rs | 125 ++++++++++++++++++++++++++++ 6 files changed, 449 insertions(+), 120 deletions(-) create mode 100644 src/seats.rs diff --git a/src/card.rs b/src/card.rs index 81fe743..f7d061e 100644 --- a/src/card.rs +++ b/src/card.rs @@ -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, ]; diff --git a/src/client.rs b/src/client.rs index 1f06635..9fb6a24 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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(); diff --git a/src/game.rs b/src/game.rs index 27fb131..3e93b9e 100644 --- a/src/game.rs +++ b/src/game.rs @@ -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 }, + 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), } } } diff --git a/src/gamestate.rs b/src/gamestate.rs index b767611..3aeb831 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -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::>(), 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}}); + } } diff --git a/src/main.rs b/src/main.rs index 77da1d7..4eb2784 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 index 0000000..d32cfa8 --- /dev/null +++ b/src/seats.rs @@ -0,0 +1,125 @@ +use std::collections::BTreeMap; + +#[derive(Debug, Clone)] +pub struct Seats { + players: BTreeMap, +} + +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 { + 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 { + 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)); + } +} -- 2.34.1