use crate::fixed::Decimal;
const K: i32 = 32;
const TANH_SCALE: i32 = 347;
fn calculate_damage_dealt(health: i32, max_health: i32) -> i32 {
assert!(health <= max_health);
if health >= 0 {
max_health - health
} else {
max_health + health.abs()
}
}
pub fn update(
rating_a: u16,
health_a: i16,
max_health_a: u8,
rating_b: u16,
health_b: i16,
max_health_b: u8,
) -> (u16, u16) {
let rating_a_fp = Decimal::from_u16(rating_a);
let rating_b_fp = Decimal::from_u16(rating_b);
let health_a_i32 = health_a as i32;
let max_health_a_i32 = max_health_a as i32;
let health_b_i32 = health_b as i32;
let max_health_b_i32 = max_health_b as i32;
let expected_a = calculate_expected_score(rating_a_fp, rating_b_fp);
let damage_dealt_by_a = calculate_damage_dealt(health_b_i32, max_health_b_i32);
let damage_dealt_by_b = calculate_damage_dealt(health_a_i32, max_health_a_i32);
let total_damage = damage_dealt_by_a + damage_dealt_by_b;
if total_damage == 0 {
return (rating_a, rating_b);
}
let actual_a = Decimal::from_int(damage_dealt_by_a).div_int(total_damage);
let k_fp = Decimal::from_int(K);
let change_a = k_fp.mul(actual_a - expected_a);
let change_b = -change_a;
let new_rating_a_fp = rating_a_fp + change_a;
let new_rating_b_fp = rating_b_fp + change_b;
let new_rating_a = new_rating_a_fp.to_u16_rounded();
let new_rating_b = new_rating_b_fp.to_u16_rounded();
(new_rating_a, new_rating_b)
}
fn calculate_expected_score(rating_a: Decimal, rating_b: Decimal) -> Decimal {
let rating_diff = (rating_b - rating_a).to_int_rounded();
let x_fp = Decimal::from_int(rating_diff).div_int(TANH_SCALE);
let x_squared = x_fp.mul(x_fp);
let twenty_seven = Decimal::from_int(27);
let numerator = x_fp.mul(twenty_seven + x_squared);
let nine = Decimal::from_int(9);
let denominator = twenty_seven + nine.mul(x_squared);
let tanh_x = numerator.div(denominator);
let one = Decimal::from_int(1);
let half = Decimal::from_frac(1, 2);
let expected = half.mul(one - tanh_x);
let min_expected = Decimal::from_frac(1, 100);
let max_expected = Decimal::from_frac(99, 100);
expected.clamp(min_expected, max_expected)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_win() {
let (new_a, new_b) = update(1200, 100, 100, 1200, 0, 100);
assert!(new_a > 1200);
assert!(new_b < 1200);
assert_eq!(new_a + new_b, 2400);
let (new_a_close, new_b_close) = update(1200, 10, 100, 1200, 0, 100);
assert!(new_a_close > 1200);
assert!(new_b_close < 1200);
assert!(new_a_close - 1200 < new_a - 1200);
let (new_a, new_b) = update(1000, 80, 80, 1400, 0, 80);
assert!(new_a > 1000);
assert!(new_b < 1400);
let gain_a = new_a - 1000;
let loss_b = 1400 - new_b;
assert_eq!(gain_a, loss_b); assert!(gain_a > 16); }
#[test]
fn test_loss() {
let (new_a, new_b) = update(1200, 0, 100, 1200, 100, 100);
assert!(new_a < 1200);
assert!(new_b > 1200);
assert_eq!(new_a + new_b, 2400);
let (new_a, new_b) = update(1400, 0, 100, 1000, 100, 100);
assert!(new_a < 1400);
assert!(new_b > 1000);
let loss_a = 1400 - new_a;
let gain_b = new_b - 1000;
assert_eq!(loss_a, gain_b); assert!(loss_a > 16); }
#[test]
fn test_draw() {
let (new_a, new_b) = update(1200, 0, 100, 1200, 0, 100);
assert_eq!(new_a, 1200);
assert_eq!(new_b, 1200);
let (new_a, new_b) = update(1200, 20, 100, 1200, 20, 100);
assert!((new_a as i32 - 1200).abs() <= 1);
assert!((new_b as i32 - 1200).abs() <= 1);
let (new_a, new_b) = update(1000, 50, 100, 1400, 50, 100);
assert!(new_a > 1000); assert!(new_b < 1400); assert_eq!((new_a as i32 - 1000), -(new_b as i32 - 1400)); }
#[test]
fn test_bounds() {
let (new_a, _) = update(0, 0, 100, 1500, 100, 100);
assert_eq!(new_a, 0);
let (new_a, _) = update(u16::MAX, 100, 100, 1000, 0, 100);
assert_eq!(new_a, u16::MAX);
}
#[test]
fn test_expected_scores() {
let expected = calculate_expected_score(Decimal::from_int(1200), Decimal::from_int(1200));
assert!((expected.raw() - 5000).abs() < 100);
let expected = calculate_expected_score(Decimal::from_int(1400), Decimal::from_int(1000));
assert!(expected.raw() > 9000);
let expected = calculate_expected_score(Decimal::from_int(1000), Decimal::from_int(1400));
assert!(expected.raw() < 1000); }
#[test]
fn test_damage_dealt() {
assert_eq!(calculate_damage_dealt(100, 100), 0);
assert_eq!(calculate_damage_dealt(0, 100), 100);
assert_eq!(calculate_damage_dealt(0, 0), 0);
}
#[test]
#[should_panic]
fn test_damage_dealt_panic() {
calculate_damage_dealt(100, 0);
}
#[test]
fn test_overkill() {
let (new_a_overkill, new_b_overkill) = update(1200, 20, 100, 1200, -30, 100);
let (new_a_normal, new_b_normal) = update(1200, 20, 100, 1200, 0, 100);
assert!(new_a_overkill > new_a_normal);
assert!(new_b_overkill < new_b_normal);
let (new_a, new_b) = update(1200, -30, 100, 1200, -50, 100);
assert!(new_a > 1200);
assert!(new_b < 1200);
let (new_a_slight, _new_b_slight) = update(1200, 5, 100, 1200, 0, 100);
let (new_a_massive, _new_b_massive) = update(1200, 5, 100, 1200, -100, 100);
assert!(new_a_massive - 1200 > new_a_slight - 1200);
}
}