From 49e99ac6cb2343817ad731d97a360685cacf3c00 Mon Sep 17 00:00:00 2001 From: Elliott Clark Date: Fri, 3 Jan 2025 10:09:45 -0600 Subject: [PATCH] perf: usee bitsets for deck and hands Summary: - Move Deck to use CardBitSet - Add FlatHand that allows indexing and ordering of cards (it follow FlatDeck's example) - Add Hand that uses CardBitSet to do it's thing - Move to using Hand and Deck in Simulation Test Plan: - CI - Tests added --- benches/rank.rs | 6 +- fuzz/fuzz_targets/fuzzer_script_2.rs | 4 +- fuzz/fuzz_targets/rank_seven.rs | 2 +- src/arena/agent/random.rs | 8 +- src/arena/game_state.rs | 4 +- src/arena/sim_builder.rs | 6 +- src/arena/simulation.rs | 7 +- src/core/card_bit_set.rs | 54 ++++++ src/core/card_iter.rs | 4 +- src/core/deck.rs | 63 +++---- src/core/flat_hand.rs | 223 +++++++++++++++++++++++ src/core/hand.rs | 262 ++++++++++----------------- src/core/mod.rs | 8 +- src/core/rank.rs | 78 +++++--- src/holdem/monte_carlo_game.rs | 29 ++- src/holdem/parse.rs | 16 +- src/holdem/starting_hand.rs | 20 +- 17 files changed, 508 insertions(+), 286 deletions(-) create mode 100644 src/core/flat_hand.rs diff --git a/benches/rank.rs b/benches/rank.rs index 24918834..99d77afa 100644 --- a/benches/rank.rs +++ b/benches/rank.rs @@ -4,17 +4,17 @@ extern crate rand; extern crate rs_poker; use criterion::Criterion; -use rs_poker::core::{Deck, FlatDeck, Hand, Rankable}; +use rs_poker::core::{Deck, FlatDeck, FlatHand, Rankable}; fn rank_one(c: &mut Criterion) { let d: FlatDeck = Deck::default().into(); - let hand = Hand::new_with_cards(d.sample(5)); + let hand = FlatHand::new_with_cards(d.sample(5)); c.bench_function("Rank one 5 card hand", move |b| b.iter(|| hand.rank())); } fn rank_best_seven(c: &mut Criterion) { let d: FlatDeck = Deck::default().into(); - let hand = Hand::new_with_cards(d.sample(7)); + let hand = FlatHand::new_with_cards(d.sample(7)); c.bench_function("Rank best 5card hand from 7", move |b| { b.iter(|| hand.rank()) }); diff --git a/fuzz/fuzz_targets/fuzzer_script_2.rs b/fuzz/fuzz_targets/fuzzer_script_2.rs index 12794f30..1dbdb6d8 100755 --- a/fuzz/fuzz_targets/fuzzer_script_2.rs +++ b/fuzz/fuzz_targets/fuzzer_script_2.rs @@ -2,12 +2,12 @@ #[macro_use] extern crate libfuzzer_sys; extern crate rs_poker; -use rs_poker::core::Hand; +use rs_poker::core::FlatHand; use std::str; fuzz_target!(|data: &[u8]| { if let Ok(s) = str::from_utf8(data) { - if let Ok(h) = Hand::new_from_str(s) { + if let Ok(h) = FlatHand::new_from_str(s) { assert!(s.len() >= h.len()); } } diff --git a/fuzz/fuzz_targets/rank_seven.rs b/fuzz/fuzz_targets/rank_seven.rs index 36ed03ac..4a51442f 100644 --- a/fuzz/fuzz_targets/rank_seven.rs +++ b/fuzz/fuzz_targets/rank_seven.rs @@ -7,7 +7,7 @@ use std::str; fuzz_target!(|data: &[u8]| { if let Ok(s) = str::from_utf8(data) { - if let Ok(h) = Hand::new_from_str(s) { + if let Ok(h) = FlatHand::new_from_str(s) { if h.len() == 7 { let r_seven = h.rank(); let r_five_max = CardIter::new(&h[..], 5) diff --git a/src/arena/agent/random.rs b/src/arena/agent/random.rs index 83a4d205..9297cdf0 100644 --- a/src/arena/agent/random.rs +++ b/src/arena/agent/random.rs @@ -136,7 +136,9 @@ impl RandomPotControlAgent { } fn clean_hands(&self, game_state: &GameState) -> Vec { - let default_hand = Hand::new_with_cards(game_state.board.clone()); + let mut default_hand = Hand::new(); + // Copy the board into the default hand + default_hand.extend(game_state.board.iter().cloned()); let to_act_idx = game_state.to_act_idx(); game_state @@ -259,8 +261,8 @@ mod tests { // Add two random cards to every hand. for hand in game_state.hands.iter_mut() { - hand.push(deck.deal().unwrap()); - hand.push(deck.deal().unwrap()); + hand.insert(deck.deal().unwrap()); + hand.insert(deck.deal().unwrap()); } let mut sim = HoldemSimulationBuilder::default() diff --git a/src/arena/game_state.rs b/src/arena/game_state.rs index d3a863b5..362f2ab5 100644 --- a/src/arena/game_state.rs +++ b/src/arena/game_state.rs @@ -317,7 +317,7 @@ impl GameState { // Count all the money in the pot. total_pot += *bet; - // Handle the case that they have no money left + // FlatHandle the case that they have no money left if *stack <= 0.0 { if *bet > 0.0 && round != Round::Starting { // If the player is out of money and they've put money in @@ -378,7 +378,7 @@ impl GameState { round_data, // No board cards vec![], - // Hands are empty + // FlatHands are empty vec![Hand::default(); num_players], // Current stacks stacks, diff --git a/src/arena/sim_builder.rs b/src/arena/sim_builder.rs index 2b9a9200..f51ec292 100644 --- a/src/arena/sim_builder.rs +++ b/src/arena/sim_builder.rs @@ -14,7 +14,7 @@ fn build_flat_deck(game_state: &GameState, rng: &mut R) -> FlatDeck { for hand in game_state.hands.iter() { for card in hand.iter() { - d.remove(*card); + d.remove(card); } } let mut flat_deck: FlatDeck = d.into(); @@ -356,7 +356,7 @@ mod tests { let c = Card::try_from(card_str).unwrap(); assert!(deck.contains(c)); deck.remove(c); - game_state.hands[idx].push(c); + game_state.hands[idx].insert(c); } fn deal_community_card(card_str: &str, deck: &mut CardBitSet, game_state: &mut GameState) { @@ -364,7 +364,7 @@ mod tests { assert!(deck.contains(c)); deck.remove(c); for h in &mut game_state.hands { - h.push(c); + h.insert(c); } game_state.board.push(c); diff --git a/src/arena/simulation.rs b/src/arena/simulation.rs index 76a99871..948344f4 100644 --- a/src/arena/simulation.rs +++ b/src/arena/simulation.rs @@ -443,7 +443,7 @@ impl HoldemSimulation { let action = self.agents[idx].act(&self.id, &self.game_state); event!(parent: &span, Level::TRACE, ?action, idx); - self.run_agent_action(action) + self.run_agent_action(action); } /// Given the action that an agent wants to take, this function will @@ -638,7 +638,10 @@ impl HoldemSimulation { // Some user might never error. // For them it's a panic. if self.panic_on_historian_error { - panic!("Historian error {}", error); + panic!( + "Historian error {}\naction={:?}\ngame_state = {:?}", + error, action, self.game_state + ); } None } diff --git a/src/core/card_bit_set.rs b/src/core/card_bit_set.rs index ff57e1de..60a4b4d6 100644 --- a/src/core/card_bit_set.rs +++ b/src/core/card_bit_set.rs @@ -3,6 +3,9 @@ use std::ops::{BitOr, BitOrAssign, BitXor, BitXorAssign}; use super::{Card, FlatDeck}; use std::fmt::Debug; +#[cfg(feature = "serde")] +use serde::ser::SerializeSeq; + /// This struct is a bitset for cards /// Each card is represented by a bit in a 64 bit integer /// @@ -112,6 +115,10 @@ impl CardBitSet { pub fn count(&self) -> usize { self.cards.count_ones() as usize } + + pub fn clear(&mut self) { + self.cards = 0; + } } impl Default for CardBitSet { @@ -248,6 +255,53 @@ impl Iterator for CardBitSetIter { } } +#[cfg(feature = "serde")] +impl serde::Serialize for CardBitSet { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.count()))?; + for card in (*self).into_iter() { + seq.serialize_element(&card)?; + } + seq.end() + } +} + +#[cfg(feature = "serde")] +struct CardBitSetVisitor; + +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for CardBitSetVisitor { + type Value = CardBitSet; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence of cards") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut deck = CardBitSet::new(); + while let Some(card) = seq.next_element()? { + deck.insert(card); + } + Ok(deck) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for CardBitSet { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_seq(CardBitSetVisitor) + } +} + #[cfg(test)] mod tests { use std::collections::HashSet; diff --git a/src/core/card_iter.rs b/src/core/card_iter.rs index 76a52807..ca6be2f1 100644 --- a/src/core/card_iter.rs +++ b/src/core/card_iter.rs @@ -99,7 +99,7 @@ mod tests { #[test] fn test_iter_one() { - let mut h = Hand::default(); + let mut h = FlatHand::default(); h.push(Card { value: Value::Two, suit: Suit::Spade, @@ -113,7 +113,7 @@ mod tests { #[test] fn test_iter_two() { - let mut h = Hand::default(); + let mut h = FlatHand::default(); h.push(Card { value: Value::Two, suit: Suit::Spade, diff --git a/src/core/deck.rs b/src/core/deck.rs index 3d57f7c0..98d48dc9 100644 --- a/src/core/deck.rs +++ b/src/core/deck.rs @@ -1,6 +1,6 @@ -use crate::core::card::{Card, Suit, Value}; -use std::collections::hash_set::{IntoIter, Iter}; -use std::collections::HashSet; +use crate::core::card::Card; + +use super::{CardBitSet, CardBitSetIter}; /// Deck struct that can tell quickly if a card is in the deck /// @@ -36,14 +36,10 @@ use std::collections::HashSet; /// println!("{:?}", card); /// } /// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] -pub struct Deck { - /// Card storage. - /// Used to figure out quickly - /// if this card is in the deck. - cards: HashSet, -} +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Deck(CardBitSet); impl Deck { /// Create a new empty deck @@ -59,43 +55,49 @@ impl Deck { /// assert_eq!(0, deck.len()); /// ``` pub fn new() -> Self { - Self { - cards: HashSet::new(), - } + Self(CardBitSet::new()) } /// Given a card, is it in the current deck? pub fn contains(&self, c: &Card) -> bool { - self.cards.contains(c) + self.0.contains(*c) } /// Given a card remove it from the deck if it is present. pub fn remove(&mut self, c: &Card) -> bool { - self.cards.remove(c) + let contains = self.contains(c); + self.0.remove(*c); + contains } /// Add a given card to the deck. pub fn insert(&mut self, c: Card) -> bool { - self.cards.insert(c) + let contains = self.contains(&c); + self.0.insert(c); + !contains } /// How many cards are there in the deck. - pub fn len(&self) -> usize { - self.cards.len() + pub fn count(&self) -> usize { + self.0.count() } /// Have all of the cards been dealt from this deck? pub fn is_empty(&self) -> bool { - self.cards.is_empty() + self.0.is_empty() } /// Get an iterator from this deck - pub fn iter(&self) -> Iter { - self.cards.iter() + pub fn iter(&self) -> CardBitSetIter { + self.0.into_iter() + } + + pub fn len(&self) -> usize { + self.0.count() } } /// Turn a deck into an iterator impl IntoIterator for Deck { type Item = Card; - type IntoIter = IntoIter; + type IntoIter = CardBitSetIter; /// Consume this deck and create a new iterator. - fn into_iter(self) -> IntoIter { - self.cards.into_iter() + fn into_iter(self) -> CardBitSetIter { + self.0.into_iter() } } @@ -108,21 +110,14 @@ impl Default for Deck { /// assert_eq!(52, Deck::default().len()); /// ``` fn default() -> Self { - let mut cards: HashSet = HashSet::new(); - for v in &Value::values() { - for s in &Suit::suits() { - cards.insert(Card { - value: *v, - suit: *s, - }); - } - } - Self { cards } + Self(CardBitSet::default()) } } #[cfg(test)] mod tests { + use crate::core::{Suit, Value}; + use super::*; #[test] diff --git a/src/core/flat_hand.rs b/src/core/flat_hand.rs new file mode 100644 index 00000000..54e85c61 --- /dev/null +++ b/src/core/flat_hand.rs @@ -0,0 +1,223 @@ +use crate::core::card::*; +use std::ops::Index; +use std::ops::{RangeFrom, RangeFull, RangeTo}; +use std::slice::Iter; + +use super::RSPokerError; + +/// Struct to hold cards. +/// +/// This doesn't have the ability to easily check if a card is +/// in the hand. So do that before adding/removing a card. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct FlatHand(Vec); + +impl FlatHand { + /// Create the hand with specific hand. + pub fn new_with_cards(cards: Vec) -> Self { + Self(cards) + } + /// From a str create a new hand. + /// + /// # Examples + /// + /// ``` + /// use rs_poker::core::FlatHand; + /// let hand = FlatHand::new_from_str("AdKd").unwrap(); + /// ``` + /// + /// Anything that can't be parsed will return an error. + /// + /// ``` + /// use rs_poker::core::FlatHand; + /// let hand = FlatHand::new_from_str("AdKx"); + /// assert!(hand.is_err()); + /// ``` + pub fn new_from_str(hand_string: &str) -> Result { + // Get the chars iterator. + let mut chars = hand_string.chars(); + // Where we will put the cards + // + // We make the assumption that the hands will have 2 plus five cards. + let mut cards: Vec = Vec::with_capacity(7); + + // Keep looping until we explicitly break + loop { + // Now try and get a char. + let vco = chars.next(); + // If there was no char then we are done. + if vco.is_none() { + break; + } else { + // If we got a value char then we should get a + // suit. + let sco = chars.next(); + // Now try and parse the two chars that we have. + let v = vco + .and_then(Value::from_char) + .ok_or(RSPokerError::UnexpectedValueChar)?; + let s = sco + .and_then(Suit::from_char) + .ok_or(RSPokerError::UnexpectedSuitChar)?; + + let c = Card { value: v, suit: s }; + + match cards.binary_search(&c) { + Ok(_) => return Err(RSPokerError::DuplicateCardInHand(c)), + Err(i) => cards.insert(i, c), + }; + } + } + + if chars.next().is_some() { + return Err(RSPokerError::UnparsedCharsRemaining); + } + + cards.reserve(7); + Ok(Self(cards)) + } + /// Add card at to the hand. + /// No verification is done at all. + pub fn push(&mut self, c: Card) { + self.0.push(c); + } + /// Truncate the hand to the given number of cards. + pub fn truncate(&mut self, len: usize) { + self.0.truncate(len) + } + /// How many cards are in this hand so far ? + pub fn len(&self) -> usize { + self.0.len() + } + /// Are there any cards at all ? + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Create an iter on the cards. + pub fn iter(&self) -> Iter { + self.0.iter() + } +} + +impl Default for FlatHand { + /// Create the default empty hand. + fn default() -> Self { + Self(Vec::with_capacity(7)) + } +} + +/// Allow indexing into the hand. +impl Index for FlatHand { + type Output = Card; + fn index(&self, index: usize) -> &Card { + &self.0[index] + } +} + +/// Allow the index to get refernce to every card. +impl Index for FlatHand { + type Output = [Card]; + fn index(&self, range: RangeFull) -> &[Card] { + &self.0[range] + } +} + +impl Index> for FlatHand { + type Output = [Card]; + fn index(&self, index: RangeTo) -> &[Card] { + &self.0[index] + } +} +impl Index> for FlatHand { + type Output = [Card]; + fn index(&self, index: RangeFrom) -> &[Card] { + &self.0[index] + } +} + +impl Extend for FlatHand { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_card() { + let mut h = FlatHand::default(); + let c = Card { + value: Value::Three, + suit: Suit::Spade, + }; + h.push(c); + // Make sure that the card was added to the vec. + // + // This will also test that has len works + assert_eq!(1, h.len()); + } + + #[test] + fn test_index() { + let mut h = FlatHand::default(); + h.push(Card { + value: Value::Four, + suit: Suit::Spade, + }); + // Make sure the card is there + assert_eq!( + Card { + value: Value::Four, + suit: Suit::Spade, + }, + h[0] + ); + } + #[test] + fn test_parse_error() { + assert!(FlatHand::new_from_str("BAD").is_err()); + assert!(FlatHand::new_from_str("Adx").is_err()); + } + + #[test] + fn test_parse_one_hand() { + let h = FlatHand::new_from_str("Ad").unwrap(); + assert_eq!(1, h.len()) + } + + #[test] + fn test_parse_empty() { + let h = FlatHand::new_from_str("").unwrap(); + assert!(h.is_empty()); + } + + #[test] + fn test_new_with_cards() { + let h = FlatHand::new_with_cards(vec![ + Card::new(Value::Jack, Suit::Spade), + Card::new(Value::Jack, Suit::Heart), + ]); + + assert_eq!(2, h.len()); + } + + #[test] + fn test_error_on_duplicate_card() { + assert!(FlatHand::new_from_str("AdAd").is_err()); + } + + #[test] + fn test_deterministic_new_from_str() { + let h = FlatHand::new_from_str("AdKd").unwrap(); + + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + assert_eq!(h, FlatHand::new_from_str("AdKd").unwrap()); + } +} diff --git a/src/core/hand.rs b/src/core/hand.rs index 4c987f37..a6759c34 100644 --- a/src/core/hand.rs +++ b/src/core/hand.rs @@ -1,59 +1,110 @@ -use crate::core::card::*; -use std::ops::Index; -use std::ops::{RangeFrom, RangeFull, RangeTo}; -use std::slice::Iter; +use super::{Card, CardBitSet, CardBitSetIter, RSPokerError, Suit, Value}; -use super::RSPokerError; - -/// Struct to hold cards. -/// -/// This doesn't have the ability to easily check if a card is -/// in the hand. So do that before adding/removing a card. +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Hand(Vec); +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Hand(CardBitSet); impl Hand { - /// Create the hand with specific hand. + /// Create a new empty hand + /// + /// # Examples + /// + /// ``` + /// use rs_poker::core::Hand; + /// + /// let hand = Hand::new(); + /// + /// assert!(hand.is_empty()); + /// ``` + pub fn new() -> Self { + Self(CardBitSet::new()) + } + pub fn new_with_cards(cards: Vec) -> Self { - Self(cards) + let mut bitset = CardBitSet::new(); + for card in cards { + bitset.insert(card); + } + Self(bitset) } - /// From a str create a new hand. + + /// Given a card, is it in the current hand? /// /// # Examples /// /// ``` - /// use rs_poker::core::Hand; - /// let hand = Hand::new_from_str("AdKd").unwrap(); + /// use rs_poker::core::{Card, Hand, Suit, Value}; + /// + /// let mut hand = Hand::new(); + /// + /// let card = Card::new(Value::Ace, Suit::Club); + /// assert!(!hand.contains(&card)); + /// + /// hand.insert(card); + /// assert!(hand.contains(&card)); /// ``` + pub fn contains(&self, c: &Card) -> bool { + self.0.contains(*c) + } + + /// Remove a card from the hand /// - /// Anything that can't be parsed will return an error. + /// # Examples /// /// ``` - /// use rs_poker::core::Hand; - /// let hand = Hand::new_from_str("AdKx"); - /// assert!(hand.is_err()); + /// use rs_poker::core::{Card, Hand, Suit, Value}; + /// + /// let mut hand = Hand::new(); + /// + /// let card = Card::new(Value::Ace, Suit::Club); + /// assert!(!hand.contains(&card)); + /// + /// hand.insert(card); + /// assert!(hand.contains(&card)); + /// + /// hand.remove(&card); + /// assert!(!hand.contains(&card)); /// ``` + pub fn remove(&mut self, c: &Card) -> bool { + let contains = self.contains(c); + self.0.remove(*c); + contains + } + + pub fn insert(&mut self, c: Card) -> bool { + let contains = self.contains(&c); + self.0.insert(c); + !contains + } + + pub fn count(&self) -> usize { + self.0.count() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn iter(&self) -> CardBitSetIter { + self.0.into_iter() + } + + pub fn clear(&mut self) { + self.0.clear(); + } + pub fn new_from_str(hand_string: &str) -> Result { - // Get the chars iterator. let mut chars = hand_string.chars(); - // Where we will put the cards - // - // We make the assumption that the hands will have 2 plus five cards. - let mut cards: Vec = Vec::with_capacity(7); + let mut bitset = CardBitSet::new(); // Keep looping until we explicitly break loop { - // Now try and get a char. let vco = chars.next(); - // If there was no char then we are done. if vco.is_none() { break; } else { - // If we got a value char then we should get a - // suit. let sco = chars.next(); - // Now try and parse the two chars that we have. let v = vco .and_then(Value::from_char) .ok_or(RSPokerError::UnexpectedValueChar)?; @@ -63,10 +114,11 @@ impl Hand { let c = Card { value: v, suit: s }; - match cards.binary_search(&c) { - Ok(_) => return Err(RSPokerError::DuplicateCardInHand(c)), - Err(i) => cards.insert(i, c), - }; + if bitset.contains(c) { + return Err(RSPokerError::DuplicateCardInHand(c)); + } else { + bitset.insert(c); + } } } @@ -74,150 +126,20 @@ impl Hand { return Err(RSPokerError::UnparsedCharsRemaining); } - cards.reserve(7); - Ok(Self(cards)) - } - /// Add card at to the hand. - /// No verification is done at all. - pub fn push(&mut self, c: Card) { - self.0.push(c); - } - /// Truncate the hand to the given number of cards. - pub fn truncate(&mut self, len: usize) { - self.0.truncate(len) - } - /// How many cards are in this hand so far ? - pub fn len(&self) -> usize { - self.0.len() - } - /// Are there any cards at all ? - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - /// Create an iter on the cards. - pub fn iter(&self) -> Iter { - self.0.iter() + Ok(Self(bitset)) } } impl Default for Hand { - /// Create the default empty hand. fn default() -> Self { - Self(Vec::with_capacity(7)) - } -} - -/// Allow indexing into the hand. -impl Index for Hand { - type Output = Card; - fn index(&self, index: usize) -> &Card { - &self.0[index] - } -} - -/// Allow the index to get refernce to every card. -impl Index for Hand { - type Output = [Card]; - fn index(&self, range: RangeFull) -> &[Card] { - &self.0[range] - } -} - -impl Index> for Hand { - type Output = [Card]; - fn index(&self, index: RangeTo) -> &[Card] { - &self.0[index] - } -} -impl Index> for Hand { - type Output = [Card]; - fn index(&self, index: RangeFrom) -> &[Card] { - &self.0[index] + Self(CardBitSet::new()) } } impl Extend for Hand { fn extend>(&mut self, iter: T) { - self.0.extend(iter); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add_card() { - let mut h = Hand::default(); - let c = Card { - value: Value::Three, - suit: Suit::Spade, - }; - h.push(c); - // Make sure that the card was added to the vec. - // - // This will also test that has len works - assert_eq!(1, h.len()); - } - - #[test] - fn test_index() { - let mut h = Hand::default(); - h.push(Card { - value: Value::Four, - suit: Suit::Spade, - }); - // Make sure the card is there - assert_eq!( - Card { - value: Value::Four, - suit: Suit::Spade, - }, - h[0] - ); - } - #[test] - fn test_parse_error() { - assert!(Hand::new_from_str("BAD").is_err()); - assert!(Hand::new_from_str("Adx").is_err()); - } - - #[test] - fn test_parse_one_hand() { - let h = Hand::new_from_str("Ad").unwrap(); - assert_eq!(1, h.len()) - } - - #[test] - fn test_parse_empty() { - let h = Hand::new_from_str("").unwrap(); - assert!(h.is_empty()); - } - - #[test] - fn test_new_with_cards() { - let h = Hand::new_with_cards(vec![ - Card::new(Value::Jack, Suit::Spade), - Card::new(Value::Jack, Suit::Heart), - ]); - - assert_eq!(2, h.len()); - } - - #[test] - fn test_error_on_duplicate_card() { - assert!(Hand::new_from_str("AdAd").is_err()); - } - - #[test] - fn test_deterministic_new_from_str() { - let h = Hand::new_from_str("AdKd").unwrap(); - - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); - assert_eq!(h, Hand::new_from_str("AdKd").unwrap()); + for card in iter { + self.insert(card); + } } } diff --git a/src/core/mod.rs b/src/core/mod.rs index 16afcc7c..11b89be0 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -8,10 +8,14 @@ mod card; /// Re-export Card, Value, and Suit pub use self::card::{Card, Suit, Value}; -/// Code related to cards in hands. +/// The bitset hand. mod hand; -/// Everything in there should be public. +/// Export the hand pub use self::hand::*; +/// Code related to cards in flattened hands. +mod flat_hand; +/// Everything in there should be public. +pub use self::flat_hand::*; /// We want to be able to iterate over five card hands. mod card_iter; diff --git a/src/core/rank.rs b/src/core/rank.rs index 55bb5b98..a5a337b0 100644 --- a/src/core/rank.rs +++ b/src/core/rank.rs @@ -1,5 +1,6 @@ use crate::core::card::Card; -use crate::core::hand::Hand; + +use super::{FlatHand, Hand}; /// All the different possible hand ranks. /// For each hand rank the u32 corresponds to @@ -98,7 +99,7 @@ fn find_flush(suit_value_sets: &[u32]) -> Option { pub trait Rankable { /// Rank the current 5 card hand. /// This will not cache the value. - fn cards(&self) -> &[Card]; + fn cards(&self) -> impl Iterator; /// Rank the cards to find the best 5 card hand. /// This will work on 5 cards or more (specifically on 7 card holdem @@ -107,9 +108,9 @@ pub trait Rankable { /// /// # Examples /// ``` - /// use rs_poker::core::{Hand, Rank, Rankable}; + /// use rs_poker::core::{FlatHand, Rank, Rankable}; /// - /// let hand = Hand::new_from_str("2h2d8d8sKd6sTh").unwrap(); + /// let hand = FlatHand::new_from_str("2h2d8d8sKd6sTh").unwrap(); /// let rank = hand.rank(); /// assert!(Rank::TwoPair(0) <= rank); /// assert!(Rank::TwoPair(u32::max_value()) >= rank); @@ -285,14 +286,33 @@ pub trait Rankable { } /// Implementation for `Hand` -impl Rankable for Hand { - fn cards(&self) -> &[Card] { - &self[..] +impl Rankable for FlatHand { + fn cards(&self) -> impl Iterator { + self.iter().copied() } } + impl Rankable for Vec { - fn cards(&self) -> &[Card] { - &self[..] + fn cards(&self) -> impl Iterator { + self.iter().copied() + } +} + +impl Rankable for [Card] { + fn cards(&self) -> impl Iterator { + self.iter().copied() + } +} + +impl Rankable for &[Card] { + fn cards(&self) -> impl Iterator { + self.iter().copied() + } +} + +impl Rankable for Hand { + fn cards(&self) -> impl Iterator { + self.iter() } } @@ -300,7 +320,7 @@ impl Rankable for Vec { mod tests { use super::*; use crate::core::card::*; - use crate::core::hand::*; + use crate::core::flat_hand::*; #[test] fn test_keep_highest() { @@ -326,7 +346,7 @@ mod tests { #[test] fn test_high_card_hand() { - let hand = Hand::new_from_str("Ad8h9cTc5c").unwrap(); + let hand = FlatHand::new_from_str("Ad8h9cTc5c").unwrap(); let rank = (1 << Value::Ace as u32) | (1 << Value::Eight as u32) | (1 << Value::Nine as u32) @@ -338,7 +358,7 @@ mod tests { #[test] fn test_flush() { - let hand = Hand::new_from_str("Ad8d9dTd5d").unwrap(); + let hand = FlatHand::new_from_str("Ad8d9dTd5d").unwrap(); let rank = (1 << Value::Ace as u32) | (1 << Value::Eight as u32) | (1 << Value::Nine as u32) @@ -350,7 +370,7 @@ mod tests { #[test] fn test_full_house() { - let hand = Hand::new_from_str("AdAc9d9c9s").unwrap(); + let hand = FlatHand::new_from_str("AdAc9d9c9s").unwrap(); let rank = ((1 << (Value::Nine as u32)) << 13) | (1 << (Value::Ace as u32)); assert!(Rank::FullHouse(rank) == hand.rank_five()); } @@ -358,7 +378,7 @@ mod tests { #[test] fn test_two_pair() { // Make a two pair hand. - let hand = Hand::new_from_str("AdAc9D9cTs").unwrap(); + let hand = FlatHand::new_from_str("AdAc9D9cTs").unwrap(); let rank = (((1 << Value::Ace as u32) | (1 << Value::Nine as u32)) << 13) | (1 << Value::Ten as u32); assert!(Rank::TwoPair(rank) == hand.rank_five()); @@ -366,7 +386,7 @@ mod tests { #[test] fn test_one_pair() { - let hand = Hand::new_from_str("AdAc9d8cTs").unwrap(); + let hand = FlatHand::new_from_str("AdAc9d8cTs").unwrap(); let rank = ((1 << Value::Ace as u32) << 13) | (1 << Value::Nine as u32) | (1 << Value::Eight as u32) @@ -377,7 +397,7 @@ mod tests { #[test] fn test_four_of_a_kind() { - let hand = Hand::new_from_str("AdAcAsAhTs").unwrap(); + let hand = FlatHand::new_from_str("AdAcAsAhTs").unwrap(); assert!( Rank::FourOfAKind((1 << (Value::Ace as u32) << 13) | (1 << (Value::Ten as u32))) == hand.rank_five() @@ -386,19 +406,19 @@ mod tests { #[test] fn test_wheel() { - let hand = Hand::new_from_str("Ad2c3s4h5s").unwrap(); + let hand = FlatHand::new_from_str("Ad2c3s4h5s").unwrap(); assert!(Rank::Straight(0) == hand.rank_five()); } #[test] fn test_straight() { - let hand = Hand::new_from_str("2c3s4h5s6d").unwrap(); + let hand = FlatHand::new_from_str("2c3s4h5s6d").unwrap(); assert!(Rank::Straight(1) == hand.rank_five()); } #[test] fn test_three_of_a_kind() { - let hand = Hand::new_from_str("2c2s2h5s6d").unwrap(); + let hand = FlatHand::new_from_str("2c2s2h5s6d").unwrap(); let rank = ((1 << (Value::Two as u32)) << 13) | (1 << (Value::Five as u32)) | (1 << (Value::Six as u32)); @@ -407,7 +427,7 @@ mod tests { #[test] fn test_rank_seven_straight_flush() { - let h = Hand::new_from_str("AdKdQdJdTd9d8d").unwrap(); + let h = FlatHand::new_from_str("AdKdQdJdTd9d8d").unwrap(); assert_eq!(Rank::StraightFlush(9), h.rank()); } @@ -415,7 +435,7 @@ mod tests { fn test_rank_seven_straight_flush_wheel() { // Make sure that we pick up the wheel straight flush // over different straight. - let h = Hand::new_from_str("2d3d4d5d6h7cAd").unwrap(); + let h = FlatHand::new_from_str("2d3d4d5d6h7cAd").unwrap(); assert_eq!(Rank::StraightFlush(0), h.rank()); } #[test] @@ -434,20 +454,20 @@ mod tests { for (idx, s) in straights.iter().enumerate() { assert_eq!( Rank::Straight(idx as u32 + 1), - Hand::new_from_str(s).unwrap().rank() + FlatHand::new_from_str(s).unwrap().rank() ); } } #[test] fn test_rank_seven_find_best_with_wheel() { - let h = Hand::new_from_str("6dKdAd2d5d4d3d").unwrap(); + let h = FlatHand::new_from_str("6dKdAd2d5d4d3d").unwrap(); assert_eq!(Rank::StraightFlush(1), h.rank()); } #[test] fn test_rank_seven_four_kind() { - let h = Hand::new_from_str("2s2h2d2cKd9h4s").unwrap(); + let h = FlatHand::new_from_str("2s2h2d2cKd9h4s").unwrap(); let four_rank = (1 << Value::Two as u32) << 13; let low_rank = 1 << Value::King as u32; assert_eq!(Rank::FourOfAKind(four_rank | low_rank), h.rank()); @@ -456,7 +476,7 @@ mod tests { #[test] fn test_rank_seven_four_plus_set() { // Four of a kind plus a set. - let h = Hand::new_from_str("2s2h2d2c8d8s8c").unwrap(); + let h = FlatHand::new_from_str("2s2h2d2c8d8s8c").unwrap(); let four_rank = (1 << Value::Two as u32) << 13; let low_rank = 1 << Value::Eight as u32; assert_eq!(Rank::FourOfAKind(four_rank | low_rank), h.rank()); @@ -465,7 +485,7 @@ mod tests { #[test] fn test_rank_seven_full_house_two_sets() { // We have two sets use the highest set. - let h = Hand::new_from_str("As2h2d2c8d8s8c").unwrap(); + let h = FlatHand::new_from_str("As2h2d2c8d8s8c").unwrap(); let set_rank = (1 << Value::Eight as u32) << 13; let low_rank = 1 << Value::Two as u32; assert_eq!(Rank::FullHouse(set_rank | low_rank), h.rank()); @@ -474,7 +494,7 @@ mod tests { #[test] fn test_rank_seven_full_house_two_pair() { // Test to make sure that we pick the best pair. - let h = Hand::new_from_str("2h2d2c8d8sKdKs").unwrap(); + let h = FlatHand::new_from_str("2h2d2c8d8sKdKs").unwrap(); let set_rank = (1 << Value::Two as u32) << 13; let low_rank = 1 << Value::King as u32; assert_eq!(Rank::FullHouse(set_rank | low_rank), h.rank()); @@ -482,7 +502,7 @@ mod tests { #[test] fn test_two_pair_from_three_pair() { - let h = Hand::new_from_str("2h2d8d8sKdKsTh").unwrap(); + let h = FlatHand::new_from_str("2h2d8d8sKdKsTh").unwrap(); let pair_rank = ((1 << Value::King as u32) | (1 << Value::Eight as u32)) << 13; let low_rank = 1 << Value::Ten as u32; assert_eq!(Rank::TwoPair(pair_rank | low_rank), h.rank()); @@ -490,7 +510,7 @@ mod tests { #[test] fn test_rank_seven_two_pair() { - let h = Hand::new_from_str("2h2d8d8sKd6sTh").unwrap(); + let h = FlatHand::new_from_str("2h2d8d8sKd6sTh").unwrap(); let pair_rank = ((1 << Value::Two as u32) | (1 << Value::Eight as u32)) << 13; let low_rank = 1 << Value::King as u32; assert_eq!(Rank::TwoPair(pair_rank | low_rank), h.rank()); diff --git a/src/holdem/monte_carlo_game.rs b/src/holdem/monte_carlo_game.rs index afdba2cd..abca0f2d 100644 --- a/src/holdem/monte_carlo_game.rs +++ b/src/holdem/monte_carlo_game.rs @@ -9,9 +9,7 @@ pub struct MonteCarloGame { deck: FlatDeck, /// Hands still playing. hands: Vec, - // The origional size of each of the hands. - // This is used to reset each hand after a round - hand_sizes: Vec, + starting_hands: Vec, // The number of community cards that will be dealt to each player. num_community_cards: usize, // The number of needed cards each round @@ -25,23 +23,20 @@ impl MonteCarloGame { let mut deck = CardBitSet::default(); let mut max_hand_size: usize = 0; let mut cards_needed = 0; - let mut hand_sizes: Vec = vec![]; for hand in &hands { - let hand_size = hand.len(); + let hand_size = hand.count(); if hand_size > 7 { return Err(RSPokerError::HoldemHandSize); } // The largest hand size sets how many community cards to add max_hand_size = max_hand_size.max(hand_size); - // But we have to keep track of each hand size to allow resetting - hand_sizes.push(hand_size); // Compute the number of cards needed per round. cards_needed += 7 - hand_size; for card in hand.iter() { - deck.remove(*card); + deck.remove(card); } } @@ -54,8 +49,8 @@ impl MonteCarloGame { Ok(Self { deck: flat_deck, + starting_hands: hands.clone(), hands, - hand_sizes, num_community_cards, cards_needed, current_offset: offset, @@ -75,7 +70,7 @@ impl MonteCarloGame { for h in &mut self.hands { h.extend(self.deck[community_start_idx..community_end_idx].to_owned()); - let hole_needed = 7 - h.len(); + let hole_needed = 7 - h.count(); let range = &self.deck[self.current_offset..self.current_offset + hole_needed]; h.extend(range.to_owned()); self.current_offset += hole_needed; @@ -107,9 +102,13 @@ impl MonteCarloGame { /// Reset the game state. pub fn reset(&mut self) { - for (h, hand_size) in self.hands.iter_mut().zip(self.hand_sizes.iter()) { - h.truncate(*hand_size); - } + self.hands + .iter_mut() + .zip(self.starting_hands.iter()) + .for_each(|(h, s)| { + h.clear(); + h.extend(s.iter()); + }); } fn shuffle_if_needed(&mut self) { if self.current_offset + self.cards_needed >= self.deck.len() { @@ -218,7 +217,7 @@ mod test { for h in hands.iter_mut() { for c in &board { - (*h).push(*c); + (*h).insert(*c); } } @@ -261,7 +260,7 @@ mod test { for h in hands.iter_mut() { for c in &board { - (*h).push(*c); + (*h).insert(*c); } } diff --git a/src/holdem/parse.rs b/src/holdem/parse.rs index 21cdc236..fddcc422 100644 --- a/src/holdem/parse.rs +++ b/src/holdem/parse.rs @@ -1,4 +1,4 @@ -use crate::core::{Card, Hand, RSPokerError, Suit, Value}; +use crate::core::{Card, FlatHand, RSPokerError, Suit, Value}; use crate::holdem::Suitedness; use std::collections::HashSet; @@ -199,11 +199,11 @@ impl RangeIter { /// `Iterator` implementation for `RangeIter` impl Iterator for RangeIter { - type Item = Hand; + type Item = FlatHand; /// Get the next value if there are any. - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { if self.has_more() { - let h = Hand::new_with_cards(vec![self.first_card(), self.second_card()]); + let h = FlatHand::new_with_cards(vec![self.first_card(), self.second_card()]); self.incr(); Some(h) } else { @@ -362,7 +362,7 @@ impl RangeParser { /// // We'll never get here /// println!("Hands = {:?}", hands); /// ``` - pub fn parse_one(r_str: &str) -> Result, RSPokerError> { + pub fn parse_one(r_str: &str) -> Result, RSPokerError> { let mut iter = r_str.chars().peekable(); let mut first_range = InclusiveValueRange { start: Value::Two, @@ -515,7 +515,7 @@ impl RangeParser { } } - let filtered: Vec = citer + let filtered: Vec = citer // Need to make sure that the first card is in the range .filter(|hand| first_range.include(hand[0].value)) // Make sure the second card is in the range @@ -564,7 +564,7 @@ impl RangeParser { /// // Filters out duplicates. /// assert_eq!(RangeParser::parse_many("AK-87s,A2s+").unwrap().len(), 72) /// ``` - pub fn parse_many(r_str: &str) -> Result, RSPokerError> { + pub fn parse_many(r_str: &str) -> Result, RSPokerError> { let all_hands: Vec<_> = r_str // Split into different ranges .split(',') @@ -574,7 +574,7 @@ impl RangeParser { .collect::, _>>()?; // Filter the unique hands. - let unique_hands: HashSet = all_hands.into_iter().flatten().collect(); + let unique_hands: HashSet = all_hands.into_iter().flatten().collect(); // Transform hands into a vec for storage Ok(unique_hands.into_iter().collect()) diff --git a/src/holdem/starting_hand.rs b/src/holdem/starting_hand.rs index 37b0ce45..e72929a7 100644 --- a/src/holdem/starting_hand.rs +++ b/src/holdem/starting_hand.rs @@ -1,4 +1,4 @@ -use crate::core::{Card, Hand, Suit, Value}; +use crate::core::{Card, FlatHand, Suit, Value}; /// Enum to represent how the suits of a hand correspond to each other. /// `Suitedness::Suited` will mean that all cards have the same suit @@ -35,7 +35,7 @@ impl Default { } /// Create a new vector of all suited hands. - fn create_suited(&self) -> Vec { + fn create_suited(&self) -> Vec { // Can't have a suited pair. Not unless you're cheating. if self.is_pair() { return vec![]; @@ -43,7 +43,7 @@ impl Default { Suit::suits() .iter() .map(|s| { - Hand::new_with_cards(vec![ + FlatHand::new_with_cards(vec![ Card { value: self.value_one, suit: *s, @@ -58,7 +58,7 @@ impl Default { } /// Create a new vector of all the off suit hands. - fn create_offsuit(&self) -> Vec { + fn create_offsuit(&self) -> Vec { // Since the values are the same there is no reason to swap the suits. let expected_hands = if self.is_pair() { 6 } else { 12 }; self.append_offsuit(Vec::with_capacity(expected_hands)) @@ -68,12 +68,12 @@ impl Default { /// then return it. /// /// @returns the passed in vector with offsuit hands appended. - fn append_offsuit(&self, mut hands: Vec) -> Vec { + fn append_offsuit(&self, mut hands: Vec) -> Vec { let suits = Suit::suits(); for (i, suit_one) in suits.iter().enumerate() { for suit_two in &suits[i + 1..] { // Push the hands in. - hands.push(Hand::new_with_cards(vec![ + hands.push(FlatHand::new_with_cards(vec![ Card { value: self.value_one, suit: *suit_one, @@ -86,7 +86,7 @@ impl Default { // If this isn't a pair then the flipped suits is needed. if self.value_one != self.value_two { - hands.push(Hand::new_with_cards(vec![ + hands.push(FlatHand::new_with_cards(vec![ Card { value: self.value_one, suit: *suit_two, @@ -104,7 +104,7 @@ impl Default { /// Get all the possible starting hands represented by the /// two values of this starting hand. - fn possible_hands(&self) -> Vec { + fn possible_hands(&self) -> Vec { match self.suited { Suitedness::Suited => self.create_suited(), Suitedness::OffSuit => self.create_offsuit(), @@ -129,7 +129,7 @@ pub struct SingleCardRange { impl SingleCardRange { /// Generate all the possible hands for this starting hand type. - fn possible_hands(&self) -> Vec { + fn possible_hands(&self) -> Vec { let mut cur_value = self.start; let mut hands = vec![]; // TODO: Make a better iterator for values. @@ -203,7 +203,7 @@ impl StartingHand { } /// From a `StartingHand` specify all the hands this could represent. - pub fn possible_hands(&self) -> Vec { + pub fn possible_hands(&self) -> Vec { match *self { Self::Def(ref h) => h.possible_hands(), Self::SingleCardRange(ref h) => h.possible_hands(),