mahjong_cli/simulate/
stats.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use chrono::Utc;
use mahjong_core::{Game, PlayerId};
use rustc_hash::FxHashMap;
use std::ops::{Add, Div};

fn average<T: Add<Output = T> + Div<Output = T> + Default + Copy + Into<f64>>(
    numbers: &Vec<T>,
) -> f64 {
    let mut total: T = Default::default();
    for numb in numbers {
        total = total + *numb;
    }
    total.into() / numbers.len() as f64
}

pub struct Stats {
    games_num: usize,
    games_per_second: Vec<(f32, usize)>,
    rounds_num: Vec<u32>,
    start_time: chrono::NaiveTime,
    top_scores: Vec<u32>,
    winners: FxHashMap<PlayerId, u32>,
}

impl Stats {
    pub fn new() -> Self {
        Self {
            games_num: 0,
            games_per_second: vec![],
            rounds_num: vec![],
            start_time: Utc::now().time(),
            top_scores: vec![],
            winners: FxHashMap::default(),
        }
    }

    pub fn complete_game(&mut self, game: &Game) {
        // `max_by_key` favors the last value so need to randomize the keys
        let mut players = game.players.clone();

        players.shuffle();

        let winner = players.iter().max_by_key(|k| game.score.get(k).unwrap());

        if let Some(winner) = winner {
            *self.winners.entry(winner.clone()).or_insert(0) += 1;
        }

        self.games_num += 1;
        self.rounds_num.push(game.round.round_index + 1);
        let top_score = game.score.0.values().max().unwrap();
        self.top_scores.push(*top_score);
    }

    pub fn print_if_interval(&mut self, seconds: usize) -> bool {
        let end_time = Utc::now().time();
        let diff = (end_time - self.start_time).num_seconds() as usize;

        if diff > seconds {
            let last_average = self.games_per_second.last().unwrap_or(&(0.0, 0));
            let games_per_s = (self.games_num as f32 - last_average.1 as f32) / diff as f32;

            self.games_per_second.push((games_per_s, self.games_num));
            let top_score = *self.top_scores.iter().max().unwrap();

            self.top_scores = vec![];

            let gps = self
                .games_per_second
                .iter()
                .map(|(games_per_s, _)| *games_per_s)
                .collect::<Vec<f32>>();
            let average_games_per_s = average(&gps);
            let average_rounds_per_game = average(&self.rounds_num);

            self.start_time = end_time;

            println!("Winners: {:?}", self.winners);
            println!("Number of games: {:?}", self.games_num);
            println!("Average games per second: {:.2}", average_games_per_s);
            println!("Average rounds per game: {:.2}", average_rounds_per_game);
            println!("Top score in last batch: {:?}", top_score);
            println!("---\n");
        }

        diff > seconds
    }
}