aboutsummaryrefslogtreecommitdiff
path: root/planetwars-server/src/modules/ranking.rs
blob: f76fbae5e9a7584a82317983e3fb5d2187821581 (plain)
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
use crate::{db::bots::Bot, DbPool};

use crate::db;
use crate::modules::matches::RunMatch;
use rand::seq::SliceRandom;
use std::time::Duration;
use tokio;

const RANKER_INTERVAL: u64 = 60;
const START_RATING: f64 = 0.0;
const SCALE: f64 = 100.0;
const MAX_UPDATE: f64 = 0.1;

pub async fn run_ranker(db_pool: DbPool) {
    // TODO: make this configurable
    // play at most one match every n seconds
    let mut interval = tokio::time::interval(Duration::from_secs(RANKER_INTERVAL));
    let db_conn = db_pool
        .get()
        .await
        .expect("could not get database connection");
    loop {
        interval.tick().await;
        let bots = db::bots::find_all_bots(&db_conn).unwrap();
        if bots.len() < 2 {
            // not enough bots to play a match
            continue;
        }
        let selected_bots: Vec<Bot> = {
            let mut rng = &mut rand::thread_rng();
            bots.choose_multiple(&mut rng, 2).cloned().collect()
        };
        play_ranking_match(selected_bots, db_pool.clone()).await;
    }
}

async fn play_ranking_match(selected_bots: Vec<Bot>, db_pool: DbPool) {
    let db_conn = db_pool.get().await.expect("could not get db pool");
    let mut code_bundles = Vec::new();
    for bot in &selected_bots {
        let code_bundle = db::bots::active_code_bundle(bot.id, &db_conn)
            .expect("could not get active code bundle");
        code_bundles.push(code_bundle);
    }

    let code_bundle_refs = code_bundles.iter().map(|b| b).collect::<Vec<_>>();

    let mut run_match = RunMatch::from_players(code_bundle_refs);
    run_match
        .store_in_database(&db_conn)
        .expect("could not store match in db");
    let outcome = run_match
        .spawn(db_pool.clone())
        .await
        .expect("running match failed");

    let mut ratings = Vec::new();
    for bot in &selected_bots {
        let rating = db::ratings::get_rating(bot.id, &db_conn)
            .expect("could not get bot rating")
            .unwrap_or(START_RATING);
        ratings.push(rating);
    }

    // simple elo rating

    let scores = match outcome.winner {
        None => vec![0.5; 2],
        Some(player_num) => {
            // TODO: please get rid of this offset
            let player_ix = player_num - 1;
            let mut scores = vec![0.0; 2];
            scores[player_ix] = 1.0;
            scores
        }
    };

    for i in 0..2 {
        let j = 1 - i;

        let scaled_difference = (ratings[j] - ratings[i]) / SCALE;
        let expected = 1.0 / (1.0 + 10f64.powf(scaled_difference));
        let new_rating = ratings[i] + MAX_UPDATE * (scores[i] - expected);
        db::ratings::set_rating(selected_bots[i].id, new_rating, &db_conn)
            .expect("could not update bot rating");
    }
}