From ed932da1c45b2b7985d47737dd1af88a4063afa8 Mon Sep 17 00:00:00 2001 From: MA Beaudet Date: Mon, 8 Nov 2021 14:47:47 +0100 Subject: [PATCH] chore!: moved card and evaluator to mods --- examples/simulation.rs | 2 +- src/card.rs | 250 ++++++++++++++++++++++++++++ src/evaluator.rs | 133 +++++++++++++++ src/lib.rs | 365 +---------------------------------------- 4 files changed, 386 insertions(+), 364 deletions(-) create mode 100644 src/card.rs create mode 100644 src/evaluator.rs diff --git a/examples/simulation.rs b/examples/simulation.rs index e3a4ad1..c3cbeab 100644 --- a/examples/simulation.rs +++ b/examples/simulation.rs @@ -1,4 +1,4 @@ -use poker_eval::{report, Algorithm, Deck, Evaluator, Rules}; +use poker_eval::{card::Deck, evaluator::Evaluator, report, Algorithm, Rules}; fn main() { let rules = Rules::Holdem; diff --git a/src/card.rs b/src/card.rs new file mode 100644 index 0000000..98423ec --- /dev/null +++ b/src/card.rs @@ -0,0 +1,250 @@ +use std::str::FromStr; + +use crate::{constants::PRIMES, MyError, Rules}; + +#[derive(Debug, Default, Clone)] +pub struct Cards(pub(crate) Vec); + +impl FromStr for Cards { + type Err = MyError; + fn from_str(s: &str) -> Result { + Ok(Self( + s.trim() + .split_whitespace() + .map(|c| Card::from_str(c).expect("Could not parse card from string")) + .collect::>(), + )) + } +} + +impl std::fmt::Display for Cards { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Cards:")?; + for card in &self.0 { + write!(f, "\t{}", card.to_string())?; + } + Ok(()) + } +} + +impl From for Vec { + fn from(c: Cards) -> Self { + c.0.into_iter().map(|c| u32::from(c)).collect() + } +} + +#[derive(Debug, Clone)] +pub struct Deck(pub(crate) Cards); + +impl Deck { + pub fn new() -> Self { + Self::default() + } + + pub fn get_with_rules(&mut self, rules: &Rules) -> Cards { + let v = match rules { + Rules::Classic => self.get(5), + + Rules::Holdem => self.get(2), + }; + v.unwrap() + } + + pub fn get_with_rules_and_player_nb(&mut self, rules: &Rules, player_nb: usize) -> Vec { + (0..player_nb) + .into_iter() + .map(|_| self.get_with_rules(rules)) + .collect() + } + + pub fn get(&mut self, n: usize) -> Option { + if self.0 .0.len() < n { + None + } else { + Some(Cards(self.0 .0.split_off(self.0 .0.len() - n))) + } + } + + #[cfg(feature = "random")] + pub fn shuffle<'a>(&'a mut self) -> &'a mut Deck { + use rand::prelude::*; + let mut rng = thread_rng(); + self.0 .0.shuffle(&mut rng); + self + } +} + +impl Default for Deck { + fn default() -> Self { + let mut deck = Vec::with_capacity(52); + let mut suit = 0x8000; + for _ in 0..4 { + for (j, prime) in PRIMES.iter().enumerate() { + let prime = *prime as u32; + deck.push( + Card::try_from(prime | ((j as u32) << 8) | suit | (1 << (16 + j))).unwrap(), + ); + } + suit >>= 1; + } + Self(Cards(deck)) + } +} + +/// ``` +/// use poker_eval::{Card}; +/// use std::str::FromStr; +/// +/// assert_eq!( +/// Card::from_str("Kd").unwrap().get(), +/// 0b00001000_00000000_01001011_00100101_u32 +/// ); +#[repr(transparent)] +#[derive(Debug, Clone, Copy)] +pub struct Card(pub(crate) u32); + +impl Card { + pub fn get(&self) -> u32 { + self.0 + } + + pub fn suit(&self) -> char { + match (self.0 >> 12) & 0xF { + 0b1000 => 'c', + 0b0100 => 'd', + 0b0010 => 'h', + 0b0001 => 's', + _ => unreachable!(), + } + } + + /// ``` + /// use poker_eval::{Card}; + /// use std::str::FromStr; + /// + /// let card = Card::from_str("Kd").unwrap(); + /// assert_eq!(card.rank(), 'K'); + /// ``` + pub fn rank(&self) -> char { + match (self.0 >> 8) & 0xF { + 0 => '2', + 1 => '3', + 2 => '4', + 3 => '5', + 4 => '6', + 5 => '7', + 6 => '8', + 7 => '9', + 8 => 'T', + 9 => 'J', + 10 => 'Q', + 11 => 'K', + 12 => 'A', + _ => unreachable!(), + } + } +} + +impl From for u32 { + fn from(c: Card) -> Self { + c.0 + } +} + +impl TryFrom for Card { + type Error = MyError; + + fn try_from(value: u32) -> Result { + Ok(Self(value)) + } +} + +/// ``` +/// use poker_eval::{Card}; +/// use std::str::FromStr; +/// +/// let card = Card::from_str("Kd").unwrap(); +/// let other = Card::from_str("Qd").unwrap(); +/// let other2 = Card::from_str("Ks").unwrap(); +/// assert!(card > other); +/// assert!(card <= other2); +/// ``` +impl PartialOrd for Card { + fn partial_cmp(&self, other: &Self) -> Option { + ((self.0 >> 8) & 0xF).partial_cmp(&((other.0 >> 8) & 0xF)) + } +} + +/// ``` +/// use poker_eval::{Card}; +/// use std::str::FromStr; +/// +/// let card = Card::from_str("Kd").unwrap(); +/// let other = Card::from_str("Ks").unwrap(); +/// assert!(card == other); +/// ``` +impl PartialEq for Card { + fn eq(&self, other: &Self) -> bool { + (self.0 >> 8) & 0xF == (other.0 >> 8) & 0xF + } +} + +impl FromStr for Card { + type Err = MyError; + + fn from_str(s: &str) -> Result { + let mut input = s.chars(); + let (r, s) = (input.next(), input.next()); + match (r, s) { + (None, None) => Err(MyError::InvalidLength), + (None, Some(_)) => Err(MyError::InvalidLength), + (Some(_), None) => Err(MyError::InvalidLength), + (Some(r), Some(s)) => { + let r = match r { + '2' => Some(0), + '3' => Some(1), + '4' => Some(2), + '5' => Some(3), + '6' => Some(4), + '7' => Some(5), + '8' => Some(6), + '9' => Some(7), + 'T' => Some(8), + 'J' => Some(9), + 'Q' => Some(10), + 'K' => Some(11), + 'A' => Some(12), + _ => None, + }; + let s = match s { + 'c' => Some(0x8000), + 'd' => Some(0x4000), + 'h' => Some(0x2000), + 's' => Some(0x1000), + _ => None, + }; + match (r, s) { + (None, None) => Err(MyError::InvalidLength), + (None, Some(_)) => Err(MyError::InvalidRank), + (Some(_), None) => Err(MyError::InvalidSuit), + (Some(r), Some(s)) => Ok(Self( + PRIMES[r as usize] as u32 | (r << 8) | s | (1 << (16 + r)), + )), + } + } + } + } +} + +/// ``` +/// use poker_eval::{Card}; +/// use std::str::FromStr; +/// +/// let card = Card::from_str("Kd").unwrap(); +/// assert_eq!(card.to_string(), "Kd"); +/// ``` +impl std::fmt::Display for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}{}", self.rank(), self.suit()) + } +} diff --git a/src/evaluator.rs b/src/evaluator.rs new file mode 100644 index 0000000..66efc98 --- /dev/null +++ b/src/evaluator.rs @@ -0,0 +1,133 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::{ + card::{Card, Cards}, + constants::{FLUSHES, HASH_ADJUST, HASH_VALUES, PERM7, UNIQUE5}, + Algorithm, Rules, +}; + +#[derive(Debug)] +pub struct Evaluator { + pub rules: Rules, + algorithm: Algorithm, + hands: Vec>, + pub table: Rc, +} + +impl Default for Evaluator { + fn default() -> Self { + Self { + rules: Default::default(), + algorithm: Default::default(), + hands: Default::default(), + table: Default::default(), + } + } +} + +impl Evaluator { + pub fn new() -> Self { + Self::default() + } + + pub fn with_rules<'a>(&'a mut self, rules: &Rules) -> &'a mut Evaluator { + self.rules = *rules; + self + } + + pub fn with_algorithm<'a>(&'a mut self, algorithm: &Algorithm) -> &'a mut Evaluator { + self.algorithm = *algorithm; + self + } + + pub fn with_hand<'a>(&'a mut self, hand: &Cards) -> &'a mut Evaluator { + self.hands.push(Rc::new(hand.clone())); + self + } + + pub fn with_hands<'a>(&'a mut self, hands: &Vec) -> &'a mut Evaluator { + for hand in hands { + self.hands.push(Rc::new(hand.clone())) + } + self + } + + pub fn with_table<'a>(&'a mut self, table: &Cards) -> &'a mut Evaluator { + self.table = Rc::new(table.clone()); + self + } + + pub fn eval(&self) -> HashMap { + let result = match self.rules { + Rules::Classic => self + .hands + .iter() + .map(|player| match self.algorithm { + Algorithm::CactusKev => { + self.eval_5hand(&player.as_ref().0[..5].try_into().unwrap()) + } + }) + .enumerate() + .collect(), + Rules::Holdem => self + .hands + .iter() + .map(|h| { + h.as_ref() + .0 + .iter() + .chain(self.table.as_ref().0.iter()) + .cloned() + .collect() + }) + .map(|player: Vec| match self.algorithm { + Algorithm::CactusKev => self.eval_7hand(&player.try_into().unwrap()), + }) + .enumerate() + .collect(), + }; + result + } + + pub fn eval_5hand(&self, cards: &[Card; 5]) -> u16 { + let [c1, c2, c3, c4, c5]: &[u32; 5] = unsafe { std::mem::transmute(cards) }; + let q = (c1 | c2 | c3 | c4 | c5) >> 16; + if c1 & c2 & c3 & c4 & c5 & 0xF000 != 0 { + FLUSHES[q as usize] + } else if UNIQUE5[q as usize] != 0 { + UNIQUE5[q as usize] + } else { + let q = (c1 & 0xff) * (c2 & 0xff) * (c3 & 0xff) * (c4 & 0xff) * (c5 & 0xff); + HASH_VALUES[Evaluator::find_fast(q) as usize] + } + } + + /// Non-optimized method of determining the best five-card hand possible of seven cards. + pub fn eval_7hand(&self, cards: &[Card; 7]) -> u16 { + let mut subhand: [Card; 5] = [Card(0); 5]; + let mut best = 9999; + + for i in 0..21 { + for j in 0..5 { + subhand[j] = cards[..][PERM7[i][j] as usize]; + } + let q = self.eval_5hand(&subhand); + if q < best { + best = q; + } + } + best + } + + pub fn find_fast(mut u: u32) -> u16 { + u += 0xe91aaa35; + u ^= u >> 16; + // u += u << 8; + u = u.wrapping_add(u << 8); + u ^= u >> 4; + let b = (u >> 8) & 0x1ff; + // let a = (u + (u << 2)) >> 19; + let a = (u.wrapping_add(u << 2)) >> 19; + u16::try_from(a).unwrap() ^ HASH_ADJUST[b as usize] + } +} diff --git a/src/lib.rs b/src/lib.rs index 270fe2c..b2f5ac9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,14 +2,13 @@ //! //! Based on Kevin L. Suffecool poker hand evaluator. //! +pub mod card; pub mod constants; +pub mod evaluator; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, - mem::transmute, - rc::Rc, - str::FromStr, sync::{Arc, Mutex}, thread, }; @@ -60,366 +59,6 @@ impl Default for Algorithm { } } -#[derive(Debug)] -pub struct Evaluator { - pub rules: Rules, - algorithm: Algorithm, - hands: Vec>, - pub table: Rc, -} - -impl Default for Evaluator { - fn default() -> Self { - Self { - rules: Default::default(), - algorithm: Default::default(), - hands: Default::default(), - table: Default::default(), - } - } -} - -impl Evaluator { - pub fn new() -> Self { - Self::default() - } - - pub fn with_rules<'a>(&'a mut self, rules: &Rules) -> &'a mut Evaluator { - self.rules = *rules; - self - } - - pub fn with_algorithm<'a>(&'a mut self, algorithm: &Algorithm) -> &'a mut Evaluator { - self.algorithm = *algorithm; - self - } - - pub fn with_hand<'a>(&'a mut self, hand: &Cards) -> &'a mut Evaluator { - self.hands.push(Rc::new(hand.clone())); - self - } - - pub fn with_hands<'a>(&'a mut self, hands: &Vec) -> &'a mut Evaluator { - for hand in hands { - self.hands.push(Rc::new(hand.clone())) - } - self - } - - pub fn with_table<'a>(&'a mut self, table: &Cards) -> &'a mut Evaluator { - self.table = Rc::new(table.clone()); - self - } - - pub fn eval(&self) -> HashMap { - let result = match self.rules { - Rules::Classic => self - .hands - .iter() - .map(|player| match self.algorithm { - Algorithm::CactusKev => { - self.eval_5hand(&player.as_ref().0[..5].try_into().unwrap()) - } - }) - .enumerate() - .collect(), - Rules::Holdem => self - .hands - .iter() - .map(|h| { - h.as_ref() - .0 - .iter() - .chain(self.table.as_ref().0.iter()) - .cloned() - .collect() - }) - .map(|player: Vec| match self.algorithm { - Algorithm::CactusKev => self.eval_7hand(&player.try_into().unwrap()), - }) - .enumerate() - .collect(), - }; - result - } - - pub fn eval_5hand(&self, cards: &[Card; 5]) -> u16 { - let [c1, c2, c3, c4, c5]: &[u32; 5] = unsafe { transmute(cards) }; - let q = (c1 | c2 | c3 | c4 | c5) >> 16; - if c1 & c2 & c3 & c4 & c5 & 0xF000 != 0 { - FLUSHES[q as usize] - } else if UNIQUE5[q as usize] != 0 { - UNIQUE5[q as usize] - } else { - let q = (c1 & 0xff) * (c2 & 0xff) * (c3 & 0xff) * (c4 & 0xff) * (c5 & 0xff); - HASH_VALUES[find_fast(q) as usize] - } - } - - /// Non-optimized method of determining the best five-card hand possible of seven cards. - pub fn eval_7hand(&self, cards: &[Card; 7]) -> u16 { - let mut subhand: [Card; 5] = [Card(0); 5]; - let mut best = 9999; - - for i in 0..21 { - for j in 0..5 { - subhand[j] = cards[..][PERM7[i][j] as usize]; - } - let q = self.eval_5hand(&subhand); - if q < best { - best = q; - } - } - best - } -} - -#[derive(Debug, Default, Clone)] -pub struct Cards(Vec); - -impl FromStr for Cards { - type Err = MyError; - fn from_str(s: &str) -> Result { - Ok(Self( - s.trim() - .split_whitespace() - .map(|c| Card::from_str(c).expect("Could not parse card from string")) - .collect::>(), - )) - } -} - -impl std::fmt::Display for Cards { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Cards:")?; - for card in &self.0 { - write!(f, "\t{}", card.to_string())?; - } - Ok(()) - } -} - -impl From for Vec { - fn from(c: Cards) -> Self { - c.0.into_iter().map(|c| u32::from(c)).collect() - } -} - -#[derive(Debug, Clone)] -pub struct Deck(Cards); - -impl Deck { - pub fn new() -> Self { - Self::default() - } - - pub fn get_with_rules(&mut self, rules: &Rules) -> Cards { - let v = match rules { - Rules::Classic => self.get(5), - - Rules::Holdem => self.get(2), - }; - v.unwrap() - } - - pub fn get_with_rules_and_player_nb(&mut self, rules: &Rules, player_nb: usize) -> Vec { - (0..player_nb) - .into_iter() - .map(|_| self.get_with_rules(rules)) - .collect() - } - - pub fn get(&mut self, n: usize) -> Option { - if self.0 .0.len() < n { - None - } else { - Some(Cards(self.0 .0.split_off(self.0 .0.len() - n))) - } - } - - #[cfg(feature = "random")] - pub fn shuffle<'a>(&'a mut self) -> &'a mut Deck { - use rand::prelude::*; - let mut rng = thread_rng(); - self.0 .0.shuffle(&mut rng); - self - } -} - -impl Default for Deck { - fn default() -> Self { - let mut deck = Vec::with_capacity(52); - let mut suit = 0x8000; - for _ in 0..4 { - for (j, prime) in PRIMES.iter().enumerate() { - let prime = *prime as u32; - deck.push( - Card::try_from(prime | ((j as u32) << 8) | suit | (1 << (16 + j))).unwrap(), - ); - } - suit >>= 1; - } - Self(Cards(deck)) - } -} - -/// ``` -/// use poker_eval::{Card}; -/// use std::str::FromStr; -/// -/// assert_eq!( -/// Card::from_str("Kd").unwrap().get(), -/// 0b00001000_00000000_01001011_00100101_u32 -/// ); -#[repr(transparent)] -#[derive(Debug, Clone, Copy)] -pub struct Card(u32); - -impl Card { - pub fn get(&self) -> u32 { - self.0 - } - - pub fn suit(&self) -> char { - match (self.0 >> 12) & 0xF { - 0b1000 => 'c', - 0b0100 => 'd', - 0b0010 => 'h', - 0b0001 => 's', - _ => unreachable!(), - } - } - - /// ``` - /// use poker_eval::{Card}; - /// use std::str::FromStr; - /// - /// let card = Card::from_str("Kd").unwrap(); - /// assert_eq!(card.rank(), 'K'); - /// ``` - pub fn rank(&self) -> char { - match (self.0 >> 8) & 0xF { - 0 => '2', - 1 => '3', - 2 => '4', - 3 => '5', - 4 => '6', - 5 => '7', - 6 => '8', - 7 => '9', - 8 => 'T', - 9 => 'J', - 10 => 'Q', - 11 => 'K', - 12 => 'A', - _ => unreachable!(), - } - } -} - -impl From for u32 { - fn from(c: Card) -> Self { - c.0 - } -} - -impl TryFrom for Card { - type Error = MyError; - - fn try_from(value: u32) -> Result { - Ok(Self(value)) - } -} - -/// ``` -/// use poker_eval::{Card}; -/// use std::str::FromStr; -/// -/// let card = Card::from_str("Kd").unwrap(); -/// let other = Card::from_str("Qd").unwrap(); -/// let other2 = Card::from_str("Ks").unwrap(); -/// assert!(card > other); -/// assert!(card <= other2); -/// ``` -impl PartialOrd for Card { - fn partial_cmp(&self, other: &Self) -> Option { - ((self.0 >> 8) & 0xF).partial_cmp(&((other.0 >> 8) & 0xF)) - } -} - -/// ``` -/// use poker_eval::{Card}; -/// use std::str::FromStr; -/// -/// let card = Card::from_str("Kd").unwrap(); -/// let other = Card::from_str("Ks").unwrap(); -/// assert!(card == other); -/// ``` -impl PartialEq for Card { - fn eq(&self, other: &Self) -> bool { - (self.0 >> 8) & 0xF == (other.0 >> 8) & 0xF - } -} - -impl FromStr for Card { - type Err = MyError; - - fn from_str(s: &str) -> Result { - let mut input = s.chars(); - let (r, s) = (input.next(), input.next()); - match (r, s) { - (None, None) => Err(MyError::InvalidLength), - (None, Some(_)) => Err(MyError::InvalidLength), - (Some(_), None) => Err(MyError::InvalidLength), - (Some(r), Some(s)) => { - let r = match r { - '2' => Some(0), - '3' => Some(1), - '4' => Some(2), - '5' => Some(3), - '6' => Some(4), - '7' => Some(5), - '8' => Some(6), - '9' => Some(7), - 'T' => Some(8), - 'J' => Some(9), - 'Q' => Some(10), - 'K' => Some(11), - 'A' => Some(12), - _ => None, - }; - let s = match s { - 'c' => Some(0x8000), - 'd' => Some(0x4000), - 'h' => Some(0x2000), - 's' => Some(0x1000), - _ => None, - }; - match (r, s) { - (None, None) => Err(MyError::InvalidLength), - (None, Some(_)) => Err(MyError::InvalidRank), - (Some(_), None) => Err(MyError::InvalidSuit), - (Some(r), Some(s)) => Ok(Self( - PRIMES[r as usize] as u32 | (r << 8) | s | (1 << (16 + r)), - )), - } - } - } - } -} - -/// ``` -/// use poker_eval::{Card}; -/// use std::str::FromStr; -/// -/// let card = Card::from_str("Kd").unwrap(); -/// assert_eq!(card.to_string(), "Kd"); -/// ``` -impl std::fmt::Display for Card { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}{}", self.rank(), self.suit()) - } -} pub fn report(result: &HashMap) -> (&usize, &u16) { for (p, s) in result.into_iter() { println!("Player {}: Score {}", p, s); -- 2.20.1