use rand::Rng;
use crate::core::{CardBitSet, Deck};
use super::{
Agent, GameState, HoldemSimulation, agent::FoldingAgent, errors::HoldemSimulationError,
historian::Historian,
};
fn build_deck(game_state: &GameState) -> Deck {
let mut d = CardBitSet::default();
for hand in game_state.hands.iter() {
let bitset: CardBitSet = (*hand).into();
d &= !bitset; }
for card in game_state.board.iter() {
d.remove(*card); }
d.into() }
fn build_agents(num_agents: usize) -> Vec<Box<dyn Agent>> {
(0..num_agents)
.map(|_| -> Box<dyn Agent> { Box::<FoldingAgent>::default() })
.collect()
}
pub struct HoldemSimulationBuilder {
agents: Option<Vec<Box<dyn Agent>>>,
historians: Vec<Box<dyn Historian>>,
game_state: Option<GameState>,
deck: Option<Deck>,
panic_on_historian_error: bool,
}
impl HoldemSimulationBuilder {
pub fn agents(mut self, agents: Vec<Box<dyn Agent>>) -> Self {
self.agents = Some(agents);
self
}
pub fn game_state(mut self, game_state: GameState) -> Self {
self.game_state = Some(game_state);
self
}
pub fn deck(mut self, deck: Deck) -> Self {
self.deck = Some(deck);
self
}
pub fn historians(mut self, historians: Vec<Box<dyn Historian>>) -> Self {
self.historians = historians;
self
}
pub fn panic_on_historian_error(mut self, panic_on_historian_error: bool) -> Self {
self.panic_on_historian_error = panic_on_historian_error;
self
}
pub fn build(self) -> Result<HoldemSimulation, HoldemSimulationError> {
let game_state = self
.game_state
.ok_or(HoldemSimulationError::NeedGameState)?;
let agents = self
.agents
.unwrap_or_else(|| build_agents(game_state.hands.len()));
let deck = self.deck.unwrap_or_else(|| build_deck(&game_state));
let mut rand = rand::rng();
let id = rand.random::<u128>();
Ok(HoldemSimulation {
agents,
game_state,
deck,
id,
historians: self.historians,
panic_on_historian_error: self.panic_on_historian_error,
})
}
}
impl Default for HoldemSimulationBuilder {
fn default() -> Self {
Self {
agents: None,
historians: vec![],
game_state: None,
deck: None,
panic_on_historian_error: true,
}
}
}
#[cfg(test)]
mod tests {
use rand::{SeedableRng, rngs::StdRng};
use crate::{arena::game_state::Round, core::Card};
use super::*;
#[test_log::test]
fn test_single_step_agent() {
let mut rng = StdRng::seed_from_u64(420);
let stacks = vec![100.0; 9];
let game_state = GameState::new_starting(stacks, 10.0, 5.0, 1.0, 0);
let mut sim = HoldemSimulationBuilder::default()
.game_state(game_state)
.build()
.unwrap();
assert_eq!(100.0, sim.game_state.stacks[1]);
assert_eq!(100.0, sim.game_state.stacks[2]);
sim.run_round(&mut rng);
assert_eq!(100.0, sim.game_state.stacks[1]);
assert_eq!(100.0, sim.game_state.stacks[2]);
sim.run_round(&mut rng);
for i in 0..9 {
assert_eq!(99.0, sim.game_state.stacks[i]);
}
sim.run_round(&mut rng);
sim.run_round(&mut rng);
assert_eq!(6.0, sim.game_state.player_bet[1]);
assert_eq!(11.0, sim.game_state.player_bet[2]);
}
#[test_log::test]
fn test_simulation_complex_showdown() {
let stacks = vec![102.0, 7.0, 12.0, 102.0, 202.0];
let mut game_state = GameState::new_starting(stacks, 10.0, 5.0, 2.0, 0);
let mut deck = CardBitSet::default();
let mut rng = rand::rng();
game_state.advance_round();
game_state.do_bet(2.0, true).unwrap(); game_state.do_bet(2.0, true).unwrap(); game_state.do_bet(2.0, true).unwrap(); game_state.do_bet(2.0, true).unwrap(); game_state.do_bet(2.0, true).unwrap(); game_state.advance_round();
deal_hand_card(0, "Ks", &mut deck, &mut game_state);
deal_hand_card(0, "Kh", &mut deck, &mut game_state);
deal_hand_card(1, "As", &mut deck, &mut game_state);
deal_hand_card(1, "Ac", &mut deck, &mut game_state);
deal_hand_card(2, "Ad", &mut deck, &mut game_state);
deal_hand_card(2, "Ah", &mut deck, &mut game_state);
deal_hand_card(3, "6d", &mut deck, &mut game_state);
deal_hand_card(3, "4d", &mut deck, &mut game_state);
deal_hand_card(4, "9d", &mut deck, &mut game_state);
deal_hand_card(4, "9s", &mut deck, &mut game_state);
game_state.advance_round();
game_state.do_bet(5.0, true).unwrap(); game_state.do_bet(10.0, true).unwrap(); game_state.fold(); game_state.do_bet(10.0, false).unwrap(); game_state.do_bet(10.0, false).unwrap(); game_state.advance_round();
deal_community_card("6c", &mut deck, &mut game_state);
deal_community_card("2d", &mut deck, &mut game_state);
deal_community_card("3d", &mut deck, &mut game_state);
game_state.advance_round();
assert_eq!(game_state.num_active_players(), 2);
game_state.do_bet(90.0, false).unwrap(); game_state.do_bet(90.0, false).unwrap(); game_state.advance_round();
assert_eq!(game_state.num_active_players(), 1);
deal_community_card("8h", &mut deck, &mut game_state);
game_state.advance_round();
game_state.do_bet(0.0, false).unwrap(); game_state.advance_round();
assert_eq!(game_state.num_active_players(), 1);
deal_community_card("8s", &mut deck, &mut game_state);
game_state.advance_round();
game_state.do_bet(100.0, false).unwrap(); game_state.advance_round();
assert_eq!(game_state.num_active_players(), 0);
let mut sim = HoldemSimulationBuilder::default()
.game_state(game_state)
.build()
.unwrap();
sim.run(&mut rng);
assert_eq!(Round::Complete, sim.game_state.round);
assert_eq!(180.0, sim.game_state.player_winnings[0]);
assert_eq!(15.0, sim.game_state.player_winnings[1]);
assert_eq!(30.0, sim.game_state.player_winnings[2]);
assert_eq!(0.0, sim.game_state.player_winnings[3]);
assert_eq!(100.0, sim.game_state.player_winnings[4]);
assert_eq!(180.0, sim.game_state.stacks[0]);
assert_eq!(15.0, sim.game_state.stacks[1]);
assert_eq!(30.0, sim.game_state.stacks[2]);
assert_eq!(100.0, sim.game_state.stacks[3]);
assert_eq!(100.0, sim.game_state.stacks[4]);
}
fn deal_hand_card(
idx: usize,
card_str: &str,
deck: &mut CardBitSet,
game_state: &mut GameState,
) {
let card = Card::try_from(card_str).unwrap();
assert!(deck.contains(card));
deck.remove(card);
game_state.hands[idx].insert(card);
}
fn deal_community_card(card_str: &str, deck: &mut CardBitSet, game_state: &mut GameState) {
let card = Card::try_from(card_str).unwrap();
assert!(deck.contains(card));
deck.remove(card);
for h in &mut game_state.hands {
h.insert(card);
}
game_state.board.push(card);
}
}