use std::{path::PathBuf, time::Instant};
use itertools::Itertools;
use rand::prelude::*;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{data::parse_toml, simulate::reporting::Report};
mod matching;
mod reporting;
mod swiss_system;
mod team_set;
use matching::MatchupGenerator;
pub use reporting::{BasicReport, NullReport};
use swiss_system::SwissSystem;
use team_set::TeamSet;
pub(super) type RngType = rand_chacha::ChaCha8Rng;
pub(super) fn make_rng() -> RngType {
RngType::from_rng(&mut rand::rng())
}
pub(super) fn make_deterministic_rng() -> RngType {
RngType::seed_from_u64(7355608)
}
#[derive(Debug, Clone)]
pub struct Simulation {
pub names: [String; 16],
pub ratings: [i16; 16],
pub sigma: f32,
pub iterations: u64,
}
impl Simulation {
pub fn new(names: [String; 16], ratings: [i16; 16], sigma: f32, iterations: u64) -> Self {
Self {
names,
ratings,
sigma,
iterations,
}
}
pub fn dummy(iterations: u64) -> Self {
Self {
names: (0..16)
.map(|i| format!("Team {}", i + 1))
.collect_array()
.unwrap(),
ratings: (0..16).map(|i| 2000 - 50 * i).collect_array().unwrap(),
sigma: 800.0,
iterations,
}
}
pub fn bench_test<R: Report>(iterations: u64) -> R {
let sim = Self::dummy(iterations);
let fresh_ss = SwissSystem::new(sim.ratings, sim.sigma);
let mut report = R::default();
let mut rng = make_deterministic_rng();
for _ in 0..iterations {
let mut ss = fresh_ss;
ss.simulate_tournament(&mut rng);
report.update(&ss);
}
report
}
pub fn run<R: Report>(&self) -> R {
let fresh_ss = SwissSystem::new(self.ratings, self.sigma);
(0..self.iterations)
.into_par_iter()
.map_init(
|| (fresh_ss, make_rng()),
|(ss, rng), _| {
ss.reset();
ss.simulate_tournament(rng);
R::from_swiss_system(ss)
},
)
.sum()
}
}
pub fn simulate<R: Report>(file: PathBuf, sigma: f32, iterations: u64) -> anyhow::Result<()> {
let now = Instant::now();
let (names, ratings) = parse_toml(file)?;
let sim = Simulation::new(names, ratings, sigma, iterations);
let report = sim.run::<R>();
println!(
"{}\n\nRun time: {} seconds",
report.format(&sim),
now.elapsed().as_millis() as f32 / 1000.0
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_test() {
let iterations = 1000;
let report = Simulation::bench_test::<BasicReport>(iterations);
assert_eq!(
(0..16)
.map(|index| report.stats[index].three_zero)
.sum::<u64>(),
iterations * 2
);
assert_eq!(
(0..16)
.map(|index| report.stats[index].advanced)
.sum::<u64>(),
iterations * 6
);
assert_eq!(
(0..16)
.map(|index| report.stats[index].zero_three)
.sum::<u64>(),
iterations * 2
);
assert!(report.stats[0].three_zero > report.stats[15].three_zero);
assert!(report.stats[0].zero_three < report.stats[15].zero_three);
}
}