はじめに
今回は以下の投稿で作成した天鳳の成績自動管理アプリに__Rating機能__を追加したお話です.
Rating計算式
Ratingは以下の天鳳形式を採用します.天鳳では成績の完全順位制を踏まえてRatingも完全順位制になっています.私達の個室では比較的順位点のWeightが高いルールを採用しているので,天鳳式のRatingを採用しました.
(以下一部抜粋)
- 順位点(10-30)に同卓プレーヤの平均による補正を行ったもの
- 初期値=R1500
- 卓の平均Rが高いほど大きく上昇します
(Rateの変動) = (試合数補正) x ( 対戦結果 + 補正値 ) x (スケーリング係数)
試合数補正(400試合未満): 1 - 試合数 x 0.002
試合数補正(400試合以上): 0.2
対戦結果(段位戦3人打ち): 1位+30 2位0 3位-30
補正値: ( 卓の平均R - 自分のR ) / 40
スケーリング係数(段位戦):1.0
もしあなたの個室では順位点より素点の方を重視するルールならば,他のRating計算式を重視したほうが,成績との相関が高いかもしれません.以下のサイトではMリーガーのRatingを3種類の計算式で算出しているので,参考にしてみてください.
実装
プリアンブル
import re
import matplotlib.pyplot as plt
Rating/Rの初期化
initial_file.txt
内の初期データ(Player名:player,初期R:init_rating,初期打荘数:init_games)によってレーティングデータを初期化します.基本はR=1500,games=0.
calc_rating.py
def initialize_rating(initial_file):
with open(initial_file) as f:
init_ratings = f.readlines()
rating = {}
games = {}
rating_history = {}
for l in init_ratings:
player = l.split()[0]
init_rating = l.split()[1]
init_games = l.split()[2]
rating[player] = float(init_rating)
games[player] = int(init_games)
rating_history[player] = [float(init_rating)]
print(games)
return rating,games,rating_history
initial_file.txt
Aさん 1500 0
Bさん 1500 0
Cさん 1500 0
Dさん 1500 0
Rの計算
- ログファイルの対戦結果からR変動値を求め,初期RにR変動値を足していく.
- 引数は以下の5つ
- 初期R
- 初期Game数
- それまでのR履歴
- ログファイルのパス
- チップの有無
- 返り値は以下の3つ
- 最終R
- 最終Game数
- R履歴
- 例えばログファイルが複数ある場合はこの関数を複数回実行すれば通算のRが計算できる.
def calc_rating(initial_rating,initial_games,initial_rating_history,logfile,tip=False):
with open(logfile) as f:
lines = f.readlines() # 1行毎にファイル終端まで全て読む(改行文字も含まれる)
rating = initial_rating
games = initial_games
rating_history = initial_rating_history
for line in lines[1:]:
# print(games)
if len(line) > 10: #変な行は飛ばす
roomid = line.split("|")[0]
time = line.split("|")[1]
rools = line.split("|")[2]
players = line.split("|")[3]
# 祝儀なしの場合
if tip == False:
l = re.split('[ ()]', players)
player1 = l[1]
player2 = l[4]
player3 = l[7]
# 祝儀ありの場合
if tip == True:
l = re.split('[ (,)]', players)
player1 = l[1]
player2 = l[5]
player3 = l[9]
rate_average = (rating[player1]+rating[player2]+rating[player3])/3.0
rate_average = round(rate_average,3)
if rate_average < 1500.0:
rate_average = 1500.0
# 試合数補正
if games[player1] < 400:
games_correction1 = 1.0 - games[player1]*0.002
if games[player1] >= 400:
games_correction1 = 0.2
if games[player2] < 400:
games_correction2 = 1.0 - games[player2]*0.002
if games[player2] >= 400:
games_correction2 = 0.2
if games[player3] < 400:
games_correction3 = 1.0 - games[player3]*0.002
if games[player3] >= 400:
games_correction3 = 0.2
# 平均R補正
averageR_correction1 = (rate_average - rating[player1])/40.0
averageR_correction2 = (rate_average - rating[player2])/40.0
averageR_correction3 = (rate_average - rating[player3])/40.0
# Rating変動
rate_delta1 = round(games_correction1 * ( 30.0 + averageR_correction1 ) * 1.0, 3) # 1st
rate_delta2 = round(games_correction2 * ( 0.0 + averageR_correction2 ) * 1.0, 3) # 2nd
rate_delta3 = round(games_correction3 * ( -30.0 + averageR_correction3 ) * 1.0, 3) # 3rd
print(rate_delta1,rate_delta2,rate_delta3)
# Rating
rating[player1] += rate_delta1
rating[player2] += rate_delta2
rating[player3] += rate_delta3
# Rating History
rating_history[player1].append(rating[player1])
rating_history[player2].append(rating[player2])
rating_history[player3].append(rating[player3])
# Games
games[player1] += 1
games[player2] += 1
games[player3] += 1
return rating,games,rating_history
R変動のグラフ化
- 折れ線(plot)と最終Rの1点プロット(scatter),最終R値の表示を行っています.
def rating_plot(rating_history):
plt.clf()
names = {"Aさん":"a","Bさん":"b","Cさん":"c","Dさん":"d"}
for player in rating_history.keys():
x = [i for i in range(len(rating_history[player]))]
y = rating_history[player]
plt.plot(x,y,linewidth=0.5,alpha=0.5)
plt.scatter(x[-1],y[-1],label=names[player])
plt.text(x[-1],y[-1]+5,int(y[-1]))
plt.legend()
plt.savefig("rating.png")
実行(ローカル環境でのテスト)
if __name__ == "__main__":
r,g,h = initialize_rating("rating.txt")
r,g,h = calc_rating(r,g,h,"logvol1.txt",tip=False)
r,g,h = calc_rating(r,g,h,"logvol2.txt",tip=True)
r,g,h = calc_rating(r,g,h,"logvol3.txt",tip=True)
rating_plot(h)
出力結果
- 私は9人で麻雀しているので9本のグラフが表示されています.
- 平均Rが1500以下の場合は切り上げているので平均的には1500より大きくなっています.
- Game数が増えていくとRの変動は小さくなっていきます(400戦目以降は変わらない).
## LINE bot への実装
- 先程のプログラムを
rating
フォルダに入れて,line botのプログラムから呼び出します. - 前回のつづきで
postback
アクションがあったときにratingを計算してグラフを送信します. - 一回計算したratingを残しておくようにすれば毎回計算しなくて済みそうですが,DBとか使うことになりそうなのでとりあえず毎回計算することにしました.
- シーズン3までログファイルがあるのでその通算Rを計算しています.
- これで毎日最新(前日までの分)のRをいつでもLINEから見られるようになりました.
tenhoulinebot.py
(中略)
import download4
import rating.calc_rating as cr
(中略)
elif postbackdata == "request_rating":
download4.download("/logvol1.txt","rating/logvol1.txt")
download4.download("/logvol2.txt","rating/logvol2.txt")
download4.download("/logvol3.txt","rating/logvol3.txt")
initial_rating,initial_games,initial_rating_history = cr.initialize_rating("rating/rating.txt")
r,g,h = cr.calc_rating(initial_rating,initial_games,initial_rating_history,"rating/logvol1.txt",tip=False)
r,g,h = cr.calc_rating(r,g,h,"rating/logvol2.txt",tip=True)
r,g,h = cr.calc_rating(r,g,h,"rating/logvol3.txt",tip=True)
cr.rating_plot(h)
bucket.upload_file("rating.png", "rating.png")
s3_image_url = s3_client.generate_presigned_url(
ClientMethod = 'get_object',
Params = {'Bucket': aws_s3_bucket, 'Key': "rating.png"},
ExpiresIn = 600,
HttpMethod = 'GET'
)
line_bot_api.reply_message(
event.reply_token,
ImageSendMessage(
original_content_url = s3_image_url,
preview_image_url = s3_image_url,
)
)
download4.upload("rating.png","/rating.png")
P.S.
今季の麻雀はすごく調子がいいです.一人勝ち状態です.このまま優勝したいと思います.
天鳳の牌譜解析もやってみたいけど,mjlog形式(xml)が難しいです...誰か教えてください...