Changeset View
Changeset View
Standalone View
Standalone View
source/tools/lobbybots/EcheLOn/ELO.py
"""Copyright (C) 2014 Wildfire Games. | # Copyright (C) 2020 Wildfire Games. | ||||
bb: #!/usr/bin/env python3 | |||||
* This file is part of 0 A.D. | # This file is part of 0 A.D. | ||||
* | # | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | # 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | # it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | # the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | # (at your option) any later version. | ||||
* | # | ||||
* 0 A.D. is distributed in the hope that it will be useful, | # 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | # GNU General Public License for more details. | ||||
* | # | ||||
* You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | # along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
""" | |||||
"""Implementation of the ELO-rating algorithm for 0ad games.""" | |||||
############ Constants ############ | |||||
# Difference between two ratings such that it is | # Difference between two ratings such that it is regarded as a "sure | ||||
# regarded as a "sure win" for the higher player. | # win" for the higher player. No points are gained or lost for such a | ||||
# No points are gained or lost for such a game. | # game. | ||||
elo_sure_win_difference = 600.0 | ELO_SURE_WIN_DIFFERENCE = 600 | ||||
# Lower ratings "move faster" and change more | # Lower ratings "move faster" and change more | ||||
# dramatically than higher ones. Anything rating above | # dramatically than higher ones. Anything rating above | ||||
# this value moves at the same rate as this value. | # this value moves at the same rate as this value. | ||||
elo_k_factor_constant_rating = 2200.0 | ELO_K_FACTOR_CONSTANT_RATING = 2200 | ||||
# This preset number of games is the number of games | # This preset number of games is the number of games where a player is | ||||
# where a player is considered "stable". | # considered "stable". Rating volatility is constant after this number. | ||||
# Rating volatility is constant after this number. | VOLATILITY_CONSTANT = 20 | ||||
volatility_constant = 20.0 | |||||
# Fair rating adjustment loses against inflation | # Fair rating adjustment loses against inflation. | ||||
# This constant will battle inflation. | # This constant will battle inflation. | ||||
# NOTE: This can be adjusted as needed by a | # NOTE: This can be adjusted as needed by a bot/server administrator | ||||
# bot/server administrator | ANTI_INFLATION = 0.015 | ||||
anti_inflation = 0.015 | |||||
############ Functions ############ | |||||
def get_rating_adjustment(rating, opponent_rating, games_played, opponent_games_played, result): | def get_rating_adjustment(rating, opponent_rating, games_played, | ||||
""" | opponent_games_played, result): # pylint: disable=unused-argument | ||||
Calculates the rating adjustment after a 1v1 game finishes using simplified ELO. | """Calculate the rating adjustment after rated 1v1 games. | ||||
The rating adjustment is calculated using a simplified | |||||
ELO-algorithm. | |||||
The given implementation doesn't work for negative ratings below | |||||
-2199. This is a known limitation which is currently considered | |||||
to be not relevant in day-to-day use. | |||||
Arguments: | Arguments: | ||||
rating, opponent_rating - Ratings of the players before this game. | rating (int): Rating of the first player before the game. | ||||
games_played, opponent_games_played - Number of games each player has played | opponent_rating (int): Rating of the second player before the | ||||
game. | |||||
games_played (int): Number of games the first player has played | |||||
before this game. | before this game. | ||||
result - 1 for the first player (rating, games_played) won, 0 for draw, or | opponent_games_played (int): Number of games the second player | ||||
-1 for the second player (opponent_rating, opponent_games_played) won. | has played before this game. | ||||
result (int): 1 if the first player won, 0 if draw or -1 if the | |||||
second player won. | |||||
Returns: | Returns: | ||||
The integer that should be subtracted from the loser's rating and added | int: the adjustment which should be applied to the rating of | ||||
to the winner's rating to get their new ratings. | the first player | ||||
TODO: Team games. | |||||
""" | """ | ||||
player_volatility = (min(games_played, volatility_constant) / volatility_constant + 0.25) / 1.25 | if rating < -2199 or opponent_rating < -2199: | ||||
rating_k_factor = 50.0 * (min(rating, elo_k_factor_constant_rating) / elo_k_factor_constant_rating + 1.0) / 2.0 | raise ValueError('Too small rating given: rating: %i, opponent rating: %i' % | ||||
(rating, opponent_rating)) | |||||
rating_k_factor = 50.0 * (min(rating, ELO_K_FACTOR_CONSTANT_RATING) / | |||||
ELO_K_FACTOR_CONSTANT_RATING + 1.0) / 2.0 | |||||
player_volatility = (min(max(0, games_played), VOLATILITY_CONSTANT) / | |||||
VOLATILITY_CONSTANT + 0.25) / 1.25 | |||||
volatility = rating_k_factor * player_volatility | volatility = rating_k_factor * player_volatility | ||||
difference = opponent_rating - rating | rating_difference = opponent_rating - rating | ||||
rating_adjustment = (rating_difference + result * ELO_SURE_WIN_DIFFERENCE) / volatility - \ | |||||
ANTI_INFLATION | |||||
if result == 1: | if result == 1: | ||||
return round(max(0, (difference + result * elo_sure_win_difference) / volatility - anti_inflation)) | return round(max(0.0, rating_adjustment)) | ||||
elif result == -1: | elif result == -1: | ||||
return round(min(0, (difference + result * elo_sure_win_difference) / volatility - anti_inflation)) | return round(min(0.0, rating_adjustment)) | ||||
else: | return round(rating_adjustment) | ||||
return round(difference / volatility - anti_inflation) | |||||
# Inflation test - A slightly negative is better than a slightly positive | |||||
# Lower rated players stop playing more often than higher rated players | |||||
# Uncomment to test. | |||||
# In this example, two evenly matched players play for 150000 games. | |||||
""" | |||||
from random import randrange | |||||
r1start = 1600 | |||||
r2start = 1600 | |||||
r1 = r1start | |||||
r2 = r2start | |||||
for x in range(0, 150000): | |||||
res = randrange(3)-1 # How often one wins against the other | |||||
if res >= 1: | |||||
res = 1 | |||||
elif res <= -1: | |||||
res = -1 | |||||
r1gain = get_rating_adjustment(r1, r2, 20, 20, res) | |||||
r2gain = get_rating_adjustment(r2, r1, 20, 20, -1 * res) | |||||
r1 += r1gain | |||||
r2 += r2gain | |||||
print(str(r1) + " " + str(r2) + " : " + str(r1 + r2-r1start - r2start)) | |||||
""" | |||||
bbUnsubmitted Not Done Inline ActionsThis test should find his way into the test scripts bb: This test should find his way into the test scripts |
Wildfire Games · Phabricator
#!/usr/bin/env python3