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

プログラミング

前回作成したドラクエ1風のシミュレーターを改造して
ドラクエ2風のチーム戦が出来るようにしました。

解説動画はこちら



さて前回のプログラムを改造して行きましょう。

前回は1vs1だったので
複数vs複数に対応するように改造します。

前回の奴をそのまま使用するのと改造するもの
新規のものといろいろ出てきますが

まずは前回の流用。
キャラクタークラスはそのまま流用できますが
チーム戦になると生存フラグが必要になってきますので
フラグを追加します、1が生存0が死亡。

ダメージ計算や素早さ計算はそのまま流用します。
import numpy as np

# キャラクタークラス
class character():

    def __init__(self,name,hp,attack,defence,agility):
        self.name = name
        self.hp = hp
        self.attack = attack
        self.defence = defence
        self.agility = agility
        self.survive = 1

# ダメージ計算
def damage_calc(c1,c2):
    base = c1.attack //2 - c2.defence//4
    rand = base//16+1
    tmp = np.random.randint(0,rand*2+1)-rand
    damage = base + tmp
    return damage

# 素早さ計算
def agility_calc(c):
    base = c.agility
    tmp = base//10
    result = np.random.randint(base-tmp,base+tmp+1)
    return result

チーム戦を行うために、キャラを複数格納できる
チームクラスを作ります。
# チームクラス
class Team():

    def __init__(self,name,teams):
        self.name = name
        self.team = teams

    def append(self,chara):
        self.team.append(chara)
        
    def delete(self,index):
        del self.team[index]


前回は2キャラの対戦なのでチームでの複数対戦だと
行動の順番を決める必要があります。

行動順番は素早さを元に計算し
数値が大きい順に行動するようにします。

その際生存フラグが1のものだけにします。

# 先頭順序の計算
def turn_calc(t1,t2):
    result = {}
    for i,c in enumerate(t1.team):
        ag = agility_calc(c)
        if c.survive==1:
            result['{0}:{1}'.format(t1.name,i)] = ag
    for i,c in enumerate(t2.team):
        ag = agility_calc(c)
        if c.survive==1:
            result['{0}:{1}'.format(t2.name,i)] = ag
    return [k for k,v in sorted(result.items(),reverse=True,key=lambda x:x[1])]


戦闘順番が決まったらその順番通りに攻撃開始です。
相手チームの中で狙う相手を決めるために
相手チームの生存者を探してインデックス値で返します。

ターン中の攻撃は攻撃者からターゲットへの
一方的なやりとりだけにします。

お互いのステータスからダメージ計算し
相手のHPから引き、HPが0になったら倒れます。

# 狙う相手を計算
def target(teams):
    index = [i for i ,c in enumerate(teams.team) if c.survive==1]
    return np.random.choice(index)

# ターン中の攻撃
def attack(c1,c2):
    damage = damage_calc(c1,c2)
    c2.hp = c2.hp - damage if c2.hp - damage>=0 else 0
    print('{0}の攻撃 : {1}に{2}のダメージ'.format(c1.name,c2.name,damage))
    print('{0}のHP : {1}'.format(c2.name,c2.hp))
    if c2.hp<=0:
        print('{0}は力尽きた!!!'.format(c2.name))
        return (c2,0)
    return (c2,9)

チームの全員の生存フラグが0になったら試合終了です。
全員の生存フラグをチェックします。

ターンが終わったら全員のステータスを出すように
ログ出力します。

# 生存チェック
def check_survived(teams):
    res = 0
    for c in teams.team:
        if c.survive==1:
            res+=1
    if res > 0:
        return True
    else:
        return False

# ログ出力
def print_log(t1,t2):
    print()
    result = ''
    for c in t1.team + t2.team:
        result += '{0} , HP : {1}\t'.format(c.name , c.hp )
    print(result)
    print()

