Spaces:
Sleeping
Sleeping
from typing import Tuple, Union, List | |
import math | |
import numpy as np | |
from trueskill import TrueSkill, Rating, rate_1vs1 | |
class EloCalculator(object): | |
""" | |
Overview: | |
A class that calculates Elo ratings for players based on game results. | |
Attributes: | |
- score (:obj:`dict`): A dictionary that maps game results to scores. | |
Interfaces: | |
``__init__``, ``get_new_rating``, ``get_new_rating_array``. | |
""" | |
score = { | |
1: 1.0, # win | |
0: 0.5, # draw | |
-1: 0.0, # lose | |
} | |
def get_new_rating(cls, | |
rating_a: int, | |
rating_b: int, | |
result: int, | |
k_factor: int = 32, | |
beta: int = 200) -> Tuple[int, int]: | |
""" | |
Overview: | |
Calculates the new ratings for two players based on their current ratings and game result. | |
Arguments: | |
- rating_a (:obj:`int`): The current rating of player A. | |
- rating_b (:obj:`int`): The current rating of player B. | |
- result (:obj:`int`): The result of the game: 1 for player A win, 0 for draw, -1 for player B win. | |
- k_factor (:obj:`int`): The K-factor used in the Elo rating system. Defaults to 32. | |
- beta (:obj:`int`): The beta value used in the Elo rating system. Defaults to 200. | |
Returns: | |
-ret (:obj:`Tuple[int, int]`): The new ratings for player A and player B, respectively. | |
""" | |
assert result in [1, 0, -1] | |
expect_a = 1. / (1. + math.pow(10, (rating_b - rating_a) / (2. * beta))) | |
expect_b = 1. / (1. + math.pow(10, (rating_a - rating_b) / (2. * beta))) | |
new_rating_a = rating_a + k_factor * (EloCalculator.score[result] - expect_a) | |
new_rating_b = rating_b + k_factor * (1 - EloCalculator.score[result] - expect_b) | |
return round(new_rating_a), round(new_rating_b) | |
def get_new_rating_array( | |
cls, | |
rating: np.ndarray, | |
result: np.ndarray, | |
game_count: np.ndarray, | |
k_factor: int = 32, | |
beta: int = 200 | |
) -> np.ndarray: | |
""" | |
Overview: | |
Calculates the new ratings for multiple players based on their current ratings, game results, \ | |
and game counts. | |
Arguments: | |
- rating (obj:`np.ndarray`): An array of current ratings for each player. | |
- result (obj:`np.ndarray`): An array of game results, where 1 represents a win, 0 represents a draw, \ | |
and -1 represents a loss. | |
- game_count (obj:`np.ndarray`): An array of game counts for each player. | |
- k_factor (obj:`int`): The K-factor used in the Elo rating system. Defaults to 32. | |
- beta (obj:`int`): The beta value used in the Elo rating system. Defaults to 200. | |
Returns: | |
-ret(obj:`np.ndarray`): An array of new ratings for each player. | |
Shapes: | |
- rating (obj:`np.ndarray`): :math:`(N, )`, N is the number of player | |
- result (obj:`np.ndarray`): :math:`(N, N)` | |
- game_count (obj:`np.ndarray`): :math:`(N, N)` | |
""" | |
rating_diff = np.expand_dims(rating, 0) - np.expand_dims(rating, 1) | |
expect = 1. / (1. + np.power(10, rating_diff / (2. * beta))) * game_count | |
delta = ((result + 1.) / 2 - expect) * (game_count > 0) | |
delta = delta.sum(axis=1) | |
return np.round(rating + k_factor * delta).astype(np.int64) | |
class PlayerRating(Rating): | |
""" | |
Overview: | |
Represents the rating of a player. | |
Interfaces: | |
``__init__``, ``__repr__``. | |
""" | |
def __init__(self, mu: float = None, sigma: float = None, elo_init: int = None) -> None: | |
super(PlayerRating, self).__init__(mu, sigma) | |
self.elo = elo_init | |
def __repr__(self) -> str: | |
c = type(self) | |
args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, self.exposure, self.elo) | |
return '%s(mu=%.3f, sigma=%.3f, exposure=%.3f, elo=%d)' % args | |
class LeagueMetricEnv(TrueSkill): | |
""" | |
Overview: | |
A class that represents a TrueSkill rating system for game players. Inherits from the TrueSkill class. \ | |
For more details, please refer to https://trueskill.org/. | |
Interfaces: | |
``__init__``, ``create_rating``, ``rate_1vs1``, ``rate_1vsC``. | |
""" | |
def __init__(self, *args, elo_init: int = 1200, **kwargs) -> None: | |
super(LeagueMetricEnv, self).__init__(*args, **kwargs) | |
self.elo_init = elo_init | |
def create_rating(self, mu: float = None, sigma: float = None, elo_init: int = None) -> PlayerRating: | |
""" | |
Overview: | |
Creates a new player rating object with the specified mean, standard deviation, and Elo rating. | |
Arguments: | |
- mu (:obj:`float`): The mean value of the player's skill rating. If not provided, the default \ | |
TrueSkill mean is used. | |
- sigma (:obj:`float`): The standard deviation of the player's skill rating. If not provided, \ | |
the default TrueSkill sigma is used. | |
- elo_init (:obj:int`): The initial Elo rating value for the player. If not provided, the default \ | |
elo_init value of the LeagueMetricEnv class is used. | |
Returns: | |
- PlayerRating: A player rating object with the specified mean, standard deviation, and Elo rating. | |
""" | |
if mu is None: | |
mu = self.mu | |
if sigma is None: | |
sigma = self.sigma | |
if elo_init is None: | |
elo_init = self.elo_init | |
return PlayerRating(mu, sigma, elo_init) | |
def _rate_1vs1(t1, t2, **kwargs): | |
t1_elo, t2_elo = t1.elo, t2.elo | |
t1, t2 = rate_1vs1(t1, t2, **kwargs) | |
if 'drawn' in kwargs: | |
result = 0 | |
else: | |
result = 1 | |
t1_elo, t2_elo = EloCalculator.get_new_rating(t1_elo, t2_elo, result) | |
t1 = PlayerRating(t1.mu, t1.sigma, t1_elo) | |
t2 = PlayerRating(t2.mu, t2.sigma, t2_elo) | |
return t1, t2 | |
def rate_1vs1(self, team1: PlayerRating, team2: PlayerRating, result: List[str] = None, **kwargs) \ | |
-> Tuple[PlayerRating, PlayerRating]: | |
""" | |
Overview: | |
Rates two teams of players against each other in a 1 vs 1 match and returns the updated ratings \ | |
for both teams. | |
Arguments: | |
- team1 (:obj:`PlayerRating`): The rating object representing the first team of players. | |
- team2 (:obj:`PlayerRating`): The rating object representing the second team of players. | |
- result (:obj:`List[str]`): The result of the match. Can be 'wins', 'draws', or 'losses'. If \ | |
not provided, the default behavior is to rate the match as a win for team1. | |
Returns: | |
- ret (:obj:`Tuple[PlayerRating, PlayerRating]`): A tuple containing the updated ratings for team1 \ | |
and team2. | |
""" | |
if result is None: | |
return self._rate_1vs1(team1, team2, **kwargs) | |
else: | |
for r in result: | |
if r == 'wins': | |
team1, team2 = self._rate_1vs1(team1, team2) | |
elif r == 'draws': | |
team1, team2 = self._rate_1vs1(team1, team2, drawn=True) | |
elif r == 'losses': | |
team2, team1 = self._rate_1vs1(team2, team1) | |
else: | |
raise RuntimeError("invalid result: {}".format(r)) | |
return team1, team2 | |
def rate_1vsC(self, team1: PlayerRating, team2: PlayerRating, result: List[str]) -> PlayerRating: | |
""" | |
Overview: | |
Rates a team of players against a single player in a 1 vs C match and returns the updated rating \ | |
for the team. | |
Arguments: | |
- team1 (:obj:`PlayerRating`): The rating object representing the team of players. | |
- team2 (:obj:`PlayerRating`): The rating object representing the single player. | |
- result (:obj:`List[str]`): The result of the match. Can be 'wins', 'draws', or 'losses'. | |
Returns: | |
- PlayerRating: The updated rating for the team of players. | |
""" | |
for r in result: | |
if r == 'wins': | |
team1, _ = self._rate_1vs1(team1, team2) | |
elif r == 'draws': | |
team1, _ = self._rate_1vs1(team1, team2, drawn=True) | |
elif r == 'losses': | |
_, team1 = self._rate_1vs1(team2, team1) | |
else: | |
raise RuntimeError("invalid result: {}".format(r)) | |
return team1 | |
get_elo = EloCalculator.get_new_rating | |
get_elo_array = EloCalculator.get_new_rating_array | |