File size: 8,821 Bytes
079c32c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
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
}
@classmethod
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)
@classmethod
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)
@staticmethod
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
|