ここから戦闘中のアルゴリズムです。
順番を決めて
攻撃対象を決めて
ダメージ計算して
HP0なら生存フラグを0にして
全員生存フラグ0なら勝負ありです。

# 戦闘
def battle(t1,t2):
    print('戦闘開始')
    print_log(t1,t2)
    while True:
        # ターン内の処理
        for m in turn_calc(t1,t2):
            tm , i , flg= m.split(':')[0] , int(m.split(':')[1]),9
            # 攻撃チームの設定
            attack_team = t1 if t1.name==tm else t2
            # ターゲットチームとターゲットの指定
            target_team = t2 if t1.name==tm else t1
            t_num = target(target_team) 
            # 戦闘結果
            target_team.team[t_num],flg = attack(attack_team.team[i],target_team.team[t_num])
            if flg==0:
                target_team.team[t_num].survive=0
            if not check_survived(t1):
                print('\n{0}チームの勝ち'.format(t2.name))
                break
            elif  not check_survived(t2):
                print('\n{0}チームの勝ち'.format(t1.name))
                break
        
        # ターン後
        if not check_survived(t1) or not check_survived(t2): 
            break
        print_log(t1,t2)
    print('戦闘終了')


さて、シミュレーションしてみましょう。
c1 = character('勇者1',100,80,40,66)
c2 = character('勇者2',100,80,40,66)
c3 = character('勇者3',100,80,40,66)
c4 = character('勇者4',200,100,50,66)

s1 = character('スライム1',180,80,30,66)
s2 = character('スライム2',180,80,30,66)
s3 = character('スライム3',180,80,30,66)
s4 = character('スライム4',380,70,40,56)

team1 = Team('勇者チーム',[c1,c2,c3,c4])
team2 = Team('スライムチーム',[s1,s2,s3])

battle(team1,team2)

結果は

戦闘開始

勇者1 , HP : 100 勇者2 , HP : 100 勇者3 , HP : 100 勇者4 , HP : 200
スライム1 , HP : 180 スライム2 , HP : 180 スライム3 , HP : 180

勇者1の攻撃 : スライム2に36のダメージ
スライム2のHP : 144
勇者2の攻撃 : スライム3に34のダメージ
スライム3のHP : 146
スライム3の攻撃 : 勇者3に29のダメージ
勇者3のHP : 71
スライム1の攻撃 : 勇者3に28のダメージ
勇者3のHP : 43
勇者4の攻撃 : スライム2に41のダメージ
スライム2のHP : 103
勇者3の攻撃 : スライム3に36のダメージ
スライム3のHP : 110
スライム2の攻撃 : 勇者4に30のダメージ
勇者4のHP : 170

勇者1 , HP : 100 勇者2 , HP : 100 勇者3 , HP : 43 勇者4 , HP : 170
スライム1 , HP : 180 スライム2 , HP : 103 スライム3 , HP : 110

スライム1の攻撃 : 勇者4に29のダメージ
勇者4のHP : 141
勇者4の攻撃 : スライム2に43のダメージ
スライム2のHP : 60
勇者1の攻撃 : スライム2に32のダメージ
スライム2のHP : 28
勇者2の攻撃 : スライム2に31のダメージ
スライム2のHP : 0
スライム2は力尽きた!!!
スライム3の攻撃 : 勇者4に27のダメージ
勇者4のHP : 114
勇者3の攻撃 : スライム3に30のダメージ
スライム3のHP : 80
スライム2の攻撃 : 勇者4に26のダメージ
勇者4のHP : 88

勇者1 , HP : 100 勇者2 , HP : 100 勇者3 , HP : 43 勇者4 , HP : 88
スライム1 , HP : 180 スライム2 , HP : 0 スライム3 , HP : 80

