use crate::prelude::MatchRules;
use crate::rules::Player;
use crate::{Error, Match, MatchState};
const FIBS_DIVISOR: f64 = 2000.0;
const FIBS_MATCH_VALUE_MULTIPLIER: f64 = 4.0;
const FIBS_EXPERIENCE_THRESHOLD: u32 = 400;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FibsRating {
pub rating: u32,
pub experience_points: u32,
}
impl Default for FibsRating {
fn default() -> Self {
FibsRating {
rating: 1500,
experience_points: 0,
}
}
}
impl FibsRating {
fn experience_bonus(self, match_length: u64) -> f64 {
if self.experience_points <= FIBS_EXPERIENCE_THRESHOLD {
5.0 - ((self.experience_points as f64 + match_length as f64) / 100.0)
} else {
1.0
}
}
}
pub trait Rating {
fn fibs(
&self,
player0_rating: &FibsRating,
player1_rating: &FibsRating,
) -> Result<(FibsRating, FibsRating), Error>;
}
impl Rating for Match {
fn fibs(
&self,
player0_rating: &FibsRating,
player1_rating: &FibsRating,
) -> Result<(FibsRating, FibsRating), Error> {
let match_length = self.get_points()?;
let winning_probability: f64 = 1.0
- (1.0
/ (10.0_f64.powf(
(player0_rating.rating as f64 - player1_rating.rating as f64)
* (match_length as f64).sqrt()
/ FIBS_DIVISOR,
) + 1.0));
let match_value: f64 = FIBS_MATCH_VALUE_MULTIPLIER * (match_length as f64).sqrt();
let point_gains = (
(match_value * (1.0 - winning_probability)),
(match_value * winning_probability),
);
let new_rating_win0 = (
(point_gains.0 * player0_rating.experience_bonus(match_length)).round() as u32,
(point_gains.0 * player1_rating.experience_bonus(match_length)).round() as u32,
);
let new_rating_win1 = (
(point_gains.1 * player0_rating.experience_bonus(match_length)).round() as u32,
(point_gains.1 * player1_rating.experience_bonus(match_length)).round() as u32,
);
match self.get_match_state()? {
MatchState::End(Player::Player0) => Ok((
FibsRating {
rating: player0_rating.rating + new_rating_win0.0,
experience_points: player0_rating.experience_points + match_length as u32,
},
FibsRating {
rating: player1_rating.rating - new_rating_win0.1,
experience_points: player1_rating.experience_points + match_length as u32,
},
)),
MatchState::End(Player::Player1) => Ok((
FibsRating {
rating: player0_rating.rating - new_rating_win1.0,
experience_points: player0_rating.experience_points + match_length as u32,
},
FibsRating {
rating: player1_rating.rating + new_rating_win1.1,
experience_points: player1_rating.experience_points + match_length as u32,
},
)),
_ => Err(Error::MatchNotEnded),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fibs_rating_player0_wins() {
let mut m = Match::new();
m.set_points(5).unwrap();
m.set_match_state(MatchState::End(Player::Player0));
let player0_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let player1_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let result = m.fibs(&player0_rating, &player1_rating);
assert!(result.is_ok());
let (new_player0, new_player1) = result.unwrap();
assert_eq!(
new_player0.experience_points,
player0_rating.experience_points + 5
);
assert_eq!(
new_player1.experience_points,
player1_rating.experience_points + 5
);
assert_eq!(new_player0.rating, 1522);
assert_eq!(new_player1.rating, 1478);
}
#[test]
fn test_fibs_rating_player0_wins_with_experience() {
let mut m = Match::new();
m.set_points(7).unwrap();
m.set_match_state(MatchState::End(Player::Player0));
let player0_rating = FibsRating {
rating: 1500,
experience_points: 500,
};
let player1_rating = FibsRating {
rating: 1925,
experience_points: 500,
};
let result = m.fibs(&player0_rating, &player1_rating);
assert!(result.is_ok());
let (new_player0, new_player1) = result.unwrap();
assert_eq!(
new_player0.experience_points,
player0_rating.experience_points + 7
);
assert_eq!(
new_player1.experience_points,
player1_rating.experience_points + 7
);
assert_eq!(new_player0.rating, 1508);
assert_eq!(new_player1.rating, 1917);
}
#[test]
fn test_fibs_rating_player0_wins_no_experience() {
let mut m = Match::new();
m.set_points(7).unwrap();
m.set_match_state(MatchState::End(Player::Player0));
let player0_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let player1_rating = FibsRating {
rating: 1925,
experience_points: 500,
};
let result = m.fibs(&player0_rating, &player1_rating);
assert!(result.is_ok());
let (new_player0, new_player1) = result.unwrap();
assert_eq!(
new_player0.experience_points,
player0_rating.experience_points + 7
);
assert_eq!(
new_player1.experience_points,
player1_rating.experience_points + 7
);
assert_eq!(new_player0.rating, 1541);
assert_eq!(new_player1.rating, 1917);
}
#[test]
fn test_fibs_rating_player1_wins_0_no_experience() {
let mut m = Match::new();
m.set_points(7).unwrap();
m.set_match_state(MatchState::End(Player::Player1));
let player0_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let player1_rating = FibsRating {
rating: 1925,
experience_points: 500,
};
let result = m.fibs(&player0_rating, &player1_rating);
assert!(result.is_ok());
let (new_player0, new_player1) = result.unwrap();
assert_eq!(
new_player0.experience_points,
player0_rating.experience_points + 7
);
assert_eq!(
new_player1.experience_points,
player1_rating.experience_points + 7
);
assert_eq!(new_player0.rating, 1489);
assert_eq!(new_player1.rating, 1927);
}
#[test]
fn test_fibs_rating_match_not_ended() {
let mut m = Match::new();
m.set_points(5).unwrap();
let player0_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let player1_rating = FibsRating {
rating: 1500,
experience_points: 0,
};
let result = m.fibs(&player0_rating, &player1_rating);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Error::MatchNotEnded);
}
#[test]
fn test_fibs_rating_default() {
let default_rating = FibsRating::default();
assert_eq!(default_rating.rating, 1500);
assert_eq!(default_rating.experience_points, 0);
}
}