pub mod chances;
pub mod count;
pub mod eval;
pub mod eval_7card;
pub mod evals;
pub mod evals_7card;
pub mod holdem_playout;
pub mod indexed;
pub mod outs;
pub mod seat_calc;
pub mod store;
use crate::types::arrays::five_card::FiveCard;
use crate::types::arrays::Evaluable;
use crate::types::poker_deck::POKER_DECK;
use ckc_rs::hand_rank::{HandRank, HandRankClass, HandRankName};
use log::debug;
use std::collections::HashMap;
use strum_macros::Display;
use strum_macros::EnumIter;
#[derive(Clone, Copy, Debug, Display, EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum HeadsUpResult {
FirstWins,
SecondWins,
Tie,
}
pub struct Evaluate;
impl Evaluate {
pub const POSSIBLE_COMBINATIONS: usize = 7937;
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn all_possible_combos() -> (
HashMap<HandRankClass, usize>,
HashMap<HandRankName, usize>,
HashMap<HandRank, bool>,
) {
let mut rank_class_count: HashMap<HandRankClass, usize> = HashMap::new();
let mut rank_name_count: HashMap<HandRankName, usize> = HashMap::new();
let mut rank_count: HashMap<HandRank, bool> = HashMap::new();
for v in POKER_DECK.combinations(5) {
let (hand, hand_rank) = FiveCard::try_from(v).unwrap().evaluate();
debug!("{} {}", hand, hand_rank);
rank_count.entry(hand_rank).or_insert(true);
let class_count = rank_class_count.entry(hand_rank.class).or_insert(0);
*class_count += 1;
}
for key in rank_count.keys() {
let rank_name = key.name;
let name_count = rank_name_count.entry(rank_name).or_insert(0);
*name_count += 1;
}
(rank_class_count, rank_name_count, rank_count)
}
#[must_use]
pub fn count_possible_hands(hands: &HashMap<HandRankClass, usize>) -> usize {
hands.values().copied().collect::<Vec<usize>>().iter().sum()
}
#[must_use]
pub fn five_cards(five_cards: FiveCard) -> HandRank {
HandRank::from(ckc_rs::evaluate::five_cards(five_cards.to_arr()))
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn percent(number: usize, total: usize) -> f32 {
match total {
0 => 0_f32,
_ => ((number as f32 * 100.0) / total as f32) as f32,
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod evaluate_tests {
use super::*;
use crate::analysis::eval::Eval;
use crate::types::arrays::five_card::FiveCard;
use ckc_rs::hand_rank::HandRankValue;
use rstest::rstest;
use strum::IntoEnumIterator;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
#[ignore]
fn eval_on_all_possible_combinations() {
init();
let (classes, names, ranks) = Evaluate::all_possible_combos();
assert_eq!(ranks.len(), 7462);
assert_eq!(classes.len(), 309);
assert_eq!(*names.get(&HandRankName::HighCard).unwrap(), 1277);
assert_eq!(*names.get(&HandRankName::Pair).unwrap(), 2860);
assert_eq!(*names.get(&HandRankName::TwoPair).unwrap(), 858);
assert_eq!(*names.get(&HandRankName::ThreeOfAKind).unwrap(), 858);
assert_eq!(*names.get(&HandRankName::Straight).unwrap(), 10);
assert_eq!(*names.get(&HandRankName::Flush).unwrap(), 1277);
assert_eq!(*names.get(&HandRankName::FullHouse).unwrap(), 156);
assert_eq!(*names.get(&HandRankName::FourOfAKind).unwrap(), 156);
assert_eq!(*names.get(&HandRankName::StraightFlush).unwrap(), 10);
let possible_hands = Evaluate::count_possible_hands(&classes);
assert_eq!(possible_hands, 2_598_960);
assert_eq!(*classes.get(&HandRankClass::RoyalFlush).unwrap(), 4);
assert_eq!(*classes.get(&HandRankClass::AceHigh).unwrap(), 502860);
let mut total_percentage = 0.0;
for v in HandRankClass::iter() {
let c = classes.get(&v);
match c {
Some(class) => {
let pec = ((*class as f32 * 100.0) / possible_hands as f32) as f32;
total_percentage += pec;
}
None => (),
}
}
assert_eq!(format!("{:.2}", total_percentage), "100.00");
}
#[rstest]
#[case("A♠ K♠ Q♠ J♠ T♠", 1, HandRankName::StraightFlush)]
#[case("A♣ 2♣ 3♣ 4♣ 5♣", 10, HandRankName::StraightFlush)]
#[case("A♠ A♥ A♦ A♣ K♠", 11, HandRankName::FourOfAKind)]
#[case("2♠ 2♥ 2♦ 2♣ 3♠", 166, HandRankName::FourOfAKind)]
#[case("A♠ A♥ A♦ K♠ K♦", 167, HandRankName::FullHouse)]
#[case("2♠ 2♥ 2♦ 3♠ 3♦", 322, HandRankName::FullHouse)]
#[case("A♠ K♠ Q♠ J♠ 9♠", 323, HandRankName::Flush)]
#[case("2♣ 3♣ 4♣ 5♣ 7♣", 1599, HandRankName::Flush)]
#[case("A♣ K♠ Q♠ J♠ T♠", 1600, HandRankName::Straight)]
#[case("A♥ 2♣ 3♣ 4♣ 5♣", 1609, HandRankName::Straight)]
#[case("A♠ A♥ A♦ K♠ Q♣", 1610, HandRankName::ThreeOfAKind)]
#[case("2♠ 2♥ 2♦ 3♠ 4♣", 2467, HandRankName::ThreeOfAKind)]
#[case("A♠ A♥ K♦ K♠ Q♣", 2468, HandRankName::TwoPair)]
#[case("3♠ 3♥ 2♦ 2♠ 4♣", 3325, HandRankName::TwoPair)]
#[case("A♠ A♥ K♠ Q♠ J♠", 3326, HandRankName::Pair)]
#[case("2♠ 2♥ 3♠ 4♠ 5♠", 6185, HandRankName::Pair)]
#[case("A♠ K♠ Q♠ J♠ 9♣", 6186, HandRankName::HighCard)]
#[case("2♣ 3♣ 4♣ 5♥ 7♣", 7462, HandRankName::HighCard)]
#[case("2♣ 3♦ 4♣ 5♥ 7♣", 7462, HandRankName::HighCard)]
fn evaluate(
#[case] index: &'static str,
#[case] hand_rank_value: HandRankValue,
#[case] hand_rank_name: HandRankName,
) {
let hand = FiveCard::try_from(index).unwrap();
let actual_hand_rank = Evaluate::five_cards(hand);
assert_eq!(hand_rank_value, actual_hand_rank.value);
assert_eq!(hand_rank_name, actual_hand_rank.name);
assert_eq!(HandRank::from(hand_rank_value), actual_hand_rank);
}
#[test]
fn percent() {
let percentage = Evaluate::percent(48, 2_598_960);
assert_eq!("0.00185%", format!("{:.5}%", percentage));
}
#[test]
fn percent__zero_numerator() {
let percentage = Evaluate::percent(0, 2_598_960);
assert_eq!("0.00000%", format!("{:.5}%", percentage));
}
#[test]
fn percent__zero_denominator() {
let percentage = Evaluate::percent(48, 0);
assert_eq!("0.00000%", format!("{:.5}%", percentage));
}
#[test]
fn display() {
let eval = Eval::try_from("AS KS QS JS TS").unwrap();
assert_eq!(
"A♠ K♠ Q♠ J♠ T♠ HandRank { value: 1, name: StraightFlush, class: RoyalFlush }",
eval.to_string()
);
}
#[test]
fn display__invalid() {
let eval = Eval::try_from("AS AS QS JS TS").unwrap();
assert_eq!(
"HandRank { value: 0, name: Invalid, class: Invalid }",
eval.rank.to_string()
);
}
}