勇者3の攻撃 : スライム3に35のダメージ
スライム3のHP : 45
勇者1の攻撃 : スライム3に36のダメージ
スライム3のHP : 9
スライム3の攻撃 : 勇者2に31のダメージ
勇者2のHP : 69
スライム1の攻撃 : 勇者3に28のダメージ
勇者3のHP : 15
勇者2の攻撃 : スライム3に34のダメージ
スライム3のHP : 0
スライム3は力尽きた!!!
勇者4の攻撃 : スライム1に43のダメージ
スライム1のHP : 137

勇者1 , HP : 100 勇者2 , HP : 69 勇者3 , HP : 15 勇者4 , HP : 88
スライム1 , HP : 137 スライム2 , HP : 0 スライム3 , HP : 0

勇者1の攻撃 : スライム1に33のダメージ
スライム1のHP : 104
勇者4の攻撃 : スライム1に41のダメージ
スライム1のHP : 63
勇者2の攻撃 : スライム1に34のダメージ
スライム1のHP : 29
勇者3の攻撃 : スライム1に36のダメージ
スライム1のHP : 0
スライム1は力尽きた!!!

勇者チームチームの勝ち
戦闘終了

見事に勇者チーム勝利wwwwwwww

お互いに殴り合うだけの
単純なシミュレーションです。

ドラクエ2風になったんじゃないかと思います。

そう考えると
ドラクエ3以降は複雑怪奇ですねーー

すぐ作れる気がしないので
この先どうなることやら・・・

今回はここまでです。
それでは。

プログラムの文法は一通り学んだんだけど
どうやってプログラム書けばいいか分からないと言う方
朗報です。

ドラクエのシミュレーターを通じて
プログラムの作り方を学んでいきましょう。

解説動画はこちら







さてまず考えることは
小さなところからコツコツとです。

まずは1行から
慣れてきたら複数行
有る程度できるようになったら数千行の奴へ挑戦

と言うようにステップを踏んで
プログラムに慣れていきましょう。

最初はコピペからで十分です。
有る程度コードが打てるようになったら
次の段階へ進みましょう。

プログラムを考える時のコツですが

プログラムに基本は
1.入力 , 2.計算 , 3.出力 を考える
です。

1.入力させるデータ(構造) が何で
2.何をどう計算させるか(アルゴリズム)がどうなってて 
3.最終的に何を出すか(結果)

と言うことを考えます。

これは1行1行にも当てはまります。
全ての行で入力 , 計算 , 出力が行われているはずです。

一体何をどうしているのかを
把握しながらコードを書くのが
プログラムを書くコツになります。

ドラクエのシミュレーションを考えてみましょう。

想定されるシーンは
ドラクエ1

勇者 vs スライム
1対1の対決です。

ここではキャラクターとしては2体。
ステータスは共通のものを使うとします。

次に何をするかと言うと

お互いに攻撃しあってHPを減らしていき
HPが0になったら戦闘は終了しますので
それまで戦いを続けます。

最終的にはどちらかが勝つことになり
これが結果となります。

さて入力データを考えていきましょう。

2体のキャラクターですが
プログラムの入力データとしては
1つのクラスにまとめることができます。

次のようなクラスでまとめられます。

class character():

    def __init__(self,name,hp,attack,defence,agility):
        self.name = name
        self.hp = hp
        self.attack = attack
        self.defence = defence
        self.agility = agility

これを呼び出してみると
c1 = character('勇者',150,60,40,60)
c2 = character('スライム',100,50,30,56)

for k,v in c1.__dict__.items():
    print(k,v)
    
for k,v in c2.__dict__.items():
    print(k,v)
name 勇者
hp 150
attack 60
defence 40
agility 60
name スライム
hp 100
attack 50
defence 30
agility 56

こんな感じで勇者とスライム君ができました。

次はアルゴリズムを考えていきます。

アルゴリズム
1.ダメージ計算

攻撃力÷2-守備力÷4=ダメージ基礎値
ランダム値(ダメージ基礎値 ÷16+1)
ダメージ = ダメージ基礎値 プラスマイナス ランダム値

