2023-12-17に更新

イロ・レーティングって何だぜ(^~^)?<その2>

読了目安:14分

前の関連記事

📖 イロ・レーティングって何だぜ(^~^)?<その1>

はじめに

ramen-tabero-futsu2.png
「 イロ・レーティングを実装しようぜ?」

kifuwarabe-futsu.png
「 まずは プレイヤーを 2人 に固定しようぜ?」

ohkina-hiyoko-futsu2.png
「 勝負は 乱数を使ったジャンケンで、 ラウンド数を入力できるようにしましょう」

kifuwarabe-futsu.png
「 👇 リポジトリは作っておいたぜ」

📖 elo-rating-system

📄 step_1_0.py:

import random
#
# python step_1_0.py
#

# 0, 1, 2 のいずれかを返す
def gyanken():
    return random.randint(0, 2)

if __name__ == "__main__":

    print("Please input round number(1-100):")
    round = int(input())

    counts = [0,0,0]

    for i in range(0, round):
        result = gyanken()

        counts[result] += 1

        if result == 0:
            print("aiko")
        elif result == 1:
            print("A win")
        elif result == 2:
            print("B win")
        else:
            print("Error")

    print(f"aiko: {counts[0]}, A win: {counts[1]}, B win: {counts[2]}")

ramen-tabero-futsu2.png
「 👆 これが ジャンケン だぜ」

ohkina-hiyoko-futsu2.png
「 レーティングを付けてみましょう」

引き分けはどう計算する?

ramen-tabero-futsu2.png
「 引分けのとき、レーティングの移動は どう計算すんの?」

kifuwarabe-futsu.png
「 👇 Wikipedia では、 チェスでは 引き分けは 0.5勝、0.5敗 としているという説明があるだけで、計算方法は書いてないぜ」

📖 イロレーティング

ramen-tabero-futsu2.png
「 じゃあ チェスのウィキを見ればいいんだ」

ramen-tabero-futsu2.png
「 0.5 勝 マイナス 0.5 敗 は 0 だから、レーティングは 0 動くらしい」

kifuwarabe-futsu.png
「 動かないのか」

レーティング差が 0 の2者のとき、対局後のレーティングはどう動く?

ramen-tabero-futsu2.png
「 レーティング差が 0 の2者のとき、対局後のレーティングはどう動くんだぜ?」

ohkina-hiyoko-futsu2.png
「 A 側の勝率が50%で、 B 側の勝率が50%で、K が 32 のときは 32 * 0.5 で 16 動くのよ」

ramen-tabero-futsu2.png
「 そうか 分かったぜ」

📄 step_1_1_0.py:

#
# python step_1_1_0.py
#
import random

games = [0,0,0]


# 0, 1, 2 のいずれかを返す
def on_gyanken_1():
    return random.randint(0, 2)


def main(
        on_gyanken,
        on_result,
        on_end):

    print("Please input round number(1-100):")
    round = int(input())


    for i in range(0, round):
        result_1 = on_gyanken()

        games[result_1] += 1

        on_result(result_1)

    on_end()


if __name__ == "__main__":

    def on_result_1(result):
        if result == 0:
            print("aiko")
        elif result == 1:
            print("A win")
        elif result == 2:
            print("B win")
        else:
            print("Error")


    def on_end_1():
        print(f"aiko: {games[0]}, A win: {games[1]}, B win: {games[2]}")


    main(on_gyanken=on_gyanken_1,
         on_result=on_result_1,
         on_end=on_end_1)

📄 step_2_0.py:

#
# python step_1_1_0.py
#
import math

from step_1_1_0 import on_gyanken_1, games, main

# R0 = 2000
ratings = [0, 2000, 2000]

# Constant K
K = 32


# Rating calculate
def get_x_by_y(y):
    if y==0:
        return 1
    else:
        return 400 * math.log10(y)


# Rating calculate
def get_y_by_x(x):
    return x ** (x / 400)


# Win rate
def get_win_rate_for_upper_rating(win_games):
    return win_games / (win_games + 1)


# Win rate
def get_win_rate_for_lower_rating(win_games):
    return 1 / (win_games + 1)


