mahjong_cli/simulate/
stats.rs

1use chrono::Utc;
2use mahjong_core::{Game, PlayerId};
3use rustc_hash::FxHashMap;
4use std::ops::{Add, Div};
5
6fn average<T: Add<Output = T> + Div<Output = T> + Default + Copy + Into<f64>>(
7    numbers: &Vec<T>,
8) -> f64 {
9    let mut total: T = Default::default();
10    for numb in numbers {
11        total = total + *numb;
12    }
13    total.into() / numbers.len() as f64
14}
15
16pub struct Stats {
17    games_num: usize,
18    games_per_second: Vec<(f32, usize)>,
19    rounds_num: Vec<u32>,
20    start_time: chrono::NaiveTime,
21    top_scores: Vec<u32>,
22    winners: FxHashMap<PlayerId, u32>,
23}
24
25impl Stats {
26    pub fn new() -> Self {
27        Self {
28            games_num: 0,
29            games_per_second: vec![],
30            rounds_num: vec![],
31            start_time: Utc::now().time(),
32            top_scores: vec![],
33            winners: FxHashMap::default(),
34        }
35    }
36
37    pub fn complete_game(&mut self, game: &Game) {
38        // `max_by_key` favors the last value so need to randomize the keys
39        let mut players = game.players.clone();
40
41        players.shuffle();
42
43        let winner = players.iter().max_by_key(|k| game.score.get(k).unwrap());
44
45        if let Some(winner) = winner {
46            *self.winners.entry(winner.clone()).or_insert(0) += 1;
47        }
48
49        self.games_num += 1;
50        self.rounds_num.push(game.round.round_index + 1);
51        let top_score = game.score.0.values().max().unwrap();
52        self.top_scores.push(*top_score);
53    }
54
55    pub fn print_if_interval(&mut self, seconds: usize) -> bool {
56        let end_time = Utc::now().time();
57        let diff = (end_time - self.start_time).num_seconds() as usize;
58
59        if diff > seconds {
60            let last_average = self.games_per_second.last().unwrap_or(&(0.0, 0));
61            let games_per_s = (self.games_num as f32 - last_average.1 as f32) / diff as f32;
62
63            self.games_per_second.push((games_per_s, self.games_num));
64            let top_score = *self.top_scores.iter().max().unwrap();
65
66            self.top_scores = vec![];
67
68            let gps = self
69                .games_per_second
70                .iter()
71                .map(|(games_per_s, _)| *games_per_s)
72                .collect::<Vec<f32>>();
73            let average_games_per_s = average(&gps);
74            let average_rounds_per_game = average(&self.rounds_num);
75
76            self.start_time = end_time;
77
78            println!("Winners: {:?}", self.winners);
79            println!("Number of games: {:?}", self.games_num);
80            println!("Average games per second: {:.2}", average_games_per_s);
81            println!("Average rounds per game: {:.2}", average_rounds_per_game);
82            println!("Top score in last batch: {:?}", top_score);
83            println!("---\n");
84        }
85
86        diff > seconds
87    }
88}