use std::sync::Arc;
use clap::Parser;
use rs_poker::core::RSPokerError;
use rs_poker::simulated_icm::simulate_icm_tournament;
use tokio::task::JoinSet;
#[derive(Debug, thiserror::Error)]
pub enum SimulateError {
#[error(transparent)]
Poker(#[from] RSPokerError),
#[error("simulation task failed to join: {0}")]
Join(#[from] tokio::task::JoinError),
}
#[derive(Parser, Debug)]
#[command(
name = "simulate",
about = "Simulate tournament outcomes to convert chip stacks into $EV",
long_about = "Run Monte Carlo simulations convert chip EV into $EV.\n\
--chips-stacks 100 90 70 60 --payments 5 3 2"
)]
pub struct SimulateArgs {
#[arg(required = true, long, num_args = 2..)]
chip_stacks: Vec<f32>,
#[arg(required = true, long, num_args = 1..)]
payments: Vec<f32>,
#[arg(long, default_value = "100000")]
iterations: usize,
}
fn simulate_chunk(count: usize, c_chips: &[i32], c_payments: &[i32], n: usize) -> Vec<f64> {
let mut local_sums = vec![0.0f64; n];
for _ in 0..count {
let res = simulate_icm_tournament(c_chips, c_payments);
for i in 0..n {
local_sums[i] += res[i] as f64;
}
}
local_sums
}
pub async fn run(args: SimulateArgs) -> Result<(), SimulateError> {
println!(
"Starting ICM simulation... {:?} iterations",
args.iterations
);
println!("Stacks: {:?}", args.chip_stacks);
println!("Payments: {:?}", args.payments);
println!();
let n = args.chip_stacks.len();
let c_chips: Arc<Vec<i32>> = Arc::new(
args.chip_stacks
.iter()
.map(|x| (x * 100.).trunc() as i32)
.collect(),
);
let c_payments: Arc<Vec<i32>> = Arc::new(
args.payments
.iter()
.map(|x| (x * 100.).trunc() as i32)
.collect(),
);
let semaphore = rs_poker::arena::cfr::build_default_limiter();
let max_in_flight = rs_poker::arena::cfr::default_limiter_permits();
let num_chunks = max_in_flight.min(args.iterations.max(1));
let base = args.iterations / num_chunks;
let remainder = args.iterations % num_chunks;
let mut set: JoinSet<Vec<f64>> = JoinSet::new();
for chunk_idx in 0..num_chunks {
let count = base + usize::from(chunk_idx < remainder);
if count == 0 {
continue;
}
let permit = Arc::clone(&semaphore)
.acquire_owned()
.await
.expect("semaphore never closed");
let c_chips = Arc::clone(&c_chips);
let c_payments = Arc::clone(&c_payments);
set.spawn(async move {
let _permit = permit;
simulate_chunk(count, &c_chips, &c_payments, n)
});
}
let mut final_sums = vec![0.0f64; n];
while let Some(joined) = set.join_next().await {
let local = joined?;
for i in 0..n {
final_sums[i] += local[i];
}
}
let results: Vec<_> = final_sums
.into_iter()
.map(|c| (0.01 * c / args.iterations as f64) as f32)
.collect();
println!("Results:");
println!("========");
println!("{:?}", results);
Ok(())
}