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
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use crate::systems::Player;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;

const NUM_TITLES: usize = 11;
const TITLE_BOUND: [i32; NUM_TITLES] = [
    -999, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2700, 3000,
];
const TITLE: [&str; NUM_TITLES] = [
    "Ne", "Pu", "Ap", "Sp", "Ex", "CM", "Ma", "IM", "GM", "IG", "LG",
];

pub struct GlobalSummary {
    mean_rating: f64,
    title_count: Vec<usize>,
}

#[derive(Serialize, Deserialize)]
pub struct PlayerSummary {
    rank: Option<usize>,
    cur_rating: i32,
    max_rating: i32,
    cur_sigma: i32,
    num_contests: usize,
    last_contest: usize,
    last_contest_time: u64,
    last_perf: i32,
    last_delta: i32,
    handle: String,
}

pub fn make_leaderboard(
    players: &HashMap<String, RefCell<Player>>,
    rated_since: u64,
) -> (GlobalSummary, Vec<PlayerSummary>) {
    let mut rating_data = Vec::with_capacity(players.len());
    let mut title_count = vec![0; NUM_TITLES];
    let sum_ratings = {
        let mut ratings: Vec<f64> = players
            .iter()
            .map(|(_, player)| player.borrow().approx_posterior.mu)
            .collect();
        ratings.sort_by(|a, b| a.partial_cmp(b).expect("NaN is unordered"));
        ratings.into_iter().sum::<f64>()
    };
    for (handle, player) in players {
        let player = player.borrow_mut();
        let num_contests = player.event_history.len();
        let last_event = player.event_history.last().unwrap();
        let max_rating = player
            .event_history
            .iter()
            .map(|event| event.display_rating)
            .max()
            .unwrap();
        let previous_rating = if num_contests == 1 {
            960 // TODO: get rid of this magic number
        } else {
            player.event_history[num_contests - 2].display_rating
        };
        rating_data.push(PlayerSummary {
            rank: None,
            cur_rating: last_event.display_rating,
            max_rating,
            cur_sigma: player.approx_posterior.sig.round() as i32,
            num_contests,
            last_contest: last_event.contest_id,
            last_contest_time: last_event.contest_time,
            last_perf: last_event.perf_score,
            last_delta: last_event.display_rating - previous_rating,
            handle: handle.clone(),
        });

        if last_event.contest_time > rated_since {
            if let Some(title_id) = (0..NUM_TITLES)
                .rev()
                .find(|&i| last_event.display_rating >= TITLE_BOUND[i])
            {
                title_count[title_id] += 1;
            }
        }
    }
    rating_data.sort_unstable_by_key(|data| (-data.cur_rating, data.handle.clone()));

    let mut rank = 0;
    for data in &mut rating_data {
        if data.last_contest_time > rated_since {
            rank += 1;
            data.rank = Some(rank);
        }
    }

    let global_summary = GlobalSummary {
        mean_rating: sum_ratings / players.len() as f64,
        title_count,
    };

    (global_summary, rating_data)
}

pub fn print_ratings(players: &HashMap<String, RefCell<Player>>, rated_since: u64) {
    let (summary, rating_data) = make_leaderboard(players, rated_since);

    let filename = "../data/ratings_output.csv";
    let file = std::fs::File::create(filename).expect("Output file not found");

    println!("Mean rating.mu = {}", summary.mean_rating);
    for i in (0..NUM_TITLES).rev() {
        println!(
            "{} {} x{:6}",
            TITLE_BOUND[i], TITLE[i], summary.title_count[i]
        );
    }
    println!("Detailed ratings saved to {}", filename);

    let mut writer = csv::Writer::from_writer(file);
    for data in rating_data {
        writer.serialize(data).unwrap();
    }
}