feat!: add Card(u32) newtype and Hand changed to Cards(Vec<Card>) newtype
authorMA Beaudet <ma@beaudet.xyz>
Sat, 6 Nov 2021 16:23:36 +0000 (17:23 +0100)
committerMA Beaudet <ma@beaudet.xyz>
Sat, 6 Nov 2021 16:40:18 +0000 (17:40 +0100)
`Hand` struct changed from Hand { cards } to newtype Cards(Vec<Card>)
Cards and Card both implement std::str::FromStr and std::fmt::Display.
Card implements TryForm<u32> 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
src/lib.rs

index 6d2fb0a..f4c972d 100644 (file)
@@ -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<Hand> = (0..4)
+    let player_hands: Vec<Cards> = (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);
 }
index 5b5017d..45a6f11 100644 (file)
@@ -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<Rc<Hand>>,
-    pub table: Rc<Hand>,
+    hands: Vec<Rc<Cards>>,
+    pub table: Rc<Cards>,
 }
 
 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<Hand>) -> &'a mut Evaluator {
+    pub fn with_hands<'a>(&'a mut self, hands: &Vec<Cards>) -> &'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<usize, u16> {
-        // 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<u32>| match self.algorithm {
-                    Algorithm::Suffecool => self.eval_7hand(&player[..].try_into().unwrap()),
+                .map(|player: Vec<Card>| 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<u32> = 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<u32>,
-}
+pub struct Cards(Vec<Card>);
 
-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<u32>) -> &'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<u32>) -> &'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<Cards> {
+        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<Self, Self::Err> {
-        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::<Vec<_>>(),
-        })
+        ))
+    }
+}
+
+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<Card> for u32 {
+    fn from(c: Card) -> Self {
+        c.0
+    }
+}
+
+impl TryFrom<u32> for Card {
+    type Error = MyError;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        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<std::cmp::Ordering> {
+        ((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<Self, Self::Err> {
+        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>) -> (&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.