Elo ratingを実装してみた
Elo rating とは
二人制ゲームのレーティングの算出法の1つ.
Wikipedia には,次の3点を基準に設計,と記述されている.
- ゲームの結果は一方の勝ち、一方の負けのみとし、引き分けは考慮しない(0.5勝0.5敗と扱うものとする)。
- 200点のレート差がある対局者間では、レートの高い側が約76パーセントの確率で勝利する。
- 平均的な対局者のレートを1500とする。
計算式については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 著・岩野 和生・中村 英史・清水 咲里訳 共立出版