昇級・降級枠の変化による順位戦シミュレーション

# はじめに

以前はスイス式に関する考察を行ったが、

[ランダムマッチングとスイス式による順位戦のシミュレーション](https://qiita.com/tibigame/items/e7390d4f938a2b3fea74)

今回は昇級・降級枠が変化した時に利害がどう変わるのかを調べる。

# 昇級

* 2人と3人のときでどう変わるかを見る。
* リーグは50人。
* 順位1位からレーティング差が10ずつ離れているとする。(1位と50位ではR500ほど違うことになる)

この仮定だと前回のプログラムがほぼそのまま使える。

```python
# 順位戦シミュレーション
def normal_simulation(rank_up_number: int):
    rank_up = []
    runner_up = []
    for x in range(simulation_number):
        league = League(player_number)
        for i in range(battle_number):
            league.random_match(i)
            league.battles(i)
        league.count_win()
        league.result_rank()
        rank_up.extend(league.result_rank_list[0:rank_up_number])
        runner_up.append(league.players[league.result_rank_list[rank_up_number]].win)
        if x % 1000 == 0:
                print(x)
    return rank_up, runner_up

def analyze_result(rank_up, runner_up):
    rank_up_list = [0] * player_number
    runner_up_list = [0] * 11
    for i in range(player_number):
        rank_up_list[i] = rank_up.count(i)
    for i in range(11):
        runner_up_list[i] = runner_up.count(i)
    return rank_up_list, runner_up_list


normala1, normala2 = normal_simulation(2)
normalb1, normalb2= normal_simulation(3)
normala_result1, normala_result2 = analyze_result(normala1, normala2)
normalb_result1, normalb_result2 = analyze_result(normalb1, normalb2)

print(f"昇級人数2, 3の差による次点の勝数の差")
for win_num in range(6, 11):
    print(f"{win_num:2d}勝: {normala_result2[win_num]}: {normalb_result2[win_num]}")

print(f"昇級人数2, 3の差による昇級確率の差")
for i, result in enumerate(zip(normala_result1, normalb_result1)):
    print(f"{i + 1:2d}位: {result[0]/100:.2f}: {result[1]/100:.2f}")
```

## 結果

```text
昇級人数2, 3の差による次点の勝数の差
 6勝: 0: 0
 7勝: 12: 76
 8勝: 3996: 6696
 9勝: 5871: 3217
10勝: 121: 11
```

昇級3人だと50人でも次点8勝を2/3にできます。全勝4人以上の確率は0.1%。
昇級2人だと次点9勝が過半数になる。全勝3人以上の確率は1.2%。

```text
昇級人数2, 3の差による昇級確率の差
 1位: 36.80: 45.84
 2位: 29.79: 40.48
 3位: 23.32: 34.48
 4位: 20.10: 29.40
 5位: 16.30: 25.33
 6位: 13.38: 20.16
 7位: 10.38: 17.13
 8位: 9.37: 14.65
 9位: 7.12: 12.44
10位: 6.03: 10.02
11位: 5.05: 8.45
12位: 4.06: 7.48
13位: 2.90: 5.49
14位: 3.06: 4.89
15位: 2.21: 4.69
16位: 1.74: 3.26
17位: 1.59: 2.95
18位: 1.21: 2.39
19位: 1.22: 2.00
20位: 0.90: 1.59
21位: 0.65: 1.38
22位: 0.69: 1.08
23位: 0.42: 0.96
24位: 0.30: 0.83
25位: 0.31: 0.45
26位: 0.21: 0.54
27位: 0.17: 0.41
28位: 0.21: 0.18
29位: 0.12: 0.29
30位: 0.07: 0.21
31位: 0.10: 0.19
32位: 0.06: 0.07
33位: 0.05: 0.06
34位: 0.01: 0.04
35位: 0.05: 0.09
36位: 0.03: 0.03
37位: 0.00: 0.01
38位: 0.00: 0.01
39位: 0.00: 0.01
40位: 0.00: 0.01
41位: 0.00: 0.00
42位: 0.01: 0.01
43位: 0.01: 0.01
44位: 0.00: 0.01
45位: 0.00: 0.00
46位: 0.00: 0.00
47位: 0.00: 0.00
48位: 0.00: 0.00
49位: 0.00: 0.00
50位: 0.00: 0.00
```

各順位で公平に昇級確率がアップしているように見えますが、
15～20位あたりに注目すると昇級確率が倍近くになってますね。

## 順位最下位に強いプレイヤーがいる場合

50位のレーティングを1位より200上としてシミュレーションを行った。

```text
昇級人数2, 3の差による昇級確率の差
 1位: 28.56: 38.52
 2位: 22.64: 33.54
 3位: 18.32: 27.55
 4位: 15.39: 22.85
 5位: 12.41: 20.14
 6位: 10.74: 16.64
 7位: 8.03: 14.40
 8位: 7.11: 11.13
 9位: 5.67: 9.41
10位: 4.51: 7.92
11位: 3.83: 7.14
12位: 3.06: 5.51
13位: 2.61: 4.90
14位: 2.56: 3.77
15位: 2.11: 3.01
16位: 1.54: 2.83
17位: 1.24: 2.39
18位: 0.97: 1.94
19位: 0.90: 1.64
20位: 0.61: 1.10
21位: 0.50: 1.10
22位: 0.44: 1.00
23位: 0.29: 0.89
24位: 0.32: 0.71
25位: 0.19: 0.49
26位: 0.19: 0.30
27位: 0.12: 0.28
28位: 0.12: 0.18
29位: 0.13: 0.22
30位: 0.06: 0.15
31位: 0.04: 0.12
32位: 0.06: 0.06
33位: 0.04: 0.06
34位: 0.00: 0.06
35位: 0.01: 0.04
36位: 0.03: 0.04
37位: 0.00: 0.03
38位: 0.02: 0.01
39位: 0.03: 0.00
40位: 0.01: 0.04
41位: 0.00: 0.00
42位: 0.00: 0.01
43位: 0.01: 0.00
44位: 0.00: 0.01
45位: 0.00: 0.01
46位: 0.00: 0.00
47位: 0.00: 0.00
48位: 0.00: 0.01
49位: 0.00: 0.00
50位: 44.58: 57.85
```

リーグの最上位よりレーティング200強いぐらいではストレート昇級の確率は半々といえる。

# 降級点シミュレーション

* 1位～49位までレーティング差10ずつ+50位だけ1位より200上の設定で行う。
* 下位10(13)人を降級点としたときに降級点確率を10000回シミュレーションにより求める。

少しソースをいじるとできる。

```python3
# 順位戦シミュレーション 変数名はそのまま
def normal_simulation(rank_up_number: int):
    rank_up = []
    runner_up = []
    for x in range(simulation_number):
        league = League(player_number)
        for i in range(battle_number):
            league.random_match(i)
            league.battles(i)
        league.count_win()
        league.result_rank()
        rank_up.extend(league.result_rank_list[-rank_up_number:]) # 順位の末端を取得するようにする
        runner_up.append(league.players[league.result_rank_list[rank_up_number]].win)
        if x % 1000 == 0:
                print(x)
    return rank_up, runner_up
```

```text
降級点人数10, 13の差による降級点確率の差
 1位: 0.04: 0.04
 2位: 0.03: 0.09
 3位: 0.03: 0.11
 4位: 0.05: 0.15
 5位: 0.10: 0.14
 6位: 0.06: 0.24
 7位: 0.15: 0.46
 8位: 0.07: 0.36
 9位: 0.14: 0.61
10位: 0.18: 0.62
11位: 0.40: 0.93
12位: 0.41: 1.25
13位: 0.50: 1.25
14位: 0.63: 1.62
15位: 0.85: 2.06
16位: 1.08: 2.19
17位: 1.49: 2.78
18位: 1.49: 3.20
19位: 1.84: 4.00
20位: 2.57: 4.92
21位: 2.89: 5.62
22位: 3.26: 6.92
23位: 4.17: 7.73
24位: 5.25: 9.40
25位: 5.71: 10.97
26位: 6.54: 12.64
27位: 8.43: 15.36
28位: 9.84: 17.78
29位: 10.74: 20.42
30位: 12.87: 23.53
31位: 14.95: 26.83
32位: 17.06: 29.69
33位: 20.22: 34.03
34位: 23.13: 37.53
35位: 26.65: 42.02
36位: 29.92: 46.37
37位: 33.39: 49.97
38位: 38.05: 54.68
39位: 42.89: 58.16
40位: 48.06: 62.14
41位: 53.06: 66.68
42位: 57.38: 69.88
43位: 62.80: 73.21
44位: 66.09: 74.72
45位: 70.04: 78.37
46位: 74.31: 81.08
47位: 77.96: 83.71
48位: 79.66: 86.38
49位: 82.57: 87.14
50位: 0.00: 0.02
```

3人増えただけで降級点確率が軒並み倍近くになってます。
下位はどうせ落ちるから上位がその分負担がかかるのですね。
上位の強豪が誰か死んでも全然おかしくない確率になっています。