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