diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b89935 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0c3c52d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "skillratings" +version = "0.1.0" +edition = "2021" +description = "Calculate a player's skill level using elo and glicko-2 algorithms." +readme= "README.md" +repository = "https://github.com/atomflunder/skillratings" +license = "MIT" +keywords = ["elo", "glicko-2", "glicko2", "skill", "rating"] +categories = ["game-development", "algorithms"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..341d0e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 atomflunder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dcac2ac --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# skillratings + +Calculate a player's skill level using [Elo](https://en.wikipedia.org/wiki/Elo_rating_system) and [Glicko-2](https://en.wikipedia.org/wiki/Glicko_rating_system#Glicko-2_algorithm) algorithms known from their usage in chess and other games. + +## Usage + +For a detailed guide on how to use this crate, head over to the documentation. + +### Elo rating system +```rust +extern crate skillratings; + +use skillratings; + +let player_one = skillratings::rating::EloRating { rating: 1000.0 }; +let player_two = skillratings::rating::EloRating { rating: 1000.0 }; + +// The outcome is from the perspective of player one. +let outcome = skillratings::outcomes::Outcomes::WIN; + +let (player_one_new, player_two_new) = skillratings::elo::elo(player_one, player_two, outcome, 32.0); +assert_eq!(player_one_new.rating, 1016.0); +assert_eq!(player_two_new.rating, 984.0); +``` + +### Glicko-2 rating system + +```rust +extern crate skillratings; + +use skillratings; + +let player_one = skillratings::rating::GlickoRating { + rating: 1500.0, + deviation: 350.0, + volatility: 0.06 +}; +let player_two = skillratings::rating::GlickoRating { + rating: 1500.0, + deviation: 350.0, + volatility: 0.06 +}; + +let outcome = skillratings::outcomes::Outcomes::WIN; + +let (player_one_new, player_two_new) = skillratings::glicko2::glicko2(player_one, player_two, outcome, 0.5); + +assert_eq!(player_one_new.rating.round(), 1662.0); +assert_eq!(player_one_new.deviation.round(), 290.0); + +assert_eq!(player_two_new.rating.round(), 1338.0); +assert_eq!(player_two_new.deviation.round(), 290.0); +``` + +# License + +This project is licensed under the [MIT License](/LICENSE). diff --git a/src/elo.rs b/src/elo.rs new file mode 100644 index 0000000..d561f6e --- /dev/null +++ b/src/elo.rs @@ -0,0 +1,128 @@ +use crate::outcomes::Outcomes; +use crate::rating::EloRating; + +/// Calculates the elo scores of two players based on their ratings and the outcome of the game. +/// +/// Takes in two players, the outcome of the game and the k-value. +/// +/// The outcome of the match is in the perspective of player_one. +/// This means `Outcomes::WIN` is a win for player_one and `Outcomes::LOSS` is a win for player_two. +/// +/// The k-value is the maximum amount of rating change from a single match. +/// In chess, k-values from 40 to 10 are used, with the most common being 32, 24 or 16. +/// The higher the number, the more volatile the ranking. +/// +/// # Example +/// ``` +/// use skillratings; +/// +/// let player_one = skillratings::rating::EloRating { rating: 1000.0 }; +/// let player_two = skillratings::rating::EloRating { rating: 1000.0 }; +/// +/// let outcome = skillratings::outcomes::Outcomes::WIN; +/// +/// let (player_one_new, player_two_new) = skillratings::elo::elo(player_one, player_two, outcome, 32.0); +/// assert_eq!(player_one_new.rating, 1016.0); +/// assert_eq!(player_two_new.rating, 984.0); +/// ``` +/// +/// # More +/// [Wikipedia Article on the Elo system](https://en.wikipedia.org/wiki/Elo_rating_system). +pub fn elo( + player_one: EloRating, + player_two: EloRating, + outcome: Outcomes, + k: f64, +) -> (EloRating, EloRating) { + let (one_expected, two_expected) = expected_score(player_one.rating, player_two.rating); + + let o = match outcome { + Outcomes::WIN => 1.0, + Outcomes::LOSS => 0.0, + Outcomes::DRAW => 0.5, + }; + + let one_new_elo = player_one.rating + k * (o - one_expected); + let two_new_elo = player_two.rating + k * ((1.0 - o) - two_expected); + + ( + EloRating { + rating: one_new_elo, + }, + EloRating { + rating: two_new_elo, + }, + ) +} + +/// Calculates the expected score of two players based on their elo rating. +/// Meant for usage in the elo function, but you can also use it to predict games yourself. +/// +/// Takes in two elo scores and returns the expected score of each player. +/// A score of 1.0 means certain win, a score of 0.0 means certain loss, and a score of 0.5 is a draw. +/// +/// #Example +/// ``` +/// use skillratings; +/// +/// let (exp_one, exp_two) = skillratings::elo::expected_score(1500.0, 1210.0); +/// assert_eq!((exp_one * 100.0).round(), 84.0); +/// assert_eq!((exp_two * 100.0).round(), 16.0); +/// ``` +pub fn expected_score(player_one_elo: f64, player_two_elo: f64) -> (f64, f64) { + ( + 1.0 / (1.0 + 10_f64.powf((player_two_elo - player_one_elo) / 400.0)), + 1.0 / (1.0 + 10_f64.powf((player_one_elo - player_two_elo) / 400.0)), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_elo() { + let (winner_new_elo, loser_new_elo) = elo( + EloRating { rating: 1000.0 }, + EloRating { rating: 1000.0 }, + Outcomes::WIN, + 32.0, + ); + assert_eq!(winner_new_elo.rating, 1016.0); + assert_eq!(loser_new_elo.rating, 984.0); + + let (winner_new_elo, loser_new_elo) = elo( + EloRating { rating: 1000.0 }, + EloRating { rating: 1000.0 }, + Outcomes::LOSS, + 32.0, + ); + assert_eq!(winner_new_elo.rating, 984.0); + assert_eq!(loser_new_elo.rating, 1016.0); + + let (winner_new_elo, loser_new_elo) = elo( + EloRating { rating: 1000.0 }, + EloRating { rating: 1000.0 }, + Outcomes::DRAW, + 32.0, + ); + assert_eq!(winner_new_elo.rating, 1000.0); + assert_eq!(loser_new_elo.rating, 1000.0); + + let (winner_new_elo, loser_new_elo) = elo( + EloRating { rating: 500.0 }, + EloRating { rating: 1500.0 }, + Outcomes::WIN, + 32.0, + ); + assert_eq!(winner_new_elo.rating.round(), 532.0); + assert_eq!(loser_new_elo.rating.round(), 1468.0); + } + + #[test] + fn test_expected_score() { + let (winner_expected, loser_expected) = expected_score(1000.0, 1000.0); + assert_eq!(winner_expected, 0.5); + assert_eq!(loser_expected, 0.5); + } +} diff --git a/src/glicko2.rs b/src/glicko2.rs new file mode 100644 index 0000000..867f565 --- /dev/null +++ b/src/glicko2.rs @@ -0,0 +1,416 @@ +use crate::{outcomes::Outcomes, rating::GlickoRating}; +use std::f64::consts::PI; + +/// Calculates the glicko-2 scores of two players based on their ratings, deviations, and the outcome of the game. +/// +/// Takes in two players, the outcome of the game and a tau constant. +/// +/// The outcome of the match is in the perspective of player_one. +/// This means `Outcomes::WIN` is a win for player_one and `Outcomes::LOSS` is a win for player_two. +/// +/// The tau constant constrains the change in volatility over time. +/// To cite Mark Glickman himself: "Reasonable choices are between 0.3 and 1.2". +/// Smaller values mean less change in volatility and vice versa. +/// The most common choice seems to be 0.5. +/// In any case, pick one value and **stick to your choice** in your system's calculations. +/// +/// # Example +/// ``` +/// use skillratings; +/// +/// let player_one = skillratings::rating::GlickoRating { rating: 1500.0, deviation: 350.0, volatility: 0.06 }; +/// let player_two = skillratings::rating::GlickoRating { rating: 1500.0, deviation: 350.0, volatility: 0.06 }; +/// +/// let outcome = skillratings::outcomes::Outcomes::WIN; +/// +/// let (player_one_new, player_two_new) = skillratings::glicko2::glicko2(player_one, player_two, outcome, 0.5); +/// +/// assert_eq!(player_one_new.rating.round(), 1662.0); +/// assert_eq!(player_one_new.deviation.round(), 290.0); +/// assert_eq!(player_one_new.volatility, 0.05999578094735206); +/// +/// assert_eq!(player_two_new.rating.round(), 1338.0); +/// assert_eq!(player_two_new.deviation.round(), 290.0); +/// assert_eq!(player_two_new.volatility, 0.05999578094735206); +/// ``` +/// +/// # More +/// [Wikipedia Article on the Glicko-2 system](https://en.wikipedia.org/wiki/Glicko-2). +/// [Example of the Glicko-2 system](http://www.glicko.net/glicko/glicko2.pdf). +pub fn glicko2( + player_one: GlickoRating, + player_two: GlickoRating, + outcome: Outcomes, + tau: f64, +) -> (GlickoRating, GlickoRating) { + // First we need to convert the ratings into the glicko-2 scale. + let player_one_rating = (player_one.rating - 1500.0) / 173.7178; + let player_two_rating = (player_two.rating - 1500.0) / 173.7178; + + // Same with the deviation. + let player_one_deviation = player_one.deviation / 173.7178; + let player_two_deviation = player_two.deviation / 173.7178; + + let outcome1 = match outcome { + Outcomes::WIN => 1.0, + Outcomes::DRAW => 0.5, + Outcomes::LOSS => 0.0, + }; + let outcome2 = 1.0 - outcome1; + + // We always need the deviation of the opponent in the g function. + let g1 = g_value(player_two_deviation); + let g2 = g_value(player_one_deviation); + + let e1 = e_value(player_one_rating, player_two_rating, g1); + let e2 = e_value(player_two_rating, player_one_rating, g2); + + let v1 = v_value(g1, e1); + let v2 = v_value(g2, e2); + + let player_one_new_volatility = new_volatility( + player_one.volatility, + delta_value(outcome1, v1, g1, e1), + player_one_deviation, + v1, + tau, + ); + let player_two_new_volatility = new_volatility( + player_two.volatility, + delta_value(outcome2, v2, g2, e2), + player_two_deviation, + v2, + tau, + ); + + let new_deviation1 = new_deviation( + new_pre_deviation(player_one_deviation, player_one_new_volatility), + v1, + ); + let new_deviation2 = new_deviation( + new_pre_deviation(player_two_deviation, player_two_new_volatility), + v2, + ); + + let new_rating1 = new_rating(player_one_rating, new_deviation1, outcome1, g1, e1); + let new_rating2 = new_rating(player_two_rating, new_deviation2, outcome2, g2, e2); + + let player_one_new = GlickoRating { + rating: (new_rating1 * 173.7178) + 1500.0, + deviation: new_deviation1 * 173.7178, + volatility: player_one_new_volatility, + }; + let player_two_new = GlickoRating { + rating: (new_rating2 * 173.7178) + 1500.0, + deviation: new_deviation2 * 173.7178, + volatility: player_two_new_volatility, + }; + + (player_one_new, player_two_new) +} + +/// Calculates the expected outcome of two players based on glicko-2, assuming no draws. +/// +/// Takes in two players and returns the probability of victory for each player, +/// again, without taking draws into calculation. +/// 1.0 means a certain victory for the player, 0.0 means certain loss. +/// Values near 0.5 mean a draw is likely to occur. +/// +/// # Example +/// ``` +/// use skillratings; +/// +/// let player_one = skillratings::rating::GlickoRating { rating: 2500.0, deviation: 41.0, volatility: 0.06 }; +/// let player_two = skillratings::rating::GlickoRating { rating: 1950.0, deviation: 320.0, volatility: 0.06 }; +/// +/// let (exp_one, exp_two) = skillratings::glicko2::expected_score(player_one, player_two); +/// +/// assert_eq!((exp_one * 100.0).round(), 90.0); +/// assert_eq!((exp_two * 100.0).round(), 10.0); +/// ``` +pub fn expected_score(player_one: GlickoRating, player_two: GlickoRating) -> (f64, f64) { + // First we need to convert the ratings into the glicko-2 scale. + let player_one_rating = (player_one.rating - 1500.0) / 173.7178; + let player_two_rating = (player_two.rating - 1500.0) / 173.7178; + + // Same with the deviation. + let player_one_deviation = player_one.deviation / 173.7178; + let player_two_deviation = player_two.deviation / 173.7178; + + let a1 = a_value( + player_one_deviation, + player_two_deviation, + player_one_rating, + player_two_rating, + ); + let a2 = a_value( + player_two_deviation, + player_one_deviation, + player_two_rating, + player_one_rating, + ); + + ( + (1.0 + (-1.0 * a1).exp()).recip(), + (1.0 + (-1.0 * a2).exp()).recip(), + ) +} + +/// The g value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn g_value(deviation: f64) -> f64 { + (1.0 + ((3.0 * deviation.powf(2.0)) / (PI.powf(2.0)))) + .sqrt() + .recip() +} + +/// The E value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn e_value(rating: f64, opponent_rating: f64, g: f64) -> f64 { + (1.0 + (-1.0 * g * (rating - opponent_rating)).exp()).recip() +} + +/// The v value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn v_value(g: f64, e: f64) -> f64 { + (g.powf(2.0) * e * (1.0 - e)).recip() +} + +/// The ∆ value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn delta_value(outcome: f64, v: f64, g: f64, e: f64) -> f64 { + v * (g * (outcome - e)) +} + +/// The f(x) value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn f_value( + x: f64, + delta_square: f64, + deviation_square: f64, + v: f64, + volatility: f64, + tau: f64, +) -> f64 { + let one = { + let i = x.exp() * (delta_square - deviation_square - v - x.exp()); + let j = 2.0 * (deviation_square + v + x.exp()); + i / j + }; + + let two = { + let i = x - volatility.powf(2.0).ln(); + let j = tau.powf(2.0); + i / j + }; + + one - two +} + +/// The A value of the expected_outcome function, based on glicko-2, +/// slightly modified to produce an expected outcome score. +/// Not found in the original paper. +fn a_value(deviation: f64, opponent_deviation: f64, rating: f64, opponent_rating: f64) -> f64 { + g_value((opponent_deviation.powf(2.0) + deviation.powf(2.0)).sqrt()) + * (rating - opponent_rating) +} + +/// The σ' value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn new_volatility(old_volatility: f64, delta: f64, deviation: f64, v: f64, tau: f64) -> f64 { + let mut a = old_volatility.powf(2.0).ln(); + let delta_squared = delta.powf(2.0); + let deviation_squared = deviation.powf(2.0); + let mut b = if delta_squared > deviation_squared + v { + delta_squared - deviation_squared - v + } else { + let mut k = 1.0; + while f_value( + a - k * tau, + delta_squared, + deviation, + v, + old_volatility, + tau, + ) < 0.0 + { + k += 1.0; + } + a - k * tau + }; + + let mut fa = f_value(a, delta_squared, deviation_squared, v, old_volatility, tau); + let mut fb = f_value(b, delta_squared, deviation_squared, v, old_volatility, tau); + + // 0.000001 is the convergence tolerance suggested by Mark Glickman. + while (b - a).abs() > 0.000001 { + let c = a + ((a - b) * fa / (fb - fa)); + let fc = f_value(c, delta_squared, deviation_squared, v, old_volatility, tau); + + if fc * fb <= 0.0 { + a = b; + fa = fb; + } else { + fa /= 2.0; + } + + b = c; + fb = fc; + } + + (a / 2.0).exp() +} + +/// The φ* value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn new_pre_deviation(deviation: f64, new_volatility: f64) -> f64 { + (deviation.powf(2.0) + new_volatility.powf(2.0)).sqrt() +} + +/// The φ' value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn new_deviation(pre_deviation: f64, v: f64) -> f64 { + ((pre_deviation.powf(2.0).recip()) + (v.recip())) + .sqrt() + .recip() +} + +/// The µ' value of the glicko-2 calculation. +/// For more information, see: http://www.glicko.net/glicko/glicko2.pdf +fn new_rating(rating: f64, new_deviation: f64, outcome: f64, g_value: f64, e_value: f64) -> f64 { + rating + (new_deviation.powf(2.0) * g_value * (outcome - e_value)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::outcomes::Outcomes; + + #[test] + fn test_equal_glicko2() { + let player1 = GlickoRating { + rating: 1520.0, + deviation: 350.0, + volatility: 0.06, + }; + + let player2 = GlickoRating { + rating: 1420.0, + deviation: 350.0, + volatility: 0.06, + }; + + let (player1new, player2new) = glicko2(player1, player2, Outcomes::WIN, 0.5); + + assert_eq!(player1new.rating.round(), 1653.0); + assert_eq!(player1new.deviation.round(), 292.0); + + assert_eq!(player2new.rating.round(), 1287.0); + assert_eq!(player2new.deviation.round(), 292.0); + } + + #[test] + fn not_equal_deviation_draw() { + let player1 = GlickoRating { + rating: 1600.0, + deviation: 350.0, + volatility: 0.06, + }; + + let player2 = GlickoRating { + rating: 1500.0, + deviation: 50.0, + volatility: 0.06, + }; + + let (player1new, player2new) = glicko2(player1, player2, Outcomes::DRAW, 0.5); + + assert_eq!(player1new.rating.round(), 1550.0); + assert_eq!(player1new.deviation.round(), 253.0); + + assert_eq!(player2new.rating.round(), 1501.0); + assert_eq!(player2new.deviation.round(), 51.0); + } + + #[test] + /// This test is taken directly from the official glicko2 example. + /// http://www.glicko.net/glicko/glicko2.pdf + fn test_glicko2() { + let player = GlickoRating { + rating: 1500.0, + deviation: 200.0, + volatility: 0.06, + }; + + let opponent_one = GlickoRating { + rating: 1400.0, + deviation: 30.0, + volatility: 0.06, + }; + + let (player, opponent_one) = glicko2(player, opponent_one, Outcomes::WIN, 0.5); + + assert_eq!(player.rating.round(), 1564.0); + assert_eq!(player.deviation.round(), 175.0); + + assert_eq!(opponent_one.rating.round(), 1398.0); + assert_eq!(opponent_one.deviation.round(), 32.0); + + let opponent_two = GlickoRating { + rating: 1550.0, + deviation: 100.0, + volatility: 0.06, + }; + + let (player, _) = glicko2(player, opponent_two, Outcomes::LOSS, 0.5); + + let opponent_three = GlickoRating { + rating: 1700.0, + deviation: 300.0, + volatility: 0.06, + }; + + let (player, _) = glicko2(player, opponent_three, Outcomes::LOSS, 0.5); + + assert_eq!(player.rating.round(), 1464.0); + assert_eq!(player.deviation.round(), 152.0); + assert_eq!(player.volatility, 0.059982355058921626); + } + + #[test] + fn test_expected_score() { + let player_one = GlickoRating { + rating: 1500.0, + deviation: 350.0, + volatility: 0.06, + }; + + let player_two = GlickoRating { + rating: 1500.0, + deviation: 350.0, + volatility: 0.06, + }; + + let (exp_one, exp_two) = expected_score(player_one, player_two); + + assert_eq!(exp_one * 100.0, 50.0); + assert_eq!(exp_two * 100.0, 50.0); + + let player_three = GlickoRating { + rating: 2000.0, + deviation: 50.0, + volatility: 0.06, + }; + + let player_four = GlickoRating { + rating: 1780.0, + deviation: 150.0, + volatility: 0.06, + }; + + let (exp_three, exp_four) = expected_score(player_three, player_four); + + assert_eq!((exp_three * 100.0).round(), 76.0); + assert_eq!((exp_four * 100.0).round(), 24.0); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..049fcd5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,17 @@ +#![warn(missing_docs)] + +//! skillratings provides functions on calculating a player's skill rating in 1v1 games. +//! Currently we support the two major skill rating systems: +//! [Elo](https://en.wikipedia.org/wiki/Elo_rating_system) and [Glicko-2](https://en.wikipedia.org/wiki/Glicko-2). + +/// Module for calculating a player's skill rating using Elo. +pub mod elo; + +/// Module for calculating a player's skill rating using Glicko-2. +pub mod glicko2; + +/// Module for the outcome of the matches. +pub mod outcomes; + +/// Module for initialising a player's skill rating with either Elo or Glicko-2. +pub mod rating; diff --git a/src/outcomes.rs b/src/outcomes.rs new file mode 100644 index 0000000..0cb87eb --- /dev/null +++ b/src/outcomes.rs @@ -0,0 +1,11 @@ +/// The possible outcomes for a match: Win, Draw, Loss. +/// Note that this is always from the perspective of player one. +/// So a win is a win for player one and a loss is a win for player two. +pub enum Outcomes { + /// A win, from player_one's perspective. + WIN, + /// A loss, from player_one's perspective. + LOSS, + /// A draw. + DRAW, +} diff --git a/src/rating.rs b/src/rating.rs new file mode 100644 index 0000000..5f3d553 --- /dev/null +++ b/src/rating.rs @@ -0,0 +1,41 @@ +/// The elo rating of a player. +/// +/// The default rating is 1000.0. +#[derive(Copy, Clone, Debug)] +pub struct EloRating { + /// The player's Elo rating number, by default 1000.0. + pub rating: f64, +} + +impl EloRating { + /// Initialise a new EloRating with a rating of 1000.0. + pub fn new() -> Self { + EloRating { rating: 1000.0 } + } +} + +/// The glicko-2 rating of a player. +/// +/// The default rating is 1500.0. +/// The default deviation is 350.0. +/// The default volatility is 0.06. +#[derive(Copy, Clone, Debug)] +pub struct GlickoRating { + /// The player's Glicko-2 rating number, by default 1500.0. + pub rating: f64, + /// The player's Glicko-2 deviation number, by default 350.0. + pub deviation: f64, + /// The player's Glicko-2 volatility number, by default 0.06. + pub volatility: f64, +} + +impl GlickoRating { + /// Initialise a new GlickoRating with a rating of 1500.0, a deviation of 350.0 and a volatility of 0.06. + pub fn new() -> Self { + GlickoRating { + rating: 1500.0, + deviation: 350.0, + volatility: 0.06, + } + } +}