From: Geoffrey Allott Date: Sat, 27 Feb 2021 22:08:51 +0000 (+0000) Subject: initial implementation of texas hold 'em X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=6c6bb6259065d9f66121bb14c8a056ed1c0d6ba3;p=pokerwave.git initial implementation of texas hold 'em --- diff --git a/src/game/action.rs b/src/game/action.rs index ed5af55..a5d66ae 100644 --- a/src/game/action.rs +++ b/src/game/action.rs @@ -54,7 +54,7 @@ impl Action { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Copy, Clone, Serialize)] pub enum ActionError { NotAuthorised, AlreadyJoined, @@ -66,6 +66,13 @@ pub enum ActionError { MustChooseTrumps, CardNotPlayable, CardNotAvailable, + StartingStackTooSmall, + StartingStackTooLarge, + CannotFold, + NotEnoughChips, + BetSizeTooSmall, + BetSizeTooLarge, + NotInHand, InvalidActionForGameType, GameHasEnded, } @@ -83,6 +90,13 @@ impl Display for ActionError { ActionError::MustChooseTrumps => f.write_str("Trumps must be chosen first"), ActionError::CardNotPlayable => f.write_str("Card is not legally playable"), ActionError::CardNotAvailable => f.write_str("Card does not exist in player's hand"), + ActionError::StartingStackTooSmall => f.write_str("Starting stack provided is too small"), + ActionError::StartingStackTooLarge => f.write_str("Starting stack provided is too large"), + ActionError::CannotFold => f.write_str("Cannot fold when able to check"), + ActionError::NotEnoughChips => f.write_str("Not enough chips to make bet"), + ActionError::BetSizeTooSmall => f.write_str("Bet size is too small"), + ActionError::BetSizeTooLarge => f.write_str("Bet size is too large"), + ActionError::NotInHand => f.write_str("Not in hand"), ActionError::InvalidActionForGameType => f.write_str("Invalid action for game type"), ActionError::GameHasEnded => f.write_str("Game has ended"), } diff --git a/src/game/mod.rs b/src/game/mod.rs index b0fd05f..c8e01f7 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,5 +1,6 @@ mod action; mod chatroom; +mod poker; mod whist; use std::collections::HashSet; diff --git a/src/game/poker.rs b/src/game/poker.rs deleted file mode 100644 index 52a90cf..0000000 --- a/src/game/poker.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub enum TexasHoldEmState { - NotStarted, - Dealing, - PostingSmallBlind, - PostingBigBlind, - PreFlopBetting, - DealingFlop, - PostFlopBetting, - DealingTurn, - TurnBetting, - DealingRiver, - RiverBetting, - Completed, -} - -pub struct TexasHoldEmSettings { - title: String, - max_players: u32, - blinds: u64, - starting_stack: u64, -} - -pub struct TexasHoldEm { - id: i64, - settings: TexasHoldEmSettings, - actions_len: usize, - state: TexasHoldEmState, - seats: Seats, - stacks: HashMap, - hands: HashMap>, - deck: HashSet, - dealer: Username, - action: Username, - bets: HashMap, - players: HashSet, - pot: u64, - community: HashSet, -} diff --git a/src/game/poker/classify.rs b/src/game/poker/classify.rs new file mode 100644 index 0000000..cad9402 --- /dev/null +++ b/src/game/poker/classify.rs @@ -0,0 +1,759 @@ +use std::cmp::Ordering; +use std::fmt::{self, Display, Formatter}; + +use crate::card::*; +use crate::card::Rank::*; +use crate::card::Suit::*; + +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}, + 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), + RoyalFlush => write!(f, "Royal Flush"), + } + } +} + +pub fn rank_7_card_hand(mut cards: [Card; 7]) -> Hand { + cards.sort_by(|a, b| b.rank.cmp(&a.rank)); + let ranks = [cards[0].rank, cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank, cards[5].rank, cards[6].rank]; + cards.sort_by(|a, b| b.suit.cmp(&a.suit)); + 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, _] => + 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)}, + } + [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)}, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Odds { + player1_wins: usize, + player2_wins: usize, + draws: usize, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Player { + Player1, + Player2, + Draw, +} + +impl Display for Player { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + Player::Player1 => "Player 1", + Player::Player2 => "Player 2", + Player::Draw => "Draw", + }) + } +} + +impl Odds { + pub fn player1_probability(&self) -> f64 { + self.player1_wins as f64 / (self.player1_wins + self.player2_wins + self.draws) as f64 + } + + pub fn player2_probability(&self) -> f64 { + self.player2_wins as f64 / (self.player1_wins + self.player2_wins + self.draws) as f64 + } + + pub fn draw_probability(&self) -> f64 { + self.draws as f64 / (self.player1_wins + self.player2_wins + self.draws) as f64 + } + + pub fn favourite(&self) -> Player { + match self.player1_wins.cmp(&self.player2_wins) { + Ordering::Greater => Player::Player1, + Ordering::Less => Player::Player2, + Ordering::Equal => Player::Draw, + } + } +} + +impl Display for Odds { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + 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) + } +} + +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() + .filter(|&&card| card != player1[0]) + .filter(|&&card| card != player1[1]) + .filter(|&&card| card != player2[0]) + .filter(|&&card| card != player2[1]) + .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 { + 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) { + Ordering::Greater => odds.player1_wins += 1, + Ordering::Less => odds.player2_wins += 1, + Ordering::Equal => odds.draws += 1, + } + } + } + } + } + } + odds +} + +#[cfg(test)] +mod test { + use super::*; + + use rand::Rng; + use rand::seq::SliceRandom; + + 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(), + ] + } + + pub fn random_7_card_draw(rng: &mut R) -> [Card; 7] { + let mut cards = FIFTY_TWO_CARD_DECK.choose_multiple(rng, 7); + [ + *cards.next().unwrap(), + *cards.next().unwrap(), + *cards.next().unwrap(), + *cards.next().unwrap(), + *cards.next().unwrap(), + *cards.next().unwrap(), + *cards.next().unwrap(), + ] + } + + fn rank_7_card_hand_naive(cards: [Card; 7]) -> Hand { + let rankings = [ + rank_5_card_hand([cards[0], cards[1], cards[2], cards[3], cards[4]]), + rank_5_card_hand([cards[0], cards[1], cards[2], cards[3], cards[5]]), + rank_5_card_hand([cards[0], cards[1], cards[2], cards[3], cards[6]]), + rank_5_card_hand([cards[0], cards[1], cards[2], cards[4], cards[5]]), + rank_5_card_hand([cards[0], cards[1], cards[2], cards[4], cards[6]]), + rank_5_card_hand([cards[0], cards[1], cards[2], cards[5], cards[6]]), + rank_5_card_hand([cards[0], cards[1], cards[3], cards[4], cards[5]]), + rank_5_card_hand([cards[0], cards[1], cards[3], cards[4], cards[6]]), + rank_5_card_hand([cards[0], cards[1], cards[3], cards[5], cards[6]]), + rank_5_card_hand([cards[0], cards[1], cards[4], cards[5], cards[6]]), + rank_5_card_hand([cards[0], cards[2], cards[3], cards[4], cards[5]]), + rank_5_card_hand([cards[0], cards[2], cards[3], cards[4], cards[6]]), + rank_5_card_hand([cards[0], cards[2], cards[3], cards[5], cards[6]]), + rank_5_card_hand([cards[0], cards[2], cards[4], cards[5], cards[6]]), + rank_5_card_hand([cards[0], cards[3], cards[4], cards[5], cards[6]]), + rank_5_card_hand([cards[1], cards[2], cards[3], cards[4], cards[5]]), + rank_5_card_hand([cards[1], cards[2], cards[3], cards[4], cards[6]]), + rank_5_card_hand([cards[1], cards[2], cards[3], cards[5], cards[6]]), + rank_5_card_hand([cards[1], cards[2], cards[4], cards[5], cards[6]]), + rank_5_card_hand([cards[1], cards[3], cards[4], cards[5], cards[6]]), + rank_5_card_hand([cards[2], cards[3], cards[4], cards[5], cards[6]]), + ]; + *rankings.iter().max().unwrap() + } + + fn rank_5_card_hand(mut cards: [Card; 5]) -> Hand { + cards.sort_by(|a, b| b.rank.cmp(&a.rank)); + let ranks = [cards[0].rank, cards[1].rank, cards[2].rank, cards[3].rank, cards[4].rank]; + 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)}, + } + } + } + + 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() + .filter(|&&card| card != player1[0]) + .filter(|&&card| card != player1[1]) + .filter(|&&card| card != player2[0]) + .filter(|&&card| card != player2[1]) + .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 { + 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) { + Ordering::Greater => odds.player1_wins += 1, + Ordering::Less => odds.player2_wins += 1, + Ordering::Equal => odds.draws += 1, + } + } + } + } + } + } + odds + } + + #[test] + fn rank_royal_flush() { + assert_eq!(rank_5_card_hand([ACE_OF_SPADES, KING_OF_SPADES, QUEEN_OF_SPADES, JACK_OF_SPADES, TEN_OF_SPADES]), RoyalFlush); + assert_eq!(rank_5_card_hand([ACE_OF_CLUBS, KING_OF_CLUBS, QUEEN_OF_CLUBS, JACK_OF_CLUBS, TEN_OF_CLUBS]), RoyalFlush); + } + + #[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}); + } + + #[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}); + } + + #[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}); + } + + #[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)}); + } + + #[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}); + } + + #[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)}); + } + + #[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}); + } + + #[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)}); + } + + #[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)}); + } + + #[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)}); + } + + #[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}); + } + + #[test] + fn rank_7_card_hands_against_naive() { + let mut rng = rand::thread_rng(); + for _ in 0..100000 { + let cards = random_7_card_draw(&mut rng); + let hand = rank_7_card_hand_naive(cards); + assert_eq!(hand, rank_7_card_hand(cards), "cards were: {} {} {} {} {} {} {}", cards[0], cards[1], cards[2], cards[3], cards[4], cards[5], cards[6]); + } + } + + #[test] + fn heads_up_odds_against_naive() { + let mut rng = rand::thread_rng(); + let cards = random_4_card_draw(&mut rng); + let player1 = [cards[0], cards[1]]; + let player2 = [cards[2], cards[3]]; + let odds = heads_up_odds_naive(player1, player2); + assert_eq!(odds, heads_up_odds(player1, player2), "cards were: {}{} vs {}{}", cards[0], cards[1], cards[2], cards[3]); + } +} diff --git a/src/game/poker/holdem.rs b/src/game/poker/holdem.rs new file mode 100644 index 0000000..c6b1da7 --- /dev/null +++ b/src/game/poker/holdem.rs @@ -0,0 +1,336 @@ +use std::collections::{HashMap, HashSet}; + +use itertools::Itertools; +use rand::seq::IteratorRandom; +use rand::thread_rng; + +use crate::card::{Card, Suit, FIFTY_TWO_CARD_DECK}; +use crate::seats::Seats; +use crate::username::Username; + +use super::super::{Action, ActionError, Game, UserAction, ValidatedUserAction}; + +#[derive(Copy, Clone, Debug)] +enum State { + NotStarted, + Dealing, + PostingSmallBlind, + PostingBigBlind, + PreFlopBetting, + DealingFlop, + PostFlopBetting, + DealingTurn, + TurnBetting, + DealingRiver, + RiverBetting, + Showdown, + Completed, +} + +#[derive(Clone, Debug)] +pub struct TexasHoldEmSettings { + title: String, + max_players: u32, + small_blind: u64, + starting_stack: u64, +} + +#[derive(Clone, Debug)] +pub struct TexasHoldEm { + id: i64, + settings: TexasHoldEmSettings, + actions_len: usize, + state: State, + seats: Seats, + stacks: HashMap, + deck: HashSet, + hands: HashMap>, + community: HashSet, + dealer: Option, + receiver: Option, + active: Option, + bets: HashMap, + players: HashSet, + pot: u64, + small_blind: u64, + big_blind: u64, +} + +impl TexasHoldEm { + pub fn new(id: i64, settings: TexasHoldEmSettings) -> Self { + let small_blind = settings.small_blind; + let big_blind = small_blind * 2; + Self { + id, + settings, + actions_len: 0, + state: State::NotStarted, + seats: Seats::new(), + stacks: HashMap::new(), + deck: FIFTY_TWO_CARD_DECK.iter().cloned().collect(), + hands: HashMap::new(), + community: HashSet::new(), + dealer: None, + receiver: None, + active: None, + bets: HashMap::new(), + players: HashSet::new(), + pot: 0, + small_blind, + big_blind, + } + } + + fn chips_to_call(&self, username: Username) -> u64 { + todo!() + } + + fn stack(&self, username: Username) -> u64 { + match self.stacks.get(&username) { + Some(&stack) => stack, + None => 0, + } + } +} + +impl Game for TexasHoldEm { + fn id(&self) -> i64 { + self.id + } + + fn players(&self) -> HashSet { + self.seats.player_set() + } + + fn actions_len(&self) -> usize { + self.actions_len + } + + fn validate_action(&self, UserAction{username, action}: UserAction) -> Result { + match (self.state, action) { + (_, 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 { + Err(ActionError::NoSeatAvailable) + } else if !self.seats.seat_is_available(seat) { + Err(ActionError::SeatNotAvailable) + } else if chips < self.settings.starting_stack { + Err(ActionError::StartingStackTooSmall) + } else if chips > self.settings.starting_stack { + Err(ActionError::StartingStackTooLarge) + } else { + Ok(ValidatedUserAction(UserAction{username, action: Action::Join{seat, chips}})) + } + } + (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{username, action: Action::Leave})) + } + (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised), + (State::NotStarted, Action::Leave) => { + Ok(ValidatedUserAction(UserAction{username, action: Action::Leave})) + } + (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.players.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{username, action: Action::Fold})), + (_, Action::Bet{chips}) if chips > self.stack(username) => Err(ActionError::NotEnoughChips), + (_, Action::Bet{chips}) if chips < self.chips_to_call(username) => Err(ActionError::BetSizeTooSmall), + (_, Action::Bet{chips}) if chips > self.chips_to_call(username) && chips < 2 * self.chips_to_call(username) => Err(ActionError::BetSizeTooSmall), + (_, Action::Bet{chips}) => Ok(ValidatedUserAction(UserAction{username, action: Action::Bet{chips}})), + (_, _) => Err(ActionError::InvalidActionForGameType), + } + } + + fn take_action(&mut self, ValidatedUserAction(UserAction{username, action}): ValidatedUserAction) -> Result<(), ActionError> { + self.actions_len += 1; + match (self.state, action) { + (_, Action::PlayCard{..}) | (_, Action::ChooseTrumps{..}) => { + Err(ActionError::InvalidActionForGameType) + } + (State::NotStarted, Action::Join{seat, chips}) => { + self.stacks.insert(username, chips); + self.seats.add_player(seat, username) + } + (State::NotStarted, Action::Leave) => { + self.stacks.remove(&username); + self.seats.remove_player(username) + } + (_, Action::NextToDeal) => { + self.dealer = Some(username); + self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect(); + self.hands.clear(); + self.community.clear(); + self.receiver = self.seats.player_after(username); + self.active = None; + self.players = self.seats.player_set(); + self.bets.clear(); + if self.pot != 0 { + error!("Logic error: pot was {} upon dealing: {:#?}", self.pot, self); + self.pot = 0; + } + Ok(()) + } + (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) { + self.receiver = None; + } else { + self.receiver = self.receiver.and_then(|player| self.seats.player_after(player)); + } + Ok(()) + } + (State::Dealing, Action::EndDeal) => { + self.state = State::PostingSmallBlind; + self.receiver = None; + Ok(()) + } + (State::PostingSmallBlind, Action::Bet{chips}) => { + *self.bets.entry(username).or_default() += chips; + *self.stacks.entry(username).or_default() -= chips; + self.state = State::PostingBigBlind; + Ok(()) + } + (State::PostingBigBlind, Action::Bet{chips}) => { + *self.bets.entry(username).or_default() += chips; + *self.stacks.entry(username).or_default() -= chips; + self.active = self.seats.player_after_in(username, &self.players); + self.state = State::PreFlopBetting; + Ok(()) + } + (_, Action::Bet{chips}) => { + if chips == 0 && self.bets.len() == self.players.len() && self.bets.values().all_equal() { + self.active = None; + self.pot += self.bets.values().sum::(); + self.bets.clear(); + self.state = match self.state { + State::PreFlopBetting => State::DealingFlop, + State::PostFlopBetting => State::DealingTurn, + State::TurnBetting => State::DealingRiver, + State::RiverBetting => State::Showdown, + state => { + error!("In unexpected state while bet of {} received: {:?}: {:?}", chips, state, self); + state + } + }; + } else { + *self.bets.entry(username).or_default() += chips; + *self.stacks.entry(username).or_default() -= chips; + self.active = self.seats.player_after_in(username, &self.players); + } + Ok(()) + } + (_, Action::Fold) => { + self.pot += *self.bets.entry(username).or_default(); + self.bets.remove(&username); + self.players.remove(&username); + self.active = self.seats.player_after_in(username, &self.players); + Ok(()) + } + (State::DealingFlop, Action::CommunityCard{card}) => { + self.community.insert(card); + if self.community.len() == 3 { + self.active = self.seats.player_after_in(username, &self.players); + self.state = State::PostFlopBetting; + } + Ok(()) + } + (State::DealingTurn, Action::CommunityCard{card}) => { + self.community.insert(card); + self.active = self.seats.player_after_in(username, &self.players); + self.state = State::TurnBetting; + Ok(()) + } + (State::DealingRiver, Action::CommunityCard{card}) => { + self.active = self.seats.player_after_in(username, &self.players); + self.state = State::RiverBetting; + Ok(()) + } + (_, Action::WinHand{chips}) if chips <= self.pot => { + self.pot -= chips; + *self.stacks.entry(username).or_default() += chips; + Ok(()) + } + (_, Action::WinGame) => { + self.state = State::Completed; + Ok(()) + } + (State::Completed, _) => Err(ActionError::GameHasEnded), + (_, _) => Err(ActionError::InvalidActionForGameType), + } + } + + fn next_dealer_action(&self) -> Option { + let mut rng = thread_rng(); + match self.state { + State::NotStarted => { + if self.seats.players_len() == self.settings.max_players as usize { // TODO + if let Some(username) = self.seats.player_set().into_iter().choose(&mut rng) { + return Some(ValidatedUserAction(UserAction{username, action: Action::NextToDeal})); + } + } + None + } + State::Dealing => { + if let Some(username) = self.receiver { + let card = self.deck.iter().choose(&mut rng).cloned(); + Some(ValidatedUserAction(UserAction{username, action: Action::ReceiveCard{card}})) + } else { + None + } + } + State::PostingSmallBlind => { + self.dealer.and_then(|dealer| self.seats.player_after(dealer)) + .map(|username| { + let chips = self.stack(username).min(self.small_blind); + ValidatedUserAction(UserAction{username, action: Action::Bet{chips}}) + }) + } + State::PostingBigBlind => { + self.dealer.and_then(|dealer| self.seats.player_after(dealer)) + .and_then(|small_blind| self.seats.player_after(small_blind)) + .map(|username| { + let chips = self.stack(username).min(self.small_blind); + ValidatedUserAction(UserAction{username, action: Action::Bet{chips}}) + }) + } + State::PreFlopBetting | State::PostFlopBetting | State::TurnBetting | State::RiverBetting => { + if self.players.len() == 1 { + if self.pot > 0 { + self.players.iter().next() + .map(|&username| ValidatedUserAction(UserAction{username, action: Action::WinHand{chips: self.pot}})) + } else if self.seats.players_len() == 1 { + self.players.iter().next() + .map(|&username| ValidatedUserAction(UserAction{username, action: Action::WinGame})) + } else { + self.stacks.iter().find(|&(_, &stack)| stack == 0) + .map(|(&username, _)| ValidatedUserAction(UserAction{username, action: Action::KnockedOut})) + } + } else { + None + } + } + State::DealingFlop | State::DealingTurn | State::DealingRiver => { + self.dealer.and_then(|username| + self.deck.iter().choose(&mut rng).map(|&card| + ValidatedUserAction(UserAction{username, action: Action::CommunityCard{card}}))) + } + State::Showdown => { + todo!() + } + State::Completed => None, + } + } +} diff --git a/src/game/poker/mod.rs b/src/game/poker/mod.rs new file mode 100644 index 0000000..49d96a6 --- /dev/null +++ b/src/game/poker/mod.rs @@ -0,0 +1,4 @@ +mod classify; +mod holdem; + +pub use self::holdem::{TexasHoldEm, TexasHoldEmSettings}; diff --git a/src/game/whist.rs b/src/game/whist.rs index 411fee7..9d00173 100644 --- a/src/game/whist.rs +++ b/src/game/whist.rs @@ -10,7 +10,7 @@ use crate::username::Username; use super::{Action, ActionError, Game, UserAction, ValidatedUserAction}; #[derive(Copy, Clone, Debug)] -enum KnockOutWhistState { +enum State { NotStarted, Dealing, ChoosingTrumps, @@ -31,7 +31,7 @@ pub struct KnockOutWhist { id: i64, settings: KnockOutWhistSettings, actions_len: usize, - state: KnockOutWhistState, + state: State, seats: Seats, dealer: Option, receiver: Option, @@ -54,7 +54,7 @@ impl KnockOutWhist { id, settings, actions_len: 0, - state: KnockOutWhistState::NotStarted, + state: State::NotStarted, seats: Seats::new(), dealer: None, receiver: None, @@ -118,7 +118,7 @@ impl Game for KnockOutWhist { (_, Action::AddOn{..}) | (_, Action::RevealCard{..}) | (_, Action::Fold) | (_, Action::Bet{..}) => { Err(ActionError::InvalidActionForGameType) } - (KnockOutWhistState::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 { @@ -129,23 +129,24 @@ impl Game for KnockOutWhist { Ok(ValidatedUserAction(UserAction{username, action: Action::Join{seat, chips: 0}})) } } - (KnockOutWhistState::Completed, Action::Join{..}) => Err(ActionError::GameHasEnded), + (State::Completed, Action::Join{..}) => Err(ActionError::GameHasEnded), (_, Action::Join{..}) => Err(ActionError::GameHasStarted), (_, _) if !self.seats.contains_player(username) => Err(ActionError::NotAuthorised), - (KnockOutWhistState::NotStarted, Action::Leave) => { + (State::NotStarted, Action::Leave) => { Ok(ValidatedUserAction(UserAction{username, action: Action::Leave})) } + (State::Completed, Action::Leave) => Err(ActionError::GameHasEnded), (_, Action::Leave) => Err(ActionError::GameHasStarted), - (KnockOutWhistState::Dealing, _) => Err(ActionError::Dealing), - (KnockOutWhistState::ChoosingTrumps, Action::ChooseTrumps{suit}) => { + (State::Dealing, _) => Err(ActionError::Dealing), + (State::ChoosingTrumps, Action::ChooseTrumps{suit}) => { if Some(username) == self.call { Ok(ValidatedUserAction(UserAction{username, action: Action::ChooseTrumps{suit}})) } else { Err(ActionError::OutOfTurn) } } - (KnockOutWhistState::ChoosingTrumps, _) => Err(ActionError::MustChooseTrumps), - (KnockOutWhistState::Playing, Action::PlayCard{card}) => { + (State::ChoosingTrumps, _) => Err(ActionError::MustChooseTrumps), + (State::Playing, Action::PlayCard{card}) => { if Some(username) != self.active { Err(ActionError::OutOfTurn) } else if !self.hand_contains_card(username, card) { @@ -156,9 +157,9 @@ impl Game for KnockOutWhist { Ok(ValidatedUserAction(UserAction{username, action: Action::PlayCard{card}})) } } - (KnockOutWhistState::CutForCall, _) => Err(ActionError::Dealing), - (KnockOutWhistState::RoundCompleted, _) => Err(ActionError::Dealing), - (KnockOutWhistState::Completed, _) => Err(ActionError::GameHasEnded), + (State::CutForCall, _) => Err(ActionError::Dealing), + (State::RoundCompleted, _) => Err(ActionError::Dealing), + (State::Completed, _) => Err(ActionError::GameHasEnded), (_, _) => Err(ActionError::InvalidActionForGameType), } } @@ -169,10 +170,10 @@ impl Game for KnockOutWhist { (_, Action::AddOn{..}) | (_, Action::Fold) | (_, Action::Bet{..}) => { Err(ActionError::InvalidActionForGameType) } - (KnockOutWhistState::NotStarted, Action::Join{seat, ..}) => { + (State::NotStarted, Action::Join{seat, ..}) => { self.seats.add_player(seat, username) } - (KnockOutWhistState::NotStarted, Action::Leave) => { + (State::NotStarted, Action::Leave) => { self.seats.remove_player(username) } (_, Action::NextToDeal) => { @@ -184,10 +185,10 @@ impl Game for KnockOutWhist { self.trumps = None; self.tricks_won.clear(); self.winners.clear(); - self.state = KnockOutWhistState::Dealing; + self.state = State::Dealing; Ok(()) } - (KnockOutWhistState::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) { @@ -197,30 +198,30 @@ impl Game for KnockOutWhist { } Ok(()) } - (KnockOutWhistState::Dealing, Action::CommunityCard{card}) => { + (State::Dealing, Action::CommunityCard{card}) => { self.trump_card = Some(card); self.trumps = Some(card.suit); Ok(()) } - (KnockOutWhistState::Dealing, Action::EndDeal) => { + (State::Dealing, Action::EndDeal) => { match self.trump_card { Some(_) => { - self.state = KnockOutWhistState::Playing; + self.state = State::Playing; self.receiver = None; self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer)); } - None => self.state = KnockOutWhistState::ChoosingTrumps, + None => self.state = State::ChoosingTrumps, } Ok(()) } - (KnockOutWhistState::ChoosingTrumps, Action::ChooseTrumps{suit}) => { + (State::ChoosingTrumps, Action::ChooseTrumps{suit}) => { self.trumps = Some(suit); self.call = None; - self.state = KnockOutWhistState::Playing; + self.state = State::Playing; self.active = self.dealer.and_then(|dealer| self.seats.player_after(dealer)); Ok(()) } - (KnockOutWhistState::Playing, Action::PlayCard{card}) => { + (State::Playing, Action::PlayCard{card}) => { if let Some(hand) = self.hands.get_mut(&username) { hand.remove(&card); } @@ -235,7 +236,7 @@ impl Game for KnockOutWhist { } Ok(()) } - (KnockOutWhistState::Playing, Action::WinTrick) => { + (State::Playing, Action::WinTrick) => { *self.tricks_won.entry(username).or_default() += 1; self.led = None; self.trick.clear(); @@ -250,7 +251,7 @@ impl Game for KnockOutWhist { self.deck = FIFTY_TWO_CARD_DECK.iter().cloned().collect(); self.hands.clear(); self.trump_card = None; - self.state = KnockOutWhistState::CutForCall; + self.state = State::CutForCall; } } self.active = None; @@ -259,16 +260,16 @@ impl Game for KnockOutWhist { } Ok(()) } - (KnockOutWhistState::Playing, Action::KnockedOut) => { + (State::Playing, Action::KnockedOut) => { self.seats.remove_player(username) } - (KnockOutWhistState::Playing, Action::WinGame) => { + (State::Playing, Action::WinGame) => { self.winners.clear(); self.winners.insert(username); - self.state = KnockOutWhistState::Completed; + self.state = State::Completed; Ok(()) } - (KnockOutWhistState::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() { @@ -291,12 +292,12 @@ impl Game for KnockOutWhist { } Ok(()) } - (KnockOutWhistState::CutForCall, Action::WinCall) | (KnockOutWhistState::Playing, Action::WinCall) => { + (State::CutForCall, Action::WinCall) | (State::Playing, Action::WinCall) => { self.call = Some(username); - self.state = KnockOutWhistState::RoundCompleted; + self.state = State::RoundCompleted; Ok(()) } - (KnockOutWhistState::Completed, _) => Err(ActionError::GameHasEnded), + (State::Completed, _) => Err(ActionError::GameHasEnded), (_, _) => Err(ActionError::InvalidActionForGameType), } } @@ -304,7 +305,7 @@ impl Game for KnockOutWhist { fn next_dealer_action(&self) -> Option { let mut rng = thread_rng(); match self.state { - KnockOutWhistState::NotStarted => { + State::NotStarted => { if self.seats.players_len() == self.settings.max_players as usize { // TODO if let Some(username) = self.seats.player_set().into_iter().choose(&mut rng) { return Some(ValidatedUserAction(UserAction{username, action: Action::NextToDeal})); @@ -312,7 +313,7 @@ impl Game for KnockOutWhist { } None } - KnockOutWhistState::Dealing => { + State::Dealing => { if let Some(username) = self.receiver { let card = self.deck.iter().choose(&mut rng).cloned(); Some(ValidatedUserAction(UserAction{username, action: Action::ReceiveCard{card}})) @@ -331,10 +332,10 @@ impl Game for KnockOutWhist { None } } - KnockOutWhistState::ChoosingTrumps => { + State::ChoosingTrumps => { None } - KnockOutWhistState::Playing => { + State::Playing => { if !self.winners.is_empty() { for username in self.seats.player_set() { if matches!(self.tricks_won.get(&username), Some(0) | None) { @@ -356,7 +357,7 @@ impl Game for KnockOutWhist { None } } - KnockOutWhistState::CutForCall => { + State::CutForCall => { if let Some(username) = self.receiver { if let Some(card) = self.deck.iter().choose(&mut rng).cloned() { Some(ValidatedUserAction(UserAction{username, action: Action::RevealCard{card}})) @@ -369,14 +370,14 @@ impl Game for KnockOutWhist { None } } - KnockOutWhistState::RoundCompleted => { + State::RoundCompleted => { if let Some(username) = self.dealer.and_then(|dealer| self.seats.player_after(dealer)) { Some(ValidatedUserAction(UserAction{username, action: Action::NextToDeal})) } else { None } } - KnockOutWhistState::Completed => { + State::Completed => { None } }