Lines
90.8 %
Functions
36.11 %
Branches
100 %
// This file is part of hnefatafl-copenhagen.
//
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{
f64::consts::{LOG10_2, PI},
fmt,
};
use serde::{Deserialize, Serialize};
/// ln 10 / 400
const Q: f64 = 0.005_756_5;
pub const CONFIDENCE_INTERVAL_95: f64 = 1.96;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Rating {
pub rating: f64,
/// Ratings Deviation
pub rd: f64,
}
impl Rating {
#[must_use]
pub fn rd_sq(&self) -> f64 {
self.rd * self.rd
pub fn to_string_rounded(&self) -> String {
format!(
"{} ± {}",
self.rating.round(),
(CONFIDENCE_INTERVAL_95 * self.rd).round()
)
/// This assumes 30 2 month periods must pass before one's rating
/// deviation is the same as a new player and that a typical RD is 50.
pub fn update_rd(&mut self) {
let c = 63.2;
let rd_new = f64::sqrt(self.rd_sq() + (c * c));
self.rd = rd_new.clamp(30.0, 350.0);
pub fn update_rating(&mut self, rating: f64, outcome: &Outcome) {
self.rating += (Q / (1.0 / self.rd_sq()) + (1.0 / self.d_sq(rating)))
* self.g()
* (outcome.score() - self.e(rating));
self.rd = f64::sqrt(1.0 / ((1.0 / self.rd_sq()) + (1.0 / self.d_sq(rating))));
fn d_sq(&self, rating: f64) -> f64 {
1.0 / ((Q * Q) * (self.g() * self.g()) * self.e(rating) * (1.0 - self.e(rating)))
fn e(&self, rating: f64) -> f64 {
1.0 / (1.0 + exp10(-self.g() * ((self.rating - rating) / 400.0)))
fn g(&self) -> f64 {
1.0 / f64::sqrt(1.0 + ((3.0 * Q * Q * self.rd_sq()) / (PI * PI)))
impl Default for Rating {
fn default() -> Self {
Self {
rating: 1_500.0,
rd: 350.0,
impl fmt::Display for Rating {
// Note: We use a FIGURE SPACE before and after the ± so
// .split_ascii_whitespace() does not treat it as a space.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ± {}", self.rating, CONFIDENCE_INTERVAL_95 * self.rd)
#[derive(Clone, Debug)]
pub enum Outcome {
Draw,
Loss,
Win,
impl Outcome {
pub fn score(&self) -> f64 {
match self {
Outcome::Draw => 0.5,
Outcome::Loss => 0.0,
Outcome::Win => 1.0,
pub fn exp10(mut exponent: f64) -> f64 {
exponent /= LOG10_2;
exponent.exp2()
#[cfg(test)]
mod tests {
use crate::glicko::Outcome;
use super::{Rating, exp10};
#[allow(clippy::float_cmp)]
#[test]
fn pow_10() {
assert_eq!(exp10(2.0).round(), 100.0);
fn rd_increases() {
let mut rating = Rating::default();
assert_eq!(rating.rd, 350.0);
rating.update_rd();
rating.rd = 30.0;
assert_eq!(rating.rd.round(), 70.0);
rating.rd = 300.0;
assert_eq!(rating.rd.round(), 307.0);
fn rating_and_rd_changes() {
let rating = Rating::default();
assert_eq!(rating.rating, 1_500.0);
let mut rating_1 = rating.clone();
rating_1.update_rating(1_600.0, &Outcome::Win);
assert_eq!(rating_1.rating.round(), 1_781.0);
let mut rating_2 = rating.clone();
rating_2.update_rating(1_500.0, &Outcome::Win);
assert_eq!(rating_2.rating.round(), 1_736.0);
let mut rating_3 = rating.clone();
rating_3.update_rating(1_500.0, &Outcome::Loss);
assert_eq!(rating_3.rating.round(), 1_264.0);
let mut rating_4 = rating.clone();
rating_4.update_rating(1_500.0, &Outcome::Loss);
assert_eq!(rating_4.rating.round(), 1_264.0);
assert_eq!(rating_4.rd.round(), 299.0);
rating_4.update_rd();
assert_eq!(rating_4.rd.round(), 305.0);
let mut rating_5 = rating.clone();
rating_5.update_rating(1_500.0, &Outcome::Draw);
assert_eq!(rating_5.rating.round(), 1_500.0);
let mut rating_6 = rating.clone();
rating_6.update_rating(1_600.0, &Outcome::Draw);
assert_eq!(rating_6.rating.round(), 1545.0);