クッキーもぐもぐ

PC関係とか映画とかゲームとかの

ガチャの確率と期待値をプログラミングで求める

概要

ガチャの確率と期待値をプログラミングで求める 主な機能としては

  • 排出率x%でy回まわしたとき1体以上でる確率を求める

  • そのときの期待値を求める

  • 0~y回までの結果をグラフに表示

アルゴリズム

1体以上でる確率は、y回まわして0体(一回もでない)の確率を1から引けば良いので

{ \displaystyle
   1 -  ((1-x)^{y})
}

もう少し拡張すると
n体以上でる確率は、y回まわしてn-1体までの確率の合計を1から引けば良い
イメージでいうと

0   ・・・  n-1  |  n ・・・・ y  
n-1体まででる確率 +  n体以上でる確率(ここを求めたい) =合計1  

排出率x%でy回まわしたとき,ちょうどi回でる確率は
{ \displaystyle
   {}_y C_i  (x^{i}(1-x)^{y-i})
}

なので0~n-1体まででる確率の合計は
{ \displaystyle
 \sum_{i=0}^{n-1}  {}_y C_i  (x^{i}(1-x)^{y-i})
}

なのでn体以上でる確率は
{ \displaystyle
 1-  \sum_{i=0}^{n-1}  {}_y C_i  (x^{i}(1-x)^{y-i})
}

ちなみに0~y体でる確率の合計はすべての可能性なので1になる { \displaystyle
  \sum_{i=0}^{y}  {}_y C_i  (x^{i}(1-x)^{y-i})  = 1
}

また各期待値は、各確率にその時出る回数iをかければ良い。つまり
{ \displaystyle
   i\times {}_y C_i  (x^{i}(1-x)^{y-i})
}

これをi(0→y) まで足したものが排出率x%でy回まわしたときの期待値
{ \displaystyle
   \sum_{i=0}^{y}  i \times {}_y C_i  (x^{i}*(1-x)^{y-i})
}

実はこれうまく出来て式を整理すると
x*yになる(二項分布コイン投げから分かる二項分布。正規分布やポアソン分布との関係性と近似について | アタリマエ!

すごい
感覚的にもあってるよね
例えば確率10%で10回したら0.1*1=1体はでそうだよね~って感じ

というわけで期待値の計算は簡単

まとめ

排出率x%でy回まわしたときの排出確率を求めるプログラムを作成
主な機能

  • 排出率x%でy回まわしたときz体以上でる確率を求める
  • そのときの期待値を求める
  • 0~y回までの結果をグラフに表示

今後追加したい機能
* コストの追加(これはガチャを回す回数に消費石、石の値段をかけるだけなので簡単

コードと結果

試しに排出確率0.5%、200回まわしたとき(200連)、1体以上でる確率をグラフで出してみる

result-image
排出確率と期待値の推移

200連しても狙ったキャラが排出されるのは65%以下・・・
期待値は0.5x200 = 1なのでちょうど200連すれば1体はでるでしょうという感じ
200連っていうと、オセロニアだと消費石だいたい800個くらい
1個あたり約50円とすると800x0=40000円
4万使ってようやく目的のキャラくるかな~という結果

0.5は高いほうで0.33,0.19とかもざらにある

やっぱ1点狙いが如何に闇で爆死しやすいか、非常にわかりやすい

逆にいえば、排出確率が3~10%になれば、結構でるのだ
3~10%というとA駒だったり、複数のターゲットがあれば実現できる

何が言いたいかというと ソシャゲのガチャを批判してるのではなく、
ガチャを引くならちゃんと確率を考えて引けば効率いいよ、という話

参考 書いたコード

# -*- coding: utf-8 -*-
import math
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import time

def c_count(n, r):#nCrを求める
    return math.factorial(n) // (math.factorial(r)*math.factorial(n - r))

def over_prob(x,y,z): #x% y-1回まわしたとき、z体以上当あたるを確率を求める  =  1-sum(0~(z-1)のprob)
    prob = []
    if y  < z:#y<z is impossible
        return 0
    else:     
        for i in range(z):#i駒当たる確率
            tmp = c_count(y,i) * (x**i) * ((1-x)**(y-i))
            prob.append(tmp)
        return 1- sum(prob)

def each_expect(x, y):#x% 0~y-1回まわしたときの各期待値を求める  期待値は二項分布より x*y
    return (np.arange(y))*x#0~yまでの配列を作成 -> xをかける

def over_predict(x, y, z):#x% 0~y-1回まわしたときの 1:z体以上でる確率  2:期待値を格納する    
    all_prob = []#z駒以上排出の確率
    for i in range(y):
        tmp = over_prob(x,i,z)
        all_prob.append(round(tmp*100,4))
    all_expect = each_expect(x, y)
    return all_prob,all_expect

#setting
a = 0.5#確率%
b = 100# ~連続引いた場合
c = 1#何体以上でる排出率か
d = 50/11 #一回まわすのにひつような石 石/回す回数

#main process
start = time.time()
all_prob,all_expect = over_predict(a/100, b+1, c)#b+1なのは0~b-1->0~bにするため
process_time = time.time() - start

#result as text
print (all_prob, all_expect)
print (str(process_time)+'s')
print(matplotlib.matplotlib_fname())

#result as graph
x = np.arange(0, b+1, 1)
y1 = np.array(all_prob)
y2 = np.array(all_expect)
fs = 15 #fontsize

##graph:1 prob
plt.subplot(1,2,1)
plt.xlabel('ガチャをまわす回数',fontsize = fs)
plt.ylabel('少なくとも'+str(c)+'体 出る確率 (%)',fontsize = fs)

plt.plot(x, y1, label=("排出率"+str(a)+"%の場合"))
plt.legend()
plt.xticks( np.arange(0, b+1, b/10) )
plt.yticks( np.arange(0, 101, 10) )
plt.grid(True)

##graph:2 expect
plt.subplot(1,2,2)
plt.xlabel('ガチャをまわす回数',fontsize = fs)
plt.ylabel('期待値 (コマ数)',fontsize = fs)

plt.plot(x, y2, label=("排出率"+str(a)+"%の場合"))
plt.legend()
plt.xticks( np.arange(0, b+1, b/10) )
plt.grid(True)

##draw graph
plt.show()