사용자 도구

사이트 도구


elo

Elo

import random
import collections
import math
 
import numpy as np
import matplotlib.pyplot as plt
from IPython import embed
 
 
def k_factor(player):
    """
    player의 k factor 계산
    """
    if player.elo < 1100:
        return 25
    elif player.elo < 2400:
        return 15
    else:
        return 10
 
 
def expected_score(player: float, opponent: float):
    """
    player와 opponent와 게임에서 얻을 수 있는 기대 점수(승률)
 
    :param player: player의 Elo
    :param opponent: opponent의 Elo
    """
    return 1 / (1 + 10 ** ((opponent - player) / 400))
 
 
def elo(old_elo, true_score, exp_score, k):
    """
    player의 새로운 Elo 값 계산
 
    :param old_elo: 이전 Elo
    :param true_score: player의 실제 점수
    :param exp_score: player의 기대 점수
    :param k: k-factor
    """
    return old_elo + k * (true_score - exp_score)
 
 
def play_game(player1, player2, n_games=1):
    score1 = player1.play()
    score2 = player2.play()
 
    player1_score = 0.0
    player2_score = 0.0
 
    for _ in range(n_games):
        if score1 > score2:
            player1_score += 1.0
            player2_score += 0.0
        elif score1 < score2:
            player1_score += 0.0
            player2_score += 1.0
        elif math.isclose(score1, score2):
            player1_score += 0.5
            player2_score += 0.5
 
    player1_score /= n_games
    player2_score /= n_games
 
    player1_exp_score = expected_score(player1.elo, player2.elo)
    player1_k = k_factor(player1)
    player1.elo = elo(player1.elo, player1_score, player1_exp_score, player1_k)
 
    player2_exp_score = expected_score(player2.elo, player1.elo)
    player2_k = k_factor(player2)
    player2.elo = elo(player2.elo, player2_score, player2_exp_score, player2_k)
    return player1, player2
 
 
def match_make(players):
    return random.sample(players, 2)
 
 
class Player:
    def __init__(self, name, performance, init_elo=1000):
        self.name = name
        self._performance = performance
        self.elo = init_elo
 
    def play(self):
        # 정규 분포보다, 균등분포가 Elo가 크게 벌어짐
        # return random.random() + self._performance
 
        # std가 커질 수록 구분이 어려움
        return random.gauss(self._performance, 1.0)
 
    def __repr__(self):
        return f'{self.name}: {self._performance}, {int(self.elo):d}'
 
 
def moving_average(xs, window_size=3):
    assert len(xs) > 0
    assert window_size > 0
 
    if window_size % 2 == 0:
        window_size += 1
 
    if window_size > 1 and len(xs) > window_size:
        weights = np.ones(window_size) / window_size
        xs_extended = [xs]
        for _ in range(window_size // 2):
            xs_extended.insert(0, xs[:1])
            xs_extended.insert(-1, xs[-1:])
        xs_extended = np.concatenate(xs_extended)
        return np.convolve(xs_extended, weights, mode='valid')
    else:
        return xs
 
 
if __name__ == '__main__':
 
    max_games = 100000
    n_game_per_iter = 10
    players = [Player('A', 1, init_elo=1200),
               Player('B', 0.5), Player('C', 0.45), Player('D', 0.40), Player('E', 0.30)]
    records = collections.deque(maxlen=max_games // n_game_per_iter)
 
    for n_games in range(max_games // n_game_per_iter):
        player1, player2 = match_make(players)
        play_game(player1, player2, n_game_per_iter)
        records.append([p.elo for p in players])
 
    for player, elo_records in zip(players, zip(*records)):
        elo_records = moving_average(elo_records, 100)
        plt.plot(elo_records, label=player.name)
 
    plt.legend()
    plt.show()
 
    # embed()

참고

elo.txt · 마지막으로 수정됨: 2024/05/08 13:34 저자 rex8312