add showdown logic
authorGeoffrey Allott <geoffrey@allott.email>
Sat, 27 Feb 2021 23:21:22 +0000 (23:21 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Sat, 27 Feb 2021 23:21:22 +0000 (23:21 +0000)
src/game/poker/holdem.rs
src/main.rs
src/util/max.rs [new file with mode: 0644]
src/util/mod.rs [new file with mode: 0644]

index c6b1da70fdd7e0a0bb7333b3a78b76ea056a99bb..905be7d176c27f4b91f88544b47dd8d916fc436c 100644 (file)
@@ -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::<Vec<_>>()))
+                    .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,
         }
index a417cb9a3288aeb057a3e3d1d43c7e33df1a471b..00cbf24f42a8c6a95078ec312a918c10439f5f45 100644 (file)
@@ -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 (file)
index 0000000..7a52101
--- /dev/null
@@ -0,0 +1,86 @@
+use std::cmp::Ordering;
+
+pub trait IteratorMaxItems : Iterator + Sized {
+    fn max_items(self) -> Vec<Self::Item>
+    where
+        Self::Item: Ord,
+    {
+        self.max_items_by(Ord::cmp)
+    }
+
+    fn max_items_by_key<B: Ord, F>(self, f: F) -> Vec<Self::Item>
+    where
+        F: FnMut(&Self::Item) -> B
+    {
+        #[inline]
+        fn key<T, B>(mut f: impl FnMut(&T) -> B) -> impl FnMut(T) -> (B, T) {
+            move |x| (f(&x), x)
+        }
+
+        #[inline]
+        fn compare<T, B: Ord>((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<F>(self, compare: F) -> Vec<Self::Item>
+    where
+        F: FnMut(&Self::Item, &Self::Item) -> Ordering,
+    {
+        #[inline]
+        fn fold<T>(mut compare: impl FnMut(&T, &T) -> Ordering) -> impl FnMut(Vec<T>, T) -> Vec<T> {
+            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<T> 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 (file)
index 0000000..adc335c
--- /dev/null
@@ -0,0 +1 @@
+pub mod max;