mod class;
mod eval;
mod hand_rank;
mod lookup_table;
mod meta;
#[cfg(feature = "static_lookup")]
pub mod static_lookup;
mod utils;
#[doc(inline)]
pub use class::EvalClass;
#[doc(inline)]
pub use eval::Eval;
use crate::{card::Card, error::EvalError, evaluate::lookup_table::LookupTable, ext::AllUnique};
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Evaluator(LookupTable);
impl Evaluator {
pub fn new() -> Self { Self(LookupTable::new()) }
pub fn evaluate<C: AsRef<[Card]>>(&self, cards: C) -> Result<Eval, EvalError> {
let cards = cards.as_ref();
if cards.all_unique() {
match cards.len() {
x if x < 5 => Err(EvalError::InvalidHandSize(x)),
5 => Ok(self.five(cards)),
_ => Ok(self.six_plus(cards)),
}
} else {
Err(EvalError::CardsNotUnique(cards.to_vec()))
}
}
fn five(&self, cards: &[Card]) -> Eval {
debug_assert_eq!(cards.len(), 5);
let detect_flush = cards
.iter()
.fold(0xF000, |acc, card| acc & card.unique_integer())
!= 0;
if detect_flush {
let bit_rank_or = cards
.iter()
.fold(0, |acc, card| acc | card.unique_integer())
>> 16;
let prime = utils::prime_product_from_rank_bits(bit_rank_or as i16);
Eval(self.0.flush_lookup[&prime])
} else {
let prime = utils::prime_product_from_hand(cards);
Eval(self.0.unsuited_lookup[&prime])
}
}
fn six_plus(&self, cards: &[Card]) -> Eval {
debug_assert!(cards.len() > 5);
let mut current_max = Eval::WORST;
let all_five_card_combos = utils::combinations_generator(cards.iter().cloned(), 5);
for combo in all_five_card_combos {
let score = self.five(&combo);
if score > current_max {
current_max = score;
}
}
current_max
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use lazy_static::lazy_static;
use super::*;
use crate::{
card::Card,
evaluate::{hand_rank::PokerHandRank, utils},
};
lazy_static! {
static ref EVALUATOR: Evaluator = Evaluator::new();
}
#[test]
fn test_all_five_card_combos() {
let gen = utils::combinations_generator(Card::generate_deck(), 5);
let evals = gen.fold(HashSet::with_capacity(7462), |mut ints, hand| {
ints.insert(EVALUATOR.evaluate(&hand).unwrap());
ints
});
assert_eq!(evals.len(), 7462);
(1..=7462).for_each(move |i| {
assert!(evals
.iter()
.any(|meta| meta.hand_rank() == PokerHandRank(i)));
});
}
#[test]
fn representative_five_card_hands() {
representative_hand_evaluates_correctly::<FiveCardHand>(5);
}
#[test]
fn representative_six_card_hands() {
representative_hand_evaluates_correctly::<SixCardHand>(6);
}
#[test]
fn representative_seven_card_hands() {
representative_hand_evaluates_correctly::<SevenCardHand>(7);
}
fn representative_hand_evaluates_correctly<T: RepresentativeHand>(hand_size: usize) {
assert!(T::ALL_HANDS.iter().all(|&hand| hand.len() == hand_size));
let mut evaluations = T::ALL_HANDS.iter().map(|&hand| {
let cards = Card::parse_to_iter(hand)
.try_collect::<Box<_>>()
.unwrap();
EVALUATOR.evaluate(cards).unwrap()
});
assert!(evaluations.next().unwrap().is_high_card());
assert!(evaluations.next().unwrap().is_pair());
assert!(evaluations.next().unwrap().is_two_pair());
assert!(evaluations.next().unwrap().is_three_of_a_kind());
assert!(evaluations.next().unwrap().is_straight());
assert!(evaluations.next().unwrap().is_flush());
assert!(evaluations.next().unwrap().is_full_house());
assert!(evaluations.next().unwrap().is_four_of_a_kind());
assert!(evaluations.next().unwrap().is_straight_flush());
assert!(evaluations.next().unwrap().is_royal_flush());
assert!(evaluations.next().is_none());
}
type Hand = &'static [&'static str];
pub trait RepresentativeHand {
const HIGH_CARD: Hand;
const PAIR: Hand;
const TWO_PAIR: Hand;
const THREE_OF_A_KIND: Hand;
const STRAIGHT: Hand;
const FLUSH: Hand;
const FULL_HOUSE: Hand;
const FOUR_OF_A_KIND: Hand;
const STRAIGHT_FLUSH: Hand;
const ROYAL_FLUSH: Hand;
const ALL_HANDS: &'static [Hand] = &[
Self::HIGH_CARD,
Self::PAIR,
Self::TWO_PAIR,
Self::THREE_OF_A_KIND,
Self::STRAIGHT,
Self::FLUSH,
Self::FULL_HOUSE,
Self::FOUR_OF_A_KIND,
Self::STRAIGHT_FLUSH,
Self::ROYAL_FLUSH,
];
}
pub struct FiveCardHand;
#[rustfmt::skip]
impl RepresentativeHand for FiveCardHand {
const HIGH_CARD: Hand = &["Ah", "8s", "6d", "4c", "2h"];
const PAIR: Hand = &["Ac", "Ah", "9s", "8d", "7c"];
const TWO_PAIR: Hand = &["Kd", "Kc", "Qh", "Qs", "Jd"];
const THREE_OF_A_KIND: Hand = &["Ac", "Ah", "As", "2d", "7c"];
const STRAIGHT: Hand = &["5h", "6c", "7d", "8s", "9h"];
const FLUSH: Hand = &["Ac", "5c", "Tc", "Jc", "8c"];
const FULL_HOUSE: Hand = &["As", "Ad", "Ac", "Kh", "Ks"];
const FOUR_OF_A_KIND: Hand = &["Ac", "Ah", "As", "Ad", "2h"];
const STRAIGHT_FLUSH: Hand = &["5s", "6s", "7s", "8s", "9s"];
const ROYAL_FLUSH: Hand = &["Th", "Jh", "Qh", "Kh", "Ah"];
}
pub struct SixCardHand;
#[rustfmt::skip]
impl RepresentativeHand for SixCardHand {
const HIGH_CARD: &'static [&'static str] = &["Ah", "8s", "6d", "4c", "2h", "Jh"];
const PAIR: &'static [&'static str] = &["Ac", "Ah", "9s", "8d", "7c", "6c"];
const TWO_PAIR: &'static [&'static str] = &["Kd", "Kc", "Qh", "Qs", "Jd", "2c"];
const THREE_OF_A_KIND: &'static [&'static str] = &["Ac", "Ah", "As", "2d", "7c", "3h"];
const STRAIGHT: &'static [&'static str] = &["5h", "6c", "7d", "8s", "9h", "2d"];
const FLUSH: &'static [&'static str] = &["Ac", "5c", "Tc", "Jc", "8c", "4h"];
const FULL_HOUSE: &'static [&'static str] = &["As", "Ad", "Ac", "Kh", "Ks", "2d"];
const FOUR_OF_A_KIND: &'static [&'static str] = &["Ac", "Ah", "As", "Ad", "2h", "3c"];
const STRAIGHT_FLUSH: &'static [&'static str] = &["5s", "6s", "7s", "8s", "9s", "Ts"];
const ROYAL_FLUSH: &'static [&'static str] = &["Th", "Jh", "Qh", "Kh", "Ah", "2c"];
}
pub struct SevenCardHand;
#[rustfmt::skip]
impl RepresentativeHand for SevenCardHand {
const HIGH_CARD: &'static [&'static str] = &["Ah", "8s", "6d", "4c", "2h", "Jh", "Ts"];
const PAIR: &'static [&'static str] = &["Ac", "Ah", "9s", "8d", "7c", "6c", "2h"];
const TWO_PAIR: &'static [&'static str] = &["Kd", "Kc", "Qh", "Qs", "Jd", "2c", "3s"];
const THREE_OF_A_KIND: &'static [&'static str] =
&["Ac", "Ah", "As", "2d", "7c", "3h", "5s"];
const STRAIGHT: &'static [&'static str] = &["5h", "6c", "7d", "8s", "9h", "2d", "Ac"];
const FLUSH: &'static [&'static str] = &["Ac", "5c", "Tc", "Jc", "8c", "4h", "As"];
const FULL_HOUSE: &'static [&'static str] = &["As", "Ad", "Ac", "Kh", "Ks", "2d", "3c"];
const FOUR_OF_A_KIND: &'static [&'static str] = &["Ac", "Ah", "As", "Ad", "2h", "3c", "4d"];
const STRAIGHT_FLUSH: &'static [&'static str] = &["5s", "6s", "7s", "8s", "9s", "Ts", "2c"];
const ROYAL_FLUSH: &'static [&'static str] = &["Th", "Jh", "Qh", "Kh", "Ah", "2c", "3s"];
}
}