use crate::analysis::chances::Chances;
use crate::analysis::eval::Eval;
use crate::analysis::outs::Outs;
use crate::games::holdem::board::Board;
use crate::games::holdem::case_eval::CaseEval;
use crate::games::holdem::case_evals::CaseEvals;
use crate::games::holdem::seat_eval::SeatEval;
use crate::games::holdem::seats::Seats;
use crate::types::card_slot::CardSlot;
use crate::types::playing_card::PlayingCard;
use crate::types::playing_cards::PlayingCards;
use ckc_rs::HandError;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use wyz::FmtForward;
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Table {
pub players: Seats,
pub board: Board,
}
impl Table {
#[must_use]
pub fn seat(number: usize) -> Table {
Table {
players: Seats::seat(number),
board: Board::default(),
}
}
pub fn from_index(index: &'static str) -> Result<Table, HandError> {
Table::try_from(index)
}
#[must_use]
pub fn sample() -> Table {
let mut rng = rand::thread_rng();
let player_count: usize = rng.gen_range(2..9);
Table::sample_number(player_count)
}
#[must_use]
pub fn sample_number(player_count: usize) -> Table {
let table = Table::seat(player_count);
let mut cards = PlayingCards::deck_shuffled();
for _ in 0..(player_count * 2) + 5 {
table.take(cards.draw_one());
}
table
}
pub fn chances_at_deal(&self) -> Chances {
self.eval_at_deal().chances()
}
pub fn chances_at_flop(&self) -> Chances {
self.eval_at_flop().chances()
}
pub fn chances_at_turn(&self) -> Chances {
self.eval_at_turn().chances()
}
pub fn chances_at_river(&self) -> Chances {
self.eval_at_river().chances()
}
pub fn dealt(&self) -> PlayingCards {
PlayingCards::default()
.combine(&self.players.dealt())
.combine(&self.board.dealt())
}
#[allow(clippy::missing_panics_doc)]
pub fn eval_at_deal(&self) -> CaseEvals {
if !self.players.is_dealt() {
return CaseEvals::default();
}
let mut evals = CaseEvals::default();
for v in self.remaining_at_deal().combinations(5) {
let cycle = PlayingCards::from(v);
evals.push(self.players.case_eval(&cycle));
}
evals
}
pub fn eval_at_flop(&self) -> CaseEvals {
if !self.board.flop.is_dealt() {
return CaseEvals::default();
}
let mut evals = CaseEvals::default();
for v in self.remaining_at_flop().combinations(2) {
let mut cycle = PlayingCards::from(v);
cycle.append(&self.board.flop.to_playing_cards());
evals.push(self.players.case_eval(&cycle));
}
evals
}
pub fn eval_at_turn(&self) -> CaseEvals {
let (_, case_evals) = self.eval_at_turn_with_outs();
case_evals
}
pub fn eval_at_turn_with_outs(&self) -> (Outs, CaseEvals) {
if !self.board.turn.is_dealt() {
return (Outs::default(), CaseEvals::default());
}
let mut evals = CaseEvals::default();
let mut outs = Outs::default();
let rem = self.remaining_at_turn();
for i1 in 0..rem.len() {
let card = *rem.get_index(i1).unwrap();
let mut cycle = PlayingCards::from(card);
cycle.append(&self.board.flop.to_playing_cards());
cycle.append(&self.board.turn.to_playing_cards());
let (out, case) = self.players.case_eval_with_outs(card, &cycle);
outs.extend(&out);
evals.push(case);
}
(outs, evals)
}
pub fn eval_at_river(&self) -> CaseEvals {
if !self.board.flop.is_dealt() {
return CaseEvals::default();
}
let mut evals = CaseEvals::default();
evals.push(self.players.case_eval(&self.board.to_playing_cards()));
evals
}
pub fn format_calc(&self) -> String {
format!(
"❯ cargo run --example calc -- -d \"{}\" -b \"{}\"",
self.players.to_playing_cards(),
self.board.to_playing_cards()
)
}
pub fn format_indexer(&self) -> String {
format!(
"❯ cargo run --example indexer -- --index \"{}\"",
self.dealt()
)
}
pub fn flop_seat_evals(&self) -> CaseEval {
let mut evals = CaseEval::default();
for seat in self.players.iter() {
evals.push(SeatEval::new_from_flop(seat.clone(), &self.board.flop));
}
evals
}
pub fn nuts_at_flop(&self) -> Eval {
self.board.flop.the_nuts()
}
pub fn player_cards_at_flop(&self, player: usize) -> PlayingCards {
match self.players.get(player) {
Some(player) => player
.to_playing_cards()
.combine(&self.board.flop.to_playing_cards()),
None => PlayingCards::default(),
}
}
pub fn player_eval_at_flop(&self, player: usize) -> Eval {
match self.player_cards_at_flop(player).to_five_cards() {
Ok(hand) => Eval::from(hand),
Err(_) => Eval::default(),
}
}
pub fn remaining_at_deal(&self) -> PlayingCards {
self.remaining()
.combine(&self.board.flop.to_playing_cards())
.combine(&self.board.turn.to_playing_cards())
.combine(&self.board.river.to_playing_cards())
}
pub fn remaining_at_flop(&self) -> PlayingCards {
self.remaining()
.combine(&self.board.turn.to_playing_cards())
.combine(&self.board.river.to_playing_cards())
}
pub fn remaining_at_turn(&self) -> PlayingCards {
self.remaining()
.combine(&self.board.river.to_playing_cards())
}
pub fn tied_or_better_at_flop(&self, eval: &Eval) -> CaseEval {
let mut evals = CaseEval::default();
for seat in self.players.iter() {
let seat_eval = SeatEval::new_from_flop(seat.clone(), &self.board.flop);
if seat_eval.eval.rank >= eval.rank {
evals.push(seat_eval);
}
}
evals
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out(&self) {
println!("Cards Dealt: {}\n", self.to_playing_cards());
println!("{}", self.players);
println!("{}", self.board);
if self.play_out_flop() && self.play_out_turn() {
self.play_out_river();
}
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_detailed(&self) {
println!("Cards Dealt: {}\n", self.to_playing_cards());
println!("{}", self.players);
println!("{}", self.board);
if self.play_out_flop() && self.play_out_possible_hands_at_flop() && self.play_out_turn() {
self.play_out_river();
}
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_deal(&self) -> bool {
if self.players.len() < 2 {
return false;
}
println!("{}", self.play_out_deal_fmt());
true
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_deal_fmt(&self) -> String {
if self.players.len() < 2 {
"Not enough players".to_string()
} else {
let mut s = String::new();
let chances = self.chances_at_deal();
for k in chances.keys() {
let row = format!(
"Seat #{} {}: {:.1}% ",
k,
self.players.get(*k).unwrap(),
chances.get(*k)
);
s.push_str(row.as_str());
}
s
}
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_flop(&self) -> bool {
if self.players.len() < 2 || !self.board.flop.is_dealt() {
return false;
}
let chances = self.chances_at_flop();
println!("\nThe Flop: {}", self.board.flop);
println!("Chances of winning:");
for k in chances.keys() {
println!(
"Seat #{} {}: {:.1}% - CURRENT HAND: {}",
k,
self.players.get(*k).unwrap(),
chances.get(*k),
self.player_eval_at_flop(*k)
);
}
let the_nuts = self.nuts_at_flop();
println!("\nThe Nuts would be: {}", the_nuts);
for seat_eval in self.who_flopped_the_nuts().iter() {
println!(
"!! Player {} flopped the nuts {} !!",
seat_eval.seat.number, seat_eval.eval
);
}
true
}
pub fn play_out_possible_hands_at_flop(&self) -> bool {
if self.players.len() < 2 || !self.board.flop.is_dealt() {
false
} else {
println!("\nPossible hands at the flop, sorted by strength:");
for (v, e) in self.board.flop.all_possible().indexed().index_map() {
println!("CKC #{} {}", v, e);
}
println!("See https://suffe.cool/poker/7462.html for a listing of all CKC numbers.");
true
}
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_turn(&self) -> bool {
if self.board.turn.is_dealt() {
let (outs, case_evals) = self.eval_at_turn_with_outs();
let chances = case_evals.chances();
println!("\nThe Turn: {}", self.board.turn);
println!("Chances of winning:");
chances.playout_with_outs(&outs);
true
} else {
false
}
}
#[allow(clippy::missing_panics_doc)]
pub fn play_out_river(&self) {
if self.board.river.is_dealt() {
let case_evals = self.eval_at_river();
let chances = case_evals.chances();
println!("\nThe River: {}", self.board.river);
chances.playout();
let winners = case_evals.winners();
println!("\nWinners:");
for winner in winners.iter() {
println!(" Seat {}: {}", winner.seat.number, winner.eval);
}
}
}
pub fn who_flopped_the_nuts(&self) -> CaseEval {
self.tied_or_better_at_flop(&self.nuts_at_flop())
}
}
impl CardSlot for Table {
fn take(&self, card: PlayingCard) -> bool {
if self.players.is_dealt() {
self.board.take(card)
} else {
self.players.take(card)
}
}
fn fold(&self) -> PlayingCards {
let mut cards = PlayingCards::default();
let folded = self.players.fold();
cards.append(&folded);
let folded = self.board.fold();
cards.append(&folded);
cards
}
fn is_dealt(&self) -> bool {
self.players.is_dealt() && self.board.is_dealt()
}
fn to_playing_cards(&self) -> PlayingCards {
let mut cards = PlayingCards::default();
cards.append(&self.players.to_playing_cards());
cards.append(&self.board.to_playing_cards());
cards
}
}
impl Display for Table {
fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
let mut out = fmt.debug_list();
out.entry(&(format!("PLAYERS: {}", self.players)).fmt_display());
out.entry(&(format!("BOARD: {}", self.board)).fmt_display());
out.finish()
}
}
impl TryFrom<&'static str> for Table {
type Error = HandError;
fn try_from(value: &'static str) -> Result<Self, Self::Error> {
let playing_cards = PlayingCards::try_from(value);
match playing_cards {
Ok(mut cards) => {
if cards.len() < 9 {
Err(HandError::NotEnoughCards)
} else if cards.len() % 2 != 1 {
Err(HandError::InvalidCardCount)
} else {
let mut table = Table::default();
table
.board
.take_from_playing_cards(&cards.draw_from_the_bottom(5));
match Seats::try_from(cards) {
Ok(players) => {
table.players = players;
Ok(table)
}
_ => Err(HandError::InvalidIndex),
}
}
}
Err(e) => Err(e),
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod holdem_table_tests {
use super::*;
#[test]
fn eval_from_flop() {
let player_count: usize = 3;
let table = Table::seat(player_count);
let index = "Q♠ 4♦ 8♥ 5♦ 2♠ 4♠ J♦ 7♠ 9♣ 8♠ 6♦ 2♦ J♥ Q♥ 6♠ 4♥ T♣ 4♣ 6♣ 8♦ A♣ 3♣ A♥ 8♣ J♠ 9♠ 7♣ T♠ Q♣ T♦ 9♦ 5♠ 7♥ 2♣ 3♥ 5♣ 7♦ 3♦ 3♠ Q♦ K♥ A♠ K♦ 9♥ K♠ 5♥ K♣ 2♥ T♥ 6♥ A♦ J♣";
let mut cards = PlayingCards::try_from(index).unwrap();
for _ in 0..(player_count * 2) + 5 {
table.take(cards.draw_one());
}
let evals = table.eval_at_flop();
assert_eq!(903, evals.len())
}
#[test]
fn from_index() {
let index = "6♣ J♥ 7♠ 8♠ 9♦ 2♣ 6♠ T♥ 3♥ 9♥ 9♠";
let table = Table::from_index(index).unwrap();
assert_eq!(index, table.dealt().to_string());
assert_eq!(
"[Seat 0: 6♣ J♥, Seat 1: 7♠ 8♠, Seat 2: 9♦ 2♣]",
table.players.to_string()
);
assert_eq!(
"[FLOP: 6♠ T♥ 3♥, TURN: 9♥, RIVER: 9♠]",
table.board.to_string()
);
}
#[test]
fn from_index__invalid_card() {
let index = "66 J♥ 7♠ 8♠ 9♦ 2♣ 6♠ T♥ 3♥ 9♥ 9♠";
let table = Table::from_index(index);
assert!(table.is_err());
assert_eq!(HandError::InvalidCard, table.unwrap_err());
}
#[test]
fn from_index__not_enough_cards() {
let index = "9♦ 2♣ 6♠ T♥ 3♥ 9♥ 9♠";
let table = Table::from_index(index);
assert!(table.is_err());
assert_eq!(HandError::NotEnoughCards, table.unwrap_err());
}
#[test]
fn from_index__invalid_card_count() {
let index = "J♥ 7♠ 8♠ 9♦ 2♣ 6♠ T♥ 3♥ 9♥ 9♠";
let table = Table::from_index(index);
assert!(table.is_err());
assert_eq!(HandError::InvalidCardCount, table.unwrap_err());
}
#[test]
fn take() {
let table = Table::seat(3);
let index = "Q♠ 4♦ 8♥ 5♦ 2♠ 4♠ J♦ 7♠ 9♣ 8♠ 6♦ 2♦ J♥ Q♥ 6♠ 4♥ T♣ 4♣ 6♣ 8♦ A♣ 3♣ A♥ 8♣ J♠ 9♠ 7♣ T♠ Q♣ T♦ 9♦ 5♠ 7♥ 2♣ 3♥ 5♣ 7♦ 3♦ 3♠ Q♦ K♥ A♠ K♦ 9♥ K♠ 5♥ K♣ 2♥ T♥ 6♥ A♦ J♣";
let mut cards = PlayingCards::try_from(index).unwrap();
table.take(cards.draw_one());
table.take(cards.draw_one());
table.take(cards.draw_one());
assert_eq!(
"[Seat 0: Q♠ __, Seat 1: 4♦ __, Seat 2: 8♥ __]",
table.players.to_string()
);
assert_eq!("Q♠ 4♦ 8♥", table.players.dealt().to_string());
assert_eq!("", table.board.dealt().to_string());
assert!(!table.players.is_dealt());
assert!(!table.board.is_dealt());
table.take(cards.draw_one());
table.take(cards.draw_one());
table.take(cards.draw_one());
assert_eq!(
"[Seat 0: Q♠ 5♦, Seat 1: 4♦ 2♠, Seat 2: 8♥ 4♠]",
table.players.to_string()
);
assert_eq!("Q♠ 5♦ 4♦ 2♠ 8♥ 4♠", table.players.dealt().to_string());
assert_eq!("", table.board.dealt().to_string());
assert_eq!("Q♠ 5♦ 4♦ 2♠ 8♥ 4♠", table.to_playing_cards().to_string());
assert!(table.players.is_dealt());
assert!(!table.board.is_dealt());
table.take(cards.draw_one());
table.take(cards.draw_one());
table.take(cards.draw_one());
table.take(cards.draw_one());
table.take(cards.draw_one());
assert_eq!("J♦ 7♠ 9♣ 8♠ 6♦", table.board.dealt().to_string());
assert_eq!(
"Q♠ 5♦ 4♦ 2♠ 8♥ 4♠ J♦ 7♠ 9♣ 8♠ 6♦",
table.to_playing_cards().to_string()
);
assert!(table.players.is_dealt());
assert!(table.board.is_dealt());
}
#[test]
fn display() {
assert_eq!(
"[PLAYERS: [], BOARD: [FLOP: __ __ __, TURN: __, RIVER: __]]",
Table::default().to_string()
);
}
#[test]
#[ignore]
fn playout_from_turn() {
let mut table = Table::default();
table.players = Seats::from_index("4♥ 3♥ A♦ J♣ 8♦ 8♣").unwrap();
table.board = Board::from_index("T♥ J♠ J♥ 3♦").unwrap();
table.play_out_flop();
table.play_out_turn();
table.play_out_river();
}
}