Skip to content

Commit

Permalink
feat: keep track of the round before simlation completes (#126)
Browse files Browse the repository at this point in the history
Summary:
When running a simulation we can transition from lots of different
rounds to complete via folding or via showdown. So keep track of what
the last round we have seen before complete'ing the sim.

Test Plan:
Ran `cargo run --release --example agent_battle`

Got:

```
Current Competition Stats: HoldemCompetition { num_rounds: 2500, total_change: [422.86856, -2458.051, 3308.6985, -1273.5121, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], max_change: [846.8667, 643.11194, 636.79913, 471.92227, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], min_change: [-641.6119, -835.31396, -411.79034, -774.67834, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], win_count: [975, 1111, 207, 212, 0, 0, 0, 0, 0, 0, 0, 0], zero_count: [273, 333, 1117, 1085, 0, 0, 0, 0, 0, 0, 0, 0], loss_count: [1252, 1056, 1176, 1203, 0, 0, 0, 0, 0, 0, 0, 0], round_before: {Preflop: 1300, Flop: 515, Showdown: 275, Turn: 262, River: 148} }
```

That's about the distribution I would have expected. Lots of folding
before starting with non-premium hands. Then we lose a lot on the flop.
Rounds after that not many fold. I doubt that the actions are GTO
but they look reasonable.
  • Loading branch information
elliottneilclark authored Jun 29, 2024
1 parent 6174278 commit 65a9299
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 43 deletions.
18 changes: 16 additions & 2 deletions src/arena/competition/holdem_competition.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{collections::VecDeque, fmt::Debug};
use std::{
collections::{HashMap, VecDeque},
fmt::Debug,
};

use crate::arena::{errors::HoldemSimulationError, HoldemSimulation};
use crate::arena::{errors::HoldemSimulationError, game_state::Round, HoldemSimulation};

use super::sim_gen::HoldemSimulationGenerator;

Expand All @@ -24,6 +27,8 @@ pub struct HoldemCompetition<T: HoldemSimulationGenerator> {
pub loss_count: Vec<usize>,
// How many times the agent has lost no money
pub zero_count: Vec<usize>,
// Count of the round before the simulation stopped
pub before_count: HashMap<Round, usize>,

/// Maximum number of HoldemSimulation's to
/// keep in a long call to `run`
Expand All @@ -50,6 +55,8 @@ impl<T: HoldemSimulationGenerator> HoldemCompetition<T> {
win_count: vec![0; MAX_PLAYERS],
loss_count: vec![0; MAX_PLAYERS],
zero_count: vec![0; MAX_PLAYERS],
// Round before stopping
before_count: HashMap::new(),
}
}

Expand Down Expand Up @@ -115,6 +122,12 @@ impl<T: HoldemSimulationGenerator> HoldemCompetition<T> {
self.zero_count[idx] += 1;
}
}
// Update the count
let count = self
.before_count
.entry(running_sim.game_state.round_before)
.or_default();
*count += 1;
}
}
impl<T: HoldemSimulationGenerator> Debug for HoldemCompetition<T> {
Expand All @@ -127,6 +140,7 @@ impl<T: HoldemSimulationGenerator> Debug for HoldemCompetition<T> {
.field("win_count", &self.win_count)
.field("zero_count", &self.zero_count)
.field("loss_count", &self.loss_count)
.field("round_before", &self.before_count)
.finish()
}
}
Expand Down
68 changes: 27 additions & 41 deletions src/arena/game_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Round {
}
}

#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Debug)]
pub struct RoundData {
// Which players were active starting this round.
pub starting_player_active: PlayerBitSet,
Expand Down Expand Up @@ -168,7 +168,7 @@ impl RoundData {
}
}

#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Debug)]
pub struct GameState {
/// The number of players that started
pub num_players: usize,
Expand Down Expand Up @@ -199,6 +199,10 @@ pub struct GameState {
pub dealer_idx: usize,
// What round this is currently
pub round: Round,
/// This is the round before we completed the game.
/// Sometimes the game completes because of
/// all the players fold in the preflop.
pub round_before: Round,
// ALl the current state of the round.
pub round_data: RoundData,
// The community cards.
Expand Down Expand Up @@ -243,6 +247,7 @@ impl GameState {
total_pot: 0.0,
hands: vec![Hand::default(); num_players],
round: Round::Starting,
round_before: Round::Starting,
board: vec![],
round_data: RoundData::new(num_players, big_blind, player_active, dealer_idx),
computed_rank: vec![None; num_players],
Expand Down Expand Up @@ -304,6 +309,9 @@ impl GameState {

fn advance_normal(&mut self) {
self.round = self.round.advance();
// We're advancing (not completing) so
// keep advanding the round_before field as well.
self.round_before = self.round;

let mut round_data = RoundData::new(
self.num_players,
Expand All @@ -321,6 +329,7 @@ impl GameState {
}

pub fn complete(&mut self) {
self.round_before = self.round;
self.round = Round::Complete;
let round_data = RoundData::new(
self.num_players,
Expand Down Expand Up @@ -453,45 +462,6 @@ impl GameState {
}
}

impl fmt::Debug for RoundData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RoundData")
.field("needs_action", &self.needs_action)
.field("num_players_need_action", &self.num_players_need_action())
.field("min_raise", &self.min_raise)
.field("bet", &self.bet)
.field("player_bet", &self.player_bet)
.field("bet_count", &self.bet_count)
.field("raise_count", &self.raise_count)
.field("to_act_idx", &self.to_act_idx)
.finish()
}
}

impl fmt::Debug for GameState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GameState")
.field("num_players", &self.num_players)
.field("num_active_players", &self.num_active_players())
.field("player_active", &self.player_active)
.field("player_all_in", &self.player_all_in)
.field("total_pot", &self.total_pot)
.field("stacks", &self.stacks)
.field("player_winnings", &self.player_winnings)
.field("big_blind", &self.big_blind)
.field("small_blind", &self.small_blind)
.field("ante", &self.ante)
.field("hands", &self.hands)
.field("dealer_idx", &self.dealer_idx)
.field("round", &self.round)
.field("round_data", &self.round_data)
.field("board", &self.board)
.field("sb_posted", &self.sb_posted)
.field("bb_posted", &self.bb_posted)
.finish()
}
}

pub trait GameStateGenerator {
fn generate(&mut self) -> GameState;
}
Expand Down Expand Up @@ -688,6 +658,7 @@ mod tests {
// Do the start and ante rounds and setup next to act
game_state.advance_round();
game_state.advance_round();
game_state.advance_round();

game_state.do_bet(10.0, true).unwrap();
game_state.do_bet(20.0, true).unwrap();
Expand All @@ -702,4 +673,19 @@ mod tests {
game_state.do_bet(33.0, false)
);
}

#[test]
fn test_gamestate_keeps_round_before_complete() {
let stacks = vec![100.0; 3];
let mut game_state = GameState::new(stacks, 10.0, 5.0, 0.0, 0);
// Simulate a game where everyone folds and the big blind wins
game_state.advance_round();
game_state.advance_round();
game_state.advance_round();
game_state.fold();
game_state.fold();
game_state.complete();
assert_eq!(Round::Complete, game_state.round);
assert_eq!(Round::Preflop, game_state.round_before);
}
}

0 comments on commit 65a9299

Please sign in to comment.