pub mod agents;
pub mod cli;
use std::fmt::Debug;
use itertools::Itertools;
use rand::Rng;
use crate::{betting::{transfer_capped, Chips}, deck::{sample_cards, CardMask}, hand::{refhand5::RefHand5, Hand, Hand5}, texas::agents::{TexasAgent, TexasAgentState}};
type PlayerID = u64;
#[derive(Debug, Clone)]
pub struct TexasStateSharedAgent {
active: bool,
bank: Chips,
}
#[derive(Debug, Clone)]
pub struct TexasStateShared {
agents: Vec<TexasStateSharedAgent>,
shared: CardMask,
pot: Chips,
bet: Chips,
}
#[derive(Debug, Clone)]
pub struct TexasStateHidden {
hole: CardMask,
}
pub struct TexasEngine {
ante: Chips,
blinds: Vec<Chips>,
agents: Vec<Box<dyn TexasAgent>>,
shared: TexasStateShared,
hiddens: Vec<TexasStateHidden>,
}
impl TexasEngine {
pub fn new(ante: Chips, blinds: Vec<Chips>) -> Self {
Self { ante, blinds, agents: Vec::new(), shared: TexasStateShared {
agents: Vec::new(),
shared: CardMask::NONE,
pot: 0.into(),
bet: 0.into(),
}, hiddens: Vec::new() }
}
pub fn add_agent(&mut self, agent: Box<dyn TexasAgent>, bank: Chips) {
self.agents.push(agent);
self.hiddens.push(TexasStateHidden { hole: CardMask::NONE });
self.shared.agents.push(TexasStateSharedAgent { active: true, bank });
self.shared.bet = bank;
}
pub fn execute<R: Rng>(&mut self, rng: &mut R) {
self.shared.shared = CardMask::NONE;
self.shared.pot = 0.into();
for (id, _) in self.agents.iter_mut().enumerate() {
self.shared.agents[id].active = if self.shared.agents[id].bank > 0.into() { true } else { false };
assert!(self.shared.agents[id].active, "Player {:} has 0 chips, which is not allowed", id);
}
let id_button = 0 as PlayerID;
assert!(self.shared.agents[id_button as usize].active);
let mut deck = CardMask::FULL;
for (id, hidden) in self.hiddens.iter_mut().enumerate() {
if !self.shared.agents[id as usize].active {
continue;
}
hidden.hole = sample_cards(deck, 2, rng);
deck = deck & !hidden.hole;
}
let mut forced = vec![self.ante; self.agents.len()];
for (idx, &bet) in self.blinds.iter().enumerate() {
forced[(id_button as usize + idx + 1) % self.agents.len()] += bet;
}
let id_after_forced = ((id_button as usize + self.blinds.len() + 1) % self.agents.len()) as PlayerID;
let id_after_button = ((id_button as usize + 1) % self.agents.len()) as PlayerID;
self.round_forced(id_after_forced, &forced);
let cards_flop = sample_cards(deck, 3, rng);
self.shared.shared = self.shared.shared | cards_flop;
deck = deck & !cards_flop;
self.round(id_after_button);
let cards_turn = sample_cards(deck, 1, rng);
self.shared.shared = self.shared.shared | cards_turn;
deck = deck & !cards_turn;
self.round(id_after_button);
let cards_river = sample_cards(deck, 1, rng);
self.shared.shared = self.shared.shared | cards_river;
deck = deck & !cards_river;
self.round(id_after_button);
self.showdown();
}
pub fn showdown(&mut self) {
let mut hands = vec![];
println!("---- SHOWDOWN ----");
let refbest5 = RefHand5::new();
println!("pot: {:}", self.shared.pot);
println!("shared: {:}", self.shared.shared);
for (id, _) in self.agents.iter_mut().enumerate() {
let hole = self.hiddens[id].hole;
let hand = refbest5.hand5(hole | self.shared.shared);
hands.push((id, hand));
if self.shared.agents[id].active {
println!("Player {:} has [hole: {:}] -> {:}", id, hole, hand);
} else {
println!("Player {:} was folded", id);
}
}
println!("WIN ORDER:");
hands.sort_by(|a, b| a.1.cmp(&b.1));
hands.reverse();
let mut last_hand: Option<Hand> = None;
let mut win_idx = 0;
for (id, hand) in hands.iter() {
if last_hand.is_none() || last_hand.unwrap() != *hand {
last_hand = Some(*hand);
win_idx += 1;
println!("WIN GROUP {:}", win_idx);
}
println!("P{:} has {:}", id, hand);
}
}
pub fn round_forced(&mut self, start: PlayerID, forced: &[Chips]) {
println!("banks: {:}", self.shared.agents.iter().enumerate().map(|(id, bank)| format!("P{:} has {:}", id, bank.bank)).collect::<Vec<_>>().join(", "));
let mut paid = vec![Chips::new(0); self.agents.len()];
for (id, &bet) in forced.iter().enumerate() {
let (actual, _) = transfer_capped(&mut self.shared.pot, &mut self.shared.agents[id].bank, bet);
paid[id] = actual;
}
let mut ask_total = *forced.iter().max().unwrap();
println!("ask_total: {:}", ask_total);
let mut id_when_stop: Option<PlayerID> = None;
let mut id = start;
loop {
if let Some(id_when_stop) = id_when_stop {
if id == id_when_stop {
break;
}
}
if self.shared.agents[id as usize].active {
if self.shared.agents[id as usize].bank > 0.into() {
let ask = ask_total - paid[id as usize];
self.shared.bet = ask;
let mut amount = self.agents[id as usize].decide(&TexasAgentState { id, hidden: &self.hiddens[id as usize], shared: &self.shared }).amount;
if amount > 0.into() && amount < ask {
println!("ERROR: P{:} tried to bet less than the current bet size of {:}, this will be considered a fold", id, ask);
amount = 0.into();
}
let (actual, allin) = transfer_capped(&mut self.shared.pot, &mut self.shared.agents[id as usize].bank, amount);
paid[id as usize] += actual;
if actual == 0.into() {
if ask > 0.into() {
println!("P{:}: FOLD", id);
self.shared.agents[id as usize].active = false;
} else {
println!("P{:}: CHECK", id);
}
} else if actual == ask {
if allin {
println!("P{:}: CALL {:} (ALL-IN)", id, actual);
} else {
println!("P{:}: CALL {:}", id, actual);
}
} else if actual > ask {
id_when_stop = Some(id);
ask_total += actual - ask;
if allin {
println!("P{:}: RAISE {:} (ALL-IN)", id, actual);
} else {
println!("P{:}: RAISE {:}", id, actual);
}
} else {
unreachable!("unexpected case");
}
}
}
if let None = id_when_stop {
id_when_stop = Some(id);
}
id = (id + 1) % self.agents.len() as PlayerID;
}
}
pub fn round(&mut self, start: PlayerID) {
let forced = vec![0.into(); self.agents.len()];
self.round_forced(start, &forced)
}
}