if __name__ == "__main__":

    def on_result_1(result):


        # レーティング差
        ab = ratings[1] - ratings[2]
        ba = ratings[2] - ratings[1]
        y = abs(ba)
        print(f"y: {y}")
        x = get_x_by_y(y)


        # 勝率
        win_rate_for_lower_rating = 1 / (x+1)
        #win_rate_for_upper_rating = x / (x+1)


        if result == 0:
            # レーティングは動きません
            print(f"aiko  >  ratings A {ratings[1]}, B {ratings[2]}")

        elif result == 1:
            offset = K * win_rate_for_lower_rating
            ratings[1] += offset
            ratings[2] -= offset
            print(f"A win  >  ratings A {ratings[1]}, B {ratings[2]}")

        elif result == 2:
            offset = K * win_rate_for_lower_rating
            ratings[2] += offset
            ratings[1] -= offset
            print(f"B win  >  ratings A {ratings[1]}, B {ratings[2]}")

        else:
            print("Error")


    def on_end_1():
        print(f"games:   aiko: {games[0]}, A win: {games[1]}, B win: {games[2]}")
        print(f"ratings: aiko: {ratings[0]}, A win: {ratings[1]}, B win: {ratings[2]}")


    main(on_gyanken=on_gyanken_1,
         on_result=on_result_1,
         on_end=on_end_1)

ramen-tabero-futsu2.png
「 👆 うーむ 分からん」

ramen-tabero-futsu2.png
「 端数は 切り捨てた方がいいのかな?」

kifuwarabe-futsu.png
「 また 明日考えようぜ?」

(📅 2023-12-12 tue) コード書いた

📄 step_2_0.py:

#
# python step_2_0.py
#
import math

from step_1_1_0 import on_gyanken_1, games, main

# R0 = 2000
ratings = [0, 2000, 2000]

# Constant K
K = 32


# 1勝するために必要な対局数(暗記表の x )を取得
# 実数でも算出できるが、(1.0 以上の数になるよう数式を調整している前提で)整数にして返す
def get_games_by_rating_difference(
        rating_difference): # 暗記表の y

    # ゼロなら
    if rating_difference==0:
        return 1

    # 負数なら
    elif rating_difference <0:
        # 負数を指定できないので、符号をひっくり返して、あとで戻す
        return -math.floor(400 * math.log10(-rating_difference))
        # マイナス符号の付ける位置で結果が変わってくるので注意
        #return math.floor(-400 * math.log10(-rating_difference))

    # 正の数なら
    else:
        return math.floor(400 * math.log10(rating_difference))


# レーティング差(暗記表の y )を取得
def get_rating_difference_by_games(
        games): # 暗記表の x : 実数
    return math.floor(10 ** (games / 400))


# Win rate : 実数
def get_win_rate_for_upper_rating(win_games):
    return win_games / (win_games + 1)


# Win rate : 実数
def get_win_rate_for_lower_rating(win_games):
    return 1 / (win_games + 1)


if __name__ == "__main__":

    def on_result_1(result):

        # あいこ
        if result == 0:
            print("""\
+------+
| aiko |
+------+\
                  """)
            # レーティングは動きません
            print(f"* ratings: A {ratings[1]}, B {ratings[2]}")

        # A が勝った
        elif result == 1:
            print("""\
+-------+
| A win |
+-------+\
                  """)

            # b から見た a とのレーティング差
            difference_b_to_a = ratings[1] - ratings[2]
            print(f"* b から見た a とのレーティング差: {difference_b_to_a}")

            # b から見た a に1勝するために必要な対局数
            games_b_to_a = get_games_by_rating_difference(difference_b_to_a)
            print(f"* b から見た a に1勝するために必要な対局数: {games_b_to_a}")

            # b から見た a への勝率
            if 0 <= difference_b_to_a:
                Wba = get_win_rate_for_upper_rating(games_b_to_a)
            else:
                Wba = get_win_rate_for_lower_rating(games_b_to_a)

            print(f"* b から見た a への勝率(Wba): {Wba}")

            offset = math.floor(K * Wba)
            ratings[1] += offset
            ratings[2] -= offset
            print(f"* K: {K},  offset: {offset},  ratings: A {ratings[1]}, B {ratings[2]}")

        # B が勝った
        elif result == 2:
            print("""\
+-------+
| B win |
+-------+\
                  """)

            # a から見た b とのレーティング差
            difference_a_to_b = ratings[2] - ratings[1]
            print(f"* a から見た b とのレーティング差: {difference_a_to_b}")

            # a から見た b に1勝するために必要な対局数
            games_a_to_b = get_games_by_rating_difference(difference_a_to_b)
            print(f"* a から見た b に1勝するために必要な対局数: {games_a_to_b}")

            # a から見た b への勝率
            if 0 <= difference_a_to_b:
                Wab = get_win_rate_for_upper_rating(games_a_to_b)
            else:
                Wab = get_win_rate_for_lower_rating(games_a_to_b)

            print(f"* a から見た b への勝率(Wab): {Wab}")

            offset = math.floor(K * Wab)
            ratings[2] += offset
            ratings[1] -= offset
            print(f"* K: {K},  offset: {offset},  ratings: A {ratings[1]}, B {ratings[2]}")

        else:
            print("Error")


    def on_end_1():
        print(f"""\
+--------+
| result |
+--------+
* games:    aiko: {games[0]:4},  A win: {games[1]:4},  B win: {games[2]:4}
* ratings:  aiko: {ratings[0]:4},  A win: {ratings[1]:4},  B win: {ratings[2]:4}\
              """)

    print("""\
+-------+
| start |
+-------+\
          """)

    # レーティングは動きません
    print(f"* ratings: A {ratings[1]}, B {ratings[2]}")


    main(on_gyanken=on_gyanken_1,
         on_result=on_result_1,
         on_end=on_end_1)

