Elo评分算法原理与实现


《社交网络》里的Mark Zackburg被女朋友甩后,在舍友的启发下,充分发挥了技术宅男自娱自乐的恶搞天分,做出了Facemash网站,对学校女生的相貌进行排名打分,结果网站访问流量过大,直接把学校网络搞瘫痪了。Facemask大受欢迎的关键就在于Zackburg基友Eduardo写在窗户上的排名公式,看电影之时就对这个排名公式非常感兴趣,上网了解下,才发现这条公式就是大名鼎鼎的ELO等级分制度。ELO的应用非常广泛,大部分棋类比赛,现在流行的MODB游戏,像11平台的DOTA天梯系统,都是采用ELO等级分。

ELO等级分制度是由匈牙利裔美国物理学家Elo创建的一个衡量各类对弈活动选手水平的评分方法,是当今对弈水平评估的公认的权威方法。被广泛应用于国际象棋、围棋、足球等运动,以及很多网游与电子竞技产业。游戏界比较著名的应用有: WOW(魔兽世界)、DOTA、LOL。

ELO计算方法
Ra:A玩家当前的积分
Rb:B玩家当前的积分
Sa:实际胜负值,胜=1,平=0.5,负=0
Ea:预期A选手的胜负值,Ea=1/(1+10^[(Rb-Ra)/400])
Eb:预期B选手的胜负值,Eb=1/(1+10^[(Ra-Rb)/400])
因为E值也为预估,则Ea+ Eb=1

R’a=Ra+K(Sa-Ea)
R’a:A玩家进行了一场比赛之后的积分
其中 K 值是一个常量系数,按照国际象棋里的标准, K 值对于大师选手为16,对于一般选手是32。K值的大小直接关系到一局游戏结束,根据胜负关系计算出的积分变化值。

关于K值
K值是一个极限值,代表理论上最多可以赢一个玩家的得分和失分,K/2就是相同rating的玩家其中一方胜利后所得的分数。国际象棋大师赛中,K=16;在大部分的游戏规则中,K=32。通常水平越高的比赛中其K值越小,这样做是为了避免少数的几场比赛就能改变高端顶尖玩家的排名。

关于分母400
公式Ea和Eb中分母的400是怎么来的呢?为何是400,不是200、100或者是其他?
根据公式可以得出,当K值相同的情况下,越高的分母,越低的积分变化。总体来说400是一个平衡的、万金油的值、让多数玩家积分保持 标准正态分布 的值。具体可以参考:http://en.chessbase.com/post/arpad-elo-and-the-elo-rating-system

实例说明
若当前A玩家积分为1500,B玩家积分为1600
预估A玩家的胜负值: Ea = 1/(1+10^[(1600-1500)/400])≈0.36
预估B玩家的胜负值: Eb = 1-Ea = 1-0.36 = 0.64
假设A玩家获胜,实际胜负值为Sa = 1
A玩家最终得分为 :R’a = 1500 + 32*(1-0.36) = 1500+20.5 = 1520
A玩家赢20分,B玩家输20分。
假设B玩家获胜,实际胜负值为Sa = 1
B队最终得分为 R’b = 1600 + 32*(1-0.64) = 1600 + 11.52 = 1612,B玩家赢12分,A玩家输12分。

class Elorating:
    ELO_RESULT_WIN = 1
    ELO_RESULT_LOSS = -1
    ELO_RESULT_TIE = 0

    ELO_RATING_DEFAULT = 1500

    ratingA = 0
    ratingB = 0

    def __init__(self, ratingA = ELO_RATING_DEFAULT, ratingB = ELO_RATING_DEFAULT):
        self.ratingA = ratingA
        self.ratingB = ratingB

    def setResult(self, result):
        scoreAwin = self.computeScore(self.ratingA, self.ratingB)
        scoreBwin = self.computeScore(self.ratingB, self.ratingA)

        score_adjust = 0
        if result == self.ELO_RESULT_WIN:
            score_adjust = 1
        elif result == self.ELO_RESULT_LOSS:
            score_adjust = 0
        else:
            score_adjust = 0.5

        self.ratingA = self.ratingA + self.computeK(self.ratingA) * (score_adjust - scoreAwin)
        self.ratingB = self.ratingB + self.computeK(self.ratingB) * (score_adjust - scoreBwin)


    def computeK(self, rating):
        if rating >= 2400:
            return 16
        elif rating >= 2100:
            return 24
        else:
            return 36


    def computeScore(self, rating1, rating2):
        return 1 / (1+pow(10, (rating1 - rating2) / 400))

    pass