Crates.io | instant-glicko-2 |
lib.rs | instant-glicko-2 |
version | 0.2.0 |
source | src |
created_at | 2022-03-31 11:56:13.017427 |
updated_at | 2023-11-12 23:01:24.043839 |
description | Implementation of the Glicko-2 rating system that allows for instant feedback after games, not just once a rating period closes |
homepage | |
repository | https://github.com/gpluscb/instant-glicko-2 |
max_upload_size | |
id | 559581 |
size | 96,745 |
This crate provides an implementation of the Glicko-2 rating system. Due to the concept of rating periods, Glicko-2 has the problem that rankings cannot easily be updated instantly after a match concludes.
This implementation aims to solve that problem by allowing fractional rating periods, so that ratings can be updated directly after every game, and not just once a rating period closes. This draws inspiration from the rating system implementation for open-source chess website Lichess, as well as two blogpost (1, 2) by Ryan Juckett on skill ratings for INVERSUS Deluxe.
For more on the implementation, I wrote something here.
Documentation for the crate can be found on Docs.rs.
To use this as a dependency, add the following line to your Cargo.toml
dependencies:
instant-glicko-2 = "0.1.0"
Example calculation from Glickman's paper using algorithm
:
use instant_glicko_2::{Parameters, PublicRating, IntoWithParameters};
use instant_glicko_2::algorithm::{self, PublicGame};
let parameters = Parameters::default().with_volatility_change(0.5);
// Create our player's rating
let mut player = PublicRating::new(1500.0, 200.0, 0.06);
// Create our opponents
// Their volatility is not specified in the paper and it doesn't matter in the calculation,
// so we're just using the default starting volatility.
let opponent_a = PublicRating::new(1400.0, 30.0, parameters.start_rating().volatility());
let opponent_b = PublicRating::new(1550.0, 100.0, parameters.start_rating().volatility());
let opponent_c = PublicRating::new(1700.0, 300.0, parameters.start_rating().volatility());
// Create match results for our player
let results = [
// Wins first game (score 1.0)
PublicGame::new(opponent_a, 1.0).into_with_parameters(parameters),
// Loses second game (score 0.0)
PublicGame::new(opponent_b, 0.0).into_with_parameters(parameters),
// Loses third game (score 0.0)
PublicGame::new(opponent_c, 0.0).into_with_parameters(parameters),
];
// Update rating after rating period
let new_rating: PublicRating = algorithm::rate_games_untimed(player.into_with_parameters(parameters), &results, 1.0, parameters).into_with_parameters(parameters);
// The rating after the rating period are very close to the results from the paper
assert!((new_rating.rating() - 1464.06).abs() < 0.01);
assert!((new_rating.deviation() - 151.52).abs() < 0.01);
assert!((new_rating.volatility() - 0.05999).abs() < 0.0001);
Different example using RatingEngine
:
use std::time::Duration;
use instant_glicko_2::{Parameters, PublicRating};
use instant_glicko_2::engine::{MatchResult, RatingEngine};
let parameters = Parameters::default();
// Create a RatingEngine with a one day rating period duration
// The first rating period starts instantly
let mut engine = RatingEngine::start_new(
Duration::from_secs(60 * 60 * 24),
Parameters::default(),
);
// Register two players
// The first player is relatively strong
let player_1_rating_old = PublicRating::new(1700.0, 300.0, 0.06);
let player_1 = engine.register_player(player_1_rating_old).0;
// The second player hasn't played any games
let player_2_rating_old = parameters.start_rating();
let player_2 = engine.register_player(player_2_rating_old).0;
// They play and player_2 wins
engine.register_result(
player_1,
player_2,
&MatchResult::Loss,
);
// Print the new ratings
// Type signatures are needed because we could also work with the internal InternalRating
// That skips one step of calculation,
// but the rating values are not as pretty and not comparable to the original Glicko ratings
let player_1_rating_new: PublicRating = engine.player_rating(player_1).0;
println!("Player 1 old rating: {player_1_rating_old:?}, new rating: {player_1_rating_new:?}");
let player_2_rating_new: PublicRating = engine.player_rating(player_2).0;
println!("Player 2 old rating: {player_2_rating_old:?}, new rating: {player_2_rating_new:?}");
// Loser's rating goes down, winner's rating goes up
assert!(player_1_rating_old.rating() > player_1_rating_new.rating());
assert!(player_2_rating_old.rating() < player_2_rating_new.rating());