use fixedbitset::FixedBitSet;
use rand::seq::IteratorRandom;
use rand::{thread_rng, Rng};
const DEFAULT_PAYMENT: i32 = 0;
#[inline]
fn award_payments(
remaining_stacks: &[i32],
payments: &[i32],
idx: usize,
other_idx: usize,
winnings: &mut [i32],
next_place: &mut usize,
) -> bool {
if remaining_stacks[idx] == 0 {
winnings[idx] += payments.get(*next_place).unwrap_or(&DEFAULT_PAYMENT);
*next_place -= 1;
if *next_place == 0 {
winnings[other_idx] += payments.get(*next_place).unwrap_or(&DEFAULT_PAYMENT);
}
true
} else {
false
}
}
pub fn simulate_icm_tournament(chip_stacks: &[i32], payments: &[i32]) -> Vec<i32> {
let mut remaining_stacks: Vec<i32> = chip_stacks.into();
let mut rng = thread_rng();
let mut next_place = remaining_stacks.len() - 1;
let mut winnings = vec![0; remaining_stacks.len()];
let mut remaining_players = FixedBitSet::with_capacity(remaining_stacks.len());
remaining_players.insert_range(..);
while next_place > 0 {
if let [hero, villan] = remaining_players
.ones()
.choose_multiple(&mut rng, 2)
.as_slice()
{
let hero_won: bool = rng.gen_bool(0.5);
let effective_stacks =
std::cmp::min(remaining_stacks[*hero], 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 award_payments(
&remaining_stacks,
payments,
*hero,
*villan,
&mut winnings,
&mut next_place,
) {
remaining_players.set(*hero, false);
}
if award_payments(
&remaining_stacks,
payments,
*villan,
*hero,
&mut winnings,
&mut next_place,
) {
remaining_players.set(*villan, false);
}
}
}
winnings
}
#[cfg(test)]
mod tests {
use super::*;
#[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| (*v as f64) / (num_trials as f64))
.collect();
assert!(
final_share[0] > final_share[1],
"The total winnings of a player with most of the chips should be above the rest."
);
dbg!(final_share);
}
#[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| (*v as f64) / (num_trials as f64))
.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);
}
dbg!(final_share);
}
}