shonen.hateblo.jp

やったこと,しらべたことを書く.

Elo ratingを実装してみた

Elo rating とは

二人制ゲームのレーティングの算出法の1つ.

Wikipedia には,次の3点を基準に設計,と記述されている.

  • ゲームの結果は一方の勝ち、一方の負けのみとし、引き分けは考慮しない(0.5勝0.5敗と扱うものとする)。
  • 200点のレート差がある対局者間では、レートの高い側が約76パーセントの確率で勝利する。
  • 平均的な対局者のレートを1500とする。

計算式についてはWikipediaを参照してください.

イロレーティング - Wikipedia

と言っても,登場する計算式は次の2つだけです.

  • 自分と相手のレーティングが与えられたとき,(Elo ratingの仮定の下)自分が勝利する確率.
  • 自分のレーティングと戦果(戦った相手のレーティング,勝ったか負けたか)の集合が与えれたとき,レーティングがどう変動するか.

Rubyのコード

コードを書いてみます.と言っても,数式をただ落とし込むだけなので難しい事は無いです.

# 自分が勝利する確率
# my_rating      : 自分のレーティング
# another_rating : 相手のレーティング
def probability_to_win(my_rating, another_rating)
    1.0 / (1.0 + 10.0**((another_rating - my_rating) / 400))
end

# レーティングを更新する
# my_rating : 自分のレーティング
# ratings   : 相手のレーティングの配列
# wincnt    : 何回勝ったか
# kei       : 定数K (16か32を指定)
def update_rating1(my_rating, ratings, wincnt, kei = 32)
    e = ratings.map{|r| probability_to_win(my_rating, r)}.reduce(:+)
    my_rating + kei.to_f*(wincnt.to_f-e)
end

# レーティングを更新する
# my_rating : 自分のレーティング
# wins      : 勝った相手のレーティングの配列
# loses     : 負けた相手のレーティングの配列
# kei       : 定数K (16か32を指定)
def update_rating2(my_rating, wins, loses, kei = 32)
    wincnt = wins.size
    e = (wins + loses).map{|r| probability_to_win(my_rating, r)}.reduce(:+)
    my_rating + kei.to_f*(wincnt.to_f-e)
end

Elo rating を使ってみる

次のような対戦ゲーム(?)を考えます.

  • 各プレイヤーは1..9の整数を2つ持つ.このうち大きい方をhigh,小さい方をlowと呼ぶことにする.
  • プレイヤー同士が戦う時,お互いはlow..highの中からランダムに整数を1つ選ぶ.選んだ整数が大きいほうが勝ち.同じ値だったら等確率に勝敗が決まる.

ルールから分かる通り,highもlowも9であるようなプレイヤーが最強です.逆にhighもlowも1であるようなプレイヤーは最弱です.

この対戦ゲームにElo ratingを適応したいです.

# playerが発揮する戦闘力
def attack(player)
    rand(player[:strength_low]..player[:strength_high])
end


# プレイヤー同士を戦わせる.
# player1が勝ったらtrue,負けたらfalse
def battle(player1, player2)
    a1 = attack(player1)
    a2 = attack(player2)
    a1 > a2 ? true : a1 < a2 ? false : rand(0..1) == 1
end


# プレイヤーのステータスを設定
N = 10
players = Array.new(N) do
    a, b = rand(0..9), rand(0..9)
    {rating: 1500, strength_low: [a,b].min, strength_high: [a,b].max }
end


# 試行
1000.times do
    wincnts = [0]*N
    (0...N).to_a.combination(2) do |i1, i2|
        if battle(players[i1], players[i2])
            wincnts[i1] += 1
        else
            wincnts[i2] += 1
        end
    end
    
    all_ratings = players.map{|pl| pl[:rating] }
    (0...N).each do |i|
        my_rating = players[i][:rating]
        players[i][:rating] = update_rating1(my_rating, all_ratings[0...i] + all_ratings[i+1...N], wincnts[i])
    end
end

# result
players.each do |player|
    p player
end

実行結果例.

{:rating=>1640.7818092589837, :strength_low=>4, :strength_high=>7}
{:rating=>1274.8488412468812, :strength_low=>2, :strength_high=>4}
{:rating=>1342.2643683879348, :strength_low=>1, :strength_high=>6}
{:rating=>1367.8488475049514, :strength_low=>2, :strength_high=>5}
{:rating=>1429.8271133561443, :strength_low=>1, :strength_high=>7}
{:rating=>2328.688989487076, :strength_low=>8, :strength_high=>9}
{:rating=>1078.317414435196, :strength_low=>1, :strength_high=>3}
{:rating=>1492.8058083762696, :strength_low=>0, :strength_high=>9}
{:rating=>1704.004924338988, :strength_low=>5, :strength_high=>6}
{:rating=>1340.611883607576, :strength_low=>2, :strength_high=>5}

弱い人(例えば7行目,low1,high3)はレーティングが低くなっています. 逆に,強い人(例えば6行目,low8,high9)はレーティングがとても高いです.

もしレーティングに興味があるなら

次の本はいかがでしょうか?

レイティング・ランキングの数理 ―No.1は誰か? Amy N.Langville ・Carl D.Meyer 著・岩野 和生・中村 英史・清水 咲里訳 共立出版