use rand::{Rng, rng, seq::SliceRandom};
pub fn simulate_icm_tournament(chip_stacks: &[i32], payments: &[i32]) -> Vec<i32> {
let mut remaining_stacks: Vec<i32> = chip_stacks.into();
let mut rng = rng();
let mut next_place = remaining_stacks.len() - 1;
let mut winnings = vec![0; remaining_stacks.len()];
let mut remaining_players: Vec<usize> = (0..chip_stacks.len()).collect();
while !remaining_players.is_empty() {
remaining_players.shuffle(&mut rng);
let hero = remaining_players.pop().expect("There should always be one");
if let Some(villan) = remaining_players.pop() {
let hero_won: bool = rng.random_bool(0.5);
let effective_stacks = remaining_stacks[hero].min(remaining_stacks[villan]);
let hero_change: i32 = if hero_won {
effective_stacks
} else {
-effective_stacks
};
remaining_stacks[hero] += hero_change;
remaining_stacks[villan] -= hero_change;
if remaining_stacks[hero] == 0 {
if next_place < payments.len() {
winnings[hero] = payments[next_place];
}
next_place -= 1;
} else {
remaining_players.push(hero);
}
if remaining_stacks[villan] == 0 {
if next_place < payments.len() {
winnings[villan] = payments[next_place];
}
next_place -= 1;
} else {
remaining_players.push(villan);
}
} else {
winnings[hero] = payments[next_place];
};
}
winnings
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_players_works() {
let payments = vec![10_000, 6_000, 4_000, 1_000, 800];
let mut rng = rng();
for num_players in &[2, 3, 4, 5, 15, 16, 32] {
let chips: Vec<i32> = (0..*num_players)
.map(|_pn| rng.random_range(1..500))
.collect();
let _res = simulate_icm_tournament(&chips, &payments);
}
}
#[test]
fn test_huge_lead_wins() {
let stacks = vec![1000, 2, 1];
let payments = vec![100, 30, 10];
let mut total_winnings = vec![0; 3];
let num_trials = 1000;
for _i in 0..num_trials {
let single_wins = simulate_icm_tournament(&stacks, &payments);
total_winnings = total_winnings
.iter()
.zip(single_wins.iter())
.map(|(a, b)| a + b)
.collect()
}
let final_share: Vec<f64> = total_winnings
.iter()
.map(|v| f64::from(*v) / f64::from(num_trials))
.collect();
assert!(
final_share[0] > final_share[1],
"The total winnings of a player with most of the chips should be above the rest."
);
}
#[test]
fn about_same() {
let stacks = vec![1000, 1000, 999];
let payments = vec![100, 30, 10];
let mut total_winnings = vec![0; 3];
let num_trials = 1000;
for _i in 0..num_trials {
let single_wins = simulate_icm_tournament(&stacks, &payments);
total_winnings = total_winnings
.iter()
.zip(single_wins.iter())
.map(|(a, b)| a + b)
.collect();
}
let final_share: Vec<f64> = total_winnings
.iter()
.map(|v| f64::from(*v) / f64::from(num_trials))
.collect();
let sum: f64 = final_share.iter().sum();
let avg = sum / (final_share.len() as f64);
for &share in final_share.iter() {
assert!(share < 1.1 * avg);
assert!(1.1 * share > avg);
}
}
}