ramen-tabero-futsu2.png
「 👆 なるべく 整数に変換してみたぜ」

kifuwarabe-futsu.png
「 プレイヤー人数を 増やそうぜ?」

ramen-tabero-futsu2.png
「 マッチングは どうすんだぜ?」

ohkina-hiyoko-futsu2.png
「 ランダム・マッチングで いいんじゃないの?」

ファイルへ保存

ramen-tabero-futsu2.png
「 その前に 大会の結果を ファイルに保存するようにしようぜ?」

        # ファイルへ保存
        with open('data/step_2_0.csv', mode='w') as f:
            f.write(f"""\
player,  win, rating
------, ----, ------
  aiko, {games[0]:4}, {ratings[0]:6}
     A, {games[1]:4}, {ratings[1]:6}
     B, {games[2]:4}, {ratings[2]:6}
""")

ramen-tabero-futsu2.png
「 👆 こんな感じかだぜ?」

ohkina-hiyoko-futsu2.png
「 それは 100局の集計にはなるけど、
大会を 100回 記録するには?」

ramen-tabero-futsu2.png
「 集計に 足し込むか、ファイルを100個作ればいいのでは?」

kifuwarabe-futsu.png
「 集計ではなく、ゲームの記録を並べるべきでは?」

ramen-tabero-futsu2.png
「 あっ、そうか……」

player_1_name, player_1_rating_before_game, player_2_name, player_2_rating_before_game, win_player, moving_rating
A, 2000, B, 2000, 2, 16
A, 1984, B, 2016, 2, 31
A, 1953, B, 2047, 2, 31
A, 1922, B, 2078, 1, -1
A, 1921, B, 2079, 0, 0

ramen-tabero-futsu2.png
「 👆 レーティングが大きい方が勝ったら、なんで もっと大きくレーティングが移動するんだぜ?」

ohkina-hiyoko-futsu2.png
「 途中の式を書きなさい」

# 1勝するために必要な対局数(暗記表の x )を取得
# 実数でも算出できるが、(1.0 以上の数になるよう数式を調整している前提で)整数にして返す
def get_games_by_rating_difference(
        rating_difference): # 暗記表の y
    return math.floor(10 ** (rating_difference / 400))

    ## ゼロなら
    #if rating_difference==0:
    #    return 1
    #
    ## 負数なら
    #elif rating_difference <0:
    #    # 負数を指定できないので、符号をひっくり返して、あとで戻す
    #    return -math.floor(400 * math.log10(-rating_difference))
    #    # マイナス符号の付ける位置で結果が変わってくるので注意
    #    #return math.floor(-400 * math.log10(-rating_difference))
    #
    ## 正の数なら
    #else:
    #    return math.floor(400 * math.log10(rating_difference))


# レーティング差(暗記表の y )を取得
def get_rating_difference_by_games(
        games): # 暗記表の x : 実数
    #return math.floor(10 ** (games / 400))
    return math.floor(400 * math.log10(games))

ramen-tabero-futsu2.png
「 👆 関数と 逆関数が 逆だったぜ」

player_1_name, player_1_rating_before_game, player_2_name, player_2_rating_before_game, win_player, moving_rating
A, 2000, B, 2000, 0, 0
A, 2000, B, 2000, 1, 16
A, 2016, B, 1984, 1, 16
A, 2032, B, 1968, 1, 16
A, 2048, B, 1952, 1, 16
A, 2064, B, 1936, 1, 21
A, 2085, B, 1915, 1, 21
A, 2106, B, 1894, 1, 24
A, 2130, B, 1870, 1, 25
A, 2155, B, 1845, 2, 32
A, 2123, B, 1877, 0, 0
A, 2123, B, 1877, 1, 25
A, 2148, B, 1852, 2, 32
A, 2116, B, 1884, 2, 32

ramen-tabero-futsu2.png
「 👆 レーティング差が広がったら、移動するレーティング量も増えてしまうの、なんでだぜ?」

