feat: add evaluator and hand structs (WIP)
authorMA Beaudet <ma@beaudet.xyz>
Sat, 6 Nov 2021 12:02:18 +0000 (13:02 +0100)
committerMA Beaudet <ma@beaudet.xyz>
Sat, 6 Nov 2021 12:02:18 +0000 (13:02 +0100)
Evaluation should be done using these structs. I will probably let some
functions available freely for wasm builds.

Hand should probably be changed to `struct Hand(Vec<Card>) where Card is
`struct Card(u32)` that impl `std::str::FromStr`, `std::fmt::Display`,
PartialEq`, `PartialOrd` and others...

src/lib.rs

index 226c49c..93b7342 100644 (file)
@@ -5,7 +5,10 @@
 pub mod constants;
 
 use std::{
+    collections::HashMap,
     convert::{TryFrom, TryInto},
+    rc::Rc,
+    str::FromStr,
     sync::{Arc, Mutex},
     thread,
 };
@@ -17,6 +20,208 @@ use constants::{
 
 use wasm_bindgen::prelude::*;
 
+#[derive(Debug)]
+pub enum MyError {
+    InvalidLength,
+}
+
+impl std::error::Error for MyError {}
+
+impl std::fmt::Display for MyError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Invalid number of cards")
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Rules {
+    Classic,
+    Holdem,
+}
+
+impl Default for Rules {
+    fn default() -> Self {
+        Self::Classic
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Algorithm {
+    Suffecool,
+}
+
+impl Default for Algorithm {
+    fn default() -> Self {
+        Self::Suffecool
+    }
+}
+
+#[derive(Debug)]
+pub struct Evaluator {
+    pub rules: Rules,
+    algorithm: Algorithm,
+    hands: Vec<Rc<Hand>>,
+    pub table: Rc<Hand>,
+}
+
+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: &Hand) -> &'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 {
+        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 {
+        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())
+                    }
+                })
+                .enumerate()
+                .collect(),
+            Rules::Holdem => self
+                .hands
+                .iter()
+                .map(|h| {
+                    h.as_ref()
+                        .cards
+                        .iter()
+                        .chain(self.table.as_ref().cards.iter())
+                        .cloned()
+                        .collect()
+                })
+                .map(|player: Vec<u32>| match self.algorithm {
+                    Algorithm::Suffecool => self.eval_7hand(&player[..].try_into().unwrap()),
+                })
+                .enumerate()
+                .collect(),
+        };
+        result
+    }
+
+    pub fn eval_5hand(&self, cards: &[u32; 5]) -> u16 {
+        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 {
+                FLUSHES[q as usize]
+            } else if UNIQUE5[q as usize] != 0 {
+                UNIQUE5[q as usize]
+            } else {
+                q = (c1 & 0xff) * (c2 & 0xff) * (c3 & 0xff) * (c4 & 0xff) * (c5 & 0xff);
+                HASH_VALUES[find_fast((q as usize).try_into().unwrap()) as usize]
+            }
+        } else {
+            9999
+        }
+    }
+
+    /// 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];
+        let mut best = 9999;
+
+        for i in 0..21 {
+            for j in 0..5 {
+                subhand[j] = cards[..][PERM7[i][j] as usize];
+            }
+            let q = eval_5hand(&subhand);
+            if q < best {
+                best = q;
+            }
+        }
+        best
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct Hand {
+    pub cards: Vec<u32>,
+}
+
+impl Hand {
+    pub fn new() -> Self {
+        Self { cards: 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());
+        }
+        self
+    }
+
+    pub fn with_rules<'a>(&'a mut self, rules: &Rules, deck: &mut Vec<u32>) -> &'a mut Hand {
+        match rules {
+            Rules::Classic => {
+                self.with_cards(deck.split_off(deck.len() - 5));
+            }
+
+            Rules::Holdem => {
+                self.with_cards(deck.split_off(deck.len() - 2));
+            }
+        };
+        self
+    }
+}
+
+impl FromStr for Hand {
+    type Err = MyError;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(Self {
+            cards: s
+                .trim()
+                .split_whitespace()
+                .map(|c| parse_card(c).expect("Could not parse card from string"))
+                .collect::<Vec<_>>(),
+        })
+    }
+}
+
 ///  Initializes a deck of 52 cards as an integer vector of lenght 52.
 ///
 /// The order in which the cards are returned is not random.