2.先頭順序
素早さ乱数の多い方が先に行動する
素早さ乱数 = 素早さ プラスマイナス10%のランダム値
例:400 = 360から440の間のランダム値

3.戦闘シーン
ダメージを算出してお互いのHPを削って
どちらかが0になったら終了


まずはダメージ計算です。
基礎値を算出してプラマイランダム値と言うのが
公式的な算出方法なようです。

シリーズによって多少の違いはありますが
基本的な考え方は一緒なのでこれを踏襲します。
import numpy as np

# ダメージ計算
def damage_calc(c1,c2):
    base = c1.attack //2 - c2.defence//4
    rand = base//16+1
    tmp = np.random.randint(0,rand*2+1)-rand
    damage = base + tmp
    return damage

次に先頭順序の計算です。

これを考えるには素早さ乱数がわからないとダメなので
先にこれを計算しておきます。
# 素早さ計算
def agility_calc(c):
    base = c.agility
    tmp = base//10
    result = np.random.randint(base-tmp,base+tmp+1)
    return result

# 先頭順序の計算
def turn_calc(c1,c2):
    a1 = agility_calc(c1)
    a2 = agility_calc(c2)
    if a1>=a2:
        return True
    else:
        return False

最後に先頭シーンを考えます。
お互いのHPを削って行って
HPが0になったら先頭終了です。

ターン中の計算と
先頭シーンの制御の部分で分けてみます。
# ターン中の計算
def turn(c1,c2):
    damage = damage_calc(c1,c2)
    c2.hp = c2.hp - damage if c2.hp - damage>=0 else 0
    print('{0}の攻撃 : {1}に{2}のダメージ'.format(c1.name,c2.name,damage))
    print('{0}のHP : {1}'.format(c2.name,c2.hp))
    if c2.hp<=0:
        print('{0}は力尽きた'.format(c2.name))
        return (c1,c2,0)
    damage = damage_calc(c2,c1)
    c1.hp = c1.hp - damage if c1.hp - damage>=0 else 0
    print('{0}の攻撃 : {1}に{2}のダメージ'.format(c2.name,c1.name,damage))
    print('{0}のHP : {1}'.format(c1.name,c1.hp))
    if c1.hp<=0:
        print('{0}は力尽きた'.format(c1.name))
        return (c1,c2,1)
    return (c1,c2,9)

# 戦闘
def battle(c1,c2):
    while True:
        # 勇者が先のターン
        if turn_calc(c1,c2):        
            c1,c2,flg = turn(c1,c2)
            if flg==0:
                print('終了 : {0}の勝ち'.format(c1.name))
                break
            elif flg==1:
                print('終了 : {0}の勝ち'.format(c2.name))
                break
        # モンスターが先のターン
        else:
            c2,c1,flg = turn(c2,c1)
            if flg==0:
                print('終了 : {0}の勝ち'.format(c2.name))
                break
            elif flg==1:
                print('終了 : {0}の勝ち'.format(c1.name))
                break


これで出来ました。

さてシミュレーションしてみましょう。
c1 = character('勇者',100,80,40,66)
c2 = character('スライム',180,80,30,66)

# 戦闘開始
battle(c1,c2)
勇者の攻撃 : スライムに33のダメージ
スライムのHP : 147
スライムの攻撃 : 勇者に31のダメージ
勇者のHP : 69
勇者の攻撃 : スライムに35のダメージ
スライムのHP : 112
スライムの攻撃 : 勇者に29のダメージ
勇者のHP : 40
スライムの攻撃 : 勇者に31のダメージ
勇者のHP : 9
勇者の攻撃 : スライムに34のダメージ
スライムのHP : 78
勇者の攻撃 : スライムに33のダメージ
スライムのHP : 45
スライムの攻撃 : 勇者に28のダメージ
勇者のHP : 0
勇者は力尽きた
終了 : スライムの勝ち

スライムが強すぎて勝てる気がしませんね。

アプリケーションは小さなプログラムの集合体です。
小さなプログラムは数行のコードだったりします。

何が入力で、何を計算させれば良いのかを
少しづつ考えていけば、次第に大きなものを
作れるようになってくと思います。

ドラクエのシミュレーションは
プログラムの初心者がプログラムの作り方を学ぶ
題材としてはうってつけだと思います。

まずは最低限の機能を実装していき
次第に大きくしていきます。

昔作った
ドラクエモンスターライトの
シミュレーターは複数 vs 複数のバトルなので
心折れそうでしたがアンドロイドアプリにすることができました。
(今は公開していませんが)

目標を決めるとそれに向かって努力するので
小さめの目標から初めて
達成を繰り返していくと言う過程を経ることで
大きな目標達成に繋げることができるのではないかと思います。

今回はここまでです。
それでは。





世の中偽物ばかりです。
A●z●●ですら、コピー商品やそっくり商品ばかりで
うんざりします。

普段目にしているものの殆どは偽物ばかりなんですよ。

しかし世の中には出会った事のない
ホンモノと言うものが存在します。

解説動画はこちら




さて仮定の話ですが
今回は人類史上まだ誰も解いたことのない難問です。

問題:
もし10万シナリオに1つの確率で時間停止系が本当だとしたら
1000本のシナリオを見たときにその中に
1つ本物が含まれる確率はいくらでしょうか?

ポアソン分布に従うと仮定してみます。
ポアソン分布とは不良品の検査などで用いるもので

例えば200個に1個の割合で不良品が発生するとしたとき
10個を取り出したら、1個不良品が混じってる確率は?

と言ったような問題に使えます。

このような確率はポアソン分布に従うとされていますので
確率の計算は
スクリーンショット 2020-05-30 16.37.56
と言う式で表すことができます。

ここでは2つの数がわかれば計算出来ます。

k=出会いたい個数
λ=確率=遭遇確率*個数

kは1シナリオでも会えれば良いので 1です。
λは10万シナリオに1本なので10万分の1 * 1000本です。


早速計算してみましょう。
# 出会いたい本数
k = 1

# 試してみる本数
n = 1000

# 遭遇確率
p = 1/100000

# 確率計算
lamb = p * n
answer = (np.e ** -lamb) * (lamb ** k) / math.factorial(k)

print(answer)

答えは
0.0099

約1%程ですね。

分かりづらいので描画するのと
色々数を変えて試してみましょう。

ポアソン分布を求めるには
scipy.statsのpoisson.pmf(出会いたい本数,確率)で
求められます。

from scipy.stats import poisson
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 出会いたい本数
x =  np.arange(0, 21, 1)
# 試してみる本数
n = 10000
# 遭遇確率
p = 1/100000

# 確率計算
lamb = p * n
y= [poisson.pmf(k, lamb) for k in x]

# 描画
plt.figure(figsize=(16,9))
plt.bar(x, y, align="center", width=0.4, color="red",alpha=0.5, label="Poisson λ= %d" % 10)
for x1,y1 in zip(x,y):
    plt.text(x1, y1+0.05 , '{:.03}'.format(y1) , size = 10, color = "green")
    plt.text(x1, y1+0.07 , '{:.01%}'.format(y1), size = 10, color = "black")
plt.xticks(np.arange(0, 21, 1))
plt.legend()
plt.show()


結果は横軸は出会える本数で縦軸はその確率です。

色々試行本数を変えて試してみると
1万本の時は1本のホンモノに出会えるには9%程ですね。

1man

10万本だと、出会えないのが36%
1本でもホンモノに出会えるのも36%ですね!!!

10man

100万本だと出会えないのが稀になってきますね!!


100man


あくまでも10万本に1本の確率で
本物が有ると仮定した時の話で有るので
出会える保証はありません。

もっと低いかもしれないし
実はもっと高い確率かもしれない。

もし
この世に存在しないと断定するのであれば
この世の全てのシナリオを確認しなければなりませんので

きっとこの世には
有るんです!!!!!!!!!!
そう信じたいんです。

そう
信じるか信じないかは貴方次第なんです。

今回はここまでです。

それでは。

ガチャなどの身近に有る確率の問題を解いてみました。


解説動画はこちら



初めに簡単な問題を

問1. 30人クラスで同じ誕生日の生徒がいる確率は?

考えたい方はここでスクロールを止めて
考えてみてください。








答え:

1組みでも誕生日が一致している確率
= 1 - 全員の誕生日が異なる確率

となるので

計算式は
スクリーンショット 2020-05-24 15.50.12


こんな感じになります。

計算めんどくさいのでPythonで解きます。
from functools import reduce
from operator import mul

n = 30
a1 = [i for i in range(365,365-n,-1)]
a2 = [365 for i in range(n)]
print('{:%}'.format(1- reduce(mul, a1)/reduce(mul, a2)))
70.631624%

出ました。

30人クラスだと7割くらいの確率で
1組みくらいは同じ誕生日の人が居そうですね。

50人までの確率を調べてみましょう。
from functools import reduce
from operator import mul
import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(16,5))
x,y = [n for n in range(2,51)] , []
for n in x:
    a1 = [i for i in range(365,365-n,-1)]
    a2 = [365 for i in range(n)]
    y.append(1-reduce(mul, a1)/reduce(mul, a2))

plt.bar(x,y)
plt.grid()
plt.show()
download

20人クラスでも40%ほど
50人クラスでは97%にもなります。

だいたい会社だと100人超えてくると思うので
1組みは同じ誕生日の人がいるんじゃないかと思います。

問2. 当たる確率1%のガチャ
100回連続で外れる確率は確率は?

ガチャの確率の問題です。

自分もよくガチャ引きますが
3倍祭りとか言ってるのに
全然当たらないですよねーーーー






答えは

100連続で外れる確率
1回引いて外れる確率の(100乗)

 と言うことで
計算は簡単です。

print('{:%}'.format((99/100)**100))
36.603234%

ガチャ確率1%だと
100回引いても3割以上は1回も当たらないんです!!

では
どれくらい引いたら当たるかを
シミュレーションしてみましょう。

当たる確率は 1-外れる確率 で計算できますね。

500回引いた際の当たる確率のシミュレーションです。
import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(16,5))
x,y = [n for n in range(1,501)],[]
for n in x:
    y.append(1-(99/100)**n)

plt.bar(x,y)
plt.grid()
plt.show()
download-1

100回引いた程度だと36%も外れるわけですから
もっと引かないと当たらないですよね。

500回ほど引けばほぼ当たりそうな感じはしますが
確実ではないと言うところがミソです。

本当に1%の確率で当たるのか??
確率はちゃんと1%なのか??

と言う疑問を解消するには
1万人ほどアンケートとってみるしか
ないんじゃないかと思います。

1%の確率で当たるガチャを100回引く
1万人ガチャシミュレーションの結果は・・・

import random

weights = [0.99,0.01]
d = {b:0 for b in range(101)}
for p in range(10000):
    a = 0
    for i in range(100):
        tf = random.choices([False,True], weights=weights)[0]
        if tf:
            a=i+1
            break
    d[a]+=1
import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(16,5))
x = list(d.keys())
y = list(d.values())

plt.bar(x,y)
plt.grid()
plt.show()
download-2

0は外れた人の数、それ以外は当たった回数で何人いるかです。

0のところは先ほどの確率で行くと36%=3600人ほど
外れているのが分かります。

1万人くらいのアンケートをとって
外れてる人が何人いるのかを調べれば
本当にガチャの確率が1%だったのかを
検証できるかもしれませんね。


今回はここまでです。
それでは。


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

解説動画はこちら




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

サイコロを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サイコロは売ってるみたいなので
これを使うと圧倒的に勝てますねーー

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



このページのトップヘ