ohkina-hiyoko-futsu2.png
「 途中の式を書きなさい」

player_1_name, player_1_rating_before_game, player_2_name, player_2_rating_before_game, win_player, moving_rating
A, 2000, B, 2000, 1, 16
A, 2016, B, 1984, 2, 32
A, 1984, B, 2016, 1, 32
A, 2016, B, 1984, 0, 0
A, 2016, B, 1984, 1, 16
A, 2032, B, 1968, 1, 16
A, 2048, B, 1952, 0, 0
A, 2048, B, 1952, 0, 0
A, 2048, B, 1952, 2, 32
A, 2016, B, 1984, 2, 32
A, 1984, B, 2016, 2, 16
A, 1968, B, 2032, 2, 16
A, 1952, B, 2048, 1, 32
A, 1984, B, 2016, 0, 0
A, 1984, B, 2016, 2, 16

ramen-tabero-futsu2.png
「 👆 コードを掃除してたら 勝手に直った……」

kifuwarabe-futsu.png
「 これだけあれば、
プレイヤーを3人に増やして ランダム・マッチングも行けるだろう」

add C 2000
remove C

ohkina-hiyoko-futsu2.png
「 👆 プレイヤーの新規追加、削除も トランザクション・データとして残しておけば
データの読取が楽じゃない?」

ramen-tabero-futsu2.png
「 command 列でも増やすかあ?」

kifuwarabe-futsu.png
「 対局の記録は、対局だけでいいのではないか?」

📅 (2023-12-16 sat ⏰ 14:29) 多プレイヤー対応へ

ramen-tabero-futsu2.png
「 イロ・レーティングって プレイヤーが2サイドなことを前提としてるよな」

kifuwarabe-futsu.png
「 お父んのプログラム読みにくいな なんでだろな?」

ohkina-hiyoko-futsu2.png
「 大会と、レーティング計算が 別れてないのよ」

ramen-tabero-futsu2.png
「 分けた方が 見やすいか……」

ramen-tabero-futsu2.png
「 あれっ? プレイヤーのデータベースも要るような……」

id        display_name  rating
--------  ------------  ------
player_1  Alice           2000
player_2  Bob             2000

ramen-tabero-futsu2.png
「 👆 こんな感じのデータベースが要るんじゃないか?」

kifuwarabe-futsu.png
「 とりあえず 連想配列で作ってみようぜ?」

6人でジャンケン大会

+-------------------+
| tournament result |
+-------------------+
* name: Alice           , rating: 2102
* name: Francisca       , rating: 2073
* name: Bob             , rating: 2013
* name: Charley         , rating: 1969
* name: Eric            , rating: 1922
* name: Dingo           , rating: 1921

ramen-tabero-futsu2.png
「 👆 ランダム・マッチングで 100ラウンド、
ジャンケンでも 確率的というだけで これだけ ばらけるしな。
ガウス分布になってくだろ」

ohkina-hiyoko-futsu2.png
「 プレイヤーにちからの差がないと
レーティングの機能は 働かないのかもしれないわねぇ」

戦闘力を付けてみた

+-------------------+
| tournament result |
+-------------------+
* name: Alice           , rating: 2379
* name: Bob             , rating: 2287
* name: Charley         , rating: 2087
* name: Dingo           , rating: 1989
* name: Eric            , rating: 1804
* name: Francisca       , rating: 1454

ramen-tabero-futsu2.png
「 👆 強さに差があると その順番には並んでるな」

kifuwarabe-futsu.png
「 レーティングが 400 以上離れているやつは 対局しないようにする必要があるんじゃないか?」

+-------------------+
| tournament result |
+-------------------+
* name: Alice           , rating: 2436
* name: Bob             , rating: 2282
* name: Charley         , rating: 2195
* name: Dingo           , rating: 1954
* name: Eric            , rating: 1672
* name: Francisca       , rating: 1461

ramen-tabero-futsu2.png
「 👆 レーティングが 400 以上離れているやつは なるべく対局しないようにしてみたぜ」

ohkina-hiyoko-futsu2.png
「 レーティングが ほとんど動かないような対局が減るから、
レーティングが よく動くようになったんじゃないの?」

次の関連記事

📖 スイス式トーナメントって何だぜ(^~^)?

.

ツイッターでシェア
みんなに共有、忘れないようにメモ

むずでょ

光速のアカウント凍結されちゃったんで……。ゲームプログラムを独習中なんだぜ☆電王戦IIに出た棋士もコンピューターもみんな好きだぜ☆▲(パソコン将棋)WCSC29一次予選36位、SDT5予選42位▲(パソコン囲碁)AI竜星戦予選16位

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント