Skip to main content

pickems/reporting/
basic.rs

1use std::{
2    iter::Sum,
3    ops::{Add, AddAssign},
4};
5
6use crate::{
7    reporting::Report,
8    simulation::{Simulation, SwissSystem},
9};
10
11/// Counts of terminal outcomes for one team across simulation iterations.
12#[derive(Debug, Clone, Copy, Default)]
13pub struct BasicStats {
14    /// Number of simulations where the team advanced 3-0.
15    pub three_zero: u64,
16    /// Number of simulations where the team advanced 3-1 or 3-2.
17    pub advancing: u64,
18    /// Number of simulations where the team was eliminated 0-3.
19    pub zero_three: u64,
20}
21
22impl AddAssign for BasicStats {
23    fn add_assign(&mut self, rhs: Self) {
24        self.three_zero += rhs.three_zero;
25        self.advancing += rhs.advancing;
26        self.zero_three += rhs.zero_three;
27    }
28}
29
30/// Report for 3-0, advancement, and 0-3 percentages for each team.
31#[derive(Debug, Clone, Copy, Default)]
32pub struct BasicReport {
33    /// Per-team outcome counts, indexed by initial seed index.
34    pub stats: [BasicStats; 16],
35}
36
37impl BasicReport {
38    /// Convert raw counts into probabilities for each terminal outcome.
39    pub(super) fn calculate_probabilities(&self, sim: &Simulation) -> [[f32; 16]; 3] {
40        let n = sim.iterations as f32;
41        let [mut three_zero, mut advancing, mut zero_three] = [[0.0; 16]; 3];
42
43        for seed in 0..16 {
44            three_zero[seed] += self.stats[seed].three_zero as f32;
45            advancing[seed] += self.stats[seed].advancing as f32;
46            zero_three[seed] += self.stats[seed].zero_three as f32;
47        }
48
49        for seed in 0..16 {
50            three_zero[seed] /= n;
51            advancing[seed] /= n;
52            zero_three[seed] /= n;
53        }
54
55        [three_zero, advancing, zero_three]
56    }
57}
58
59impl Add for BasicReport {
60    type Output = Self;
61
62    fn add(mut self, rhs: Self) -> Self::Output {
63        for i in 0..self.stats.len() {
64            self.stats[i] += rhs.stats[i];
65        }
66
67        self
68    }
69}
70
71impl Sum for BasicReport {
72    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
73        iter.fold(Self::default(), |acc, report| acc + report)
74    }
75}
76
77impl Report for BasicReport {
78    fn update(&mut self, ss: &SwissSystem) {
79        for (seed, result) in self.stats.iter_mut().enumerate() {
80            match (ss.wins[seed], ss.losses[seed]) {
81                (3, 0) => result.three_zero += 1,
82                (3, _) => result.advancing += 1,
83                (0, 3) => result.zero_three += 1,
84                _ => {}
85            }
86        }
87    }
88
89    fn format(&self, sim: &Simulation) -> String {
90        let probabilities = self.calculate_probabilities(sim);
91        let mut out = Vec::new();
92
93        // Setup access indices and titles for each field of stats.
94        let fields: [(usize, &str); 3] = [(0, "3-0"), (1, "3-1 or 3-2"), (2, "0-3")];
95
96        // Process each field of stats.
97        for (index, title) in fields {
98            out.push(format!("\nMost likely to {title}:"));
99
100            let mut results = sim
101                .teams
102                .names
103                .iter()
104                .zip(probabilities[index])
105                .collect::<Vec<_>>();
106
107            results.sort_by(|(_, a), (_, b)| b.total_cmp(a));
108
109            // Format each result into a string.
110            for (i, (name, result)) in results.into_iter().enumerate() {
111                out.push(format!(
112                    "{num:<4}{name:<20}{percent:>6.1}%",
113                    num = format!("{}.", i + 1),
114                    name = name,
115                    percent = (result * 1000.0).round() / 10.0
116                ));
117            }
118        }
119
120        out.join("\n")
121    }
122}