From: Geoffrey Allott Date: Sat, 27 Feb 2021 23:21:22 +0000 (+0000) Subject: add showdown logic X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=86269031790a5afe29947c376a1915c39fea726f;p=pokerwave.git add showdown logic --- diff --git a/src/game/poker/holdem.rs b/src/game/poker/holdem.rs index c6b1da7..905be7d 100644 --- a/src/game/poker/holdem.rs +++ b/src/game/poker/holdem.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; use itertools::Itertools; use rand::seq::IteratorRandom; @@ -7,9 +8,12 @@ use rand::thread_rng; use crate::card::{Card, Suit, FIFTY_TWO_CARD_DECK}; use crate::seats::Seats; use crate::username::Username; +use crate::util::max::IteratorMaxItems; use super::super::{Action, ActionError, Game, UserAction, ValidatedUserAction}; +use super::classify::rank_7_card_hand; + #[derive(Copy, Clone, Debug)] enum State { NotStarted, @@ -163,7 +167,7 @@ impl Game for TexasHoldEm { self.stacks.insert(username, chips); self.seats.add_player(seat, username) } - (State::NotStarted, Action::Leave) => { + (State::NotStarted, Action::Leave) | (_, Action::KnockedOut) => { self.stacks.remove(&username); self.seats.remove_player(username) } @@ -236,6 +240,7 @@ impl Game for TexasHoldEm { self.pot += *self.bets.entry(username).or_default(); self.bets.remove(&username); self.players.remove(&username); + self.hands.remove(&username); self.active = self.seats.player_after_in(username, &self.players); Ok(()) } @@ -314,9 +319,13 @@ impl Game for TexasHoldEm { } else if self.seats.players_len() == 1 { self.players.iter().next() .map(|&username| ValidatedUserAction(UserAction{username, action: Action::WinGame})) + } else if let Some((&username, _)) = self.stacks.iter().find(|&(_, &stack)| stack == 0) { + Some(ValidatedUserAction(UserAction{username, action: Action::KnockedOut})) + } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { + Some(ValidatedUserAction(UserAction{username, action: Action::NextToDeal})) } else { - self.stacks.iter().find(|&(_, &stack)| stack == 0) - .map(|(&username, _)| ValidatedUserAction(UserAction{username, action: Action::KnockedOut})) + error!("Logic error: no dealer could be chosen: {:#?}", self); + None } } else { None @@ -327,8 +336,23 @@ impl Game for TexasHoldEm { self.deck.iter().choose(&mut rng).map(|&card| ValidatedUserAction(UserAction{username, action: Action::CommunityCard{card}}))) } + State::Showdown if self.pot == 0 => { + if let Some((&username, _)) = self.stacks.iter().find(|&(_, &stack)| stack == 0) { + Some(ValidatedUserAction(UserAction{username, action: Action::KnockedOut})) + } else if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { + Some(ValidatedUserAction(UserAction{username, action: Action::NextToDeal})) + } else { + error!("Logic error: no dealer could be chosen: {:#?}", self); + None + } + } State::Showdown => { - todo!() + let winning_hands = 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); + winning_hands.first() + .map(|&(username, _)| ValidatedUserAction(UserAction{username, action: Action::WinHand{chips: self.pot / winning_hands.len() as u64}})) } State::Completed => None, } diff --git a/src/main.rs b/src/main.rs index a417cb9..00cbf24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ mod hands; mod seats; mod server; mod username; +mod util; use crate::api::ServerMessage; use crate::client::{ClientInterest, ConnectionState}; diff --git a/src/util/max.rs b/src/util/max.rs new file mode 100644 index 0000000..7a52101 --- /dev/null +++ b/src/util/max.rs @@ -0,0 +1,86 @@ +use std::cmp::Ordering; + +pub trait IteratorMaxItems : Iterator + Sized { + fn max_items(self) -> Vec + where + Self::Item: Ord, + { + self.max_items_by(Ord::cmp) + } + + fn max_items_by_key(self, f: F) -> Vec + where + F: FnMut(&Self::Item) -> B + { + #[inline] + fn key(mut f: impl FnMut(&T) -> B) -> impl FnMut(T) -> (B, T) { + move |x| (f(&x), x) + } + + #[inline] + fn compare((x_p, _): &(B, T), (y_p, _): &(B, T)) -> Ordering { + x_p.cmp(y_p) + } + + self.map(key(f)) + .max_items_by(compare) + .into_iter() + .map(|(_, x)| x) + .collect() + } + + fn max_items_by(self, compare: F) -> Vec + where + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + #[inline] + fn fold(mut compare: impl FnMut(&T, &T) -> Ordering) -> impl FnMut(Vec, T) -> Vec { + move |mut vec, t| { + let ordering = match vec.first() { + Some(item) => compare(&t, item), + None => Ordering::Greater, + }; + match ordering { + Ordering::Greater => vec![t], + Ordering::Equal => {vec.push(t); vec}, + Ordering::Less => vec, + } + } + } + + self.fold(Vec::new(), fold(compare)) + } +} + +impl IteratorMaxItems for T where T: Iterator{} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_max_items() { + assert_eq!(0, [(); 0].iter().max_items().len()); + } + + #[test] + fn single_max_item() { + assert_eq!(vec![&1], [1].iter().max_items()); + assert_eq!(vec![&5], [1, 2, 3, 4, 5].iter().max_items()); + assert_eq!(vec![&5], [5, 4, 3, 2, 1].iter().max_items()); + assert_eq!(vec![&5], [3, 2, 5, 4, 1].iter().max_items()); + } + + #[test] + fn multiple_max_items() { + assert_eq!(vec![&1, &1], [1, 1].iter().max_items()); + assert_eq!(vec![&5, &5], [5, 2, 3, 4, 5].iter().max_items()); + assert_eq!(vec![&5, &5], [1, 2, 5, 5, 4].iter().max_items()); + assert_eq!(vec![&5, &5, &5, &5], [5, 2, 5, 5, 5].iter().max_items()); + } + + #[test] + fn multiple_max_items_by_key() { + assert_eq!(vec![&("a", 5), &("e", 5)], [("a", 5), ("b", 4), ("c", 3), ("d", 2), ("e", 5)].iter().max_items_by_key(|&&(_, x)| x)); + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..adc335c --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub mod max;