Skip to content

Commit

Permalink
Add uscf rating algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
atomflunder committed Oct 20, 2022
1 parent 2b69ae0 commit 912ad0f
Show file tree
Hide file tree
Showing 12 changed files with 759 additions and 36 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

This is a broad overview of the changes that have been made over the lifespan of this library.

## v0.16.0 - 20220-10-19
## v0.17.0 - 2022-10-21

- Added USCF rating algorithms

## v0.16.0 - 2022-10-19

- Added Glicko-Boost algorithm
- Added boolean parameter to results tuple in Sticko rating period function to indicate advantages
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "skillratings"
version = "0.16.0"
version = "0.17.0"
edition = "2021"
description = "Calculate a player's skill rating using algorithms like Elo, Glicko, Glicko-2, TrueSkill and many more."
readme = "README.md"
Expand Down
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ This library is incredibly lightweight (no dependencies), user-friendly, and of

Currently supported algorithms:

- [Elo](https://docs.rs/skillratings/latest/skillratings/elo/index.html)
- [Glicko](https://docs.rs/skillratings/latest/skillratings/glicko/index.html)
- [Glicko-2](https://docs.rs/skillratings/latest/skillratings/glicko2/index.html)
- [TrueSkill](https://docs.rs/skillratings/latest/skillratings/trueskill/index.html)
- [Weng-Lin (Bayesian Approxmation System)](https://docs.rs/skillratings/latest/skillratings/weng_lin/index.html)
- [Sticko (Stephenson Rating System)](https://docs.rs/skillratings/latest/skillratings/sticko/index.html)
- [Glicko-Boost](https://docs.rs/skillratings/latest/skillratings/glicko_boost/index.html)
- [EGF (European Go Federation)](https://docs.rs/skillratings/latest/skillratings/egf/index.html)
- [DWZ (Deutsche Wertungszahl)](https://docs.rs/skillratings/latest/skillratings/dwz/index.html)
- [Ingo](https://docs.rs/skillratings/latest/skillratings/ingo/index.html)

Most of these are mainly known from their usage in chess and various other games.
- [Elo](https://docs.rs/skillratings/latest/skillratings/elo/)
- [Glicko](https://docs.rs/skillratings/latest/skillratings/glicko/)
- [Glicko-2](https://docs.rs/skillratings/latest/skillratings/glicko2/)
- [TrueSkill](https://docs.rs/skillratings/latest/skillratings/trueskill/)
- [Weng-Lin (Bayesian Approxmation System)](https://docs.rs/skillratings/latest/skillratings/weng_lin/)
- [Sticko (Stephenson Rating System)](https://docs.rs/skillratings/latest/skillratings/sticko/)
- [Glicko-Boost](https://docs.rs/skillratings/latest/skillratings/glicko_boost/)
- [USCF (US Chess Federation Ratings)](https://docs.rs/skillratings/latest/skillratings/uscf/)
- [EGF (European Go Federation)](https://docs.rs/skillratings/latest/skillratings/egf/)
- [DWZ (Deutsche Wertungszahl)](https://docs.rs/skillratings/latest/skillratings/dwz/)
- [Ingo](https://docs.rs/skillratings/latest/skillratings/ingo/)

Most of these are known from their usage in chess and various other games.

## Installation

Expand All @@ -36,7 +37,7 @@ Alternatively, you can add the following to your `Cargo.toml` file manually:

```toml
[dependencies]
skillratings = "0.16"
skillratings = "0.17"
```

## Usage and Examples
Expand Down
27 changes: 27 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,30 @@ impl Default for GlickoBoostConfig {
Self::new()
}
}

#[derive(Clone, Copy, Debug)]
/// Constants used in the USCF Rating calculations.
pub struct USCFConfig {
/// The t value controls the difficulty of earning bonus rating points.
/// The higher the t value, the more difficult it is.
///
/// The USCF changes this value periodically.
/// As of 2022, the last change was in May 2017 where this was set from 12 to 14.
/// The lowest value was 6, from 2008 to 2012.
/// By default set to 14.0.
pub t: f64,
}

impl USCFConfig {
#[must_use]
/// Initialize a new `USCFConfig` with a t value of 14.0.
pub const fn new() -> Self {
Self { t: 14.0 }
}
}

impl Default for USCFConfig {
fn default() -> Self {
Self::new()
}
}
3 changes: 0 additions & 3 deletions src/dwz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ pub fn dwz_rating_period(player: &DWZRating, results: &Vec<(DWZRating, Outcomes)
.map(|r| expected_score(player, &r.0).0)
.sum::<f64>();

#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
let new_rating = (800.0
/ (e_value(
player.rating,
Expand Down Expand Up @@ -244,7 +243,6 @@ pub fn expected_score(player_one: &DWZRating, player_two: &DWZRating) -> (f64, f
(exp_one, exp_two)
}

#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
#[must_use]
/// Gets a proper first [`DWZRating`].
///
Expand Down Expand Up @@ -438,7 +436,6 @@ fn e_value(rating: f64, age: usize, score: f64, expected_score: f64, index: usiz
if e <= 5.0 {
e = 5.0;
} else if b == 0.0 {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
if e >= 30.0_f64.min(5.0 * index as f64) {
e = 30.0_f64.min(5.0 * index as f64);
}
Expand Down
1 change: 0 additions & 1 deletion src/ingo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ pub fn ingo(
)
}

#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
#[must_use]
/// The "traditional" way of calculating a [`IngoRating`] of a player in a rating period.
///
Expand Down
14 changes: 10 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
clippy::pedantic,
clippy::nursery,
clippy::unwrap_used,
clippy::expect_used,
clippy::as_conversions
clippy::expect_used
)]
#![allow(
clippy::module_name_repetitions, // This is turned off because of the rating values
clippy::doc_markdown, // This is turned off because "TrueSkill" shows up as a false positive
clippy::cast_precision_loss, // We need to cast usizes to f64s in places where precision is not that important
clippy::cast_lossless
)]
#![allow(clippy::module_name_repetitions, clippy::doc_markdown, clippy::ptr_arg)]

//! Skillratings provides functions on calculating a player's skill rating.
//!
Expand All @@ -18,6 +22,7 @@
//! - **[`Weng-Lin`](crate::weng_lin)**
//! - **[`Sticko`](crate::sticko)**
//! - **[`Glicko-Boost`](crate::glicko_boost)**
//! - **[`USCF (US Chess Federation)`](crate::uscf)**
//! - **[`EGF (European Go Federation)`](crate::egf)**
//! - **[`DWZ (Deutsche Wertungszahl)`](crate::dwz)**
//! - **[`Ingo`](crate::ingo)**
Expand All @@ -39,7 +44,7 @@
//!
//! ```toml
//! [dependencies]
//! skillratings = "0.16"
//! skillratings = "0.17"
//! ```
//!
//! # Examples
Expand Down Expand Up @@ -207,4 +212,5 @@ pub mod outcomes;
pub mod rating;
pub mod sticko;
pub mod trueskill;
pub mod uscf;
pub mod weng_lin;
72 changes: 72 additions & 0 deletions src/rating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ impl From<DWZRating> for EloRating {
}
}

impl From<USCFRating> for EloRating {
fn from(u: USCFRating) -> Self {
if u.rating > 2060.0 {
Self {
rating: (u.rating - 180.0) / 0.94,
}
} else {
Self {
rating: (u.rating - 20.0) / 1.02,
}
}
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
/// The Glicko rating for a player.
///
Expand Down Expand Up @@ -469,3 +483,61 @@ impl From<StickoRating> for GlickoBoostRating {
}
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
/// The USCF (US Chess Federation) rating for a player.
///
/// The age is the actual age of the player,
/// if unsure or unavailable the official guidelines say to set this to `26`,
/// if the player is inferred to be an adult, or to `15` if not.
///
/// The default rating is dependent on the age of the player.
/// If the player is 26 or older this will be 1300.0, if the player is 15 the rating will be 750.0.
/// The minimum rating value is set to be 100.0.
pub struct USCFRating {
/// The player's USCF rating number.
pub rating: f64,
/// The player's completed games.
pub games: usize,
}

impl USCFRating {
#[must_use]
/// Initialize a new `USCFRating` with a new rating dependent on the age of the player.
/// The age is the actual age of the player, if unsure or unavailable set this to `26`.
/// The rating of a 26 year old will be 1300.0.
pub fn new(age: usize) -> Self {
Self {
rating: if age < 2 {
100.0
} else if age > 26 {
1300.0
} else {
age as f64 * 50.0
},
games: 0,
}
}
}

impl Default for USCFRating {
fn default() -> Self {
Self::new(26)
}
}

impl From<EloRating> for USCFRating {
fn from(e: EloRating) -> Self {
if e.rating > 2000.0 {
Self {
rating: 0.94f64.mul_add(e.rating, 180.0),
games: 10,
}
} else {
Self {
rating: 1.02f64.mul_add(e.rating, 20.0),
games: 5,
}
}
}
}
1 change: 0 additions & 1 deletion src/sticko.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ pub fn sticko_rating_period(
return decay_deviation(player, config);
}

#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
let matches = results.len() as f64;

let d_sq: f64 = (q.powi(2)
Expand Down
11 changes: 0 additions & 11 deletions src/trueskill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ pub fn trueskill_rating_period(
}

#[must_use]
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
/// Calculates the [`TrueSkillRating`] of two teams based on their ratings, uncertainties, and the outcome of the game.
///
/// Takes in two teams as a Vec of [`TrueSkillRating`]s, the outcome of the game as an [`Outcome`](Outcomes) and a [`TrueSkillConfig`].
Expand Down Expand Up @@ -473,11 +472,6 @@ pub fn match_quality(
}

#[must_use]
#[allow(
clippy::as_conversions,
clippy::cast_precision_loss,
clippy::needless_pass_by_value
)]
/// Gets the quality of the match, which is equal to the probability that the match will end in a draw.
/// The higher the Value, the better the quality of the match.
///
Expand Down Expand Up @@ -599,11 +593,6 @@ pub fn expected_score(
}

#[must_use]
#[allow(
clippy::needless_pass_by_value,
clippy::as_conversions,
clippy::cast_precision_loss
)]
/// Calculates the expected outcome of two teams based on TrueSkill.
///
/// Takes in two teams as Vec of [`TrueSkillRating`]s and returns the probability of victory for each player as an [`f64`] between 1.0 and 0.0.
Expand Down
Loading

0 comments on commit 912ad0f

Please sign in to comment.