1use crate::data_processing::write_slice_to_file;
2use crate::systems::{Player, PlayerEvent};
3use serde::{Deserialize, Serialize};
4use std::cell::RefCell;
5use std::collections::HashMap;
6
7const NUM_TITLES: usize = 11;
8const TITLE_BOUND: [i32; NUM_TITLES] = [
9 -999, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2700, 3000,
10];
11const TITLE: [&str; NUM_TITLES] = [
12 "Ne", "Pu", "Ap", "Sp", "Ex", "CM", "Ma", "IM", "GM", "IG", "LG",
13];
14
15pub struct GlobalSummary {
16 mean_rating: f64,
17 title_count: Vec<usize>,
18}
19
20#[derive(Serialize, Deserialize)]
21pub struct PlayerSummary {
22 rank: Option<usize>,
23 display_rating: i32,
24 max_rating: i32,
25 cur_sigma: i32,
26 num_contests: usize,
27 last_contest_index: usize,
28 last_contest_time: u64,
29 last_perf: i32,
30 last_change: i32,
31 handle: String,
32}
33
34pub fn get_display_rating_from_ints(mu: i32, sig: i32) -> i32 {
35 mu - 2 * (sig - 80)
39}
40
41pub fn get_display_rating(event: &PlayerEvent) -> i32 {
42 get_display_rating_from_ints(event.rating_mu, event.rating_sig)
43}
44
45pub fn make_leaderboard(
46 players: &HashMap<String, RefCell<Player>>,
47 rated_since: u64,
48) -> (GlobalSummary, Vec<PlayerSummary>) {
49 let mut rating_data = Vec::with_capacity(players.len());
50 let mut title_count = vec![0; NUM_TITLES];
51 let sum_ratings = {
52 let mut ratings: Vec<f64> = players
53 .iter()
54 .map(|(_, player)| player.borrow().approx_posterior.mu)
55 .collect();
56 ratings.sort_by(|a, b| a.partial_cmp(b).expect("NaN is unordered"));
57 ratings.into_iter().sum::<f64>()
58 };
59 for (handle, player) in players {
60 let player = player.borrow_mut();
61 let num_contests = player.event_history.len();
62 let last_event = player.event_history.last().unwrap();
63 let max_rating = player
64 .event_history
65 .iter()
66 .map(get_display_rating)
67 .max()
68 .unwrap();
69 let display_rating = get_display_rating(&last_event);
70 let prev_rating = if num_contests == 1 {
71 get_display_rating_from_ints(1500, 350)
72 } else {
73 get_display_rating(&player.event_history[num_contests - 2])
74 };
75 rating_data.push(PlayerSummary {
76 rank: None,
77 display_rating,
78 max_rating,
79 cur_sigma: player.approx_posterior.sig.round() as i32,
80 num_contests,
81 last_contest_index: last_event.contest_index,
82 last_contest_time: player.update_time,
83 last_perf: last_event.perf_score,
84 last_change: display_rating - prev_rating,
85 handle: handle.clone(),
86 });
87
88 if player.update_time > rated_since {
89 if let Some(title_id) = (0..NUM_TITLES)
90 .rev()
91 .find(|&i| get_display_rating(&last_event) >= TITLE_BOUND[i])
92 {
93 title_count[title_id] += 1;
94 }
95 }
96 }
97 rating_data.sort_unstable_by_key(|data| (-data.display_rating, data.handle.clone()));
98
99 let mut rank = 0;
100 for data in &mut rating_data {
101 if data.last_contest_time > rated_since {
102 rank += 1;
103 data.rank = Some(rank);
104 }
105 }
106
107 let global_summary = GlobalSummary {
108 mean_rating: sum_ratings / players.len() as f64,
109 title_count,
110 };
111
112 (global_summary, rating_data)
113}
114
115pub fn print_ratings(
116 players: &HashMap<String, RefCell<Player>>,
117 rated_since: u64,
118 dir: impl AsRef<std::path::Path>,
119) {
120 let (summary, rating_data) = make_leaderboard(players, rated_since);
123
124 println!("Mean rating.mu = {}", summary.mean_rating);
125 for i in (0..NUM_TITLES).rev() {
126 println!(
127 "{} {} x{:6}",
128 TITLE_BOUND[i], TITLE[i], summary.title_count[i]
129 );
130 }
131
132 let filename = dir.as_ref().join("all_players.csv");
133 write_slice_to_file(&rating_data, &filename);
134}