グラブルルーレット選択問題（8th Anniversaryキャンペーン）

（3/11 17:03追記　軽微な修正）
突入確率最大化 → 突入失敗確率最小化に変更

3月10日(木)より8th Anniversaryキャンペーンが開催されました。
毎日最高100連ガチャ無料ルーレットキャンペーンにて以下の２種類のルーレットから選択できます。

・ガチャピンルーレット(チャレンジ)
・ムックルーレット(堅実)

これらはゲージ増加量やガチャの回数の期待値が均等になるように調整されています。

騎空士の皆様は、できればグランデフェス又はレジェンドフェスの期間内にガチャピンモードに突入したいはずです。

本記事ではゲージ量の取り得る状態を離散化し、マルコフ連鎖を用いてフェス期間内におけるガチャピンモードの突入確率を最大化する方法を紹介します。
（フェス中に２回以上ガチャピンモードに入る場合を考慮しないため多少ズレがあります。）

今回は、２種類のルーレットの他に以下の選択肢も用意します。
・ルーレットを選択しない（ゲージ量を現状維持するため）

言語：Python

まず、パラメータを設定します。
```python:
import numpy as np

# 現在のゲージ量（%）
Init_Gauge = 50

# フェスまでの日数（今日からフェス前日までのルーレットを回す回数）
T1 = 3

# フェスの日数（フェス中にルーレットを回す回数、100連やガチャピンモード確定日は除く）
T2 = 3
# https://granbluefantasy.jp/pages/?p=40509 より抜粋
'''
下記の期間で、「100連ガチャ無料」の当選確率が100%になります。

期間1：2022年3月23日(水) 5:00 ～ 2022年3月24日(木) 4:59
期間2：2022年3月30日(水) 5:00 ～ 2022年3月31日(木) 4:59
'''

# ゲージ増加量は以下のツイートを参考に設定しています
# https://twitter.com/nelnoa01/status/1501581644507283457?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1501581644507283457%7Ctwgr%5E%7Ctwcon%5Es1_&ref_url=https%3A%2F%2Fscoutneo.com%2F1420%2F
# チャレンジのゲージ増加量（10連／20連／30連）(%)
Gauge_Challenge = [23, 12.5, 2.5]

# 堅実のゲージ増加量（20連／30連）(%)
Gauge_Steady = [12.5, 7.5]

# ゲージ量の刻み幅（%）
Width = 0.5

# ゲージ量の取り得る状態の種類（0から99.5まで0.5刻み＝200通り）
Steps = int(1/(Width/100))

# ルーレットの選択方法の場合の数（チャレンジ／堅実／回さない）
Cases = 3**(T1+T2)

# 「今日のルーレット選択別フェス中のガチャピンモード突入確率」
Max_Entry_Prob = [0, 0, 0]
```
続いて、各サイトを参考にルーレットの抽選確率とマルコフ行列を定義します。（実際とは異なる可能性があります）
```python:
# https://granbluefantasy.jp/pages/?p=40509 より抜粋
'''
※「ガチャピンルーレット(チャレンジ)」では、ガチャピンゲージの量に応じて、ガチャピンゾーンの当選確率が変化します
※「ムックルーレット(堅実)」では、ガチャピンゲージの量に応じて、ガチャピンゾーンと「無料10連ガチャ3回」の当選確率が変化します
'''
# https://scoutneo.com/1420/ を参考に確率を設定しています

# ゲージ依存のルーレットの抽選確率
# チャレンジルーレット：10連／20連／30連／100連／ガチャピンモード
def Prob_Challenge(gauge):
  prob_gachapin = 0.1 + gauge*Width/4/100
  y = [0.45-prob_gachapin, 0.3, 0.22, 0.03, prob_gachapin]
  return y

# 堅実ルーレット：20連／30連／ガチャピンモード
def Prob_Steady(gauge):
  prob_gachapin = gauge*Width/4/100
  prob_30 = 0.18 + gauge*Width/4/100
  y = [1-prob_gachapin-prob_30, prob_30, prob_gachapin]
  return y

# ゲージ量のマルコフ行列
def Gauge_Markov_Matrix_Challenge():
  y = np.zeros((Steps,Steps))
  for i in range(Steps):
    prob = Prob_Challenge(i)
    for j in range(3):
      gauge_increase = i + int(Gauge_Challenge[j]/Width)
      if gauge_increase < Steps:
        y[gauge_increase,i] += prob[j]
      else:
        y[0,i] += prob[j]
    y[i,i] += prob[3]
    y[0,i] += prob[4]
  return y

def Gauge_Markov_Matrix_Steady():
  y = np.zeros((Steps,Steps))
  for i in range(Steps):
    prob = Prob_Steady(i)
    for j in range(2):
      gauge_increase = i + int(Gauge_Steady[j]/Width)
      if gauge_increase < Steps:
        y[gauge_increase,i] += prob[j]
      else:
        y[0,i] += prob[j]
    y[0,i] += prob[2]
  return y

# マルコフ行列の定義
GMMC = Gauge_Markov_Matrix_Challenge()
GMMS = Gauge_Markov_Matrix_Steady()
```
ここからは数値計算になります。
```python:
# 数値計算
print("***** Calculation start *****")

for i in range(Cases):
  if i%200 == 0:
    print('start：'+str(i))

  # フェス中にルーレットを回さない場合の排除（メリットが無いため）
  invalid = 0
  for j in range(T1,T1+T2):
    if (i//3**j)%3 != 2:
      continue
    invalid = 1
  if invalid == 1:
    continue

  # 現在の状態分布
  state = np.zeros(Steps)
  state[int(Init_Gauge/Width)] = 1

  # ガチャピンモード突入失敗率
  entry_failure_prob = 1

  # 突入率の計算
  for j in range(T1+T2):
    # j日目の選択方法（チャレンジ／堅実／回さない）
    method = (i//3**j)%3
    
    state0 = state[0]
    # 状態分布にマルコフ行列を掛ける
    if method == 0:
      state = GMMC.dot(state)
    elif method == 1:
      state = GMMS.dot(state)

    # 突入失敗率の乗算
    if (method==0)&(j>=T1):
      entry_failure_prob *= 1 - (state[0] - state0 * 0.03)
    elif (method==1)&(j>=T1):
      entry_failure_prob *= 1- state[0]

  # 突入確率最大化
  Max_Entry_Prob[i%3] = max(Max_Entry_Prob[i%3],1-entry_failure_prob)

print("***** Calculation end *****")

# 結果
print("Prob of Gachapin Mode in the Fes by today's selection")
print('Challenge Roulette（Gachapin）：'+str(Max_Entry_Prob[0]*100)+' %')
print('Steady Roulette（Mukku）：'+str(Max_Entry_Prob[1]*100)+' %')
print('Not to select any roulette：'+str(Max_Entry_Prob[2]*100)+' %')
```
実行結果

***** Calculation start *****
start：0
start：200
start：400
start：600
***** Calculation end *****
Prob of Gachapin Mode in the Fes by today's selection
Challenge Roulette（Gachapin）：61.51693228119619 %
Steady Roulette（Mukku）：63.58250364576455 %
Not to select any roulette：62.42349760287161 %

・次回
https://qiita.com/toqoo/items/d2338d0e605dd63208f5