feat(sim): per-bot starting_chips override
BotConfig.starting_chips (None = game default). Table tracks per-seat starting stacks so reset_bankroll_each_hand restores each bot to its own depth. Enables asymmetric stack experiments (hero deep vs opponents short and vice versa).
This commit is contained in:
parent
b9963bfbb1
commit
b4c501ec8c
4 changed files with 51 additions and 6 deletions
|
|
@ -96,6 +96,7 @@ where
|
|||
/// bot_type: "call_bot".to_string(),
|
||||
/// name: Some("Alice".to_string()),
|
||||
/// seat: None,
|
||||
/// starting_chips: None,
|
||||
/// parameters: HashMap::new(),
|
||||
/// };
|
||||
///
|
||||
|
|
@ -141,6 +142,7 @@ mod tests {
|
|||
bot_type: bot_type.to_string(),
|
||||
name: name.map(|s| s.to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,6 +258,11 @@ pub struct BotConfig {
|
|||
#[serde(default)]
|
||||
pub seat: Option<SeatIndex>,
|
||||
|
||||
/// Per-bot starting stack override (`None` = game starting_chips).
|
||||
/// Enables asymmetric stack-depth experiments.
|
||||
#[serde(default)]
|
||||
pub starting_chips: Option<ChipCount>,
|
||||
|
||||
/// Custom parameters for the bot.
|
||||
#[serde(default)]
|
||||
pub parameters: HashMap<String, toml::Value>,
|
||||
|
|
@ -1091,6 +1096,7 @@ min_raise = 50
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -1277,18 +1283,21 @@ min_raise = 50
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("A".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("B".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "random_bot".to_string(),
|
||||
name: Some("C".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -1335,6 +1344,7 @@ min_raise = 50
|
|||
bot_type: "bot".to_string(),
|
||||
name: None,
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
};
|
||||
9
|
||||
|
|
@ -1387,18 +1397,21 @@ min_raise = 50
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("A".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("B".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "random_bot".to_string(),
|
||||
name: Some("C".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -1450,6 +1463,7 @@ min_raise = 50
|
|||
bot_type: "bot".to_string(),
|
||||
name: None,
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
};
|
||||
9
|
||||
|
|
|
|||
|
|
@ -61,14 +61,15 @@ pub fn run_cash_game(config: &GameConfig) -> Result<(SimulationStats, Table), Ga
|
|||
// Store the name before seating (seating consumes the bot).
|
||||
let name = bot.name().to_string();
|
||||
player_names.insert(player_id, name);
|
||||
starting_stacks.insert(player_id, config.starting_chips);
|
||||
let bot_stack = bot_config.starting_chips.unwrap_or(config.starting_chips);
|
||||
starting_stacks.insert(player_id, bot_stack);
|
||||
|
||||
// 3. Seat the bot.
|
||||
let seat_index = bot_config
|
||||
.seat
|
||||
.unwrap_or_else(|| table.find_empty_seat().unwrap_or(i as u8));
|
||||
|
||||
table.seat_player(seat_index, bot, config.starting_chips)?;
|
||||
table.seat_player(seat_index, bot, bot_stack)?;
|
||||
}
|
||||
|
||||
// 4. Run hands and collect stats.
|
||||
|
|
@ -423,6 +424,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
|
|
@ -437,18 +439,21 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: Some(0),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Bob".to_string()),
|
||||
seat: Some(1),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("Charlie".to_string()),
|
||||
seat: Some(2),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -536,6 +541,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
|
|
@ -550,6 +556,7 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Lonely".to_string()),
|
||||
seat: None,
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
}],
|
||||
};
|
||||
|
|
@ -577,6 +584,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
|
|
@ -591,12 +599,14 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: Some(0),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "super_ai".to_string(),
|
||||
name: Some("Bob".to_string()),
|
||||
seat: Some(1),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -625,6 +635,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
|
|
@ -640,12 +651,14 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("A".to_string()),
|
||||
seat: None, // auto-assign
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("B".to_string()),
|
||||
seat: None, // auto-assign
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -692,6 +705,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: true,
|
||||
|
|
@ -706,12 +720,14 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: Some(0),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Bob".to_string()),
|
||||
seat: Some(1),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -773,6 +789,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: true,
|
||||
|
|
@ -787,12 +804,14 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: Some(0),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("Bob".to_string()),
|
||||
seat: Some(1),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -836,6 +855,7 @@ mod tests {
|
|||
rake_percentage: 0.0,
|
||||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
cash_out_threshold: 0,
|
||||
bankroll_refill_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: true,
|
||||
|
|
@ -850,12 +870,14 @@ mod tests {
|
|||
bot_type: "call_bot".to_string(),
|
||||
name: Some("Alice".to_string()),
|
||||
seat: Some(0),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
BotConfig {
|
||||
bot_type: "fold_bot".to_string(),
|
||||
name: Some("Bob".to_string()),
|
||||
seat: Some(1),
|
||||
starting_chips: None,
|
||||
parameters: HashMap::new(),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ pub struct Table {
|
|||
config: TableConfig,
|
||||
/// All seats at the table; `None` for empty seats.
|
||||
seats: Vec<Option<Seat>>,
|
||||
|
||||
/// Per-seat starting stack for resets (defaults to config.starting_chips;
|
||||
/// records the buy-in of each seated player so per-bot overrides reset
|
||||
/// to their own depth).
|
||||
starting_by_seat: Vec<ChipCount>,
|
||||
/// Current dealer button position (seat index).
|
||||
button: SeatIndex,
|
||||
/// The currently running hand, if any.
|
||||
|
|
@ -118,9 +123,11 @@ impl Table {
|
|||
let bankroll_refill_threshold = config.bankroll_refill_threshold;
|
||||
let randomize_seats = config.randomize_seats;
|
||||
let seats = (0..config.num_seats).map(|_| None).collect();
|
||||
let starting_by_seat = (0..config.num_seats).map(|_| starting_chips).collect();
|
||||
Self {
|
||||
config,
|
||||
seats,
|
||||
starting_by_seat,
|
||||
button: 0,
|
||||
current_hand: None,
|
||||
hand_number: 0,
|
||||
|
|
@ -168,6 +175,7 @@ impl Table {
|
|||
}
|
||||
|
||||
let mut seat_obj = Seat::new(seat, player, buy_in);
|
||||
self.starting_by_seat[seat as usize] = buy_in;
|
||||
|
||||
// Build a GameInfo snapshot so the player can initialise.
|
||||
let game_info = self.build_game_info();
|
||||
|
|
@ -598,15 +606,14 @@ impl Table {
|
|||
/// Reset all seated players' stacks to starting_chips.
|
||||
/// Track the change in their bankroll.
|
||||
fn reset_stacks_to_starting(&mut self) {
|
||||
let starting = self.starting_chips;
|
||||
for slot in &mut self.seats {
|
||||
for (slot, starting) in &mut self.seats.iter_mut().zip(self.starting_by_seat.iter()) {
|
||||
if let Some(seat) = slot.as_mut() {
|
||||
// The "cost" of resetting is the difference between
|
||||
// starting and current stack.
|
||||
// If they have more than starting, they profit.
|
||||
// If they have less, they lost.
|
||||
seat.bankroll += seat.stack as i64 - starting as i64;
|
||||
seat.stack = starting;
|
||||
seat.bankroll += seat.stack as i64 - *starting as i64;
|
||||
seat.stack = *starting;
|
||||
// Re-activate sitting-out players (they have chips again).
|
||||
if seat.state == PlayerState::SittingOut {
|
||||
seat.state = PlayerState::Active;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue