乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

カイジ

今回は昔懐かしい
カイジの沼のシミュレーションです。

解説動画はこちら




カイジの沼のシミュレーション

沼とは

漫画『賭博破戒録カイジ』に登場する架空のパチンコ台

帝愛グループの運営するカジノに設置されている
パチンコの中でも特別な台で玉が1発あたり4000円
挑戦は最低300万円からという設定

その一方で、今までの挑戦者が消費した玉が
全てプールされる仕組みになっており
運良く大当たりを引ければ総取りで莫大な額の金を手にできるとか


仕掛け・役物

ここからは確率を計算するための
沼のギミックについてです。

1.釘の森

釘が密集した配置になっている「釘の森」
通常時の設定Cは1/100程度

2.スル

開閉する羽根のある電動役物「スルー」
釘の森を突破した玉を更にふるい落とす
確率は不明なので
ここでは通過率を1/5としておきます

3.3段クルーン

沼の本丸である「三段クルーン」
円形の皿に穴が3つ/4つ/5つ空いていて
当たりの穴に入ると次の段に進める

数学的にはクルーンに入ってさえしまえば
約1/60の確立で当たる


というような設定にはなっていますが
漫画の設定上ではこの台にはイカサマがあり
この確率では入らないようにはなっています。

しかし、今回は考慮しないで
イカサマなしだとどうなるかを見ていきたいと思います。



沼のシミュレーションコード


簡易な確率計算で大当たりしたかどうかだけ返す関数を用いて
シミュレーションしていきます。
最大回数は10万回(4億円相当)とします。

import numpy as np
import random
import pandas as pd
import matplotlib.pyplot as plt

# 確率でヒットするかどうかを判定する
def is_hit(n: int) -> bool:
    return random.randint(1, n) == 1

def numa_charenge():
  # 釘の森の通過
  mori = is_hit(100)
  if not mori:
    return False

  # スルーの通過
  throw = is_hit(5)
  if not throw:
    return False

  # クルーン1段目通過
  first = is_hit(3)
  if not first:
    return False

  # クルーン2段目通過
  second = is_hit(4)
  if not second:
    return False

  # クルーン3段目通過
  third = is_hit(5)
  if not third:
    return False
  else:
    return True


# 1000回施行して測ってみる
ball_price = 4000 # パチンコ玉の価格

def numa_result(num):
  all_result = []
  for a in range(1000):
    cumulative = 0
    for i in range(num):
      cumulative += ball_price
      is_atari = numa_charenge()
      if is_atari:
        all_result.append([i+1, cumulative,1])
        break
      if i == num-1:
        all_result.append([i+1, cumulative,0])
  return all_result

num = 100000 # 1回あたりの最大施行回数
all_result = numa_result(num)

df = pd.DataFrame(all_result,columns=["balls","cumulative","hit"])
df.head()
balls cumulative hit
0 30959 123836000 1
1 29604 118416000 1
...

plt.figure(figsize=(12, 6))
df["cumulative"].hist(bins=20)
plt.show()
download

おおよそこのような結果になります。


どれくらい注ぎ込めば当たるのか?


シミュレーション結果は使った額の累計を入れているので
それを用いれば、その金額以下の回数と
全体の回数から大体の確率を求められます。
times_300 = len(df[df["cumulative"]<3000000])
print(f"300万円で大当たりになる可能性 : {times_300/1000*100} % ")

なおこのシミュレーションの結果は
幾何分布に近似するため以下のようなコードで
回数あたりの大当たり確率の理論値が出せます。
# 幾何分布上の理論値
for i in range(10):
  p = 1 / 30000
  n = (i+1) * 10000
  prob = 1 - (1 - p)**n
  print(f"{i+1:02}万回で当たる確率 : {prob:.6f}")
01万回で当たる確率 : 0.283473
02万回で当たる確率 : 0.486589
03万回で当たる確率 : 0.632127
04万回で当たる確率 : 0.736409
05万回で当たる確率 : 0.811130
06万回で当たる確率 : 0.864669
07万回で当たる確率 : 0.903032
08万回で当たる確率 : 0.930520
09万回で当たる確率 : 0.950215
10万回で当たる確率 : 0.964328


シミュレーション結果もこの理論値に
近似してますね。


まとめ


沼の設定が玉が1発あたり4000円
3万発に1回の確率(100x5x3x4x5)
で当たるのだとしたら3億注ぎ込めば
9割くらいは大当たりするのではないか?

あくまでイカサマが無い事が前提ですが.....
イカサマさえなければ無尽蔵にお金を注ぎ込めるなら
勝てる気がしなくもない今日この頃でした。

それでは

無料マンガアプリでカイジを見ていたら
気になって仕方なくなってしまいました。

解説動画はこちら




さてチンチロリンのルールですが
こんな内容です。

サイコロを3つ同時に振って役が揃うまで振る(最大3回まで)

強い役が勝つ

役名条件
ピンゾロ1・1・1
ゾロ目2・2・2 , 3・3・3 , 4・4・4 , 5・5・5 , 6・6・6
シゴロ4・5・6
出目の大きい順番2枚が同じ、残った1つの目
ションベン役がなかった場合,お椀からサイコロが出た場合
ヒフミ1・2・3

他にも細かなルールは有りますが
今回は割愛します。

さてまずはチンチロリンの役を判定するプログラムを
作ってみましょう。

def tintiro_hand(h):
    # ピンゾロ
    if all([h[0]==1,h[1]==1,h[2]==1]):
        return 1
    # ゾロ目
    if h[0]==h[1] and h[1]==h[2]:
        return 2
    # シゴロ
    if [4,5,6]==list(sorted(h)):
        return 3
        # ヒフミ
    if [1,2,3]==list(sorted(h)):
        return 11
    # 出目 and ションベン
    calc = {}
    for n in h:
        if n in calc:
            calc[n]+= 1
        else:
            calc[n]=1
    if 2 in calc.values():
        return 3 + 7-sorted(calc.items(),key=lambda x:x[1])[0][0]
    else:
        return 10

def judge(h1,h_2):
    if h1==h_2:
        return 'DRAW'
    if h1<h_2:
        return 'WIN'
    else:
        return 'LOSE'


チンチロリンではサイコロを3つ使います。

このサイコロの組み合わせは
itertoolsで求めることが出来ます。

まずは通常のサイコロで戦った際の勝敗を見てみましょう。
import itertools
from fractions import Fraction

hands1 = list(itertools.product([1,2,3,4,5,6],repeat=3))
hands2 = list(itertools.product([1,2,3,4,5,6],repeat=3))
wins = {}
for hand1 in hands1:
    for hand2 in hands2:
        w = judge(tintiro_hand(hand1),tintiro_hand(hand2))
        if w in wins:
            wins[w] +=1
        else:
            wins[w] = 1
total = sum(wins.values())
draw,win,lose =wins['DRAW'],wins['WIN'],wins['LOSE']
print('DRAW\t' , Fraction(draw,total) , ' \t{:%}'.format(draw/total))
print('WIN \t'    , Fraction(win ,total)  , '\t{:%}'.format(win/total))
print('LOSE\t'   , Fraction(lose,total)  , '\t{:%}'.format(lose/total))

DRAW	 1639/5832  	28.103567%
WIN 	 4193/11664 	35.948217%
LOSE	 4193/11664 	35.948217%

だいたい36%ほどで勝ったり負けたりですね。

マンガではここで特別なサイコロが登場します。
それがシゴロ賽です!!!

4,5,6しか出目のない特殊なサイコロ

地下労働施設の班長
大槻が用いたイカサマサイコロです。

これを使うと極端に役の出方が変わります。

通常のサイコロと勝敗を比べてみましょう。
import itertools
from fractions import Fraction

hands1 = list(itertools.product([1,2,3,4,5,6],repeat=3))
hands2 = list(itertools.product([4,5,6,4,5,6],repeat=3))
wins = {}
for hand1 in hands1:
    for hand2 in hands2:
        w = judge(tintiro_hand(hand1),tintiro_hand(hand2))
        if w in wins:
            wins[w] +=1
        else:
            wins[w] = 1
total = sum(wins.values())
draw,win,lose =wins['DRAW'],wins['WIN'],wins['LOSE']
print('DRAW\t' , Fraction(draw,total) , ' \t{:%}'.format(draw/total))
print('WIN \t'    , Fraction(win ,total)  , '\t{:%}'.format(win/total))
print('LOSE\t'   , Fraction(lose,total)  , '\t{:%}'.format(lose/total))

DRAW	 107/1944 	5.504115%
WIN 	 175/1944 	9.002058%
LOSE	 277/324   	85.493827%

相手側の勝率が極端に変わりますね!!!

通常時35%ほどだった負け確率が85%と
50%ほど負ける確率が増加します。

実際に役が出る確率はどういう確率でしょうか?

お互いのサイコロの出目でみてみましょう。

サイコロの出目の組み合わせは
お互い 6**3 で216通りあります。

通常のサイコロを振ってみると
import itertools
import matplotlib.pyplot as plt
%matplotlib inline

hands1 = list(itertools.product([1,2,3,4,5,6],repeat=3))
hands = {}
for hand1 in hands1:
    h = tintiro_hand(hand1)
    if h in hands:
        hands[h] +=1
    else:
        hands[h] = 1

plt.figure(figsize=(12,6))
x = [k for k,v in sorted(hands.items())]
y = [v for k,v in sorted(hands.items())]
for x1,y1 in zip(x,y):
    plt.text(x1, y1+1 , y1 , size = 10, color = "green")
    plt.text(x1, y1+10 , '{:.01%}'.format(y1/216), size = 10, color = "black")
label = ['111','ゾロ目','シゴロ','6','5','4','3','2','1','ションベン','123']
plt.bar(x,y,tick_label=label)
plt.grid()
plt.show()
download-1
こんな確率ですね。
今回は3回振りなおしを考慮していないので
出目を考えるとションベンになる確率が高いですね。

これと比べてシゴロ賽はどうでしょうか?
import itertools
import matplotlib.pyplot as plt
%matplotlib inline

hands = list(itertools.product([4,5,6,4,5,6],repeat=3))
hands2 = {i:0 for i in range(1,12)}
for hand in hands:
    h2 = tintiro_hand(hand)
    if h2 in hands2:
        hands2[h2] +=1
    else:
        hands2[h2] = 1

plt.figure(figsize=(12,6))
x = [k for k,v in sorted(hands2.items())]
y = [v for k,v in sorted(hands2.items())]
for x1,y1 in zip(x,y):
    plt.text(x1, y1+1 , y1 , size = 10, color = "green")
    plt.text(x1, y1+10 , '{:.01%}'.format(y1/216), size = 10, color = "black")
label = ['111','ゾロ目','シゴロ','6','5','4','3','2','1','ションベン','123']
plt.bar(x,y,tick_label=label)
plt.grid()
plt.show()
download-2

強い手の確率が極端に上がります。

見比べてみると瞭然ですね。
スクリーンショット 2020-05-23 17.10.35

マンガではどうやってこのイカサマを見つけたかというと
出目をメモってる三好がいて
その出目の歪さからイカサマサイコロに気づいた・・・
という感じでした。

やはり、データの整備とデータから発見できるかという
まさにデータサイエンス的な要素をもつ
このチンチロリン編は

賭博破戒録カイジの第3章に出てきます。

みていない方はぜひご賞味ください。

最後は倍、いや100倍返しでwww

とまあ、456サイコロは売ってるみたいなので
これを使うと圧倒的に勝てますねーー

やはり、統計って大事ですよねー
今回はこれまでです
それでは



カイジファイナルゲームを観てきました。

そこで新作ゲームがあったので
プログラムで
検証していきたいと思います。

検証動画はこちら




さて
まずはゴールドジャンケンについてですね

この映画の内容では
政治家向けの接待ジャンケンと言う設定です。

ルールは
・3回のジャンケン勝負
・最低1回は必ずグーを出す必要がある(純金を握る)
・純金を手にして(グーの時)勝った場合、その純金はもらえる

3回勝負のうち
1回はグーを出すと言うのがポイントですね

映画では登場人物の
高倉という官僚が考えたというもので

カイジと対戦する機会があります。

その場合は
特別ルールが加算されていました。

カイジ戦特別ルール
・カイジが1回でも勝てば勝利。
・あいこの場合、高倉の勝ち。
・純金を握って勝つごとに1億円の金塊が貰える。

高倉は3回の勝ちを狙っているのでした。


と言うことで
早速プログラムで検証していきたいと思います。

まずジャンケンの手は
パー・グー・チョキ・
しか無いので
その重複ありの組み合わせになります。

ただし一度はグーを出さないといけないので
パーとチョキだけのケースを除くことを考えると
import itertools

# 手の内
seq = ['パー','グー','チョキ']

# 出せる手を求める

# itertools.product で直積を求める
# 1度でもグーを出す必要がある
hand = [h for h in itertools.product(seq,repeat=3) if 'グー' in h]

print(len(hand))

for h in hand:
    print(h)
