pythonシミュレーションでネット麻雀(天鳳、雀魂)の段位の比較をやってみた

## 今回の試みのきっかけ
天鳳や雀魂といったネットで遊べる麻雀ゲームでは、段位という制度が存在し、天鳳では10級から10段、天鳳位まで、雀魂では初心、雀士、雀傑、雀豪、雀聖、魂天(各段位に1,2,3の小区分けがある)となっています。段位は上にいくにつれて昇段する難易度が上がっていきます。
ネット麻雀をプレイされる方の中には、両方プレイしている人もいたり、片方だけをプレイされる人もいたりと様々です。Twitter上の麻雀関係のコミュニティでは度々「天鳳と雀魂の段位はどう対応するのか」という話題が上がります。具体的には、天鳳7段と雀魂の魂天はどっちが強いのかとかそういう話です。ここに関しては主観的な議論が多く、定量的な分析はあまり行われてきませんでした。

## 今回やること
テーマは「**天鳳と雀魂の段位を定量的に比較すること**」です。
これに対して様々なアプローチが考えられますが、今回の手法は以下のようなものになります。
1. 両ゲームの最高段位である天鳳位と魂天両方を達成した4名の成績を使用。
2. 4名それぞれについて、天鳳での成績を用いて天鳳での昇段確率、雀魂での成績を用いて雀魂での昇段確率を求める。
3. 4名それぞれについて、「昇段確率が高い方が難易度が低い」という原則に基づき段位の難易度を比べる。難易度順が4人で共通していればその信頼度が高いとみなして結論を出す。

同じ人物で各段位での昇段確率を比べることで、実力の影響を排除し、純粋に段位の難易度がわかるという寸法です。

## 昇段確率の求め方
まず段位制度について説明します。
両ゲームにおいて、1位をとるとプラス何pt、4位を取るとマイナス何pt...という順位点の配分が存在します。これによって毎試合各人の持ちptが変動し、一定ラインを超えたら段位が上昇し、逆に一定ラインを下回ったら段位が降下するという仕組みです。段位が上がるごとに配分が厳しくなって、難易度が上がっていきます。
各人について、1位率から4位率までの順位分布がネット上に公開されています。
今回はモンテカルロシミュレーションを用いて、規定試合数で昇段できたかどうかという試行を1000回繰り返し、昇段確率を算出します。

<details><summary>雀魂用のシミュレーションコード</summary><div>

```Python
#雀魂昇段シミュレーション
#入力事項: 段位、1~3位率、想定半荘数N
#出力結果: N半荘以内に昇段/降段する確率
import numpy as np
import copy
from math import ceil

#段位と順位分布(順位とその確率)の設定
Dan = input('現在の段位を入力　>>')
rate_1 = float(input('1位率を入力 >>'))
rate_2 = float(input('2位率を入力 >>'))
rate_3 = float(input('3位率を入力 >>'))
rate_4 = 1 - rate_1 - rate_2 - rate_3
print('4位率は{}です'.format(rate_4))

#順位分布と順位ptをリストに入れる
rate_list = [rate_1, rate_2, rate_3, rate_4]
Dan_list = ['雀豪1', '雀豪2', '雀豪3', '雀聖1', '雀聖2', '雀聖3']
required_pt = [2800,3200,3600,4000,6000,9000]
for i in range(6):
    if Dan == Dan_list[i]:
        required_syoudan = required_pt[i]
        rate_pt = [125, 60, -5, -180-15*i]
        
#設定された順位分布のもとでN半荘打って昇段したか否かを記録する(試行回数1000回)
pt_now = int(input('現在のptを入力してください >>'))
rankpt_list = list()
N = int(input('何半荘想定か入力してください　>>'))

for j in range(1000):
    rankpt = 0
    for i in range(N):
        n = np.random.choice([1, 2, 3, 4], p = rate_list)
        #順位nをとるとnに付随する順位ptを加算
        rankpt += rate_pt[n-1]
        rankpt += ceil((np.random.normal((5-n)*10000, 5000))/1000) -25  #素点ptを加算
        if rankpt >= required_syoudan - pt_now or rankpt <= - pt_now:
                break  #N半荘打つ中で一瞬でも昇段必要ptを超えたら昇段なのでループを抜ける。
                       #降段必要ptを下回ると降段なのでその場合もループを抜ける。
    rankpt_list.append(rankpt)
rankpt_list2 = copy.copy(rankpt_list)
#rankpt_listにはN半荘打って得た総獲得ptが1000個並んでいる
#一瞬でも昇段必要ptを超えたあるいは降段必要ptを下回ったものはそのまま打つのをやめている
#以下ではrankpt_listの中で昇段必要ptを超えているものをカウントし、その割合を計算している

for i in range(1000):   
    if rankpt_list2[i] >= required_syoudan - pt_now:
        rankpt_list2[i] = True
    else: rankpt_list2[i] = False
x = sum(rankpt_list2)/len(rankpt_list2)  #昇段確率(降段せずに、N半荘の中のどこかで昇段に達する確率)
print('{}半荘打って昇段できる確率(試行回数1000回)は{}%です'.format(N, 100*x))

```

</div></details>

##  結果と考察
シミュレーションの結果は下の通り。
|昇段確率|雀魂||天鳳||||
|:---|:---|:---|:---|:---|:---|:---|
|人/段位|聖2→聖3|聖3→魂天|7段→8段|8段→9段|9段→10段|10段→天鳳位|
|A|95.6%|92.4%|97.4%|88.0%|67.2%|37.8%|
|B|91.8%|83.0%|95.0%|79.4%|58.0%|29.0%|
|C|93.0%|89.0%|88.0%|62.1%|33.7%|10.8%|
|D|93.0%|84.6%|77.2%|52.0%|22.0%|6.8%|

分かったこととしては、
・　聖3→魂天は「天鳳8段→9段より簡単」
・　聖3→魂天は天鳳7段→8段と同程度の難易度
・　ネット麻雀最強クラスの4人でも10段から天鳳位になるのはかなり難しい。

### 昇段確率の信頼区間の話
今回は標本から得た確率をそのまま昇段確率としていますが、真の昇段確率は多少ずれている可能性があります。そこで、初級統計学で学ぶ母比率pの区間推定を行えば、以下のような95%の確率で成立する不等式が導けます。
![Qiita](https://assets.st-note.com/production/uploads/images/81541853/picture_pc_e818c9307792b5352a1830a088cdd716.png?width=2000&height=2000&fit=bounds&format=jpg&quality=85)
これに基づけば、試行回数n=1000なので、真の昇段確率の信頼区間のおよその幅は標本確率±2.5%となります。
この事実から、上の考察は標本確率を用いても問題ないことがわかります。

### 今回の手法の課題
今回の手法は、同じ実力を持った人が今までの雀魂と天鳳の成績と同じレベルでもう一度段位戦を繰り返して昇段確率を算出すれば、実力の影響を排除して段位の比較ができるというコンセプトでした。この手法で生じる問題点を述べていきます。

・　**4名それぞれについて雀魂と天鳳を打った時期が異なるため、1人の人物に着目したとしても実力が変化している可能性**
雀魂のリリース時期がごく最近である一方、天鳳は10数年前から始まっているので、打った時期は間違いなく異なります。その中で実力が変化しているかもしれません。

・ **天鳳を打った時は運が良くて、雀魂を打った時は運が悪かったかもしれない**
これについては4名の試合数が天鳳については2000以上、雀魂は1000以上あるため、影響としてはかなり小さいと考えています。

## 終わりに
今回使用した昇段確率は何にでも使えそうなので、別のテーマでも使えないか試していこうと思います。