use crate::analysis::eval::Eval;
use crate::games::holdem::hand::Hand;
use crate::types::arrays::Vectorable;
use crate::types::playing_card::PlayingCard;
use crate::types::playing_cards::PlayingCards;
use crate::types::{PileOfCards, U32Card};
use ckc_rs::PokerCard;
use itertools::Itertools;
use log::debug;
use serde::{Deserialize, Serialize};
use std::sync::mpsc;
use std::{fmt, mem};
use wincounter::{Win, Wins};
#[derive(
Serialize, Deserialize, Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
)]
pub struct HeadsUp {
pub first: Hand,
pub second: Hand,
}
impl HeadsUp {
pub const PREFLOP_COMBO_COUNT: usize = 1_712_304;
pub const DEFAULT_WORKER_COUNT: usize = 10;
#[must_use]
pub fn new(first: Hand, second: Hand) -> HeadsUp {
if first.is_blank() || second.is_blank() {
HeadsUp::default()
} else if first > second {
HeadsUp { first, second }
} else {
HeadsUp {
first: second,
second: first,
}
}
}
#[must_use]
pub fn from_index(index: &'static str) -> HeadsUp {
HeadsUp::from(index)
}
#[must_use]
pub fn is_blank(&self) -> bool {
self.has(PlayingCard::BLANK)
}
#[must_use]
pub fn seven_from(&self, five: &PlayingCards) -> (PlayingCards, PlayingCards) {
(
five.combine(&self.first.to_playing_cards()),
five.combine(&self.second.to_playing_cards()),
)
}
#[must_use]
pub fn best_from_seven(&self, five: &PlayingCards) -> (Eval, Eval) {
let (first_seven, second_seven) = self.seven_from(five);
(
first_seven.eval_7cards().unwrap(),
second_seven.eval_7cards().unwrap(),
)
}
#[must_use]
pub fn odds_preflop(&self) -> wincounter::result::HeadsUp {
self.odds_preflop_with_worker_count(HeadsUp::DEFAULT_WORKER_COUNT)
}
#[must_use]
pub fn odds_preflop_with_worker_count(
&self,
worker_count: usize,
) -> wincounter::result::HeadsUp {
let wins = self.wins_preflop_with_worker_count(worker_count);
let (first, ties) = wins.wins_for(Win::FIRST);
let (second, _) = wins.wins_for(Win::SECOND);
wincounter::result::HeadsUp::new(first - ties, second - ties, ties)
}
#[must_use]
pub fn odds_to_string(&self, heads_up: wincounter::result::HeadsUp) -> String {
format!(
"{} {}, {:.2}% ({}), {:.2}% ({}), {:.2}% ({})",
self.first,
self.second,
heads_up.percentage_first(),
heads_up.first_wins,
heads_up.percentage_second(),
heads_up.second_wins,
heads_up.percentage_ties(),
heads_up.ties
)
}
#[must_use]
pub fn wins_preflop(&self) -> Wins {
self.wins_preflop_with_worker_count(HeadsUp::DEFAULT_WORKER_COUNT)
}
#[allow(unused_must_use, clippy::comparison_chain)]
#[must_use]
pub fn wins_preflop_with_worker_count(&self, worker_count: usize) -> Wins {
let mut wins = Wins::default();
let remaining = self.remaining();
let combos = remaining.combinations(5);
let chunks = combos.chunks((HeadsUp::PREFLOP_COMBO_COUNT / worker_count).max(1));
let (sender, receiver) = mpsc::channel();
for chunk in &chunks {
for combo in chunk {
let sender = sender.clone();
let board = PlayingCards::from(combo);
let (eval1, eval2) = self.best_from_seven(&board);
if eval1.rank > eval2.rank {
sender.send(Win::FIRST);
} else if eval2.rank > eval1.rank {
debug!(" Player 2 Wins: {} - {}", board, eval2);
sender.send(Win::SECOND);
} else {
debug!(" Tie: {} - {} / {}", board, eval1, eval2);
sender.send(Win::FIRST | Win::SECOND);
}
}
}
mem::drop(sender);
for received in receiver {
wins.add_win(received);
}
wins
}
}
impl fmt::Display for HeadsUp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.first, self.second,)
}
}
impl From<&'static str> for HeadsUp {
fn from(s: &'static str) -> Self {
let v: Vec<&str> = s.split_whitespace().collect();
if v.len() == 4 {
HeadsUp::new(
Hand::new(
PlayingCard::from(*v.get(0).unwrap()),
PlayingCard::from(*v.get(1).unwrap()),
),
Hand::new(
PlayingCard::from(*v.get(2).unwrap()),
PlayingCard::from(*v.get(3).unwrap()),
),
)
} else {
HeadsUp::default()
}
}
}
impl PileOfCards<PlayingCard> for HeadsUp {
fn has(&self, playing_card: PlayingCard) -> bool {
self.first.has(playing_card) || self.second.has(playing_card)
}
}
impl PileOfCards<U32Card> for HeadsUp {
fn has(&self, card_number: U32Card) -> bool {
self.first.has(card_number) || self.second.has(card_number)
}
}
impl Vectorable for HeadsUp {
#[must_use]
fn to_vec(&self) -> Vec<U32Card> {
vec![
self.first.first().as_u32(),
self.first.second().as_u32(),
self.second.first().as_u32(),
self.second.second().as_u32(),
]
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod games_holdem_heads_up_tests {
use super::*;
use crate::types::playing_card::PlayingCard;
use ckc_rs::CardNumber;
#[test]
fn new() {
let aces = Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES);
let kq = Hand::new(PlayingCard::QUEEN_DIAMONDS, PlayingCard::KING_DIAMONDS);
let headsup = HeadsUp::new(kq, aces);
assert_eq!(
headsup.first,
Hand::new(PlayingCard::ACE_SPADES, PlayingCard::ACE_CLUBS)
);
assert_eq!(
headsup.second,
Hand::new(PlayingCard::KING_DIAMONDS, PlayingCard::QUEEN_DIAMONDS)
);
assert_eq!(
headsup,
HeadsUp {
first: Hand::new(PlayingCard::ACE_SPADES, PlayingCard::ACE_CLUBS),
second: Hand::new(PlayingCard::KING_DIAMONDS, PlayingCard::QUEEN_DIAMONDS)
}
)
}
#[test]
fn is_blank() {
assert!(HeadsUp::default().is_blank());
assert!(HeadsUp::new(
Hand::default(),
Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES)
)
.is_blank());
assert!(HeadsUp::new(
Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES),
Hand::default()
)
.is_blank());
assert!(!HeadsUp::new(
Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES),
Hand::new(PlayingCard::ACE_DIAMONDS, PlayingCard::JACK_CLUBS)
)
.is_blank());
}
#[test]
fn display() {
assert_eq!(
"A♥ Q♥ K♥ J♥",
HeadsUp::new(
Hand::new(PlayingCard::JACK_HEARTS, PlayingCard::KING_HEARTS),
Hand::new(PlayingCard::QUEEN_HEARTS, PlayingCard::ACE_HEARTS)
)
.to_string()
);
}
#[test]
fn has() {
let aces = Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES);
let kq = Hand::new(PlayingCard::QUEEN_DIAMONDS, PlayingCard::KING_DIAMONDS);
let headsup = HeadsUp::new(kq, aces);
assert!(headsup.has(PlayingCard::ACE_CLUBS));
assert!(headsup.has(CardNumber::QUEEN_DIAMONDS));
assert!(!headsup.has(PlayingCard::JACK_CLUBS));
assert!(!headsup.has(CardNumber::NINE_SPADES));
}
#[test]
fn to_vec() {
let aces = Hand::new(PlayingCard::ACE_CLUBS, PlayingCard::ACE_SPADES);
let kq = Hand::new(PlayingCard::QUEEN_DIAMONDS, PlayingCard::KING_DIAMONDS);
let headsup = HeadsUp::new(kq, aces);
assert_eq!(
headsup.to_vec(),
vec![
CardNumber::ACE_SPADES,
CardNumber::ACE_CLUBS,
CardNumber::KING_DIAMONDS,
CardNumber::QUEEN_DIAMONDS
]
);
}
#[test]
fn odds_to_string() {
let hup = HeadsUp::from("6♠ 6♥ 5♦ 5♣");
let result = wincounter::result::HeadsUp::new(1365284, 314904, 32116);
assert_eq!(
"6♠ 6♥ 5♦ 5♣, 79.73% (1365284), 18.39% (314904), 1.88% (32116)",
hup.odds_to_string(result).to_string()
);
}
#[test]
fn vectorable__remaining() {
let hup = HeadsUp::from("6♠ 6♥ 5♦ 5♣");
assert_eq!(48, hup.remaining().len());
assert_eq!(
HeadsUp::PREFLOP_COMBO_COUNT,
hup.remaining().combinations(5).count()
);
}
}