From 90cc49e7ae32824d80293a65fe5800bff53a187a Mon Sep 17 00:00:00 2001 From: MA Beaudet Date: Sat, 6 Nov 2021 17:23:36 +0100 Subject: [PATCH] feat!: add Card(u32) newtype and Hand changed to Cards(Vec) newtype `Hand` struct changed from Hand { cards } to newtype Cards(Vec) Cards and Card both implement std::str::FromStr and std::fmt::Display. Card implements TryForm PartialEq and PartialOrd (compares ranks) Card provides get(), suit() and rank() methods, mostly helpers for std::fmt::Display Cards now implements init_deck(), shuffle_cards() and removen(name might change) Simulation example updated --- examples/simulation.rs | 35 +++-- src/lib.rs | 287 +++++++++++++++++++++++++++++++++++------ 2 files changed, 269 insertions(+), 53 deletions(-) diff --git a/examples/simulation.rs b/examples/simulation.rs index 6d2fb0a..f4c972d 100644 --- a/examples/simulation.rs +++ b/examples/simulation.rs @@ -1,25 +1,36 @@ -use poker_eval::{init_deck, shuffle_deck, Evaluator, Hand, Rules}; +use poker_eval::{report, Algorithm, Cards, Evaluator, Rules}; fn main() { - let rules = Rules::Classic; - let deck = init_deck(); - let mut deck = shuffle_deck(deck); - let mut table = Hand::new(); + let rules = Rules::Holdem; + let algorithm = Algorithm::CactusKev; + let mut deck = Cards::init_deck(); + deck.shuffle_cards(); - let player_hands: Vec = (0..4) + let player_hands: Vec = (0..4) .into_iter() - .map(|_| Hand::new().with_rules(&rules, &mut deck).clone()) + .map(|_| Cards::new().with_rules(&rules, &mut deck).clone()) .collect(); - table.cards = deck.split_off(deck.len() - 5); + let mut table = Cards::new(); + table.with_cards(deck.removen(5).unwrap()); let result = Evaluator::new() .with_rules(&rules) + .with_algorithm(&algorithm) .with_hands(&player_hands) .with_table(&table) .eval(); - println!("Table: {:?}", table.cards); - println!("Results: {:?}", result); - let winner = result.iter().min_by(|a, b| a.1.cmp(&b.1)).unwrap(); - println!("Winner: {:?}", winner); + println!("Poker evaluator simulation\n"); + for (i, hand) in player_hands.iter().enumerate() { + println!("Player {}: {}", i, hand); + } + match rules { + Rules::Holdem => println!("Table: {}", table), + Rules::Classic => (), + } + println!(); + // println!("Results: {:?}", result); + let _winner = report(&result); + // let winner = result.iter().min_by(|a, b| a.1.cmp(&b.1)).unwrap(); + // println!("Winner: Player {} with score {}", winner.0, winner.1); } diff --git a/src/lib.rs b/src/lib.rs index 5b5017d..45a6f11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ use std::{ use constants::{ EXPECTED_FREQ, FLUSH, FLUSHES, FOUR_OF_A_KIND, FULL_HOUSE, HASH_ADJUST, HASH_VALUES, HIGH_CARD, ONE_PAIR, PERM7, PRIMES, STRAIGHT, STRAIGHT_FLUSH, THREE_OF_A_KIND, TWO_PAIR, UNIQUE5, + VALUE_STR, }; use wasm_bindgen::prelude::*; @@ -23,6 +24,8 @@ use wasm_bindgen::prelude::*; #[derive(Debug)] pub enum MyError { InvalidLength, + InvalidRank, + InvalidSuit, } impl std::error::Error for MyError {} @@ -47,12 +50,12 @@ impl Default for Rules { #[derive(Debug, Clone, Copy)] pub enum Algorithm { - Suffecool, + CactusKev, } impl Default for Algorithm { fn default() -> Self { - Self::Suffecool + Self::CactusKev } } @@ -60,8 +63,8 @@ impl Default for Algorithm { pub struct Evaluator { pub rules: Rules, algorithm: Algorithm, - hands: Vec>, - pub table: Rc, + hands: Vec>, + pub table: Rc, } impl Default for Evaluator { @@ -90,37 +93,30 @@ impl Evaluator { self } - pub fn with_hand<'a>(&'a mut self, hand: &Hand) -> &'a mut Evaluator { + 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 { + 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: &Hand) -> &'a mut Evaluator { + 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 { - // match cards.len() { - // 7 => Ok(eval_7hand(&cards.try_into().unwrap())), - // 5 => Ok(eval_5hand(&cards.try_into().unwrap())), - // _ => Err(MyError::InvalidLength), - // } let result = match self.rules { Rules::Classic => self .hands .iter() .map(|player| match self.algorithm { - Algorithm::Suffecool => { - self.eval_5hand(player.as_ref().cards[..].try_into().unwrap()) - } + Algorithm::CactusKev => self.eval_5hand(player.as_ref().clone()), }) .enumerate() .collect(), @@ -129,14 +125,14 @@ impl Evaluator { .iter() .map(|h| { h.as_ref() - .cards + .0 .iter() - .chain(self.table.as_ref().cards.iter()) + .chain(self.table.as_ref().0.iter()) .cloned() .collect() }) - .map(|player: Vec| match self.algorithm { - Algorithm::Suffecool => self.eval_7hand(&player[..].try_into().unwrap()), + .map(|player: Vec| match self.algorithm { + Algorithm::CactusKev => self.eval_7hand(Cards(player)), }) .enumerate() .collect(), @@ -144,7 +140,8 @@ impl Evaluator { result } - pub fn eval_5hand(&self, cards: &[u32; 5]) -> u16 { + pub fn eval_5hand(&self, cards: Cards) -> u16 { + let cards: Vec = cards.0.into_iter().map(|c| u32::from(c)).collect(); if let [c1, c2, c3, c4, c5] = cards[..5] { let mut q = (c1 | c2 | c3 | c4 | c5) >> 16; if c1 & c2 & c3 & c4 & c5 & 0xF000 != 0 { @@ -161,15 +158,15 @@ impl Evaluator { } /// Non-optimized method of determining the best five-card hand possible of seven cards. - pub fn eval_7hand(&self, cards: &[u32; 7]) -> u16 { - let mut subhand: [u32; 5] = [0; 5]; + pub fn eval_7hand(&self, cards: Cards) -> 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]; + subhand[j] = cards.0[..][PERM7[i][j] as usize]; } - let q = eval_5hand(&subhand); + let q = self.eval_5hand(Cards(subhand.into())); if q < best { best = q; } @@ -179,47 +176,255 @@ impl Evaluator { } #[derive(Debug, Default, Clone)] -pub struct Hand { - pub cards: Vec, -} +pub struct Cards(Vec); -impl Hand { +impl Cards { pub fn new() -> Self { - Self { cards: Vec::new() } + Self(Vec::new()) } - pub fn with_cards<'a>(&'a mut self, cards: Vec) -> &'a mut Hand { - for card in cards { - self.cards.push(card.clone()); + pub fn with_cards<'a>(&'a mut self, cards: Cards) -> &'a mut Cards { + for card in cards.0 { + self.0.push(card.clone()); } self } - pub fn with_rules<'a>(&'a mut self, rules: &Rules, deck: &mut Vec) -> &'a mut Hand { + pub fn with_rules<'a>(&'a mut self, rules: &Rules, deck: &mut Cards) -> &'a mut Cards { match rules { Rules::Classic => { - self.with_cards(deck.split_off(deck.len() - 5)); + self.with_cards(deck.removen(5).unwrap()); } Rules::Holdem => { - self.with_cards(deck.split_off(deck.len() - 2)); + self.with_cards(deck.removen(2).unwrap()); } }; self } + + pub fn removen(&mut self, n: usize) -> Option { + if self.0.len() < n { + None + } else { + Some(Cards(self.0.split_off(self.0.len() - n))) + } + } + + pub fn init_deck() -> Cards { + let mut deck = Vec::new(); + 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 | (u32::try_from(j).unwrap() << 8) | suit | (1 << (16 + j)), + ) + .unwrap(), + ); + } + suit >>= 1; + } + Cards(deck) + } + + #[cfg(feature = "random")] + pub fn shuffle_cards<'a>(&'a mut self) -> &'a mut Cards { + use rand::prelude::*; + let mut rng = thread_rng(); + self.0.shuffle(&mut rng); + self + } } -impl FromStr for Hand { +impl FromStr for Cards { type Err = MyError; fn from_str(s: &str) -> Result { - Ok(Self { - cards: s - .trim() + Ok(Self( + s.trim() .split_whitespace() - .map(|c| parse_card(c).expect("Could not parse card from string")) + .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(()) + } +} + +/// ``` +/// use poker_eval::{Card}; +/// use std::str::FromStr; +/// +/// assert_eq!( +/// Card::from_str("Kd").unwrap().get(), +/// 0b00001000_00000000_01001011_00100101_u32 +/// ); +#[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 u32 | (u32::try_from(r).unwrap() << 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); + println!("Hand value: {}", VALUE_STR[hand_rank(*s) as usize]); + println!(); } + let winner = result.iter().min_by(|a, b| a.1.cmp(&b.1)).unwrap().clone(); + println!("Winner: Player {} with score {}", winner.0, winner.1); + winner } /// Initializes a deck of 52 cards as an integer vector of lenght 52. -- 2.20.1