19
('パー', 'パー', 'グー')
('パー', 'グー', 'パー')
('パー', 'グー', 'グー')
('パー', 'グー', 'チョキ')
('パー', 'チョキ', 'グー')
('グー', 'パー', 'パー')
('グー', 'パー', 'グー')
('グー', 'パー', 'チョキ')
('グー', 'グー', 'パー')
('グー', 'グー', 'グー')
('グー', 'グー', 'チョキ')
('グー', 'チョキ', 'パー')
('グー', 'チョキ', 'グー')
('グー', 'チョキ', 'チョキ')
('チョキ', 'パー', 'グー')
('チョキ', 'グー', 'パー')
('チョキ', 'グー', 'グー')
('チョキ', 'グー', 'チョキ')
('チョキ', 'チョキ', 'グー')

27通りのうち
19ケースの組み合わせが残ります。

次にこの19ケースのみで
総当たり戦を考えてみます。

高倉に対してカイジはあいこでも負けます。
カイジが勝てるのは勝つ組み合わせの時だけです。

19ケース同士の総当たりだと
361通りの対戦パターンがあります。
count=[]
for k,t in itertools.product(hand,hand):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    count.append(tmp)
    print(k,t,tmp)

ここから勝敗結果を求めていくと
result = {}
for c in count:
    key = str(c)
    if key in result:
        result[key]+=1
    else:
        result[key]=1

for k,v in result.items():
    print(k,v)
{'kaiji': 0, 'takakura': 3} 115
{'kaiji': 1, 'takakura': 2} 159
{'kaiji': 2, 'takakura': 1} 75
{'kaiji': 3, 'takakura': 0} 12

カイジが1回でも勝てるケースは
361分の246ケースあります。

しかし、もう一つのポイントがあります。

2手目までにグーを出していない場合は
3手目でグーを出さないといけないので
相手に負けてしまうケースが存在します。
(相手も2手目までにグーを出していなければ引き分け)

ということは最後にグーを残すことは
無いだろうと思われるので
最後にグーを残してしまうケースを考えると

hand2 = []
for h in hand:
    if h[0]!='グー' and h[1]!='グー':
        print(h)
    else:
        hand2.append(h)

('パー', 'パー', 'グー')
('パー', 'チョキ', 'グー')
('チョキ', 'パー', 'グー')
('チョキ', 'チョキ', 'グー')

4ケースあります。

これを除いて考えてみることにします。

19-4で15ケースですね。
count=[]
for k,t in itertools.product(hand2,hand2):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    count.append(tmp)
    print(k,t,tmp)

result = {}
for c in count:
    key = str(c)
    if key in result:
        result[key]+=1
    else:
        result[key]=1

for k,v in result.items():
    print(k,v)

{'kaiji': 0, 'takakura': 3} 78
{'kaiji': 1, 'takakura': 2} 99
{'kaiji': 2, 'takakura': 1} 42
{'kaiji': 3, 'takakura': 0} 6

結果としては

225ケースのうち
147回はカイジが1回でも勝てるようですね。

割合としては
65%くらいの確率で
1回は勝てることになるんじゃ
なかろうかと。

そもそも
このゲームのルールだと
カイジが有利なのかもしれませんね。

2回以上勝てたケースが
有利な手順かもしれないので
それを調べてみます。
w = {1:{'パー':0, 'グー':0,'チョキ':0},2:{'パー':0, 'グー':0,'チョキ':0},3:{'パー':0, 'グー':0,'チョキ':0}}
for k,t in itertools.product(hand2,hand2):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    if "'kaiji': 2" in str(tmp) or "'kaiji': 3" in str(tmp):
        print(k,t,tmp)
        w[1][k[0]]+=1
        w[2][k[1]]+=1
        w[3][k[2]]+=1
print(w)
{1: {'パー': 15, 'グー': 27, 'チョキ': 6},
 2: {'パー': 15, 'グー': 27, 'チョキ': 6},
 3: {'パー': 16, 'グー': 16, 'チョキ': 16}}


手の結果から考慮すると

1手目は「パー」か「グー」
2手目は1手目「パー」なら「グー」, 1手目「グー」なら「パー」
3手目は「パー」か「チョキ」

というパターンで出すと
勝ちやすいのかもしれない・・・


てか
そもそも
ジャンケンに必勝法なんて
存在しないですよねーーーーーーー

カイジが
勝てたのかどうかは
カイジファイナルゲームをご覧ください


それでは

このページのトップヘ