From 0d3697a1f8059ff2a1722975bc2fccb96e014477 Mon Sep 17 00:00:00 2001 From: Geoffrey Allott Date: Sat, 6 Mar 2021 11:02:20 +0000 Subject: [PATCH] apply rustfmt --- rustfmt.toml | 3 + src/auth.rs | 2 +- src/card.rs | 104 ++-- src/client.rs | 162 +++--- src/dealer.rs | 10 +- src/game/action.rs | 2 +- src/game/chatroom.rs | 43 +- src/game/mod.rs | 13 +- src/game/poker/classify.rs | 1034 +++++++++++++++++++----------------- src/game/poker/holdem.rs | 221 +++----- src/game/whist.rs | 135 ++--- src/main.rs | 63 ++- src/rng.rs | 24 +- src/seats.rs | 10 +- src/server.rs | 111 ++-- src/username.rs | 35 +- src/util/dedup.rs | 22 +- src/util/max.rs | 21 +- src/util/timestamp.rs | 12 +- 19 files changed, 965 insertions(+), 1062 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..b0d7ea9 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 160 +use_field_init_shorthand = true +use_small_heuristics = "Max" diff --git a/src/auth.rs b/src/auth.rs index fd3049a..92db48e 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -9,7 +9,7 @@ impl Auth { pub fn verify(&self, _challenge: &str, signature: &str) -> bool { match self { Auth::NoLogin => false, - Auth::Plain{password} => signature == password, + Auth::Plain { password } => signature == password, } } } diff --git a/src/card.rs b/src/card.rs index aa09f78..8ffb0f0 100644 --- a/src/card.rs +++ b/src/card.rs @@ -71,61 +71,61 @@ impl Display for Card { } } -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 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 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 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 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 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 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 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] = [ ACE_OF_SPADES, diff --git a/src/client.rs b/src/client.rs index 2ff9f91..b2bee74 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use futures::stream::{Stream, StreamExt, empty, iter, once}; +use futures::stream::{empty, iter, once, Stream, StreamExt}; use crate::api::{ClientMessage, ServerMessage}; use crate::game::{Game, GameList, UserAction}; @@ -16,10 +16,7 @@ pub struct ConnectionState { pub enum ClientState { Connected, LoginAuthIssued { username: Username, challenge: String }, - LoggedIn { - username: Username, - state: LoggedInState, - }, + LoggedIn { username: Username, state: LoggedInState }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -39,31 +36,28 @@ pub enum LoggedInState { impl ConnectionState { pub fn new(server: ServerState) -> Self { - Self { - server, - client: ClientState::Connected, - } + Self { server, client: ClientState::Connected } } - pub async fn retrieve_updates(&mut self, update: ClientInterest) -> impl Stream { + pub async fn retrieve_updates(&mut self, update: ClientInterest) -> impl Stream { match update { ClientInterest::GameList => match &mut self.client { - ClientState::LoggedIn{state: LoggedInState::InLobby{ref mut game_list}, ..} => { + ClientState::LoggedIn { state: LoggedInState::InLobby { ref mut game_list }, .. } => { let from = game_list.games_len(); match self.server.game_list(from).await { Ok(games) => { for game in games.clone() { game_list.push(game); } - iter(games).map(|game| ServerMessage::NewGame{game}).boxed() + iter(games).map(|game| ServerMessage::NewGame { game }).boxed() } - Err(err) => once(async move { ServerMessage::ProtocolError{reason: err.to_string()} }).boxed(), + Err(err) => once(async move { ServerMessage::ProtocolError { reason: err.to_string() } }).boxed(), } } _ => empty().boxed(), - } - ClientInterest::Game{id} => match &mut self.client { - &mut ClientState::LoggedIn{username, state: LoggedInState::InGame{ref mut game}} if game.id() == id => { + }, + ClientInterest::Game { id } => match &mut self.client { + &mut ClientState::LoggedIn { username, state: LoggedInState::InGame { ref mut game } } if game.id() == id => { let id = game.id(); let from = game.actions_len(); match self.server.game_state(id, from).await { @@ -74,31 +68,31 @@ impl ConnectionState { error!("Action from database failed to apply: {}", err); } } - iter(actions_view).map(|action| ServerMessage::NewAction{action}).boxed() + iter(actions_view).map(|action| ServerMessage::NewAction { action }).boxed() } - Err(err) => once(async move { ServerMessage::ProtocolError{reason: err.to_string()} }).boxed(), + Err(err) => once(async move { ServerMessage::ProtocolError { reason: err.to_string() } }).boxed(), } } _ => empty().boxed(), - } - ClientInterest::User{..} => empty().boxed(), // TODO - ClientInterest::Timeout{..} => empty().boxed(), // TODO + }, + ClientInterest::User { .. } => empty().boxed(), // TODO + ClientInterest::Timeout { .. } => empty().boxed(), // TODO } } pub fn interests(&self) -> HashSet { let mut ret = HashSet::new(); - if let &ClientState::LoggedIn{username, ref state} = &self.client { - ret.insert(ClientInterest::User{username}); + if let &ClientState::LoggedIn { username, ref state } = &self.client { + ret.insert(ClientInterest::User { username }); match state { - LoggedInState::Idle => {}, - LoggedInState::InLobby{..} => { + LoggedInState::Idle => {} + LoggedInState::InLobby { .. } => { ret.insert(ClientInterest::GameList); } - LoggedInState::InGame{ref game} => { - ret.insert(ClientInterest::Game{id: game.id()}); + LoggedInState::InGame { ref game } => { + ret.insert(ClientInterest::Game { id: game.id() }); for username in game.players() { - ret.insert(ClientInterest::User{username}); + ret.insert(ClientInterest::User { username }); } } } @@ -119,46 +113,42 @@ impl ConnectionState { async fn message_response(&mut self, message: ClientMessage) -> ServerMessage { match (&mut self.client, message) { - (_, ClientMessage::CreateUser{username, auth, nickname}) => { - match self.server.create_user(username, auth, &nickname).await { - Ok(()) => ServerMessage::CreateUserSuccess, - Err(_) => ServerMessage::CreateUserFailure{reason: "User already exists".to_string()}, - } - } - (&mut ClientState::Connected, ClientMessage::Login{username}) => { + (_, ClientMessage::CreateUser { username, auth, nickname }) => match self.server.create_user(username, auth, &nickname).await { + Ok(()) => ServerMessage::CreateUserSuccess, + Err(_) => ServerMessage::CreateUserFailure { reason: "User already exists".to_string() }, + }, + (&mut ClientState::Connected, ClientMessage::Login { username }) => { let challenge = format!("{:032x}{:032x}", rand::random::(), rand::random::()); - self.client = ClientState::LoginAuthIssued{username, challenge: challenge.clone()}; - ServerMessage::LoginAuthChallenge{challenge} + self.client = ClientState::LoginAuthIssued { username, challenge: challenge.clone() }; + ServerMessage::LoginAuthChallenge { challenge } } - (&mut ClientState::LoginAuthIssued{username, ref challenge}, ClientMessage::LoginAuthResponse{signature}) => { + (&mut ClientState::LoginAuthIssued { username, ref challenge }, ClientMessage::LoginAuthResponse { signature }) => { if self.server.verify(username, &challenge, &signature).await { - self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle}; + self.client = ClientState::LoggedIn { username: username.clone(), state: LoggedInState::Idle }; ServerMessage::LoginSuccess } else { self.client = ClientState::Connected; - ServerMessage::LoginFailure{reason: "Invalid username or password".to_string()} + ServerMessage::LoginFailure { reason: "Invalid username or password".to_string() } } } - (&mut ClientState::LoggedIn{username, ..}, ClientMessage::JoinLobby{filter}) => { + (&mut ClientState::LoggedIn { username, .. }, ClientMessage::JoinLobby { filter }) => { let mut game_list = GameList::new(filter); match self.server.game_list(0).await { Ok(games) => { for game in games.clone() { game_list.push(game); } - self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby{game_list}}; - ServerMessage::JoinLobbySuccess{games} + self.client = ClientState::LoggedIn { username: username.clone(), state: LoggedInState::InLobby { game_list } }; + ServerMessage::JoinLobbySuccess { games } } - Err(err) => ServerMessage::JoinLobbyFailure{reason: err.to_string()}, - } - } - (&mut ClientState::LoggedIn{..}, ClientMessage::CreateGame{settings}) => { - match self.server.create_game(settings).await { - Ok(id) => ServerMessage::CreateGameSuccess{id}, - Err(err) => ServerMessage::CreateGameFailure{reason: err.to_string()}, + Err(err) => ServerMessage::JoinLobbyFailure { reason: err.to_string() }, } } - (&mut ClientState::LoggedIn{username, ..}, ClientMessage::JoinGame{id}) => { + (&mut ClientState::LoggedIn { .. }, ClientMessage::CreateGame { settings }) => match self.server.create_game(settings).await { + Ok(id) => ServerMessage::CreateGameSuccess { id }, + Err(err) => ServerMessage::CreateGameFailure { reason: err.to_string() }, + }, + (&mut ClientState::LoggedIn { username, .. }, ClientMessage::JoinGame { id }) => { match (self.server.game_summary(id).await, self.server.game_state(id, 0).await, self.server.game_seed(id).await) { (Ok(summary), Ok(actions), Ok(seed)) => { let actions_view = actions.iter().map(|action| action.view_for(username)).collect(); @@ -168,79 +158,73 @@ impl ConnectionState { error!("Action from database failed to apply: {}", err); } } - self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InGame{game}}; - ServerMessage::JoinGameSuccess{summary, actions: actions_view} + self.client = ClientState::LoggedIn { username: username.clone(), state: LoggedInState::InGame { game } }; + ServerMessage::JoinGameSuccess { summary, actions: actions_view } } - (Err(err), _, _) | (_, Err(err), _) | (_, _, Err(err)) => ServerMessage::JoinGameFailure{reason: err.to_string()}, + (Err(err), _, _) | (_, Err(err), _) | (_, _, Err(err)) => ServerMessage::JoinGameFailure { reason: err.to_string() }, } } - (&mut ClientState::LoggedIn{username, state: LoggedInState::InGame{ref mut game}}, ClientMessage::TakeAction{action}) => { + (&mut ClientState::LoggedIn { username, state: LoggedInState::InGame { ref mut game } }, ClientMessage::TakeAction { action }) => { let timestamp = match self.server.now().await { Ok(timestamp) => timestamp, - Err(err) => return ServerMessage::InternalError{reason: err.to_string()}, + Err(err) => return ServerMessage::InternalError { reason: err.to_string() }, }; - let action = UserAction{timestamp, username, action}; + let action = UserAction { timestamp, username, action }; let id = game.id(); loop { let len = game.actions_len(); match game.validate_action(action.clone()) { Ok(validated) => match self.server.take_action(id, len, &validated).await { Ok(ActionStatus::Committed) => match game.take_action(validated.clone()) { - Ok(()) => return ServerMessage::TakeActionSuccess{action}, - Err(err) => return ServerMessage::TakeActionFailure{action, reason: err.to_string()}, - } + Ok(()) => return ServerMessage::TakeActionSuccess { action }, + Err(err) => return ServerMessage::TakeActionFailure { action, reason: err.to_string() }, + }, Ok(ActionStatus::Interrupted) => { debug!("Action {:?} was interrupted - updating game state", action); match self.server.game_state(id, len).await { - Ok(actions) => for new_action in actions { - if let Err(err) = game.take_action(new_action) { - return ServerMessage::TakeActionFailure{action, reason: err.to_string()}; + Ok(actions) => { + for new_action in actions { + if let Err(err) = game.take_action(new_action) { + return ServerMessage::TakeActionFailure { action, reason: err.to_string() }; + } } } Err(err) => { - return ServerMessage::TakeActionFailure{action, reason: err.to_string()}; + return ServerMessage::TakeActionFailure { action, reason: err.to_string() }; } } } - Err(err) => return ServerMessage::TakeActionFailure{action, reason: err.to_string()}, - } - Err(err) => return ServerMessage::TakeActionFailure{action, reason: err.to_string()}, + Err(err) => return ServerMessage::TakeActionFailure { action, reason: err.to_string() }, + }, + Err(err) => return ServerMessage::TakeActionFailure { action, reason: err.to_string() }, } } } - (&mut ClientState::LoggedIn{username, state: LoggedInState::InLobby{..}}, ClientMessage::LeaveLobby) => { - self.client = ClientState::LoggedIn{username, state: LoggedInState::Idle}; + (&mut ClientState::LoggedIn { username, state: LoggedInState::InLobby { .. } }, ClientMessage::LeaveLobby) => { + self.client = ClientState::LoggedIn { username, state: LoggedInState::Idle }; ServerMessage::LeaveLobbySuccess } - (&mut ClientState::LoggedIn{..}, ClientMessage::LeaveLobby) => { - ServerMessage::LeaveLobbyFailure{reason: "Not in lobby".to_string()} - } - (&mut ClientState::LoggedIn{username, state: LoggedInState::InGame{..}}, ClientMessage::LeaveGame) => { - self.client = ClientState::LoggedIn{username, state: LoggedInState::Idle}; + (&mut ClientState::LoggedIn { .. }, ClientMessage::LeaveLobby) => ServerMessage::LeaveLobbyFailure { reason: "Not in lobby".to_string() }, + (&mut ClientState::LoggedIn { username, state: LoggedInState::InGame { .. } }, ClientMessage::LeaveGame) => { + self.client = ClientState::LoggedIn { username, state: LoggedInState::Idle }; ServerMessage::LeaveGameSuccess } - (&mut ClientState::LoggedIn{..}, ClientMessage::LeaveGame) => { - ServerMessage::LeaveGameFailure{reason: "Not in game".to_string()} - } - (&mut ClientState::LoggedIn{..}, ClientMessage::Logout) => { + (&mut ClientState::LoggedIn { .. }, ClientMessage::LeaveGame) => ServerMessage::LeaveGameFailure { reason: "Not in game".to_string() }, + (&mut ClientState::LoggedIn { .. }, ClientMessage::Logout) => { self.client = ClientState::Connected; ServerMessage::LogoutSuccess } - (&mut ClientState::LoggedIn{username, ..}, ClientMessage::ChangeAuth{auth}) => { - match self.server.set_user_auth(username, auth).await { - Ok(()) => ServerMessage::ChangeAuthSuccess, - Err(err) => ServerMessage::ChangeAuthFailure{reason: err.to_string()}, - } - } - (&mut ClientState::LoggedIn{username, ..}, ClientMessage::ChangeNickname{nickname}) => { + (&mut ClientState::LoggedIn { username, .. }, ClientMessage::ChangeAuth { auth }) => match self.server.set_user_auth(username, auth).await { + Ok(()) => ServerMessage::ChangeAuthSuccess, + Err(err) => ServerMessage::ChangeAuthFailure { reason: err.to_string() }, + }, + (&mut ClientState::LoggedIn { username, .. }, ClientMessage::ChangeNickname { nickname }) => { match self.server.set_user_nickname(username, &nickname).await { Ok(()) => ServerMessage::ChangeNicknameSuccess, - Err(err) => ServerMessage::ChangeNicknameFailure{reason: err.to_string()}, + Err(err) => ServerMessage::ChangeNicknameFailure { reason: err.to_string() }, } } - (_, _) => { - ServerMessage::ProtocolError{reason: "Protocol error".to_string() } - } + (_, _) => ServerMessage::ProtocolError { reason: "Protocol error".to_string() }, } } } diff --git a/src/dealer.rs b/src/dealer.rs index 12e1bab..c7a8b62 100644 --- a/src/dealer.rs +++ b/src/dealer.rs @@ -4,7 +4,7 @@ use futures::{channel::mpsc::Receiver, StreamExt}; use redis::{ErrorKind, RedisError, RedisResult}; use crate::client::ClientInterest; -use crate::game::{Game, DealerAction, ValidatedUserAction}; +use crate::game::{DealerAction, Game, ValidatedUserAction}; use crate::server::{ActionStatus, ServerState}; use crate::util::dedup::DedupReadyExt; @@ -26,13 +26,13 @@ enum Termination { impl Dealer { pub async fn new(mut server: ServerState, id: i64) -> RedisResult { let mut interests = HashSet::new(); - interests.insert(ClientInterest::Game{id}); - interests.insert(ClientInterest::Timeout{id}); + interests.insert(ClientInterest::Game { id }); + interests.insert(ClientInterest::Timeout { id }); server.register_interests(interests).await; let summary = server.game_summary(id).await?; let seed = server.game_seed(id).await?; let game = Game::new(summary, seed); - let mut dealer = Dealer{server, dealer: DealerState{game}}; + let mut dealer = Dealer { server, dealer: DealerState { game } }; dealer.retrieve_updates().await?; Ok(dealer) } @@ -94,7 +94,7 @@ impl Dealer { ActionStatus::Committed => match game.take_action(action) { Ok(()) => return Ok(ActionStatus::Committed), Err(err) => return Err(RedisError::from((ErrorKind::ClientError, "Invalid action", err.to_string()))), - } + }, ActionStatus::Interrupted => { debug!("Action {:?} was interrupted", action); Ok(ActionStatus::Interrupted) diff --git a/src/game/action.rs b/src/game/action.rs index 15c99c2..85fac7b 100644 --- a/src/game/action.rs +++ b/src/game/action.rs @@ -62,7 +62,7 @@ pub enum Action { impl Action { pub fn anonymise(&self) -> Self { match self { - Action::ReceiveCard{..} => Action::ReceiveCard{card: None}, + Action::ReceiveCard { .. } => Action::ReceiveCard { card: None }, action => action.clone(), } } diff --git a/src/game/chatroom.rs b/src/game/chatroom.rs index 6cd8399..02d7467 100644 --- a/src/game/chatroom.rs +++ b/src/game/chatroom.rs @@ -1,11 +1,11 @@ use std::collections::HashSet; -use crate::username::{DEALER, Username}; use crate::game::{Action, ActionError, DealerAction}; +use crate::username::{Username, DEALER}; use crate::util::timestamp::Timestamp; -use super::{Game, UserAction}; use super::ValidatedUserAction; +use super::{Game, UserAction}; #[derive(Debug, Clone)] enum ChatroomAction { @@ -36,12 +36,7 @@ pub struct Chatroom { impl Chatroom { pub fn new(id: i64, settings: ChatroomSettings) -> Self { - Chatroom { - id, - settings, - messages: Vec::new(), - users: HashSet::new(), - } + Chatroom { id, settings, messages: Vec::new(), users: HashSet::new() } } } @@ -60,29 +55,29 @@ impl Game for Chatroom { fn validate_action(&self, action: UserAction) -> Result { match action.action { - Action::Join{..} if !self.users.contains(&action.username) => Ok(ValidatedUserAction(action)), - Action::Join{..} => Err(ActionError::AlreadyJoined), - Action::Message{..} if self.users.contains(&action.username) => Ok(ValidatedUserAction(action)), - Action::Message{..} => Err(ActionError::NotAuthorised), + Action::Join { .. } if !self.users.contains(&action.username) => Ok(ValidatedUserAction(action)), + Action::Join { .. } => Err(ActionError::AlreadyJoined), + Action::Message { .. } if self.users.contains(&action.username) => Ok(ValidatedUserAction(action)), + Action::Message { .. } => Err(ActionError::NotAuthorised), Action::Leave if self.users.contains(&action.username) => Ok(ValidatedUserAction(action)), Action::Leave => Err(ActionError::NotAuthorised), _ => Err(ActionError::InvalidActionForGameType), } } - fn take_action(&mut self, ValidatedUserAction(UserAction{timestamp, username, action}): ValidatedUserAction) -> Result<(), ActionError> { + fn take_action(&mut self, ValidatedUserAction(UserAction { timestamp, username, action }): ValidatedUserAction) -> Result<(), ActionError> { match action { - Action::Join{..} => { - self.messages.push(ChatroomUserAction{timestamp, username, action: ChatroomAction::Join}); + Action::Join { .. } => { + self.messages.push(ChatroomUserAction { timestamp, username, action: ChatroomAction::Join }); self.users.insert(username); Ok(()) } - Action::Message{message} => { - self.messages.push(ChatroomUserAction{timestamp, username, action: ChatroomAction::Message(message)}); + Action::Message { message } => { + self.messages.push(ChatroomUserAction { timestamp, username, action: ChatroomAction::Message(message) }); Ok(()) } Action::Leave => { - self.messages.push(ChatroomUserAction{timestamp, username, action: ChatroomAction::Leave}); + self.messages.push(ChatroomUserAction { timestamp, username, action: ChatroomAction::Leave }); self.users.remove(&username); Ok(()) } @@ -92,13 +87,11 @@ impl Game for Chatroom { fn next_dealer_action(&self, timestamp: Timestamp) -> DealerAction { match self.messages.len() { - n if n % 10 == 0 => DealerAction::TakeAction( - ValidatedUserAction(UserAction{ - timestamp, - username: DEALER, - action: Action::Message{message: format!("{} messages posted so far", n)} - }) - ), + 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 e7088b7..a115677 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -11,12 +11,12 @@ use crate::username::Username; use crate::util::timestamp::Timestamp; use self::chatroom::{Chatroom, ChatroomSettings}; -use self::whist::{KnockOutWhist, KnockOutWhistSettings}; use self::poker::{TexasHoldEm, TexasHoldEmSettings}; +use self::whist::{KnockOutWhist, KnockOutWhistSettings}; pub use self::action::{Action, ActionError, DealerAction, UserAction, ValidatedUserAction}; -pub trait Game : Debug + CloneBoxGame + Send + Sync { +pub trait Game: Debug + CloneBoxGame + Send + Sync { fn id(&self) -> i64; fn players(&self) -> HashSet; fn actions_len(&self) -> usize; @@ -42,7 +42,7 @@ impl Clone for Box { } impl dyn Game { - pub fn new(GameSummary{id, settings}: GameSummary, seed: Seed) -> Box { + pub fn new(GameSummary { id, settings }: GameSummary, seed: Seed) -> Box { match settings { GameSettings::Chatroom(settings) => Box::new(Chatroom::new(id, settings)), GameSettings::KnockOutWhist(settings) => Box::new(KnockOutWhist::new(id, settings, seed)), @@ -68,10 +68,7 @@ pub struct GameList { impl GameList { pub fn new(filter: String) -> Self { - Self { - filter, - games: Vec::new(), - } + Self { filter, games: Vec::new() } } pub fn games_len(&self) -> usize { @@ -91,7 +88,7 @@ pub struct GameSummary { impl GameSummary { pub fn new(id: i64, settings: GameSettings) -> Self { - Self{id, settings} + Self { id, settings } } pub fn id(&self) -> i64 { diff --git a/src/game/poker/classify.rs b/src/game/poker/classify.rs index 99124bc..a75d029 100644 --- a/src/game/poker/classify.rs +++ b/src/game/poker/classify.rs @@ -1,38 +1,40 @@ use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; -use crate::card::*; use crate::card::Rank::*; use crate::card::Suit::*; +use crate::card::*; use self::Hand::*; #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] pub enum Hand { - HighCard{kickers: (Rank, Rank, Rank, Rank, Rank)}, - Pair{pair: Rank, kickers: (Rank, Rank, Rank)}, - TwoPair{pairs: (Rank, Rank), kicker: Rank}, - ThreeOfAKind{trips: Rank, kickers: (Rank, Rank)}, - Straight{high_card: Rank}, - Flush{flush: (Rank, Rank, Rank, Rank, Rank)}, - FullHouse{trips: Rank, pair: Rank}, - FourOfAKind{quads: Rank, kicker: Rank}, - StraightFlush{high_card: Rank}, + HighCard { kickers: (Rank, Rank, Rank, Rank, Rank) }, + Pair { pair: Rank, kickers: (Rank, Rank, Rank) }, + TwoPair { pairs: (Rank, Rank), kicker: Rank }, + ThreeOfAKind { trips: Rank, kickers: (Rank, Rank) }, + Straight { high_card: Rank }, + Flush { flush: (Rank, Rank, Rank, Rank, Rank) }, + FullHouse { trips: Rank, pair: Rank }, + FourOfAKind { quads: Rank, kicker: Rank }, + StraightFlush { high_card: Rank }, RoyalFlush, } impl Display for Hand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - HighCard{kickers: (kicker1, kicker2, kicker3, kicker4, kicker5)} => write!(f, "High Card {}, {}{}{}{} Kickers", kicker1, kicker2, kicker3, kicker4, kicker5), - Pair{pair, kickers: (kicker1, kicker2, kicker3)} => write!(f, "Pair of {}s, {}{}{} Kickers", pair, kicker1, kicker2, kicker3), - TwoPair{pairs: (pair1, pair2), kicker} => write!(f, "Two Pair, {}s & {}s, {} Kicker", pair1, pair2, kicker), - ThreeOfAKind{trips, kickers: (kicker1, kicker2)} => write!(f, "Three of a Kind, {}s, {}{} Kickers", trips, kicker1, kicker2), - Straight{high_card} => write!(f, "Straight, {} High", high_card), - Flush{flush: (rank1, rank2, rank3, rank4, rank5)} => write!(f, "Flush, {}{}{}{}{}", rank1, rank2, rank3, rank4, rank5), - FullHouse{trips, pair} => write!(f, "Full House, {}s full of {}s", trips, pair), - FourOfAKind{quads, kicker} => write!(f, "Four of a Kind, {}s, {} Kicker", quads, kicker), - StraightFlush{high_card} => write!(f, "Straight Flush, {} High", high_card), + HighCard { kickers: (kicker1, kicker2, kicker3, kicker4, kicker5) } => { + write!(f, "High Card {}, {}{}{}{} Kickers", kicker1, kicker2, kicker3, kicker4, kicker5) + } + Pair { pair, kickers: (kicker1, kicker2, kicker3) } => write!(f, "Pair of {}s, {}{}{} Kickers", pair, kicker1, kicker2, kicker3), + TwoPair { pairs: (pair1, pair2), kicker } => write!(f, "Two Pair, {}s & {}s, {} Kicker", pair1, pair2, kicker), + ThreeOfAKind { trips, kickers: (kicker1, kicker2) } => write!(f, "Three of a Kind, {}s, {}{} Kickers", trips, kicker1, kicker2), + Straight { high_card } => write!(f, "Straight, {} High", high_card), + Flush { flush: (rank1, rank2, rank3, rank4, rank5) } => write!(f, "Flush, {}{}{}{}{}", rank1, rank2, rank3, rank4, rank5), + FullHouse { trips, pair } => write!(f, "Full House, {}s full of {}s", trips, pair), + FourOfAKind { quads, kicker } => write!(f, "Four of a Kind, {}s, {} Kicker", quads, kicker), + StraightFlush { high_card } => write!(f, "Straight Flush, {} High", high_card), RoyalFlush => write!(f, "Royal Flush"), } } @@ -45,395 +47,391 @@ pub fn rank_7_card_hand(mut cards: [Card; 7]) -> Hand { let suits = [cards[0].suit, cards[1].suit, cards[2].suit, cards[3].suit, cards[4].suit, cards[5].suit, cards[6].suit]; match suits { - [Spades, Spades, Spades, Spades, Spades, Spades, Spades] | - [Hearts, Hearts, Hearts, Hearts, Hearts, Hearts, Hearts] | - [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] | - [Clubs, Clubs, Clubs, Clubs, Clubs, Clubs, Clubs] => - match ranks { - [Ace, King, Queen, Jack, Ten, _, _] => RoyalFlush, - [King, Queen, Jack, Ten, Nine, _, _] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight, _, _] => StraightFlush{high_card: Queen}, - [_, Queen, Jack, Ten, Nine, Eight, _] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven, _, _] => StraightFlush{high_card: Jack}, - [_, Jack, Ten, Nine, Eight, Seven, _] => StraightFlush{high_card: Jack}, - [_, _, Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six, _, _] => StraightFlush{high_card: Ten}, - [_, Ten, Nine, Eight, Seven, Six, _] => StraightFlush{high_card: Ten}, - [_, _, Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five, _, _] => StraightFlush{high_card: Nine}, - [_, Nine, Eight, Seven, Six, Five, _] => StraightFlush{high_card: Nine}, - [_, _, Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four, _, _] => StraightFlush{high_card: Eight}, - [_, Eight, Seven, Six, Five, Four, _] => StraightFlush{high_card: Eight}, - [_, _, Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three, _, _] => StraightFlush{high_card: Seven}, - [_, Seven, Six, Five, Four, Three, _] => StraightFlush{high_card: Seven}, - [_, _, Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two, _, _] => StraightFlush{high_card: Six}, - [_, Six, Five, Four, Three, Two, _] => StraightFlush{high_card: Six}, - [_, _, Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, _, _, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5, _, _] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [Spades, Spades, Spades, Spades, Spades, Spades, _] | - [Hearts, Hearts, Hearts, Hearts, Hearts, Hearts, _] | - [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _] => + [Spades, Spades, Spades, Spades, Spades, Spades, Spades] + | [Hearts, Hearts, Hearts, Hearts, Hearts, Hearts, Hearts] + | [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] + | [Clubs, Clubs, Clubs, Clubs, Clubs, Clubs, Clubs] => match ranks { + [Ace, King, Queen, Jack, Ten, _, _] => RoyalFlush, + [King, Queen, Jack, Ten, Nine, _, _] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight, _, _] => StraightFlush { high_card: Queen }, + [_, Queen, Jack, Ten, Nine, Eight, _] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven, _, _] => StraightFlush { high_card: Jack }, + [_, Jack, Ten, Nine, Eight, Seven, _] => StraightFlush { high_card: Jack }, + [_, _, Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six, _, _] => StraightFlush { high_card: Ten }, + [_, Ten, Nine, Eight, Seven, Six, _] => StraightFlush { high_card: Ten }, + [_, _, Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five, _, _] => StraightFlush { high_card: Nine }, + [_, Nine, Eight, Seven, Six, Five, _] => StraightFlush { high_card: Nine }, + [_, _, Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four, _, _] => StraightFlush { high_card: Eight }, + [_, Eight, Seven, Six, Five, Four, _] => StraightFlush { high_card: Eight }, + [_, _, Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three, _, _] => StraightFlush { high_card: Seven }, + [_, Seven, Six, Five, Four, Three, _] => StraightFlush { high_card: Seven }, + [_, _, Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two, _, _] => StraightFlush { high_card: Six }, + [_, Six, Five, Four, Three, Two, _] => StraightFlush { high_card: Six }, + [_, _, Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, _, _, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5, _, _] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, + }, + [Spades, Spades, Spades, Spades, Spades, Spades, _] + | [Hearts, Hearts, Hearts, Hearts, Hearts, Hearts, _] + | [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _] => { match [cards[0].rank, cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank] { [Ace, King, Queen, Jack, Ten, _] => RoyalFlush, - [King, Queen, Jack, Ten, Nine, _] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight, _] => StraightFlush{high_card: Queen}, - [_, Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven, _] => StraightFlush{high_card: Jack}, - [_, Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six, _] => StraightFlush{high_card: Ten}, - [_, Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five, _] => StraightFlush{high_card: Nine}, - [_, Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four, _] => StraightFlush{high_card: Eight}, - [_, Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three, _] => StraightFlush{high_card: Seven}, - [_, Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two, _] => StraightFlush{high_card: Six}, - [_, Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, _, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5, _] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [_, Hearts, Hearts, Hearts, Hearts, Hearts, Hearts] | - [_, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] | - [_, Clubs, Clubs, Clubs, Clubs, Clubs, Clubs] => - match [cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank, cards[6].rank] { - [Ace, King, Queen, Jack, Ten, _] => RoyalFlush, - [King, Queen, Jack, Ten, Nine, _] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight, _] => StraightFlush{high_card: Queen}, - [_, Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven, _] => StraightFlush{high_card: Jack}, - [_, Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six, _] => StraightFlush{high_card: Ten}, - [_, Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five, _] => StraightFlush{high_card: Nine}, - [_, Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four, _] => StraightFlush{high_card: Eight}, - [_, Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three, _] => StraightFlush{high_card: Seven}, - [_, Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two, _] => StraightFlush{high_card: Six}, - [_, Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, _, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5, _] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, + [King, Queen, Jack, Ten, Nine, _] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight, _] => StraightFlush { high_card: Queen }, + [_, Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven, _] => StraightFlush { high_card: Jack }, + [_, Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six, _] => StraightFlush { high_card: Ten }, + [_, Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five, _] => StraightFlush { high_card: Nine }, + [_, Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four, _] => StraightFlush { high_card: Eight }, + [_, Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three, _] => StraightFlush { high_card: Seven }, + [_, Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two, _] => StraightFlush { high_card: Six }, + [_, Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, _, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5, _] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, } - [Spades, Spades, Spades, Spades, Spades, _, _] | - [Hearts, Hearts, Hearts, Hearts, Hearts, _, _] | - [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _, _] => - match [cards[0].rank, cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank] { - [Ace, King, Queen, Jack, Ten] => RoyalFlush, - [King, Queen, Jack, Ten, Nine] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [_, Hearts, Hearts, Hearts, Hearts, Hearts, _] | - [_, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _] => + } + [_, Hearts, Hearts, Hearts, Hearts, Hearts, Hearts] + | [_, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] + | [_, Clubs, Clubs, Clubs, Clubs, Clubs, Clubs] => match [cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank, cards[6].rank] { + [Ace, King, Queen, Jack, Ten, _] => RoyalFlush, + [King, Queen, Jack, Ten, Nine, _] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight, _] => StraightFlush { high_card: Queen }, + [_, Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven, _] => StraightFlush { high_card: Jack }, + [_, Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six, _] => StraightFlush { high_card: Ten }, + [_, Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five, _] => StraightFlush { high_card: Nine }, + [_, Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four, _] => StraightFlush { high_card: Eight }, + [_, Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three, _] => StraightFlush { high_card: Seven }, + [_, Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two, _] => StraightFlush { high_card: Six }, + [_, Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, _, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5, _] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, + }, + [Spades, Spades, Spades, Spades, Spades, _, _] + | [Hearts, Hearts, Hearts, Hearts, Hearts, _, _] + | [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _, _] => match [cards[0].rank, cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank] { + [Ace, King, Queen, Jack, Ten] => RoyalFlush, + [King, Queen, Jack, Ten, Nine] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, + }, + [_, Hearts, Hearts, Hearts, Hearts, Hearts, _] | [_, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds, _] => { match [cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank] { [Ace, King, Queen, Jack, Ten] => RoyalFlush, - [King, Queen, Jack, Ten, Nine] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [_, _, Hearts, Hearts, Hearts, Hearts, Hearts] | - [_, _, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] | - [_, _, Clubs, Clubs, Clubs, Clubs, Clubs] => - match [cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank, cards[6].rank] { - [Ace, King, Queen, Jack, Ten] => RoyalFlush, - [King, Queen, Jack, Ten, Nine] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [_, _, _, _, _, _, _] => - match ranks { - [quad1, quad2, quad3, quad4, kicker, _, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - [kicker, quad1, quad2, quad3, quad4, _, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - [kicker, _, quad1, quad2, quad3, quad4, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - [kicker, _, _, quad1, quad2, quad3, quad4] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - - [trip1, trip2, trip3, pair1, pair2, _, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [trip1, trip2, trip3, _, pair1, pair2, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [trip1, trip2, trip3, _, _, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, trip1, trip2, trip3, pair1, pair2, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, trip1, trip2, trip3, _, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [pair1, pair2, trip1, trip2, trip3, _, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [pair1, pair2, _, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [pair1, pair2, _, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, _, trip1, trip2, trip3, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, pair1, pair2, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, pair1, pair2, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [_, _, pair1, pair2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - - [Ace, King, Queen, Jack, Ten, _, _] => Straight{high_card: Ace}, - [Ace, King, Queen, Jack, _, Ten, _] => Straight{high_card: Ace}, - [Ace, King, Queen, Jack, _, _, Ten] => Straight{high_card: Ace}, - [Ace, King, Queen, _, Jack, Ten, _] => Straight{high_card: Ace}, - [Ace, King, Queen, _, Jack, _, Ten] => Straight{high_card: Ace}, - [Ace, King, Queen, _, _, Jack, Ten] => Straight{high_card: Ace}, - [Ace, King, _, Queen, Jack, Ten, _] => Straight{high_card: Ace}, - [Ace, King, _, Queen, Jack, _, Ten] => Straight{high_card: Ace}, - [Ace, King, _, Queen, _, Jack, Ten] => Straight{high_card: Ace}, - [Ace, King, _, _, Queen, Jack, Ten] => Straight{high_card: Ace}, - [Ace, _, King, Queen, Jack, Ten, _] => Straight{high_card: Ace}, - [Ace, _, King, Queen, Jack, _, Ten] => Straight{high_card: Ace}, - [Ace, _, King, Queen, _, Jack, Ten] => Straight{high_card: Ace}, - [Ace, _, King, _, Queen, Jack, Ten] => Straight{high_card: Ace}, - [Ace, _, _, King, Queen, Jack, Ten] => Straight{high_card: Ace}, - [_, Ace, King, Queen, Jack, Ten, _] => Straight{high_card: Ace}, - [_, Ace, King, Queen, Jack, _, Ten] => Straight{high_card: Ace}, - [_, Ace, King, Queen, _, Jack, Ten] => Straight{high_card: Ace}, - [_, Ace, King, _, Queen, Jack, Ten] => Straight{high_card: Ace}, - [_, Ace, _, King, Queen, Jack, Ten] => Straight{high_card: Ace}, - [_, _, Ace, King, Queen, Jack, Ten] => Straight{high_card: Ace}, - - [King, Queen, Jack, Ten, Nine, _, _] => Straight{high_card: King}, - [King, Queen, Jack, Ten, _, Nine, _] => Straight{high_card: King}, - [King, Queen, Jack, Ten, _, _, Nine] => Straight{high_card: King}, - [King, Queen, Jack, _, Ten, Nine, _] => Straight{high_card: King}, - [King, Queen, Jack, _, Ten, _, Nine] => Straight{high_card: King}, - [King, Queen, Jack, _, _, Ten, Nine] => Straight{high_card: King}, - [King, Queen, _, Jack, Ten, Nine, _] => Straight{high_card: King}, - [King, Queen, _, Jack, Ten, _, Nine] => Straight{high_card: King}, - [King, Queen, _, Jack, _, Ten, Nine] => Straight{high_card: King}, - [King, Queen, _, _, Jack, Ten, Nine] => Straight{high_card: King}, - [King, _, Queen, Jack, Ten, Nine, _] => Straight{high_card: King}, - [King, _, Queen, Jack, Ten, _, Nine] => Straight{high_card: King}, - [King, _, Queen, Jack, _, Ten, Nine] => Straight{high_card: King}, - [King, _, Queen, _, Jack, Ten, Nine] => Straight{high_card: King}, - [King, _, _, Queen, Jack, Ten, Nine] => Straight{high_card: King}, - [_, King, Queen, Jack, Ten, Nine, _] => Straight{high_card: King}, - [_, King, Queen, Jack, Ten, _, Nine] => Straight{high_card: King}, - [_, King, Queen, Jack, _, Ten, Nine] => Straight{high_card: King}, - [_, King, Queen, _, Jack, Ten, Nine] => Straight{high_card: King}, - [_, King, _, Queen, Jack, Ten, Nine] => Straight{high_card: King}, - [_, _, King, Queen, Jack, Ten, Nine] => Straight{high_card: King}, - - [Queen, Jack, Ten, Nine, Eight, _, _] => Straight{high_card: Queen}, - [Queen, Jack, Ten, Nine, _, Eight, _] => Straight{high_card: Queen}, - [Queen, Jack, Ten, Nine, _, _, Eight] => Straight{high_card: Queen}, - [Queen, Jack, Ten, _, Nine, Eight, _] => Straight{high_card: Queen}, - [Queen, Jack, Ten, _, Nine, _, Eight] => Straight{high_card: Queen}, - [Queen, Jack, Ten, _, _, Nine, Eight] => Straight{high_card: Queen}, - [Queen, Jack, _, Ten, Nine, Eight, _] => Straight{high_card: Queen}, - [Queen, Jack, _, Ten, Nine, _, Eight] => Straight{high_card: Queen}, - [Queen, Jack, _, Ten, _, Nine, Eight] => Straight{high_card: Queen}, - [Queen, Jack, _, _, Ten, Nine, Eight] => Straight{high_card: Queen}, - [Queen, _, Jack, Ten, Nine, Eight, _] => Straight{high_card: Queen}, - [Queen, _, Jack, Ten, Nine, _, Eight] => Straight{high_card: Queen}, - [Queen, _, Jack, Ten, _, Nine, Eight] => Straight{high_card: Queen}, - [Queen, _, Jack, _, Ten, Nine, Eight] => Straight{high_card: Queen}, - [Queen, _, _, Jack, Ten, Nine, Eight] => Straight{high_card: Queen}, - [_, Queen, Jack, Ten, Nine, Eight, _] => Straight{high_card: Queen}, - [_, Queen, Jack, Ten, Nine, _, Eight] => Straight{high_card: Queen}, - [_, Queen, Jack, Ten, _, Nine, Eight] => Straight{high_card: Queen}, - [_, Queen, Jack, _, Ten, Nine, Eight] => Straight{high_card: Queen}, - [_, Queen, _, Jack, Ten, Nine, Eight] => Straight{high_card: Queen}, - [_, _, Queen, Jack, Ten, Nine, Eight] => Straight{high_card: Queen}, - - [Jack, Ten, Nine, Eight, Seven, _, _] => Straight{high_card: Jack}, - [Jack, Ten, Nine, Eight, _, Seven, _] => Straight{high_card: Jack}, - [Jack, Ten, Nine, Eight, _, _, Seven] => Straight{high_card: Jack}, - [Jack, Ten, Nine, _, Eight, Seven, _] => Straight{high_card: Jack}, - [Jack, Ten, Nine, _, Eight, _, Seven] => Straight{high_card: Jack}, - [Jack, Ten, Nine, _, _, Eight, Seven] => Straight{high_card: Jack}, - [Jack, Ten, _, Nine, Eight, Seven, _] => Straight{high_card: Jack}, - [Jack, Ten, _, Nine, Eight, _, Seven] => Straight{high_card: Jack}, - [Jack, Ten, _, Nine, _, Eight, Seven] => Straight{high_card: Jack}, - [Jack, Ten, _, _, Nine, Eight, Seven] => Straight{high_card: Jack}, - [Jack, _, Ten, Nine, Eight, Seven, _] => Straight{high_card: Jack}, - [Jack, _, Ten, Nine, Eight, _, Seven] => Straight{high_card: Jack}, - [Jack, _, Ten, Nine, _, Eight, Seven] => Straight{high_card: Jack}, - [Jack, _, Ten, _, Nine, Eight, Seven] => Straight{high_card: Jack}, - [Jack, _, _, Ten, Nine, Eight, Seven] => Straight{high_card: Jack}, - [_, Jack, Ten, Nine, Eight, Seven, _] => Straight{high_card: Jack}, - [_, Jack, Ten, Nine, Eight, _, Seven] => Straight{high_card: Jack}, - [_, Jack, Ten, Nine, _, Eight, Seven] => Straight{high_card: Jack}, - [_, Jack, Ten, _, Nine, Eight, Seven] => Straight{high_card: Jack}, - [_, Jack, _, Ten, Nine, Eight, Seven] => Straight{high_card: Jack}, - [_, _, Jack, Ten, Nine, Eight, Seven] => Straight{high_card: Jack}, - - [Ten, Nine, Eight, Seven, Six, _, _] => Straight{high_card: Ten}, - [Ten, Nine, Eight, Seven, _, Six, _] => Straight{high_card: Ten}, - [Ten, Nine, Eight, Seven, _, _, Six] => Straight{high_card: Ten}, - [Ten, Nine, Eight, _, Seven, Six, _] => Straight{high_card: Ten}, - [Ten, Nine, Eight, _, Seven, _, Six] => Straight{high_card: Ten}, - [Ten, Nine, Eight, _, _, Seven, Six] => Straight{high_card: Ten}, - [Ten, Nine, _, Eight, Seven, Six, _] => Straight{high_card: Ten}, - [Ten, Nine, _, Eight, Seven, _, Six] => Straight{high_card: Ten}, - [Ten, Nine, _, Eight, _, Seven, Six] => Straight{high_card: Ten}, - [Ten, Nine, _, _, Eight, Seven, Six] => Straight{high_card: Ten}, - [Ten, _, Nine, Eight, Seven, Six, _] => Straight{high_card: Ten}, - [Ten, _, Nine, Eight, Seven, _, Six] => Straight{high_card: Ten}, - [Ten, _, Nine, Eight, _, Seven, Six] => Straight{high_card: Ten}, - [Ten, _, Nine, _, Eight, Seven, Six] => Straight{high_card: Ten}, - [Ten, _, _, Nine, Eight, Seven, Six] => Straight{high_card: Ten}, - [_, Ten, Nine, Eight, Seven, Six, _] => Straight{high_card: Ten}, - [_, Ten, Nine, Eight, Seven, _, Six] => Straight{high_card: Ten}, - [_, Ten, Nine, Eight, _, Seven, Six] => Straight{high_card: Ten}, - [_, Ten, Nine, _, Eight, Seven, Six] => Straight{high_card: Ten}, - [_, Ten, _, Nine, Eight, Seven, Six] => Straight{high_card: Ten}, - [_, _, Ten, Nine, Eight, Seven, Six] => Straight{high_card: Ten}, - - [Nine, Eight, Seven, Six, Five, _, _] => Straight{high_card: Nine}, - [Nine, Eight, Seven, Six, _, Five, _] => Straight{high_card: Nine}, - [Nine, Eight, Seven, Six, _, _, Five] => Straight{high_card: Nine}, - [Nine, Eight, Seven, _, Six, Five, _] => Straight{high_card: Nine}, - [Nine, Eight, Seven, _, Six, _, Five] => Straight{high_card: Nine}, - [Nine, Eight, Seven, _, _, Six, Five] => Straight{high_card: Nine}, - [Nine, Eight, _, Seven, Six, Five, _] => Straight{high_card: Nine}, - [Nine, Eight, _, Seven, Six, _, Five] => Straight{high_card: Nine}, - [Nine, Eight, _, Seven, _, Six, Five] => Straight{high_card: Nine}, - [Nine, Eight, _, _, Seven, Six, Five] => Straight{high_card: Nine}, - [Nine, _, Eight, Seven, Six, Five, _] => Straight{high_card: Nine}, - [Nine, _, Eight, Seven, Six, _, Five] => Straight{high_card: Nine}, - [Nine, _, Eight, Seven, _, Six, Five] => Straight{high_card: Nine}, - [Nine, _, Eight, _, Seven, Six, Five] => Straight{high_card: Nine}, - [Nine, _, _, Eight, Seven, Six, Five] => Straight{high_card: Nine}, - [_, Nine, Eight, Seven, Six, Five, _] => Straight{high_card: Nine}, - [_, Nine, Eight, Seven, Six, _, Five] => Straight{high_card: Nine}, - [_, Nine, Eight, Seven, _, Six, Five] => Straight{high_card: Nine}, - [_, Nine, Eight, _, Seven, Six, Five] => Straight{high_card: Nine}, - [_, Nine, _, Eight, Seven, Six, Five] => Straight{high_card: Nine}, - [_, _, Nine, Eight, Seven, Six, Five] => Straight{high_card: Nine}, - - [Eight, Seven, Six, Five, Four, _, _] => Straight{high_card: Eight}, - [Eight, Seven, Six, Five, _, Four, _] => Straight{high_card: Eight}, - [Eight, Seven, Six, Five, _, _, Four] => Straight{high_card: Eight}, - [Eight, Seven, Six, _, Five, Four, _] => Straight{high_card: Eight}, - [Eight, Seven, Six, _, Five, _, Four] => Straight{high_card: Eight}, - [Eight, Seven, Six, _, _, Five, Four] => Straight{high_card: Eight}, - [Eight, Seven, _, Six, Five, Four, _] => Straight{high_card: Eight}, - [Eight, Seven, _, Six, Five, _, Four] => Straight{high_card: Eight}, - [Eight, Seven, _, Six, _, Five, Four] => Straight{high_card: Eight}, - [Eight, Seven, _, _, Six, Five, Four] => Straight{high_card: Eight}, - [Eight, _, Seven, Six, Five, Four, _] => Straight{high_card: Eight}, - [Eight, _, Seven, Six, Five, _, Four] => Straight{high_card: Eight}, - [Eight, _, Seven, Six, _, Five, Four] => Straight{high_card: Eight}, - [Eight, _, Seven, _, Six, Five, Four] => Straight{high_card: Eight}, - [Eight, _, _, Seven, Six, Five, Four] => Straight{high_card: Eight}, - [_, Eight, Seven, Six, Five, Four, _] => Straight{high_card: Eight}, - [_, Eight, Seven, Six, Five, _, Four] => Straight{high_card: Eight}, - [_, Eight, Seven, Six, _, Five, Four] => Straight{high_card: Eight}, - [_, Eight, Seven, _, Six, Five, Four] => Straight{high_card: Eight}, - [_, Eight, _, Seven, Six, Five, Four] => Straight{high_card: Eight}, - [_, _, Eight, Seven, Six, Five, Four] => Straight{high_card: Eight}, - - [Seven, Six, Five, Four, Three, _, _] => Straight{high_card: Seven}, - [Seven, Six, Five, Four, _, Three, _] => Straight{high_card: Seven}, - [Seven, Six, Five, Four, _, _, Three] => Straight{high_card: Seven}, - [Seven, Six, Five, _, Four, Three, _] => Straight{high_card: Seven}, - [Seven, Six, Five, _, Four, _, Three] => Straight{high_card: Seven}, - [Seven, Six, Five, _, _, Four, Three] => Straight{high_card: Seven}, - [Seven, Six, _, Five, Four, Three, _] => Straight{high_card: Seven}, - [Seven, Six, _, Five, Four, _, Three] => Straight{high_card: Seven}, - [Seven, Six, _, Five, _, Four, Three] => Straight{high_card: Seven}, - [Seven, Six, _, _, Five, Four, Three] => Straight{high_card: Seven}, - [Seven, _, Six, Five, Four, Three, _] => Straight{high_card: Seven}, - [Seven, _, Six, Five, Four, _, Three] => Straight{high_card: Seven}, - [Seven, _, Six, Five, _, Four, Three] => Straight{high_card: Seven}, - [Seven, _, Six, _, Five, Four, Three] => Straight{high_card: Seven}, - [Seven, _, _, Six, Five, Four, Three] => Straight{high_card: Seven}, - [_, Seven, Six, Five, Four, Three, _] => Straight{high_card: Seven}, - [_, Seven, Six, Five, Four, _, Three] => Straight{high_card: Seven}, - [_, Seven, Six, Five, _, Four, Three] => Straight{high_card: Seven}, - [_, Seven, Six, _, Five, Four, Three] => Straight{high_card: Seven}, - [_, Seven, _, Six, Five, Four, Three] => Straight{high_card: Seven}, - [_, _, Seven, Six, Five, Four, Three] => Straight{high_card: Seven}, - - [Six, Five, Four, Three, Two, _, _] => Straight{high_card: Six}, - [Six, Five, Four, Three, _, Two, _] => Straight{high_card: Six}, - [Six, Five, Four, Three, _, _, Two] => Straight{high_card: Six}, - [Six, Five, Four, _, Three, Two, _] => Straight{high_card: Six}, - [Six, Five, Four, _, Three, _, Two] => Straight{high_card: Six}, - [Six, Five, Four, _, _, Three, Two] => Straight{high_card: Six}, - [Six, Five, _, Four, Three, Two, _] => Straight{high_card: Six}, - [Six, Five, _, Four, Three, _, Two] => Straight{high_card: Six}, - [Six, Five, _, Four, _, Three, Two] => Straight{high_card: Six}, - [Six, Five, _, _, Four, Three, Two] => Straight{high_card: Six}, - [Six, _, Five, Four, Three, Two, _] => Straight{high_card: Six}, - [Six, _, Five, Four, Three, _, Two] => Straight{high_card: Six}, - [Six, _, Five, Four, _, Three, Two] => Straight{high_card: Six}, - [Six, _, Five, _, Four, Three, Two] => Straight{high_card: Six}, - [Six, _, _, Five, Four, Three, Two] => Straight{high_card: Six}, - [_, Six, Five, Four, Three, Two, _] => Straight{high_card: Six}, - [_, Six, Five, Four, Three, _, Two] => Straight{high_card: Six}, - [_, Six, Five, Four, _, Three, Two] => Straight{high_card: Six}, - [_, Six, Five, _, Four, Three, Two] => Straight{high_card: Six}, - [_, Six, _, Five, Four, Three, Two] => Straight{high_card: Six}, - [_, _, Six, Five, Four, Three, Two] => Straight{high_card: Six}, - - [Ace, Five, Four, Three, Two, _, _] => Straight{high_card: Five}, - [Ace, Five, Four, Three, _, Two, _] => Straight{high_card: Five}, - [Ace, Five, Four, Three, _, _, Two] => Straight{high_card: Five}, - [Ace, Five, Four, _, Three, Two, _] => Straight{high_card: Five}, - [Ace, Five, Four, _, Three, _, Two] => Straight{high_card: Five}, - [Ace, Five, Four, _, _, Three, Two] => Straight{high_card: Five}, - [Ace, Five, _, Four, Three, Two, _] => Straight{high_card: Five}, - [Ace, Five, _, Four, Three, _, Two] => Straight{high_card: Five}, - [Ace, Five, _, Four, _, Three, Two] => Straight{high_card: Five}, - [Ace, Five, _, _, Four, Three, Two] => Straight{high_card: Five}, - [Ace, _, Five, Four, Three, Two, _] => Straight{high_card: Five}, - [Ace, _, Five, Four, Three, _, Two] => Straight{high_card: Five}, - [Ace, _, Five, Four, _, Three, Two] => Straight{high_card: Five}, - [Ace, _, Five, _, Four, Three, Two] => Straight{high_card: Five}, - [Ace, _, _, Five, Four, Three, Two] => Straight{high_card: Five}, - [_, Ace, Five, Four, Three, Two, _] => Straight{high_card: Five}, - [_, Ace, Five, Four, Three, _, Two] => Straight{high_card: Five}, - [_, Ace, Five, Four, _, Three, Two] => Straight{high_card: Five}, - [_, Ace, Five, _, Four, Three, Two] => Straight{high_card: Five}, - [_, Ace, _, Five, Four, Three, Two] => Straight{high_card: Five}, - [_, _, Ace, Five, Four, Three, Two] => Straight{high_card: Five}, - - [trip1, trip2, trip3, kicker1, kicker2, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, trip1, trip2, trip3, kicker2, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, kicker2, trip1, trip2, trip3, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, kicker2, _, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, kicker2, _, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - - [pair1, pair2, pair3, pair4, kicker, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [pair1, pair2, kicker, pair3, pair4, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [pair1, pair2, kicker, _, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [pair1, pair2, kicker, _, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, pair1, pair2, pair3, pair4, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, pair1, pair2, _, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, pair1, pair2, _, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, _, pair1, pair2, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, _, pair1, pair2, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, _, _, pair1, pair2, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - - [pair1, pair2, kicker1, kicker2, kicker3, _, _] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, pair1, pair2, kicker2, kicker3, _, _] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, pair1, pair2, kicker3, _, _] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, kicker3, pair1, pair2, _, _] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, kicker3, _, pair1, pair2, _] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, kicker3, _, _, pair1, pair2] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - - [rank1, rank2, rank3, rank4, rank5, _, _] => HighCard{kickers: (rank1, rank2, rank3, rank4, rank5)}, + [King, Queen, Jack, Ten, Nine] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, } + } + [_, _, Hearts, Hearts, Hearts, Hearts, Hearts] + | [_, _, Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] + | [_, _, Clubs, Clubs, Clubs, Clubs, Clubs] => match [cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank, cards[6].rank] { + [Ace, King, Queen, Jack, Ten] => RoyalFlush, + [King, Queen, Jack, Ten, Nine] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, + }, + [_, _, _, _, _, _, _] => match ranks { + [quad1, quad2, quad3, quad4, kicker, _, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + [kicker, quad1, quad2, quad3, quad4, _, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + [kicker, _, quad1, quad2, quad3, quad4, _] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + [kicker, _, _, quad1, quad2, quad3, quad4] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + + [trip1, trip2, trip3, pair1, pair2, _, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [trip1, trip2, trip3, _, pair1, pair2, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [trip1, trip2, trip3, _, _, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, trip1, trip2, trip3, pair1, pair2, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, trip1, trip2, trip3, _, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [pair1, pair2, trip1, trip2, trip3, _, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [pair1, pair2, _, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [pair1, pair2, _, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, _, trip1, trip2, trip3, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, pair1, pair2, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, pair1, pair2, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [_, _, pair1, pair2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + + [Ace, King, Queen, Jack, Ten, _, _] => Straight { high_card: Ace }, + [Ace, King, Queen, Jack, _, Ten, _] => Straight { high_card: Ace }, + [Ace, King, Queen, Jack, _, _, Ten] => Straight { high_card: Ace }, + [Ace, King, Queen, _, Jack, Ten, _] => Straight { high_card: Ace }, + [Ace, King, Queen, _, Jack, _, Ten] => Straight { high_card: Ace }, + [Ace, King, Queen, _, _, Jack, Ten] => Straight { high_card: Ace }, + [Ace, King, _, Queen, Jack, Ten, _] => Straight { high_card: Ace }, + [Ace, King, _, Queen, Jack, _, Ten] => Straight { high_card: Ace }, + [Ace, King, _, Queen, _, Jack, Ten] => Straight { high_card: Ace }, + [Ace, King, _, _, Queen, Jack, Ten] => Straight { high_card: Ace }, + [Ace, _, King, Queen, Jack, Ten, _] => Straight { high_card: Ace }, + [Ace, _, King, Queen, Jack, _, Ten] => Straight { high_card: Ace }, + [Ace, _, King, Queen, _, Jack, Ten] => Straight { high_card: Ace }, + [Ace, _, King, _, Queen, Jack, Ten] => Straight { high_card: Ace }, + [Ace, _, _, King, Queen, Jack, Ten] => Straight { high_card: Ace }, + [_, Ace, King, Queen, Jack, Ten, _] => Straight { high_card: Ace }, + [_, Ace, King, Queen, Jack, _, Ten] => Straight { high_card: Ace }, + [_, Ace, King, Queen, _, Jack, Ten] => Straight { high_card: Ace }, + [_, Ace, King, _, Queen, Jack, Ten] => Straight { high_card: Ace }, + [_, Ace, _, King, Queen, Jack, Ten] => Straight { high_card: Ace }, + [_, _, Ace, King, Queen, Jack, Ten] => Straight { high_card: Ace }, + + [King, Queen, Jack, Ten, Nine, _, _] => Straight { high_card: King }, + [King, Queen, Jack, Ten, _, Nine, _] => Straight { high_card: King }, + [King, Queen, Jack, Ten, _, _, Nine] => Straight { high_card: King }, + [King, Queen, Jack, _, Ten, Nine, _] => Straight { high_card: King }, + [King, Queen, Jack, _, Ten, _, Nine] => Straight { high_card: King }, + [King, Queen, Jack, _, _, Ten, Nine] => Straight { high_card: King }, + [King, Queen, _, Jack, Ten, Nine, _] => Straight { high_card: King }, + [King, Queen, _, Jack, Ten, _, Nine] => Straight { high_card: King }, + [King, Queen, _, Jack, _, Ten, Nine] => Straight { high_card: King }, + [King, Queen, _, _, Jack, Ten, Nine] => Straight { high_card: King }, + [King, _, Queen, Jack, Ten, Nine, _] => Straight { high_card: King }, + [King, _, Queen, Jack, Ten, _, Nine] => Straight { high_card: King }, + [King, _, Queen, Jack, _, Ten, Nine] => Straight { high_card: King }, + [King, _, Queen, _, Jack, Ten, Nine] => Straight { high_card: King }, + [King, _, _, Queen, Jack, Ten, Nine] => Straight { high_card: King }, + [_, King, Queen, Jack, Ten, Nine, _] => Straight { high_card: King }, + [_, King, Queen, Jack, Ten, _, Nine] => Straight { high_card: King }, + [_, King, Queen, Jack, _, Ten, Nine] => Straight { high_card: King }, + [_, King, Queen, _, Jack, Ten, Nine] => Straight { high_card: King }, + [_, King, _, Queen, Jack, Ten, Nine] => Straight { high_card: King }, + [_, _, King, Queen, Jack, Ten, Nine] => Straight { high_card: King }, + + [Queen, Jack, Ten, Nine, Eight, _, _] => Straight { high_card: Queen }, + [Queen, Jack, Ten, Nine, _, Eight, _] => Straight { high_card: Queen }, + [Queen, Jack, Ten, Nine, _, _, Eight] => Straight { high_card: Queen }, + [Queen, Jack, Ten, _, Nine, Eight, _] => Straight { high_card: Queen }, + [Queen, Jack, Ten, _, Nine, _, Eight] => Straight { high_card: Queen }, + [Queen, Jack, Ten, _, _, Nine, Eight] => Straight { high_card: Queen }, + [Queen, Jack, _, Ten, Nine, Eight, _] => Straight { high_card: Queen }, + [Queen, Jack, _, Ten, Nine, _, Eight] => Straight { high_card: Queen }, + [Queen, Jack, _, Ten, _, Nine, Eight] => Straight { high_card: Queen }, + [Queen, Jack, _, _, Ten, Nine, Eight] => Straight { high_card: Queen }, + [Queen, _, Jack, Ten, Nine, Eight, _] => Straight { high_card: Queen }, + [Queen, _, Jack, Ten, Nine, _, Eight] => Straight { high_card: Queen }, + [Queen, _, Jack, Ten, _, Nine, Eight] => Straight { high_card: Queen }, + [Queen, _, Jack, _, Ten, Nine, Eight] => Straight { high_card: Queen }, + [Queen, _, _, Jack, Ten, Nine, Eight] => Straight { high_card: Queen }, + [_, Queen, Jack, Ten, Nine, Eight, _] => Straight { high_card: Queen }, + [_, Queen, Jack, Ten, Nine, _, Eight] => Straight { high_card: Queen }, + [_, Queen, Jack, Ten, _, Nine, Eight] => Straight { high_card: Queen }, + [_, Queen, Jack, _, Ten, Nine, Eight] => Straight { high_card: Queen }, + [_, Queen, _, Jack, Ten, Nine, Eight] => Straight { high_card: Queen }, + [_, _, Queen, Jack, Ten, Nine, Eight] => Straight { high_card: Queen }, + + [Jack, Ten, Nine, Eight, Seven, _, _] => Straight { high_card: Jack }, + [Jack, Ten, Nine, Eight, _, Seven, _] => Straight { high_card: Jack }, + [Jack, Ten, Nine, Eight, _, _, Seven] => Straight { high_card: Jack }, + [Jack, Ten, Nine, _, Eight, Seven, _] => Straight { high_card: Jack }, + [Jack, Ten, Nine, _, Eight, _, Seven] => Straight { high_card: Jack }, + [Jack, Ten, Nine, _, _, Eight, Seven] => Straight { high_card: Jack }, + [Jack, Ten, _, Nine, Eight, Seven, _] => Straight { high_card: Jack }, + [Jack, Ten, _, Nine, Eight, _, Seven] => Straight { high_card: Jack }, + [Jack, Ten, _, Nine, _, Eight, Seven] => Straight { high_card: Jack }, + [Jack, Ten, _, _, Nine, Eight, Seven] => Straight { high_card: Jack }, + [Jack, _, Ten, Nine, Eight, Seven, _] => Straight { high_card: Jack }, + [Jack, _, Ten, Nine, Eight, _, Seven] => Straight { high_card: Jack }, + [Jack, _, Ten, Nine, _, Eight, Seven] => Straight { high_card: Jack }, + [Jack, _, Ten, _, Nine, Eight, Seven] => Straight { high_card: Jack }, + [Jack, _, _, Ten, Nine, Eight, Seven] => Straight { high_card: Jack }, + [_, Jack, Ten, Nine, Eight, Seven, _] => Straight { high_card: Jack }, + [_, Jack, Ten, Nine, Eight, _, Seven] => Straight { high_card: Jack }, + [_, Jack, Ten, Nine, _, Eight, Seven] => Straight { high_card: Jack }, + [_, Jack, Ten, _, Nine, Eight, Seven] => Straight { high_card: Jack }, + [_, Jack, _, Ten, Nine, Eight, Seven] => Straight { high_card: Jack }, + [_, _, Jack, Ten, Nine, Eight, Seven] => Straight { high_card: Jack }, + + [Ten, Nine, Eight, Seven, Six, _, _] => Straight { high_card: Ten }, + [Ten, Nine, Eight, Seven, _, Six, _] => Straight { high_card: Ten }, + [Ten, Nine, Eight, Seven, _, _, Six] => Straight { high_card: Ten }, + [Ten, Nine, Eight, _, Seven, Six, _] => Straight { high_card: Ten }, + [Ten, Nine, Eight, _, Seven, _, Six] => Straight { high_card: Ten }, + [Ten, Nine, Eight, _, _, Seven, Six] => Straight { high_card: Ten }, + [Ten, Nine, _, Eight, Seven, Six, _] => Straight { high_card: Ten }, + [Ten, Nine, _, Eight, Seven, _, Six] => Straight { high_card: Ten }, + [Ten, Nine, _, Eight, _, Seven, Six] => Straight { high_card: Ten }, + [Ten, Nine, _, _, Eight, Seven, Six] => Straight { high_card: Ten }, + [Ten, _, Nine, Eight, Seven, Six, _] => Straight { high_card: Ten }, + [Ten, _, Nine, Eight, Seven, _, Six] => Straight { high_card: Ten }, + [Ten, _, Nine, Eight, _, Seven, Six] => Straight { high_card: Ten }, + [Ten, _, Nine, _, Eight, Seven, Six] => Straight { high_card: Ten }, + [Ten, _, _, Nine, Eight, Seven, Six] => Straight { high_card: Ten }, + [_, Ten, Nine, Eight, Seven, Six, _] => Straight { high_card: Ten }, + [_, Ten, Nine, Eight, Seven, _, Six] => Straight { high_card: Ten }, + [_, Ten, Nine, Eight, _, Seven, Six] => Straight { high_card: Ten }, + [_, Ten, Nine, _, Eight, Seven, Six] => Straight { high_card: Ten }, + [_, Ten, _, Nine, Eight, Seven, Six] => Straight { high_card: Ten }, + [_, _, Ten, Nine, Eight, Seven, Six] => Straight { high_card: Ten }, + + [Nine, Eight, Seven, Six, Five, _, _] => Straight { high_card: Nine }, + [Nine, Eight, Seven, Six, _, Five, _] => Straight { high_card: Nine }, + [Nine, Eight, Seven, Six, _, _, Five] => Straight { high_card: Nine }, + [Nine, Eight, Seven, _, Six, Five, _] => Straight { high_card: Nine }, + [Nine, Eight, Seven, _, Six, _, Five] => Straight { high_card: Nine }, + [Nine, Eight, Seven, _, _, Six, Five] => Straight { high_card: Nine }, + [Nine, Eight, _, Seven, Six, Five, _] => Straight { high_card: Nine }, + [Nine, Eight, _, Seven, Six, _, Five] => Straight { high_card: Nine }, + [Nine, Eight, _, Seven, _, Six, Five] => Straight { high_card: Nine }, + [Nine, Eight, _, _, Seven, Six, Five] => Straight { high_card: Nine }, + [Nine, _, Eight, Seven, Six, Five, _] => Straight { high_card: Nine }, + [Nine, _, Eight, Seven, Six, _, Five] => Straight { high_card: Nine }, + [Nine, _, Eight, Seven, _, Six, Five] => Straight { high_card: Nine }, + [Nine, _, Eight, _, Seven, Six, Five] => Straight { high_card: Nine }, + [Nine, _, _, Eight, Seven, Six, Five] => Straight { high_card: Nine }, + [_, Nine, Eight, Seven, Six, Five, _] => Straight { high_card: Nine }, + [_, Nine, Eight, Seven, Six, _, Five] => Straight { high_card: Nine }, + [_, Nine, Eight, Seven, _, Six, Five] => Straight { high_card: Nine }, + [_, Nine, Eight, _, Seven, Six, Five] => Straight { high_card: Nine }, + [_, Nine, _, Eight, Seven, Six, Five] => Straight { high_card: Nine }, + [_, _, Nine, Eight, Seven, Six, Five] => Straight { high_card: Nine }, + + [Eight, Seven, Six, Five, Four, _, _] => Straight { high_card: Eight }, + [Eight, Seven, Six, Five, _, Four, _] => Straight { high_card: Eight }, + [Eight, Seven, Six, Five, _, _, Four] => Straight { high_card: Eight }, + [Eight, Seven, Six, _, Five, Four, _] => Straight { high_card: Eight }, + [Eight, Seven, Six, _, Five, _, Four] => Straight { high_card: Eight }, + [Eight, Seven, Six, _, _, Five, Four] => Straight { high_card: Eight }, + [Eight, Seven, _, Six, Five, Four, _] => Straight { high_card: Eight }, + [Eight, Seven, _, Six, Five, _, Four] => Straight { high_card: Eight }, + [Eight, Seven, _, Six, _, Five, Four] => Straight { high_card: Eight }, + [Eight, Seven, _, _, Six, Five, Four] => Straight { high_card: Eight }, + [Eight, _, Seven, Six, Five, Four, _] => Straight { high_card: Eight }, + [Eight, _, Seven, Six, Five, _, Four] => Straight { high_card: Eight }, + [Eight, _, Seven, Six, _, Five, Four] => Straight { high_card: Eight }, + [Eight, _, Seven, _, Six, Five, Four] => Straight { high_card: Eight }, + [Eight, _, _, Seven, Six, Five, Four] => Straight { high_card: Eight }, + [_, Eight, Seven, Six, Five, Four, _] => Straight { high_card: Eight }, + [_, Eight, Seven, Six, Five, _, Four] => Straight { high_card: Eight }, + [_, Eight, Seven, Six, _, Five, Four] => Straight { high_card: Eight }, + [_, Eight, Seven, _, Six, Five, Four] => Straight { high_card: Eight }, + [_, Eight, _, Seven, Six, Five, Four] => Straight { high_card: Eight }, + [_, _, Eight, Seven, Six, Five, Four] => Straight { high_card: Eight }, + + [Seven, Six, Five, Four, Three, _, _] => Straight { high_card: Seven }, + [Seven, Six, Five, Four, _, Three, _] => Straight { high_card: Seven }, + [Seven, Six, Five, Four, _, _, Three] => Straight { high_card: Seven }, + [Seven, Six, Five, _, Four, Three, _] => Straight { high_card: Seven }, + [Seven, Six, Five, _, Four, _, Three] => Straight { high_card: Seven }, + [Seven, Six, Five, _, _, Four, Three] => Straight { high_card: Seven }, + [Seven, Six, _, Five, Four, Three, _] => Straight { high_card: Seven }, + [Seven, Six, _, Five, Four, _, Three] => Straight { high_card: Seven }, + [Seven, Six, _, Five, _, Four, Three] => Straight { high_card: Seven }, + [Seven, Six, _, _, Five, Four, Three] => Straight { high_card: Seven }, + [Seven, _, Six, Five, Four, Three, _] => Straight { high_card: Seven }, + [Seven, _, Six, Five, Four, _, Three] => Straight { high_card: Seven }, + [Seven, _, Six, Five, _, Four, Three] => Straight { high_card: Seven }, + [Seven, _, Six, _, Five, Four, Three] => Straight { high_card: Seven }, + [Seven, _, _, Six, Five, Four, Three] => Straight { high_card: Seven }, + [_, Seven, Six, Five, Four, Three, _] => Straight { high_card: Seven }, + [_, Seven, Six, Five, Four, _, Three] => Straight { high_card: Seven }, + [_, Seven, Six, Five, _, Four, Three] => Straight { high_card: Seven }, + [_, Seven, Six, _, Five, Four, Three] => Straight { high_card: Seven }, + [_, Seven, _, Six, Five, Four, Three] => Straight { high_card: Seven }, + [_, _, Seven, Six, Five, Four, Three] => Straight { high_card: Seven }, + + [Six, Five, Four, Three, Two, _, _] => Straight { high_card: Six }, + [Six, Five, Four, Three, _, Two, _] => Straight { high_card: Six }, + [Six, Five, Four, Three, _, _, Two] => Straight { high_card: Six }, + [Six, Five, Four, _, Three, Two, _] => Straight { high_card: Six }, + [Six, Five, Four, _, Three, _, Two] => Straight { high_card: Six }, + [Six, Five, Four, _, _, Three, Two] => Straight { high_card: Six }, + [Six, Five, _, Four, Three, Two, _] => Straight { high_card: Six }, + [Six, Five, _, Four, Three, _, Two] => Straight { high_card: Six }, + [Six, Five, _, Four, _, Three, Two] => Straight { high_card: Six }, + [Six, Five, _, _, Four, Three, Two] => Straight { high_card: Six }, + [Six, _, Five, Four, Three, Two, _] => Straight { high_card: Six }, + [Six, _, Five, Four, Three, _, Two] => Straight { high_card: Six }, + [Six, _, Five, Four, _, Three, Two] => Straight { high_card: Six }, + [Six, _, Five, _, Four, Three, Two] => Straight { high_card: Six }, + [Six, _, _, Five, Four, Three, Two] => Straight { high_card: Six }, + [_, Six, Five, Four, Three, Two, _] => Straight { high_card: Six }, + [_, Six, Five, Four, Three, _, Two] => Straight { high_card: Six }, + [_, Six, Five, Four, _, Three, Two] => Straight { high_card: Six }, + [_, Six, Five, _, Four, Three, Two] => Straight { high_card: Six }, + [_, Six, _, Five, Four, Three, Two] => Straight { high_card: Six }, + [_, _, Six, Five, Four, Three, Two] => Straight { high_card: Six }, + + [Ace, Five, Four, Three, Two, _, _] => Straight { high_card: Five }, + [Ace, Five, Four, Three, _, Two, _] => Straight { high_card: Five }, + [Ace, Five, Four, Three, _, _, Two] => Straight { high_card: Five }, + [Ace, Five, Four, _, Three, Two, _] => Straight { high_card: Five }, + [Ace, Five, Four, _, Three, _, Two] => Straight { high_card: Five }, + [Ace, Five, Four, _, _, Three, Two] => Straight { high_card: Five }, + [Ace, Five, _, Four, Three, Two, _] => Straight { high_card: Five }, + [Ace, Five, _, Four, Three, _, Two] => Straight { high_card: Five }, + [Ace, Five, _, Four, _, Three, Two] => Straight { high_card: Five }, + [Ace, Five, _, _, Four, Three, Two] => Straight { high_card: Five }, + [Ace, _, Five, Four, Three, Two, _] => Straight { high_card: Five }, + [Ace, _, Five, Four, Three, _, Two] => Straight { high_card: Five }, + [Ace, _, Five, Four, _, Three, Two] => Straight { high_card: Five }, + [Ace, _, Five, _, Four, Three, Two] => Straight { high_card: Five }, + [Ace, _, _, Five, Four, Three, Two] => Straight { high_card: Five }, + [_, Ace, Five, Four, Three, Two, _] => Straight { high_card: Five }, + [_, Ace, Five, Four, Three, _, Two] => Straight { high_card: Five }, + [_, Ace, Five, Four, _, Three, Two] => Straight { high_card: Five }, + [_, Ace, Five, _, Four, Three, Two] => Straight { high_card: Five }, + [_, Ace, _, Five, Four, Three, Two] => Straight { high_card: Five }, + [_, _, Ace, Five, Four, Three, Two] => Straight { high_card: Five }, + + [trip1, trip2, trip3, kicker1, kicker2, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, trip1, trip2, trip3, kicker2, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, kicker2, trip1, trip2, trip3, _, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, kicker2, _, trip1, trip2, trip3, _] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, kicker2, _, _, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + + [pair1, pair2, pair3, pair4, kicker, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [pair1, pair2, kicker, pair3, pair4, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [pair1, pair2, kicker, _, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [pair1, pair2, kicker, _, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, pair1, pair2, pair3, pair4, _, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, pair1, pair2, _, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, pair1, pair2, _, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, _, pair1, pair2, pair3, pair4, _] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, _, pair1, pair2, _, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, _, _, pair1, pair2, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + + [pair1, pair2, kicker1, kicker2, kicker3, _, _] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, pair1, pair2, kicker2, kicker3, _, _] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, pair1, pair2, kicker3, _, _] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, kicker3, pair1, pair2, _, _] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, kicker3, _, pair1, pair2, _] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, kicker3, _, _, pair1, pair2] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + + [rank1, rank2, rank3, rank4, rank5, _, _] => HighCard { kickers: (rank1, rank2, rank3, rank4, rank5) }, + }, } } @@ -486,30 +484,31 @@ impl Odds { impl Display for Odds { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Player 1: {:.02}%, Player 2: {:.02}%, Draw: {:.02}%", + write!( + f, + "Player 1: {:.02}%, Player 2: {:.02}%, Draw: {:.02}%", self.player1_probability() * 100.0, self.player2_probability() * 100.0, - self.draw_probability() * 100.0) + self.draw_probability() * 100.0 + ) } } pub fn heads_up_odds(player1: [Card; 2], player2: [Card; 2]) -> Odds { - let mut odds = Odds { - player1_wins: 0, - player2_wins: 0, - draws: 0, - }; - let cards: Vec = FIFTY_TWO_CARD_DECK.iter() + let mut odds = Odds { player1_wins: 0, player2_wins: 0, draws: 0 }; + let cards: Vec = FIFTY_TWO_CARD_DECK + .iter() .filter(|&&card| card != player1[0]) .filter(|&&card| card != player1[1]) .filter(|&&card| card != player2[0]) .filter(|&&card| card != player2[1]) - .cloned().collect(); + .cloned() + .collect(); for i in 0..48 { - for j in i+1..48 { - for k in j+1..48 { - for l in k+1..48 { - for m in l+1..48 { + for j in i + 1..48 { + for k in j + 1..48 { + for l in k + 1..48 { + for m in l + 1..48 { let player1_rank = rank_7_card_hand([player1[0], player1[1], cards[i], cards[j], cards[k], cards[l], cards[m]]); let player2_rank = rank_7_card_hand([player2[0], player2[1], cards[i], cards[j], cards[k], cards[l], cards[m]]); match player1_rank.cmp(&player2_rank) { @@ -529,17 +528,12 @@ pub fn heads_up_odds(player1: [Card; 2], player2: [Card; 2]) -> Odds { mod test { use super::*; - use rand::Rng; use rand::seq::SliceRandom; + use rand::Rng; pub fn random_4_card_draw(rng: &mut R) -> [Card; 4] { let mut cards = FIFTY_TWO_CARD_DECK.choose_multiple(rng, 4); - [ - *cards.next().unwrap(), - *cards.next().unwrap(), - *cards.next().unwrap(), - *cards.next().unwrap(), - ] + [*cards.next().unwrap(), *cards.next().unwrap(), *cards.next().unwrap(), *cards.next().unwrap()] } pub fn random_7_card_draw(rng: &mut R) -> [Card; 7] { @@ -588,71 +582,67 @@ mod test { let suits = [cards[0].suit, cards[1].suit, cards[2].suit, cards[3].suit, cards[4].suit]; match suits { - [Spades, Spades, Spades, Spades, Spades] | - [Hearts, Hearts, Hearts, Hearts, Hearts] | - [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] | - [Clubs, Clubs, Clubs, Clubs, Clubs] => - match ranks { - [Ace, King, Queen, Jack, Ten] => RoyalFlush, - [King, Queen, Jack, Ten, Nine] => StraightFlush{high_card: King}, - [Queen, Jack, Ten, Nine, Eight] => StraightFlush{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven] => StraightFlush{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six] => StraightFlush{high_card: Ten}, - [Nine, Eight, Seven, Six, Five] => StraightFlush{high_card: Nine}, - [Eight, Seven, Six, Five, Four] => StraightFlush{high_card: Eight}, - [Seven, Six, Five, Four, Three] => StraightFlush{high_card: Seven}, - [Six, Five, Four, Three, Two] => StraightFlush{high_card: Six}, - [Ace, Five, Four, Three, Two] => StraightFlush{high_card: Five}, - [rank1, rank2, rank3, rank4, rank5] => Flush{flush: (rank1, rank2, rank3, rank4, rank5)}, - } - [_, _, _, _, _] => - match ranks { - [quad1, quad2, quad3, quad4, kicker] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - [kicker, quad1, quad2, quad3, quad4] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind{quads: quad1, kicker: kicker}, - [trip1, trip2, trip3, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [pair1, pair2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse{trips: trip1, pair: pair1}, - [Ace, King, Queen, Jack, Ten] => Straight{high_card: Ace}, - [King, Queen, Jack, Ten, Nine] => Straight{high_card: King}, - [Queen, Jack, Ten, Nine, Eight] => Straight{high_card: Queen}, - [Jack, Ten, Nine, Eight, Seven] => Straight{high_card: Jack}, - [Ten, Nine, Eight, Seven, Six] => Straight{high_card: Ten}, - [Nine, Eight, Seven, Six, Five] => Straight{high_card: Nine}, - [Eight, Seven, Six, Five, Four] => Straight{high_card: Eight}, - [Seven, Six, Five, Four, Three] => Straight{high_card: Seven}, - [Six, Five, Four, Three, Two] => Straight{high_card: Six}, - [Ace, Five, Four, Three, Two] => Straight{high_card: Five}, - [trip1, trip2, trip3, kicker1, kicker2] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, trip1, trip2, trip3, kicker2] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [kicker1, kicker2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind{trips: trip1, kickers: (kicker1, kicker2)}, - [pair1, pair2, pair3, pair4, kicker] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [pair1, pair2, kicker, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [kicker, pair1, pair2, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair{pairs: (pair1, pair3), kicker: kicker}, - [pair1, pair2, kicker1, kicker2, kicker3] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, pair1, pair2, kicker2, kicker3] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, pair1, pair2, kicker3] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [kicker1, kicker2, kicker3, pair1, pair2] if pair1 == pair2 => Pair{pair: pair1, kickers: (kicker1, kicker2, kicker3)}, - [rank1, rank2, rank3, rank4, rank5] => HighCard{kickers: (rank1, rank2, rank3, rank4, rank5)}, - } + [Spades, Spades, Spades, Spades, Spades] + | [Hearts, Hearts, Hearts, Hearts, Hearts] + | [Diamonds, Diamonds, Diamonds, Diamonds, Diamonds] + | [Clubs, Clubs, Clubs, Clubs, Clubs] => match ranks { + [Ace, King, Queen, Jack, Ten] => RoyalFlush, + [King, Queen, Jack, Ten, Nine] => StraightFlush { high_card: King }, + [Queen, Jack, Ten, Nine, Eight] => StraightFlush { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven] => StraightFlush { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six] => StraightFlush { high_card: Ten }, + [Nine, Eight, Seven, Six, Five] => StraightFlush { high_card: Nine }, + [Eight, Seven, Six, Five, Four] => StraightFlush { high_card: Eight }, + [Seven, Six, Five, Four, Three] => StraightFlush { high_card: Seven }, + [Six, Five, Four, Three, Two] => StraightFlush { high_card: Six }, + [Ace, Five, Four, Three, Two] => StraightFlush { high_card: Five }, + [rank1, rank2, rank3, rank4, rank5] => Flush { flush: (rank1, rank2, rank3, rank4, rank5) }, + }, + [_, _, _, _, _] => match ranks { + [quad1, quad2, quad3, quad4, kicker] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + [kicker, quad1, quad2, quad3, quad4] if quad1 == quad2 && quad2 == quad3 && quad3 == quad4 => FourOfAKind { quads: quad1, kicker }, + [trip1, trip2, trip3, pair1, pair2] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [pair1, pair2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 && pair1 == pair2 => FullHouse { trips: trip1, pair: pair1 }, + [Ace, King, Queen, Jack, Ten] => Straight { high_card: Ace }, + [King, Queen, Jack, Ten, Nine] => Straight { high_card: King }, + [Queen, Jack, Ten, Nine, Eight] => Straight { high_card: Queen }, + [Jack, Ten, Nine, Eight, Seven] => Straight { high_card: Jack }, + [Ten, Nine, Eight, Seven, Six] => Straight { high_card: Ten }, + [Nine, Eight, Seven, Six, Five] => Straight { high_card: Nine }, + [Eight, Seven, Six, Five, Four] => Straight { high_card: Eight }, + [Seven, Six, Five, Four, Three] => Straight { high_card: Seven }, + [Six, Five, Four, Three, Two] => Straight { high_card: Six }, + [Ace, Five, Four, Three, Two] => Straight { high_card: Five }, + [trip1, trip2, trip3, kicker1, kicker2] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, trip1, trip2, trip3, kicker2] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [kicker1, kicker2, trip1, trip2, trip3] if trip1 == trip2 && trip2 == trip3 => ThreeOfAKind { trips: trip1, kickers: (kicker1, kicker2) }, + [pair1, pair2, pair3, pair4, kicker] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [pair1, pair2, kicker, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [kicker, pair1, pair2, pair3, pair4] if pair1 == pair2 && pair3 == pair4 => TwoPair { pairs: (pair1, pair3), kicker }, + [pair1, pair2, kicker1, kicker2, kicker3] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, pair1, pair2, kicker2, kicker3] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, pair1, pair2, kicker3] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [kicker1, kicker2, kicker3, pair1, pair2] if pair1 == pair2 => Pair { pair: pair1, kickers: (kicker1, kicker2, kicker3) }, + [rank1, rank2, rank3, rank4, rank5] => HighCard { kickers: (rank1, rank2, rank3, rank4, rank5) }, + }, } } pub fn heads_up_odds_naive(player1: [Card; 2], player2: [Card; 2]) -> Odds { - let mut odds = Odds { - player1_wins: 0, - player2_wins: 0, - draws: 0, - }; - let cards: Vec = FIFTY_TWO_CARD_DECK.iter() + let mut odds = Odds { player1_wins: 0, player2_wins: 0, draws: 0 }; + let cards: Vec = FIFTY_TWO_CARD_DECK + .iter() .filter(|&&card| card != player1[0]) .filter(|&&card| card != player1[1]) .filter(|&&card| card != player2[0]) .filter(|&&card| card != player2[1]) - .cloned().collect(); + .cloned() + .collect(); for i in 0..48 { - for j in i+1..48 { - for k in j+1..48 { - for l in k+1..48 { - for m in l+1..48 { + for j in i + 1..48 { + for k in j + 1..48 { + for l in k + 1..48 { + for m in l + 1..48 { let player1_rank = rank_7_card_hand([player1[0], player1[1], cards[i], cards[j], cards[k], cards[l], cards[m]]); let player2_rank = rank_7_card_hand([player2[0], player2[1], cards[i], cards[j], cards[k], cards[l], cards[m]]); match player1_rank.cmp(&player2_rank) { @@ -676,66 +666,114 @@ mod test { #[test] fn rank_straight_flush() { - assert_eq!(rank_5_card_hand([KING_OF_SPADES, QUEEN_OF_SPADES, JACK_OF_SPADES, TEN_OF_SPADES, NINE_OF_SPADES]), StraightFlush{high_card: King}); - assert_eq!(rank_5_card_hand([FIVE_OF_CLUBS, FOUR_OF_CLUBS, THREE_OF_CLUBS, TWO_OF_CLUBS, ACE_OF_CLUBS]), StraightFlush{high_card: Five}); + assert_eq!(rank_5_card_hand([KING_OF_SPADES, QUEEN_OF_SPADES, JACK_OF_SPADES, TEN_OF_SPADES, NINE_OF_SPADES]), StraightFlush { high_card: King }); + assert_eq!(rank_5_card_hand([FIVE_OF_CLUBS, FOUR_OF_CLUBS, THREE_OF_CLUBS, TWO_OF_CLUBS, ACE_OF_CLUBS]), StraightFlush { high_card: Five }); } #[test] fn rank_four_of_a_kind() { - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, THREE_OF_DIAMONDS, THREE_OF_HEARTS, THREE_OF_SPADES, ACE_OF_CLUBS]), FourOfAKind{quads: Three, kicker: Ace}); - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, THREE_OF_HEARTS, THREE_OF_SPADES, THREE_OF_DIAMONDS]), FourOfAKind{quads: Three, kicker: Two}); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, THREE_OF_DIAMONDS, THREE_OF_HEARTS, THREE_OF_SPADES, ACE_OF_CLUBS]), + FourOfAKind { quads: Three, kicker: Ace } + ); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, THREE_OF_HEARTS, THREE_OF_SPADES, THREE_OF_DIAMONDS]), + FourOfAKind { quads: Three, kicker: Two } + ); } #[test] fn rank_full_house() { - assert_eq!(rank_5_card_hand([KING_OF_HEARTS, THREE_OF_DIAMONDS, THREE_OF_HEARTS, KING_OF_DIAMONDS, KING_OF_SPADES]), FullHouse{trips: King, pair: Three}); - assert_eq!(rank_5_card_hand([THREE_OF_SPADES, THREE_OF_DIAMONDS, THREE_OF_HEARTS, KING_OF_DIAMONDS, KING_OF_SPADES]), FullHouse{trips: Three, pair: King}); + assert_eq!( + rank_5_card_hand([KING_OF_HEARTS, THREE_OF_DIAMONDS, THREE_OF_HEARTS, KING_OF_DIAMONDS, KING_OF_SPADES]), + FullHouse { trips: King, pair: Three } + ); + assert_eq!( + rank_5_card_hand([THREE_OF_SPADES, THREE_OF_DIAMONDS, THREE_OF_HEARTS, KING_OF_DIAMONDS, KING_OF_SPADES]), + FullHouse { trips: Three, pair: King } + ); } #[test] fn rank_flush() { - assert_eq!(rank_5_card_hand([THREE_OF_SPADES, SEVEN_OF_SPADES, ACE_OF_SPADES, FOUR_OF_SPADES, QUEEN_OF_SPADES]), Flush{flush: (Ace, Queen, Seven, Four, Three)}); - assert_eq!(rank_5_card_hand([TWO_OF_HEARTS, EIGHT_OF_HEARTS, FIVE_OF_HEARTS, SEVEN_OF_HEARTS, NINE_OF_HEARTS]), Flush{flush: (Nine, Eight, Seven, Five, Two)}); + assert_eq!( + rank_5_card_hand([THREE_OF_SPADES, SEVEN_OF_SPADES, ACE_OF_SPADES, FOUR_OF_SPADES, QUEEN_OF_SPADES]), + Flush { flush: (Ace, Queen, Seven, Four, Three) } + ); + assert_eq!( + rank_5_card_hand([TWO_OF_HEARTS, EIGHT_OF_HEARTS, FIVE_OF_HEARTS, SEVEN_OF_HEARTS, NINE_OF_HEARTS]), + Flush { flush: (Nine, Eight, Seven, Five, Two) } + ); } #[test] fn rank_straight() { - assert_eq!(rank_5_card_hand([EIGHT_OF_HEARTS, QUEEN_OF_SPADES, JACK_OF_SPADES, TEN_OF_SPADES, NINE_OF_SPADES]), Straight{high_card: Queen}); - assert_eq!(rank_5_card_hand([FIVE_OF_CLUBS, FOUR_OF_SPADES, THREE_OF_CLUBS, TWO_OF_HEARTS, ACE_OF_CLUBS]), Straight{high_card: Five}); + assert_eq!(rank_5_card_hand([EIGHT_OF_HEARTS, QUEEN_OF_SPADES, JACK_OF_SPADES, TEN_OF_SPADES, NINE_OF_SPADES]), Straight { high_card: Queen }); + assert_eq!(rank_5_card_hand([FIVE_OF_CLUBS, FOUR_OF_SPADES, THREE_OF_CLUBS, TWO_OF_HEARTS, ACE_OF_CLUBS]), Straight { high_card: Five }); } #[test] fn rank_three_of_a_kind() { - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, THREE_OF_DIAMONDS, SEVEN_OF_CLUBS, THREE_OF_SPADES, ACE_OF_CLUBS]), ThreeOfAKind{trips: Three, kickers: (Ace, Seven)}); - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, THREE_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), ThreeOfAKind{trips: Three, kickers: (Six, Two)}); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, THREE_OF_DIAMONDS, SEVEN_OF_CLUBS, THREE_OF_SPADES, ACE_OF_CLUBS]), + ThreeOfAKind { trips: Three, kickers: (Ace, Seven) } + ); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, THREE_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), + ThreeOfAKind { trips: Three, kickers: (Six, Two) } + ); } #[test] fn rank_two_pair() { - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, SEVEN_OF_HEARTS, SEVEN_OF_CLUBS, THREE_OF_SPADES, ACE_OF_CLUBS]), TwoPair{pairs: (Seven, Three), kicker: Ace}); - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, SIX_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), TwoPair{pairs: (Six, Three), kicker: Two}); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, SEVEN_OF_HEARTS, SEVEN_OF_CLUBS, THREE_OF_SPADES, ACE_OF_CLUBS]), + TwoPair { pairs: (Seven, Three), kicker: Ace } + ); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, SIX_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), + TwoPair { pairs: (Six, Three), kicker: Two } + ); } #[test] fn rank_pair() { - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, SEVEN_OF_HEARTS, NINE_OF_HEARTS, THREE_OF_SPADES, ACE_OF_CLUBS]), Pair{pair: Three, kickers: (Ace, Nine, Seven)}); - assert_eq!(rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, SEVEN_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), Pair{pair: Three, kickers: (Seven, Six, Two)}); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, SEVEN_OF_HEARTS, NINE_OF_HEARTS, THREE_OF_SPADES, ACE_OF_CLUBS]), + Pair { pair: Three, kickers: (Ace, Nine, Seven) } + ); + assert_eq!( + rank_5_card_hand([THREE_OF_CLUBS, TWO_OF_HEARTS, SEVEN_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), + Pair { pair: Three, kickers: (Seven, Six, Two) } + ); } #[test] fn rank_high_card() { - assert_eq!(rank_5_card_hand([ACE_OF_SPADES, TWO_OF_HEARTS, SEVEN_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), HighCard{kickers: (Ace, Seven, Six, Three, Two)}); - assert_eq!(rank_5_card_hand([EIGHT_OF_SPADES, SIX_OF_DIAMONDS, SEVEN_OF_HEARTS, KING_OF_HEARTS, FOUR_OF_CLUBS]), HighCard{kickers: (King, Eight, Seven, Six, Four)}); + assert_eq!( + rank_5_card_hand([ACE_OF_SPADES, TWO_OF_HEARTS, SEVEN_OF_HEARTS, SIX_OF_CLUBS, THREE_OF_DIAMONDS]), + HighCard { kickers: (Ace, Seven, Six, Three, Two) } + ); + assert_eq!( + rank_5_card_hand([EIGHT_OF_SPADES, SIX_OF_DIAMONDS, SEVEN_OF_HEARTS, KING_OF_HEARTS, FOUR_OF_CLUBS]), + HighCard { kickers: (King, Eight, Seven, Six, Four) } + ); } #[test] fn rank_7_card_pair() { - assert_eq!(rank_7_card_hand([NINE_OF_CLUBS, TWO_OF_SPADES, SIX_OF_CLUBS, ACE_OF_DIAMONDS, FOUR_OF_HEARTS, THREE_OF_HEARTS, FOUR_OF_CLUBS]), Pair{pair: Four, kickers: (Ace, Nine, Six)}); + assert_eq!( + rank_7_card_hand([NINE_OF_CLUBS, TWO_OF_SPADES, SIX_OF_CLUBS, ACE_OF_DIAMONDS, FOUR_OF_HEARTS, THREE_OF_HEARTS, FOUR_OF_CLUBS]), + Pair { pair: Four, kickers: (Ace, Nine, Six) } + ); } #[test] fn rank_7_card_two_pair() { - assert_eq!(rank_7_card_hand([SIX_OF_HEARTS, JACK_OF_SPADES, TWO_OF_DIAMONDS, TEN_OF_CLUBS, TWO_OF_CLUBS, SIX_OF_DIAMONDS, FOUR_OF_SPADES]), TwoPair{pairs: (Six, Two), kicker: Jack}); + assert_eq!( + rank_7_card_hand([SIX_OF_HEARTS, JACK_OF_SPADES, TWO_OF_DIAMONDS, TEN_OF_CLUBS, TWO_OF_CLUBS, SIX_OF_DIAMONDS, FOUR_OF_SPADES]), + TwoPair { pairs: (Six, Two), kicker: Jack } + ); } #[test] diff --git a/src/game/poker/holdem.rs b/src/game/poker/holdem.rs index ba3dce0..3ac130b 100644 --- a/src/game/poker/holdem.rs +++ b/src/game/poker/holdem.rs @@ -4,8 +4,8 @@ use std::convert::TryInto; use itertools::Itertools; use crate::card::{Card, FIFTY_TWO_CARD_DECK}; -use crate::seats::Seats; use crate::rng::{Seed, WaveRng}; +use crate::seats::Seats; use crate::username::Username; use crate::util::{max::IteratorMaxItems, timestamp::Timestamp}; @@ -133,34 +133,27 @@ impl TexasHoldEm { } fn all_bets_are_in(&self) -> bool { - self.in_hand.iter().filter(|&&username| self.is_able_to_bet(username)) - .all(|username| self.bets.contains_key(username)) + self.in_hand.iter().filter(|&&username| self.is_able_to_bet(username)).all(|username| self.bets.contains_key(username)) } fn player_is_all_in(&self, username: Username) -> bool { matches!(self.stacks.get(&username), Some(&0) | None) } - fn bets_of_players_who_are_not_all_in(&self) -> impl Iterator + '_ { - self.bets.iter() - .filter(move |&(&username, _)| !self.player_is_all_in(username)) - .map(|(_, &bet)| bet) + fn bets_of_players_who_are_not_all_in(&self) -> impl Iterator + '_ { + self.bets.iter().filter(move |&(&username, _)| !self.player_is_all_in(username)).map(|(_, &bet)| bet) } fn all_bets_are_equal(&self) -> bool { self.players_able_to_bet() == 0 - || self.bets_of_players_who_are_not_all_in().all_equal() - && self.bets.values().cloned().max() <= self.bets_of_players_who_are_not_all_in().max() + || self.bets_of_players_who_are_not_all_in().all_equal() && self.bets.values().cloned().max() <= self.bets_of_players_who_are_not_all_in().max() } fn big_blind_player(&self) -> Option { if self.seats.players_len() == 2 { - self.dealer - .and_then(|dealer| self.seats.player_after(dealer)) + self.dealer.and_then(|dealer| self.seats.player_after(dealer)) } else { - self.dealer - .and_then(|dealer| self.seats.player_after(dealer)) - .and_then(|small_blind| self.seats.player_after(small_blind)) + self.dealer.and_then(|dealer| self.seats.player_after(dealer)).and_then(|small_blind| self.seats.player_after(small_blind)) } } @@ -172,8 +165,7 @@ impl TexasHoldEm { } } } - self.all_bets_are_in() && self.all_bets_are_equal() - || self.players_able_to_bet() <= 1 && self.bets.len() == 0 + self.all_bets_are_in() && self.all_bets_are_equal() || self.players_able_to_bet() <= 1 && self.bets.len() == 0 } fn remove_ghosts(&mut self) -> Result<(), ActionError> { @@ -217,12 +209,10 @@ impl Game for TexasHoldEm { self.actions_len } - fn validate_action(&self, UserAction{timestamp, username, action}: UserAction) -> Result { + fn validate_action(&self, UserAction { timestamp, username, action }: UserAction) -> Result { match (self.state, action) { - (_, Action::PlayCard{..}) | (_, Action::ChooseTrumps{..}) => { - Err(ActionError::InvalidActionForGameType) - } - (State::NotStarted, Action::Join{seat, chips}) => { + (_, Action::PlayCard { .. }) | (_, Action::ChooseTrumps { .. }) => Err(ActionError::InvalidActionForGameType), + (State::NotStarted, Action::Join { seat, chips }) => { if self.seats.contains_player(username) { Err(ActionError::AlreadyJoined) } else if self.seats.players_len() > self.settings.max_players as usize { @@ -234,31 +224,27 @@ impl Game for TexasHoldEm { } else if chips > self.settings.starting_stack { Err(ActionError::StartingStackTooLarge) } else { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Join{seat, chips}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Join { seat, chips } })) } } - (State::Completed, Action::Join{..}) => Err(ActionError::GameHasEnded), - (_, Action::Join{..}) => Err(ActionError::GameHasStarted), + (State::Completed, Action::Join { .. }) => Err(ActionError::GameHasEnded), + (_, Action::Join { .. }) => Err(ActionError::GameHasStarted), (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised), - (State::NotStarted, Action::Leave) => { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Leave})) - } + (State::NotStarted, Action::Leave) => Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Leave })), (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised), (State::Completed, _) => Err(ActionError::GameHasEnded), (_, Action::Leave) => Err(ActionError::GameHasStarted), - (State::Dealing, _) | (State::DealingFlop, _) | (State::DealingTurn, _) | (State::DealingRiver, _) => { - Err(ActionError::Dealing) - } - (_, Action::Fold) | (_, Action::Bet{..}) if !self.in_hand.contains(&username) => Err(ActionError::NotInHand), - (_, Action::Fold) | (_, Action::Bet{..}) if self.active != Some(username) => Err(ActionError::OutOfTurn), + (State::Dealing, _) | (State::DealingFlop, _) | (State::DealingTurn, _) | (State::DealingRiver, _) => Err(ActionError::Dealing), + (_, Action::Fold) | (_, Action::Bet { .. }) if !self.in_hand.contains(&username) => Err(ActionError::NotInHand), + (_, Action::Fold) | (_, Action::Bet { .. }) if self.active != Some(username) => Err(ActionError::OutOfTurn), (_, Action::Fold) if self.chips_to_call(username) == 0 => Err(ActionError::CannotFold), - (_, Action::Fold) => Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Fold})), - (_, Action::Bet{chips}) => { + (_, Action::Fold) => Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Fold })), + (_, Action::Bet { chips }) => { let stack = self.stack(username); if chips > stack { Err(ActionError::NotEnoughChips) } else if chips == stack { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Bet{chips}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Bet { chips } })) } else { let to_call = self.chips_to_call(username); let min_raise = self.min_raise(); @@ -267,7 +253,7 @@ impl Game for TexasHoldEm { } else if chips > to_call && chips < to_call + min_raise { Err(ActionError::BetSizeTooSmall) } else { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Bet{chips}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Bet { chips } })) } } } @@ -275,15 +261,13 @@ impl Game for TexasHoldEm { } } - fn take_action(&mut self, ValidatedUserAction(UserAction{timestamp, username, action}): ValidatedUserAction) -> Result<(), ActionError> { + fn take_action(&mut self, ValidatedUserAction(UserAction { timestamp, username, action }): ValidatedUserAction) -> Result<(), ActionError> { self.actions_len += 1; self.last_action_time = Some(timestamp); self.rng.advance(); match (self.state, action) { - (_, Action::PlayCard{..}) | (_, Action::ChooseTrumps{..}) => { - Err(ActionError::InvalidActionForGameType) - } - (State::NotStarted, Action::Join{seat, chips}) => { + (_, Action::PlayCard { .. }) | (_, Action::ChooseTrumps { .. }) => Err(ActionError::InvalidActionForGameType), + (State::NotStarted, Action::Join { seat, chips }) => { self.stacks.insert(username, chips); self.seats.add_player(seat, username) } @@ -327,7 +311,7 @@ impl Game for TexasHoldEm { self.state = State::Dealing; Ok(()) } - (State::Dealing, Action::ReceiveCard{card: Some(card)}) => { + (State::Dealing, Action::ReceiveCard { card: Some(card) }) => { self.deck.remove(&card); self.hands.entry(username).or_default().insert(card); if self.hands.values().all(|hand| hand.len() == 2) { @@ -342,14 +326,14 @@ impl Game for TexasHoldEm { self.receiver = None; Ok(()) } - (State::PostingSmallBlind, Action::PostBlind{chips}) => { + (State::PostingSmallBlind, Action::PostBlind { chips }) => { *self.bets.entry(username).or_default() += chips; *self.committed.entry(username).or_default() += chips; *self.stacks.entry(username).or_default() -= chips; self.state = State::PostingBigBlind; Ok(()) } - (State::PostingBigBlind, Action::PostBlind{chips}) => { + (State::PostingBigBlind, Action::PostBlind { chips }) => { *self.bets.entry(username).or_default() += chips; *self.committed.entry(username).or_default() += chips; *self.stacks.entry(username).or_default() -= chips; @@ -357,7 +341,7 @@ impl Game for TexasHoldEm { self.state = State::PreFlopBetting; Ok(()) } - (_, Action::Bet{chips}) => { + (_, Action::Bet { chips }) => { *self.bets.entry(username).or_default() += chips; *self.committed.entry(username).or_default() += chips; *self.stacks.entry(username).or_default() -= chips; @@ -384,15 +368,12 @@ impl Game for TexasHoldEm { } Ok(()) } - (State::DealingFlop, Action::CommunityCard{card}) => { + (State::DealingFlop, Action::CommunityCard { card }) => { self.community.insert(card); self.deck.remove(&card); if self.community.len() == 3 { - self.active = if self.betting_round_completed() { - None - } else { - self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) - }; + self.active = + if self.betting_round_completed() { None } else { self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) }; self.state = match self.active { Some(_) => State::PostFlopBetting, None => State::DealingTurn, @@ -400,35 +381,29 @@ impl Game for TexasHoldEm { } Ok(()) } - (State::DealingTurn, Action::CommunityCard{card}) => { + (State::DealingTurn, Action::CommunityCard { card }) => { self.community.insert(card); self.deck.remove(&card); - self.active = if self.betting_round_completed() { - None - } else { - self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) - }; + self.active = + if self.betting_round_completed() { None } else { self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) }; self.state = match self.active { Some(_) => State::TurnBetting, None => State::DealingRiver, }; Ok(()) } - (State::DealingRiver, Action::CommunityCard{card}) => { + (State::DealingRiver, Action::CommunityCard { card }) => { self.community.insert(card); self.deck.remove(&card); - self.active = if self.betting_round_completed() { - None - } else { - self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) - }; + self.active = + if self.betting_round_completed() { None } else { self.seats.player_after_where(username, |username| self.is_able_to_bet(username)) }; self.state = match self.active { Some(_) => State::RiverBetting, None => State::Showdown, }; Ok(()) } - (_, Action::WinHand{chips, ..}) if chips <= self.pot => { + (_, Action::WinHand { chips, .. }) if chips <= self.pot => { self.pot -= chips; *self.stacks.entry(username).or_default() += chips; self.hands.remove(&username); @@ -448,11 +423,10 @@ impl Game for TexasHoldEm { let mut rng = self.rng.clone(); match self.state { State::NotStarted => { - if self.seats.players_len() == self.settings.max_players as usize { // TODO + if self.seats.players_len() == self.settings.max_players as usize { + // TODO if let Some(username) = rng.choose_from(self.seats.player_set()) { - return DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) - ); + return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal })); } } DealerAction::WaitForPlayer @@ -460,13 +434,9 @@ impl Game for TexasHoldEm { State::Dealing => { if let Some(username) = self.receiver { let card = rng.choose_from(&self.deck).cloned(); - DealerAction::TakeAction( - 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 { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::EndDeal })) } else { error!("Expected to deal a card but there was no dealer: {:#?}", self); DealerAction::Leave @@ -475,9 +445,7 @@ impl Game for TexasHoldEm { State::PostingSmallBlind if self.seats.players_len() == 2 => { 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}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::PostBlind { chips } })) } else { error!("There is no player to post the small blind: {:#?}", self); DealerAction::Leave @@ -486,9 +454,7 @@ impl Game for TexasHoldEm { State::PostingSmallBlind => { 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}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::PostBlind { chips } })) } else { error!("There is no player to post the small blind: {:#?}", self); DealerAction::Leave @@ -497,23 +463,18 @@ impl Game for TexasHoldEm { State::PostingBigBlind if self.seats.players_len() == 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}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::PostBlind { chips } })) } else { error!("There is no player to post the big blind: {:#?}", self); DealerAction::Leave } } State::PostingBigBlind => { - if let Some(username) = self.dealer - .and_then(|dealer| self.seats.player_after(dealer)) - .and_then(|small_blind| self.seats.player_after(small_blind)) + if let Some(username) = + self.dealer.and_then(|dealer| self.seats.player_after(dealer)).and_then(|small_blind| self.seats.player_after(small_blind)) { let chips = self.stack(username).min(self.small_blind * 2); - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::PostBlind{chips}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::PostBlind { chips } })) } else { error!("There is no player to post the big blind: {:#?}", self); DealerAction::Leave @@ -523,37 +484,26 @@ impl Game for TexasHoldEm { if self.in_hand.len() <= 1 { if self.pot > 0 { if let Some(&username) = self.in_hand.iter().next() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction { - timestamp, - username, - action: Action::WinHand { - chips: self.pot, - hand: None, - } - }) - ) + 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 { if let Some(&username) = self.seats.player_set().iter().next() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) - ) + 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) { - DealerAction::TakeAction( - 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)) { - DealerAction::TakeAction( - 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); DealerAction::Leave @@ -561,9 +511,7 @@ impl Game for TexasHoldEm { } else if let (Some(last_time), Some(username), Some(timeout)) = (self.last_action_time, self.active, self.settings.action_timeout) { let timeout_time = last_time.plus_millis(timeout); if timestamp >= timeout_time { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::TimeoutFold}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::TimeoutFold })) } else { DealerAction::WaitUntil(timeout_time) } @@ -574,9 +522,7 @@ impl Game for TexasHoldEm { State::DealingFlop | State::DealingTurn | State::DealingRiver => { 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}}) - ) + 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 @@ -588,29 +534,25 @@ impl Game for TexasHoldEm { } State::Showdown if self.pot == 0 => { if let Some((&username, _)) = self.stacks.iter().find(|&(_, &stack)| stack == 0) { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::KnockedOut}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::KnockedOut })) } else if self.seats.players_len() == 1 { if let Some(&username) = self.seats.player_set().iter().next() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) - ) + 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)) { - DealerAction::TakeAction( - 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); DealerAction::Leave } } State::Showdown => { - let winning_hands: Vec<_> = self.hands.iter() + let winning_hands: Vec<_> = self + .hands + .iter() .map(|(&username, hand)| (username, hand.iter().chain(self.community.iter()).cloned().collect::>())) .filter_map(|(username, cards)| cards.try_into().ok().map(rank_7_card_hand).map(|hand| (username, hand))) .max_items_by_key(|(_, hand)| *hand); @@ -618,16 +560,14 @@ impl Game for TexasHoldEm { info!("Showdown: all hands: {:?}", self.hands); info!("Showdown: winning hands: {:?}", winning_hands); if let Some(&(username, hand)) = winning_hands.first() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction { - timestamp, - username, - action: Action::WinHand { - chips: (self.pot / winning_hands.len() as u64).min(self.max_winnings(username)), - hand: Some(hand.to_string()), - } - }) - ) + 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 @@ -646,7 +586,7 @@ mod tests { let mut game = TexasHoldEm::new(0, settings, seed); for action in actions { match action.action { - Action::Join{..} | Action::Bet{..} | Action::Fold => { + Action::Join { .. } | Action::Bet { .. } | Action::Fold => { let validated = game.validate_action(action.clone()).unwrap(); assert_eq!(ValidatedUserAction(action), validated); game.take_action(validated).unwrap(); @@ -1080,7 +1020,8 @@ mod tests { ]"#; let actions = serde_json::from_str(actions).unwrap(); - let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; + let settings = + r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; let settings = serde_json::from_str(settings).unwrap(); let seed = r#"{"rng":"ChaCha20","seed":"48e2f45eb4a1ac6bc4ab4f2368ba2d9b0d7c1f132d7fc7f51036e92112dae136"}"#; @@ -1088,7 +1029,7 @@ mod tests { test_game(actions, settings, seed); } - + #[test] fn four_player_game_fold_triggers_next_card() { let actions = r#"[ @@ -1140,7 +1081,8 @@ mod tests { ]"#; let actions = serde_json::from_str(actions).unwrap(); - let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; + let settings = + r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; let settings = serde_json::from_str(settings).unwrap(); let seed = r#"{"rng":"ChaCha20","seed":"48e2f45eb4a1ac6bc4ab4f2368ba2d9b0d7c1f132d7fc7f51036e92112dae136"}"#; @@ -1249,7 +1191,8 @@ mod tests { ]"#; let actions = serde_json::from_str(actions).unwrap(); - let settings = r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; + let settings = + r#"{"format":"TexasHoldEm","title":"4-Player TexasHoldEm Test","max_players":4,"small_blind":25,"starting_stack":1000,"action_timeout":null}"#; let settings = serde_json::from_str(settings).unwrap(); let seed = r#"{"rng":"ChaCha20","seed":"092b99f45313fff167029dc7420ed69a92becae492e09b65bc06ddcaae3c9e9c"}"#; diff --git a/src/game/whist.rs b/src/game/whist.rs index fa5b989..dc7d7bc 100644 --- a/src/game/whist.rs +++ b/src/game/whist.rs @@ -74,30 +74,24 @@ impl KnockOutWhist { } fn hand_contains_card(&self, username: Username, card: Card) -> bool { - self.hands.get(&username) - .map_or(false, |hand| hand.contains(&card)) + self.hands.get(&username).map_or(false, |hand| hand.contains(&card)) } fn hand_contains_suit(&self, username: Username, suit: Suit) -> bool { - self.hands.get(&username) - .map_or(false, |hand| hand.iter().any(|card| card.suit == suit)) + self.hands.get(&username).map_or(false, |hand| hand.iter().any(|card| card.suit == suit)) } fn trick_winner(&self) -> Option { - if self.trick.len() < self.seats.players_len() { return None; } - let highest_trump = self.trick.iter() - .filter(|(_, card)| Some(card.suit) == self.trumps) - .max_by_key(|(_, card)| card.rank); - let highest_led = self.trick.iter() - .filter(|(_, card)| Some(card.suit) == self.led) - .max_by_key(|(_, card)| card.rank); + if self.trick.len() < self.seats.players_len() { + return None; + } + let highest_trump = self.trick.iter().filter(|(_, card)| Some(card.suit) == self.trumps).max_by_key(|(_, card)| card.rank); + let highest_led = self.trick.iter().filter(|(_, card)| Some(card.suit) == self.led).max_by_key(|(_, card)| card.rank); highest_trump.or(highest_led).map(|(username, _)| *username) } - fn cards_by_player(&self) -> impl Iterator + '_ { - self.hands.iter() - .map(|(username, hand)| hand.iter().map(move |card| (*username, *card))) - .flatten() + fn cards_by_player(&self) -> impl Iterator + '_ { + self.hands.iter().map(|(username, hand)| hand.iter().map(move |card| (*username, *card))).flatten() } } @@ -114,12 +108,12 @@ impl Game for KnockOutWhist { self.actions_len } - fn validate_action(&self, UserAction{timestamp, username, action}: UserAction) -> Result { + fn validate_action(&self, UserAction { timestamp, username, action }: UserAction) -> Result { match (self.state, action) { - (_, Action::AddOn{..}) | (_, Action::RevealCard{..}) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet{..}) => { + (_, Action::AddOn { .. }) | (_, Action::RevealCard { .. }) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet { .. }) => { Err(ActionError::InvalidActionForGameType) } - (State::NotStarted, Action::Join{seat, ..}) => { + (State::NotStarted, Action::Join { seat, .. }) => { if self.seats.contains_player(username) { Err(ActionError::AlreadyJoined) } else if self.seats.players_len() > self.settings.max_players as usize { @@ -127,27 +121,25 @@ impl Game for KnockOutWhist { } else if !self.seats.seat_is_available(seat) { Err(ActionError::SeatNotAvailable) } else { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Join{seat, chips: 0}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Join { seat, chips: 0 } })) } } - (State::Completed, Action::Join{..}) => Err(ActionError::GameHasEnded), - (_, Action::Join{..}) => Err(ActionError::GameHasStarted), + (State::Completed, Action::Join { .. }) => Err(ActionError::GameHasEnded), + (_, Action::Join { .. }) => Err(ActionError::GameHasStarted), (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised), - (State::NotStarted, Action::Leave) => { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::Leave})) - } + (State::NotStarted, Action::Leave) => Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::Leave })), (State::Completed, Action::Leave) => Err(ActionError::GameHasEnded), (_, Action::Leave) => Err(ActionError::GameHasStarted), (State::Dealing, _) => Err(ActionError::Dealing), - (State::ChoosingTrumps, Action::ChooseTrumps{suit}) => { + (State::ChoosingTrumps, Action::ChooseTrumps { suit }) => { if Some(username) == self.call { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::ChooseTrumps{suit}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::ChooseTrumps { suit } })) } else { Err(ActionError::OutOfTurn) } } (State::ChoosingTrumps, _) => Err(ActionError::MustChooseTrumps), - (State::Playing, Action::PlayCard{card}) => { + (State::Playing, Action::PlayCard { card }) => { if Some(username) != self.active { Err(ActionError::OutOfTurn) } else if !self.hand_contains_card(username, card) { @@ -155,7 +147,7 @@ impl Game for KnockOutWhist { } else if matches!(self.led, Some(led) if card.suit != led && self.hand_contains_suit(username, led)) { Err(ActionError::CardNotPlayable) } else { - Ok(ValidatedUserAction(UserAction{timestamp, username, action: Action::PlayCard{card}})) + Ok(ValidatedUserAction(UserAction { timestamp, username, action: Action::PlayCard { card } })) } } (State::CutForCall, _) => Err(ActionError::Dealing), @@ -165,19 +157,13 @@ impl Game for KnockOutWhist { } } - fn take_action(&mut self, ValidatedUserAction(UserAction{username, action, ..}): ValidatedUserAction) -> Result<(), ActionError> { + fn take_action(&mut self, ValidatedUserAction(UserAction { username, action, .. }): ValidatedUserAction) -> Result<(), ActionError> { self.actions_len += 1; self.rng.advance(); match (self.state, action) { - (_, Action::AddOn{..}) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet{..}) => { - Err(ActionError::InvalidActionForGameType) - } - (State::NotStarted, Action::Join{seat, ..}) => { - self.seats.add_player(seat, username) - } - (State::NotStarted, Action::Leave) => { - self.seats.remove_player(username) - } + (_, Action::AddOn { .. }) | (_, Action::Fold) | (_, Action::TimeoutFold) | (_, Action::Bet { .. }) => Err(ActionError::InvalidActionForGameType), + (State::NotStarted, Action::Join { seat, .. }) => self.seats.add_player(seat, username), + (State::NotStarted, Action::Leave) => self.seats.remove_player(username), (_, Action::NextToDeal) => { self.dealer = Some(username); self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect(); @@ -190,7 +176,7 @@ impl Game for KnockOutWhist { self.state = State::Dealing; Ok(()) } - (State::Dealing, Action::ReceiveCard{card: Some(card)}) => { + (State::Dealing, Action::ReceiveCard { card: Some(card) }) => { self.deck.remove(&card); self.hands.entry(username).or_default().insert(card); if self.hands.values().all(|hand| hand.len() == self.cards_to_deal as usize) { @@ -200,7 +186,7 @@ impl Game for KnockOutWhist { } Ok(()) } - (State::Dealing, Action::CommunityCard{card}) => { + (State::Dealing, Action::CommunityCard { card }) => { self.trump_card = Some(card); self.trumps = Some(card.suit); Ok(()) @@ -216,14 +202,14 @@ impl Game for KnockOutWhist { } Ok(()) } - (State::ChoosingTrumps, Action::ChooseTrumps{suit}) => { + (State::ChoosingTrumps, Action::ChooseTrumps { suit }) => { self.trumps = Some(suit); self.call = None; self.state = State::Playing; self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer)); Ok(()) } - (State::Playing, Action::PlayCard{card}) => { + (State::Playing, Action::PlayCard { card }) => { if let Some(hand) = self.hands.get_mut(&username) { hand.remove(&card); } @@ -260,16 +246,14 @@ impl Game for KnockOutWhist { } Ok(()) } - (State::Playing, Action::KnockedOut) => { - self.seats.remove_player(username) - } + (State::Playing, Action::KnockedOut) => self.seats.remove_player(username), (State::Playing, Action::WinGame) => { self.winners.clear(); self.winners.insert(username); self.state = State::Completed; Ok(()) } - (State::CutForCall, Action::RevealCard{card}) => { + (State::CutForCall, Action::RevealCard { card }) => { self.deck.remove(&card); self.hands.entry(username).or_default().insert(card); if self.hands.values().map(HashSet::len).sum::() == self.winners.len() { @@ -306,11 +290,10 @@ impl Game for KnockOutWhist { let mut rng = self.rng.clone(); match self.state { State::NotStarted => { - if self.seats.players_len() == self.settings.max_players as usize { // TODO + if self.seats.players_len() == self.settings.max_players as usize { + // TODO if let Some(username) = rng.choose_from(self.seats.player_set()) { - return DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) - ); + return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal })); } } DealerAction::WaitForPlayer @@ -318,59 +301,45 @@ impl Game for KnockOutWhist { State::Dealing => { if let Some(username) = self.receiver { let card = rng.choose_from(&self.deck).cloned(); - DealerAction::TakeAction( - 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) { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::CommunityCard{card}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::CommunityCard { card } })) } else { error!("Expected to deal a card but none were left in deck"); DealerAction::Leave } } - (Some(_), _) | (None, Some(_)) => DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::EndDeal}) - ), + (Some(_), _) | (None, Some(_)) => { + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::EndDeal })) + } } } else { error!("Expected to deal a card but there was no dealer"); DealerAction::Leave } } - State::ChoosingTrumps => { - DealerAction::WaitForPlayer - } + State::ChoosingTrumps => 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 DealerAction::TakeAction( - 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 DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinGame}) - ); + return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinGame })); } } if let Some(username) = self.call { - return DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall}) - ); + return DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinCall })); } DealerAction::WaitForPlayer } else if let Some(username) = self.trick_winner() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinTrick}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinTrick })) } else { DealerAction::WaitForPlayer } @@ -378,17 +347,13 @@ impl Game for KnockOutWhist { State::CutForCall => { if let Some(username) = self.receiver { if let Some(card) = rng.choose_from(&self.deck).cloned() { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::RevealCard{card}}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::RevealCard { card } })) } else { error!("Expected to cut for call but there were no cards left in the deck"); DealerAction::Leave } } else if let Some(username) = self.call { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::WinCall}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::WinCall })) } else { error!("Cutting for call but there is nobody to receive a card"); DealerAction::Leave @@ -396,17 +361,13 @@ impl Game for KnockOutWhist { } State::RoundCompleted => { if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { - DealerAction::TakeAction( - ValidatedUserAction(UserAction{timestamp, username, action: Action::NextToDeal}) - ) + DealerAction::TakeAction(ValidatedUserAction(UserAction { timestamp, username, action: Action::NextToDeal })) } else { error!("Round completed but there is nobody to deal"); DealerAction::Leave } } - State::Completed => { - DealerAction::Leave - } + State::Completed => DealerAction::Leave, } } } @@ -419,7 +380,7 @@ mod tests { let mut game = KnockOutWhist::new(0, settings, seed); for action in actions { match action.action { - Action::Join{..} | Action::PlayCard{..} | Action::ChooseTrumps{..} => { + Action::Join { .. } | Action::PlayCard { .. } | Action::ChooseTrumps { .. } => { let validated = game.validate_action(action.clone()).unwrap(); assert_eq!(ValidatedUserAction(action), validated); game.take_action(validated).unwrap(); diff --git a/src/main.rs b/src/main.rs index 9f3e20a..b1dd49a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,25 @@ -#[macro_use] extern crate log; -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; use std::collections::HashSet; use std::convert::TryFrom; use std::mem::swap; use async_std::task::spawn; -use futures::{channel::mpsc::{channel, Receiver}, future::{Either, select}, select, FutureExt, SinkExt, StreamExt, pin_mut, stream::{self, FuturesUnordered}}; +use futures::{ + channel::mpsc::{channel, Receiver}, + future::{select, Either}, + pin_mut, select, + stream::{self, FuturesUnordered}, + FutureExt, SinkExt, StreamExt, +}; use redis::{aio::PubSub, Client, Msg}; use signal_hook::consts::signal::*; use signal_hook_async_std::Signals; -use tide::{Body, Request, Error, Response, StatusCode}; use tide::utils::After; +use tide::{Body, Error, Request, Response, StatusCode}; //use tide_rustls::TlsListener; use tide_websockets::{Message, WebSocket, WebSocketConnection}; @@ -56,7 +64,7 @@ pub async fn handle_client_interest(mut connection: PubSub, mut new_clients: Rec let mut next_interest = connection_on_message.next().fuse(); let mut next_client = new_clients.next().fuse(); let mut next_client_interest = FuturesUnordered::new(); - for (index, Client{ref mut sender, ..}) in clients.iter_mut().enumerate() { + for (index, Client { ref mut sender, .. }) in clients.iter_mut().enumerate() { next_client_interest.push(sender.register_interest.next().map(move |interest| (index, interest))); } let mut next_client_interest = next_client_interest.select_next_some(); @@ -85,15 +93,15 @@ pub async fn handle_client_interest(mut connection: PubSub, mut new_clients: Rec } action } { - Action::AddClient{sender} => { + Action::AddClient { sender } => { debug!("handle_client_interest: Action::AddClient {{ clients[{}] }}", clients.len()); - clients.push(Client{sender, interests: HashSet::new()}); + clients.push(Client { sender, interests: HashSet::new() }); } - Action::RegisterInterest{index, mut client_interests} => { + Action::RegisterInterest { index, mut client_interests } => { debug!("handle_client_interest: Action::RegisterInterest{{ client_interests: {:?}, index: {:?} }}", client_interests, index); - let interests_prior: HashSet = clients.iter().map(|Client{ref interests, ..}| interests.clone()).flatten().collect(); + let interests_prior: HashSet = clients.iter().map(|Client { ref interests, .. }| interests.clone()).flatten().collect(); swap(&mut clients[index].interests, &mut client_interests); - let interests_after: HashSet = clients.iter().map(|Client{ref interests, ..}| interests.clone()).flatten().collect(); + let interests_after: HashSet = clients.iter().map(|Client { ref interests, .. }| interests.clone()).flatten().collect(); for interest in &interests_after - &interests_prior { debug!("handle_client_interest: Subscribing to {:?}", interest); if let Err(err) = connection.subscribe(interest).await { @@ -115,11 +123,11 @@ pub async fn handle_client_interest(mut connection: PubSub, mut new_clients: Rec } } } - Action::SendInterest{interest} => { + Action::SendInterest { interest } => { debug!("handle_client_interest: Action::SendInterest {{ interest: {:?} }}", interest); match TryFrom::try_from(interest) { Ok(interest) => { - for (index, Client{sender, interests}) in clients.iter_mut().enumerate() { + for (index, Client { sender, interests }) in clients.iter_mut().enumerate() { if interests.contains(&interest) { debug!("handle_client_interest: Sending {:?} to clients[{}]", interest, index); if let Err(err) = sender.interest.send(interest.clone()).await { @@ -127,18 +135,18 @@ pub async fn handle_client_interest(mut connection: PubSub, mut new_clients: Rec } } } - }, - Err(ClientInterestFromMsgError::TimeoutMessageWasNotExpired) => {}, - Err(ClientInterestFromMsgError::InvalidChannelName{channel_name}) => { + } + Err(ClientInterestFromMsgError::TimeoutMessageWasNotExpired) => {} + Err(ClientInterestFromMsgError::InvalidChannelName { channel_name }) => { error!("handle_client_interest: Failed to interest {} to ClientInterest", channel_name); } } } - Action::RemoveClient{index} => { + Action::RemoveClient { index } => { debug!("handle_client_interest: Action::RemoveClient {{ index: {:?} }}", index); - let interests_prior: HashSet = clients.iter().map(|Client{ref interests, ..}| interests.clone()).flatten().collect(); + let interests_prior: HashSet = clients.iter().map(|Client { ref interests, .. }| interests.clone()).flatten().collect(); clients.remove(index); - let interests_after: HashSet = clients.iter().map(|Client{ref interests, ..}| interests.clone()).flatten().collect(); + let interests_after: HashSet = clients.iter().map(|Client { ref interests, .. }| interests.clone()).flatten().collect(); for interest in &interests_prior - &interests_after { debug!("handle_client_interest: Unsubscribing from {:?}", interest); if let Err(err) = connection.unsubscribe(interest).await { @@ -186,7 +194,9 @@ async fn handle_new_games(server: Server) -> Result<(), std::io::Error> { return Err(std::io::Error::new(std::io::ErrorKind::Other, err)); } } - if let Some(ClientInterest::GameList) = update_stream.next().await { continue; } + if let Some(ClientInterest::GameList) = update_stream.next().await { + continue; + } return Ok(()); } } @@ -202,7 +212,7 @@ pub async fn handle_websocket_request(request: Request, stream: WebSocke Either::Left(Ok(Message::Text(input))) => { let response = match serde_json::from_str(&input) { Ok(message) => client.apply_message(message).await, - Err(err) => ServerMessage::ProtocolError{reason: err.to_string()}, + Err(err) => ServerMessage::ProtocolError { reason: err.to_string() }, }; stream.send_json(&response).await?; } @@ -241,12 +251,8 @@ async fn handle_signals(mut signals: Signals) -> Result<(), std::io::Error> { async fn serve_404(response: Response) -> Result { match response.status() { - StatusCode::NotFound => - Ok(Response::builder(404) - .body(Body::from_file("site/404.html").await?) - .content_type("text/html") - .build()), - _ => Ok(response) + StatusCode::NotFound => Ok(Response::builder(404).body(Body::from_file("site/404.html").await?).content_type("text/html").build()), + _ => Ok(response), } } @@ -282,7 +288,10 @@ async fn main() -> Result<(), Error> { let app = app.listen("0.0.0.0:8080"); pin_mut!(app, handle_client_interest, handle_new_games, signal_handler); - select(select(app, select(handle_client_interest, handle_new_games).map(|f| f.factor_first().0)).map(|f| f.factor_first().0), signal_handler).await.factor_first().0?; + select(select(app, select(handle_client_interest, handle_new_games).map(|f| f.factor_first().0)).map(|f| f.factor_first().0), signal_handler) + .await + .factor_first() + .0?; info!("Pokerwave shut down gracefully."); diff --git a/src/rng.rs b/src/rng.rs index 45f7ecc..5d29227 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,8 +1,8 @@ -use std::cmp::{Ord, Eq}; +use std::cmp::{Eq, Ord}; use getrandom::getrandom; use itertools::Itertools; -use rand::{SeedableRng, CryptoRng, Rng, RngCore, Error, seq::IteratorRandom}; +use rand::{seq::IteratorRandom, CryptoRng, Error, Rng, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] @@ -10,7 +10,7 @@ use rand_chacha::ChaCha20Rng; pub enum Seed { ChaCha20 { #[serde(with = "hex")] - seed: [u8; 32] + seed: [u8; 32], }, } @@ -18,14 +18,12 @@ impl Seed { pub fn cha_cha_20_from_entropy() -> Self { let mut seed = [0u8; 32]; getrandom(&mut seed).expect("getrandom failed when generating random seed"); - Self::ChaCha20{seed} + Self::ChaCha20 { seed } } pub fn into_rng(self) -> WaveRng { match self { - Seed::ChaCha20{seed} => { - WaveRng::ChaCha20(ChaCha20Rng::from_seed(seed)) - } + Seed::ChaCha20 { seed } => WaveRng::ChaCha20(ChaCha20Rng::from_seed(seed)), } } } @@ -42,7 +40,7 @@ impl WaveRng { }; } - pub fn choose_from>(&mut self, elements: I) -> Option { + pub fn choose_from>(&mut self, elements: I) -> Option { elements.into_iter().sorted().choose_stable(self) } } @@ -92,9 +90,8 @@ mod tests { *i = rng.next_u32(); } let expected = [ - 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, - 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, - 0x69b687c3, 0x8665eeb2, + 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, + 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2, ]; assert_eq!(results, expected); @@ -102,9 +99,8 @@ mod tests { *i = rng.next_u32(); } let expected = [ - 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, - 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, - 0x1f0ae1ac, 0x6f4d794b, + 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, + 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b, ]; assert_eq!(results, expected); } diff --git a/src/seats.rs b/src/seats.rs index 7bc7fdc..66b489d 100644 --- a/src/seats.rs +++ b/src/seats.rs @@ -10,15 +10,13 @@ pub struct Seats { impl Seats { pub fn new() -> Self { - Self { - players: BTreeMap::new(), - } + Self { players: BTreeMap::new() } } pub fn add_player(&mut self, seat: u32, username: Username) -> Result<(), ActionError> { match self.players.insert(seat, username) { Some(_) => Err(ActionError::SeatNotAvailable), - None => Ok(()) + None => Ok(()), } } @@ -34,7 +32,7 @@ impl Seats { } fn player_after_seat(&self, seat: u32) -> Option { - if let Some((_, &name)) = self.players.range(seat+1..).next() { + if let Some((_, &name)) = self.players.range(seat + 1..).next() { Some(name) } else if let Some((_, &name)) = self.players.range(..seat).next() { Some(name) @@ -46,7 +44,7 @@ impl Seats { pub fn player_after_where(&self, username: Username, condition: impl Fn(Username) -> bool) -> Option { for (&seat, &player) in &self.players { if player == username { - for (_, &name) in self.players.range(seat+1..) { + for (_, &name) in self.players.range(seat + 1..) { if condition(name) { return Some(name); } diff --git a/src/server.rs b/src/server.rs index 3f804c7..a45956a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,9 +1,12 @@ use std::collections::HashSet; use std::convert::TryFrom; -use futures::{channel::mpsc::{Receiver, Sender, channel}, SinkExt}; -use redis::{AsyncCommands, ErrorKind, FromRedisValue, Msg, RedisError, RedisResult, RedisWrite, Script, ToRedisArgs, Value, aio::MultiplexedConnection, cmd}; -use serde::{Serialize, Deserialize}; +use futures::{ + channel::mpsc::{channel, Receiver, Sender}, + SinkExt, +}; +use redis::{aio::MultiplexedConnection, cmd, AsyncCommands, ErrorKind, FromRedisValue, Msg, RedisError, RedisResult, RedisWrite, Script, ToRedisArgs, Value}; +use serde::{Deserialize, Serialize}; use crate::auth::Auth; use crate::client::ClientInterest; @@ -35,14 +38,14 @@ fn client_interest_channel() -> (ClientInterestSender, ClientInterestReceiver) { const INTEREST_CHANNEL_BUFFER: usize = 1024; let (register_interest_tx, register_interest_rx) = channel(REGISTER_INTEREST_CHANNEL_BUFFER); let (interest_tx, interest_rx) = channel(INTEREST_CHANNEL_BUFFER); - let tx = ClientInterestSender{register_interest: register_interest_rx, interest: interest_tx}; - let rx = ClientInterestReceiver{register_interest: register_interest_tx, interest: interest_rx}; + let tx = ClientInterestSender { register_interest: register_interest_rx, interest: interest_tx }; + let rx = ClientInterestReceiver { register_interest: register_interest_tx, interest: interest_rx }; (tx, rx) } #[derive(Debug)] pub enum ClientInterestFromMsgError { - InvalidChannelName{channel_name: String}, + InvalidChannelName { channel_name: String }, TimeoutMessageWasNotExpired, } @@ -54,41 +57,42 @@ impl TryFrom for ClientInterest { Ok(ClientInterest::GameList) } else if let Some(username) = channel_name.strip_prefix("__keyspace@0__:user:") { match username.parse() { - Ok(username) => Ok(ClientInterest::User{username}), - Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()}), + Ok(username) => Ok(ClientInterest::User { username }), + Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName { channel_name: channel_name.to_string() }), } } else if let Some(key) = channel_name.strip_prefix("__keyspace@0__:game:") { if let Some(id) = key.strip_suffix(":actions") { match id.parse() { - Ok(id) => Ok(ClientInterest::Game{id}), - Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()}), + Ok(id) => Ok(ClientInterest::Game { id }), + Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName { channel_name: channel_name.to_string() }), } } else if let Some(id) = key.strip_suffix(":timeout") { match msg.get_payload::() { Ok(str) if str == "expired" => match id.parse() { - Ok(id) => Ok(ClientInterest::Timeout{id}), - Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()}), + Ok(id) => Ok(ClientInterest::Timeout { id }), + Err(_) => Err(ClientInterestFromMsgError::InvalidChannelName { channel_name: channel_name.to_string() }), }, _ => Err(ClientInterestFromMsgError::TimeoutMessageWasNotExpired), } } else { - Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()}) + Err(ClientInterestFromMsgError::InvalidChannelName { channel_name: channel_name.to_string() }) } } else { - Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()}) + Err(ClientInterestFromMsgError::InvalidChannelName { channel_name: channel_name.to_string() }) } } } impl ToRedisArgs for ClientInterest { fn write_redis_args(&self, out: &mut W) - where W: ?Sized + RedisWrite + where + W: ?Sized + RedisWrite, { match self { ClientInterest::GameList => out.write_arg(b"__keyspace@0__:game:list"), - ClientInterest::Game{id} => out.write_arg_fmt(format!("__keyspace@0__:game:{}:actions", id)), - ClientInterest::User{username} => out.write_arg_fmt(format!("__keyspace@0__:user:{}", username)), - ClientInterest::Timeout{id} => out.write_arg_fmt(format!("__keyspace@0__:game:{}:timeout", id)), + ClientInterest::Game { id } => out.write_arg_fmt(format!("__keyspace@0__:game:{}:actions", id)), + ClientInterest::User { username } => out.write_arg_fmt(format!("__keyspace@0__:user:{}", username)), + ClientInterest::Timeout { id } => out.write_arg_fmt(format!("__keyspace@0__:game:{}:timeout", id)), } } } @@ -108,19 +112,16 @@ const TAKE_ACTION_LUA_SCRIPT: &'static str = r#" impl Server { pub fn new(redis: MultiplexedConnection, register_update_stream: Sender) -> Self { - Self { - redis, - register_update_stream - } + Self { redis, register_update_stream } } pub async fn new_state(&self) -> (ServerState, Receiver) { - let (client_interest_sender, ClientInterestReceiver{register_interest, interest}) = client_interest_channel(); + let (client_interest_sender, ClientInterestReceiver { register_interest, interest }) = client_interest_channel(); if let Err(err) = self.register_update_stream.clone().send(client_interest_sender).await { error!("new_state: Send failed: {}", err); } let take_action_script = Script::new(TAKE_ACTION_LUA_SCRIPT); - (ServerState{redis: self.redis.clone(), register_interest, take_action_script}, interest) + (ServerState { redis: self.redis.clone(), register_interest, take_action_script }, interest) } } @@ -253,7 +254,7 @@ impl FromRedisValue for ActionStatus { match value { Value::Int(0) => Ok(ActionStatus::Interrupted), Value::Int(_) => Ok(ActionStatus::Committed), - _ => Err(RedisError::from((ErrorKind::TypeError, "ActionStatus may only be converted from an integer"))) + _ => Err(RedisError::from((ErrorKind::TypeError, "ActionStatus may only be converted from an integer"))), } } } @@ -268,29 +269,27 @@ impl AsJson { } impl FromRedisValue for AsJson - where T: for<'a> Deserialize<'a> +where + T: for<'a> Deserialize<'a>, { fn from_redis_value(value: &Value) -> RedisResult { match value { - Value::Data(ref bytes) => serde_json::from_slice(bytes).map(AsJson) - .map_err(|err| { - error!("Failed to parse JSON from redis: {}", err); - RedisError::from(( - ErrorKind::TypeError, - "Failed to parse as JSON", - err.to_string() - )) - }), + Value::Data(ref bytes) => serde_json::from_slice(bytes).map(AsJson).map_err(|err| { + error!("Failed to parse JSON from redis: {}", err); + RedisError::from((ErrorKind::TypeError, "Failed to parse as JSON", err.to_string())) + }), _ => Err(RedisError::from((ErrorKind::TypeError, "Value was not Value::Data"))), } } } impl ToRedisArgs for AsJson - where T: Serialize +where + T: Serialize, { fn write_redis_args(&self, out: &mut W) - where W: ?Sized + RedisWrite + where + W: ?Sized + RedisWrite, { match serde_json::to_vec(&self.0) { Ok(bytes) => out.write_arg(&bytes), @@ -311,50 +310,32 @@ mod tests { #[test] fn convert_channel_names_to_interest() { - let msg = Value::Bulk(vec![ - Value::Data(b"message".to_vec()), - Value::Data(b"__keyspace@0__:game:list".to_vec()), - Value::Data(b"expire".to_vec()), - ]); + let msg = Value::Bulk(vec![Value::Data(b"message".to_vec()), Value::Data(b"__keyspace@0__:game:list".to_vec()), Value::Data(b"expire".to_vec())]); let msg = Msg::from_value(&msg).unwrap(); let interest = ClientInterest::try_from(msg).unwrap(); assert_eq!(ClientInterest::GameList, interest); - let msg = Value::Bulk(vec![ - Value::Data(b"message".to_vec()), - Value::Data(b"__keyspace@0__:user:player1".to_vec()), - Value::Data(b"set".to_vec()), - ]); + let msg = Value::Bulk(vec![Value::Data(b"message".to_vec()), Value::Data(b"__keyspace@0__:user:player1".to_vec()), Value::Data(b"set".to_vec())]); let msg = Msg::from_value(&msg).unwrap(); let interest = ClientInterest::try_from(msg).unwrap(); - assert_eq!(ClientInterest::User{username: "player1".parse().unwrap()}, interest); + assert_eq!(ClientInterest::User { username: "player1".parse().unwrap() }, interest); - let msg = Value::Bulk(vec![ - Value::Data(b"message".to_vec()), - Value::Data(b"__keyspace@0__:game:12345:actions".to_vec()), - Value::Data(b"rpush".to_vec()), - ]); + let msg = + Value::Bulk(vec![Value::Data(b"message".to_vec()), Value::Data(b"__keyspace@0__:game:12345:actions".to_vec()), Value::Data(b"rpush".to_vec())]); let msg = Msg::from_value(&msg).unwrap(); let interest = ClientInterest::try_from(msg).unwrap(); - assert_eq!(ClientInterest::Game{id: 12345}, interest); + assert_eq!(ClientInterest::Game { id: 12345 }, interest); - let msg = Value::Bulk(vec![ - Value::Data(b"message".to_vec()), - Value::Data(b"__keyspace@0__:game:12345:timeout".to_vec()), - Value::Data(b"expired".to_vec()), - ]); + let msg = + Value::Bulk(vec![Value::Data(b"message".to_vec()), Value::Data(b"__keyspace@0__:game:12345:timeout".to_vec()), Value::Data(b"expired".to_vec())]); let msg = Msg::from_value(&msg).unwrap(); let interest = ClientInterest::try_from(msg).unwrap(); - assert_eq!(ClientInterest::Timeout{id: 12345}, interest); + assert_eq!(ClientInterest::Timeout { id: 12345 }, interest); } #[test] fn ignores_timeout_key_set() { - let msg = Value::Bulk(vec![ - Value::Data(b"message".to_vec()), - Value::Data(b"__keyspace@0__:game:12345:timeout".to_vec()), - Value::Data(b"set".to_vec()), - ]); + let msg = Value::Bulk(vec![Value::Data(b"message".to_vec()), Value::Data(b"__keyspace@0__:game:12345:timeout".to_vec()), Value::Data(b"set".to_vec())]); let msg = Msg::from_value(&msg).unwrap(); assert!(ClientInterest::try_from(msg).is_err()); } diff --git a/src/username.rs b/src/username.rs index cdf9d9c..b505c74 100644 --- a/src/username.rs +++ b/src/username.rs @@ -1,7 +1,11 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::str::{self, FromStr, Utf8Error}; -use serde::{Serialize, Deserialize, Serializer, Deserializer, ser::Error, de::{self, Visitor}}; +use serde::{ + de::{self, Visitor}, + ser::Error, + Deserialize, Deserializer, Serialize, Serializer, +}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(align(8))] @@ -14,19 +18,12 @@ impl Username { const MAX_LENGTH: usize = 32; fn as_str(&self) -> Result<&str, Utf8Error> { - str::from_utf8(&self.username) - .map(|str| str.trim_end_matches('\0')) + str::from_utf8(&self.username).map(|str| str.trim_end_matches('\0')) } } -pub const DEALER: Username = Username { - username: [ - b'd', b'e', b'a', b'l', b'e', b'r', 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - ], -}; +pub const DEALER: Username = + Username { username: [b'd', b'e', b'a', b'l', b'e', b'r', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }; impl Display for Username { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -61,14 +58,15 @@ impl FromStr for Username { } else { let mut username = [0; 32]; username[0..len].copy_from_slice(bytes); - Ok(Username{username}) + Ok(Username { username }) } } } impl Serialize for Username { fn serialize(&self, serializer: S) -> Result - where S: Serializer + where + S: Serializer, { match self.as_str() { Ok(str) => serializer.serialize_str(str), @@ -79,7 +77,8 @@ impl Serialize for Username { impl<'de> Deserialize<'de> for Username { fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> + where + D: Deserializer<'de>, { struct StrVisitor; impl Visitor<'_> for StrVisitor { @@ -89,7 +88,8 @@ impl<'de> Deserialize<'de> for Username { } fn visit_str(self, str: &str) -> Result - where E: de::Error + where + E: de::Error, { str.parse().map_err(E::custom) } @@ -105,7 +105,10 @@ mod tests { #[test] fn deserialize_username() { let username: Username = serde_json::from_str(r#""user1234""#).unwrap(); - assert_eq!(username, Username{username: [b'u', b's', b'e', b'r', b'1', b'2', b'3', b'4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}); + assert_eq!( + username, + Username { username: [b'u', b's', b'e', b'r', b'1', b'2', b'3', b'4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } + ); assert!(serde_json::from_str::(r#""\0""#).is_err()); assert!(serde_json::from_str::(r#""this_is_a_very_long_username_that_fails_to_parse""#).is_err()); } diff --git a/src/util/dedup.rs b/src/util/dedup.rs index e68d5ce..056bfe1 100644 --- a/src/util/dedup.rs +++ b/src/util/dedup.rs @@ -14,17 +14,14 @@ pub struct DedupReady { item: Option, } -impl DedupReady { +impl DedupReady { fn new(stream: S) -> Self { - Self { - stream: stream.fuse(), - item: None, - } + Self { stream: stream.fuse(), item: None } } } impl Stream for DedupReady -where +where S::Item: PartialEq, { type Item = S::Item; @@ -40,12 +37,12 @@ where return Poll::Ready(Some(next)); } Poll::Ready(None) | Poll::Pending => return Poll::Ready(this.item.take()), - } + }, None => match this.stream.as_mut().poll_next(cx) { Poll::Ready(Some(item)) => *this.item = Some(item), Poll::Ready(None) => return Poll::Ready(None), Poll::Pending => return Poll::Pending, - } + }, } } } @@ -60,7 +57,7 @@ where } } -pub trait DedupReadyExt : Stream { +pub trait DedupReadyExt: Stream { fn dedup_ready(self) -> DedupReady where Self::Item: PartialEq, @@ -76,7 +73,12 @@ impl DedupReadyExt for S {} mod tests { use super::*; - use futures::{channel::mpsc::channel, pin_mut, stream::{empty, once, iter}, SinkExt}; + use futures::{ + channel::mpsc::channel, + pin_mut, + stream::{empty, iter, once}, + SinkExt, + }; #[async_std::test] async fn empty_stream() { diff --git a/src/util/max.rs b/src/util/max.rs index b7ef75e..5dd6469 100644 --- a/src/util/max.rs +++ b/src/util/max.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use std::convert::identity; use std::iter::FromIterator; -pub trait IteratorMaxItems : Iterator + Sized { +pub trait IteratorMaxItems: Iterator + Sized { fn max_items(self) -> Vec where Self::Item: Ord, @@ -39,11 +39,7 @@ pub trait IteratorMaxItems : Iterator + Sized { move |(_, x)| g(x) } - self.map(key(f)) - .max_items_by(compare) - .into_iter() - .map(snd_map(g)) - .collect() + self.map(key(f)).max_items_by(compare).into_iter().map(snd_map(g)).collect() } fn max_items_by(self, compare: F) -> Vec @@ -59,7 +55,10 @@ pub trait IteratorMaxItems : Iterator + Sized { }; match ordering { Ordering::Greater => vec![t], - Ordering::Equal => {vec.push(t); vec}, + Ordering::Equal => { + vec.push(t); + vec + } Ordering::Less => vec, } } @@ -69,7 +68,7 @@ pub trait IteratorMaxItems : Iterator + Sized { } } -impl IteratorMaxItems for T where T: Iterator{} +impl IteratorMaxItems for T where T: Iterator {} #[cfg(test)] mod tests { @@ -98,15 +97,13 @@ mod tests { #[test] fn multiple_max_items_by_key() { - let result: Vec<_> = [("a", 5), ("b", 4), ("c", 3), ("d", 2), ("e", 5)].iter() - .max_items_by_key(|&&(_, x)| x); + let result: Vec<_> = [("a", 5), ("b", 4), ("c", 3), ("d", 2), ("e", 5)].iter().max_items_by_key(|&&(_, x)| x); assert_eq!(vec![&("a", 5), &("e", 5)], result); } #[test] fn multiple_max_items_by_key_map() { - let result: Vec<_> = [("a", 5), ("b", 4), ("c", 3), ("d", 2), ("e", 5)].iter() - .max_items_by_key_map(|&&(_, x)| x, |&(s, _)| s); + let result: Vec<_> = [("a", 5), ("b", 4), ("c", 3), ("d", 2), ("e", 5)].iter().max_items_by_key_map(|&&(_, x)| x, |&(s, _)| s); assert_eq!(vec!["a", "e"], result); } } diff --git a/src/util/timestamp.rs b/src/util/timestamp.rs index 9f5e554..7b7f8e0 100644 --- a/src/util/timestamp.rs +++ b/src/util/timestamp.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display, Formatter}; -use redis::{FromRedisValue, Value, RedisResult, ToRedisArgs, RedisWrite, NumericBehavior, RedisError, ErrorKind}; +use redis::{ErrorKind, FromRedisValue, NumericBehavior, RedisError, RedisResult, RedisWrite, ToRedisArgs, Value}; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] @@ -28,7 +28,7 @@ impl FromRedisValue for Timestamp { Ok(Timestamp(secs * 1000 + micros / 1000)) } _ => Err(RedisError::from((ErrorKind::TypeError, "Expected exactly 2 integers, secs and micros"))), - } + }, _ => Err(RedisError::from((ErrorKind::TypeError, "Expected exactly 2 integers, secs and micros"))), } } @@ -36,7 +36,8 @@ impl FromRedisValue for Timestamp { impl ToRedisArgs for Timestamp { fn write_redis_args(&self, out: &mut W) - where W: ?Sized + RedisWrite + where + W: ?Sized + RedisWrite, { self.0.write_redis_args(out) } @@ -56,10 +57,7 @@ mod tests { #[test] fn from_redis_time_command() { - let value = Value::Bulk(vec![ - Value::Data(b"1614792013".to_vec()), - Value::Data(b"938726".to_vec()), - ]); + let value = Value::Bulk(vec![Value::Data(b"1614792013".to_vec()), Value::Data(b"938726".to_vec())]); let timestamp = Timestamp::from_redis_value(&value).unwrap(); assert_eq!(Timestamp(1614792013938), timestamp); } -- 2.34.1