乙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割くらいは大当たりするのではないか?

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

それでは

今回は最近流行りのポーカー
に関する確率のシミュレーションです

解説動画はこちら


 

ポーカーについて

ポーカーはトランプ5枚の手札の組み合わせで
役を作るゲームです。

1.ハイカード(強いカードの所持 2 < A)
2.ワンペア(同じ数字の組み合わせが1つ)
3.ツーペア(同じ数字の組み合わせが2つ)
4.スリーカード(同じ数字3つ)
5.ストレート(2,3,4,5,6 などの数字の並び)
6.フラッシュ(同じスートの組み合わせ HDCS)
7.フルハウス(スリーカードに加えて、同じ数字の組み合わせが1つ)
8.フォーカード(同じ数字4つ)
9.ストレートフラッシュ(ストレート + フラッシュ)
10.ロイヤルフラッシュ(AKQJTのフラッシュ)


テキサスホールデムルール


ポーカーのルールの一つで
プレイヤーそれぞれに配られた2枚のカードと
プレイヤー全員が共有する公開された
コミュニティカード"枚の計7枚で役を作り
チップをベットするなどの駆け引きを行うゲームルールです
(ベット周りの詳細なルールは割愛)


ここからはテキサスホールデムの
初期手札による勝率がどうなるのかを
検証するシミュレーションプログラムについてです。




役を計算するプログラム

シミュレーションを行うには
ポーカーの役の判定を行うプログラムが必要ですね

import random
from collections import Counter
from itertools import combinations

# ポーカーの役
POKER_HANDS = {
    0: "ハイカード",
    1: "ワンペア",
    2: "ツーペア",
    3: "スリーカード",
    4: "ストレート",
    5: "フラッシュ",
    6: "フルハウス",
    7: "フォーカード",
    8: "ストレートフラッシュ",
    9: "ロイヤルストレートフラッシュ"
}

# カードのランクとスート(HA=ハートのエース, D2=ダイヤの2)
RANKS = "23456789TJQKA"
SUITS = "HDSC"  # ハート, ダイヤ, スペード, クラブ
DECK = [s + r for r in RANKS for s in SUITS]

# 役の評価(スコアをタプルで返す)
def evaluate_hand(hand):
    ranks = sorted([RANKS.index(c[1]) for c in hand], reverse=True)
    suits = [c[0] for c in hand]
    rank_counts = Counter(ranks)
    flush = len(set(suits)) == 1
    straight = len(rank_counts) == 5 and (max(ranks) - min(ranks) == 4 or set(ranks) == {12, 3, 2, 1, 0})  # A-2-3-4-5対応

    # 役の判定(同率はRankで比較)
    if straight and flush:
        if set(ranks) == {12, 11, 10, 9, 8}:  # A, K, Q, J, 10
            return (9, max(ranks))  # ロイヤルストレートフラッシュ
        return (8, max(ranks)) # ストレートフラッシュ
    if 4 in rank_counts.values():
        return (7, max(k for k, v in rank_counts.items() if v == 4))  # フォーカード
    if 3 in rank_counts.values() and 2 in rank_counts.values():
        return (6, max(k for k, v in rank_counts.items() if v == 3))  # フルハウス
    if flush:
        return (5, ranks)  # フラッシュ
    if straight:
        return (4, max(ranks))  # ストレート
    if 3 in rank_counts.values():
        return (3, max(k for k, v in rank_counts.items() if v == 3))  # スリーカード
    if list(rank_counts.values()).count(2) == 2:
        return (2, sorted([k for k, v in rank_counts.items() if v == 2], reverse=True))  # ツーペア
    if 2 in rank_counts.values():
        return (1, max(k for k, v in rank_counts.items() if v == 2))  # ワンペア
    return (0, ranks)  # ハイカード

これを用いてシミュレーションを行なっていきます。



テキサスホールデムのシミュレーション

初期手札2枚と公開札5枚で、役を作り、勝率がどうなるか
4人で対戦をする際の勝率を出す
シミュレーションプログラムです。

user_num のところが対戦人数になるので
変更すれば、その人数での確率を求めることができます。
# モンテカルロ法で勝率計算
def monte_carlo_win_rate(my_hand, num_simulations=10000):
    wins = 0
    user_num = 3
    for _ in range(num_simulations):
        deck = DECK.copy()
        for card in my_hand:
            deck.remove(card)

        # 4人分の手札をランダムに配る
        random.shuffle(deck)
        opponent_hands = [deck[i * 2: (i + 1) * 2] for i in range(user_num)]
        community_cards = deck[6:11]

        # 各プレイヤーのベストハンドを評価
        my_best = max(evaluate_hand(list(comb)) for comb in combinations(my_hand + community_cards, 5))
        opponent_best = [max(evaluate_hand(list(comb)) for comb in combinations(hand + community_cards, 5)) for hand in opponent_hands]

        # 自分が最も強い手を持っているか判定
        if my_best > max(opponent_best):
            wins += 1

    return wins / num_simulations

# 例: 自分の手札をセットして勝率を計算
my_hand = ["HA", "HK"]  # ハートのエース・キング
win_rate = monte_carlo_win_rate(my_hand, num_simulations=5000)
print(f"勝率: {win_rate:.2%}")
勝率: 33.02%

カードの組み合わせを変えれば
その都度計算が行えます。



初期カードの組み合わせでの勝率

2種の13 * 13 枚の組み合わせにおける
勝率を計算してみましょう。

RANKS = "23456789TJQKA"
H_DECK = [s + r for r in RANKS for s in "H"]
D_DECK = [s + r for r in RANKS for s in "D"]
combi = [[h, d] for h in H_DECK for d in D_DECK]
result = {":".join(c):0 for c in combi}
for my_hand in combi:
  result[":".join(my_hand)] = monte_carlo_win_rate(my_hand, num_simulations=5000)

これで、13x13=169通りの結果が出せます。

これをヒートマップにしてみましょう。


勝率をヒートマップにする


import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# キーのソート用の関数
def get_rank_order(card):
    return RANKS.index(card[1])

# キーを分離してデータフレームを作成
data_list = []
for key, value in result.items():
    row_key, col_key = key.split(':')
    data_list.append({'row': row_key, 'col': col_key, 'value': value})

df = pd.DataFrame(data_list)
unique_rows = sorted(df['row'].unique(), key=get_rank_order)
unique_cols = sorted(df['col'].unique(), key=get_rank_order)

# ピボットテーブルを作成
pivot_df = df.pivot(index='row', columns='col', values='value')

# インデックスと列を順序通りに並び替え
pivot_df = pivot_df.reindex(index=unique_rows, columns=unique_cols)

# ヒートマップの作成
plt.figure(figsize=(10, 8))
sns.heatmap(pivot_df,
            cmap='RdYlGn',
            vmin=0,
            vmax=1,
            annot=True,
            fmt='.2f',
            cbar_kws={'label': 'Value'})

plt.title('Win rate for first hand combination')
plt.tight_layout()
plt.show()
heatmap

やはり、最初にワンペアを持っているだけで勝率は高くなりますね
とはいえ、それだけでは勝てないのが
このテキサスホールデムルールの面白いところ

初期手札の読み合いやベットなどの駆け引き
この辺りが組み合わさることで
ゲーム性が高くかなり面白いものになっています。

とはいえ
日本ではまだまだ、カジノがないので
ポーカーを楽しむには
単純なゲームとしてのポーカーしか出来ません

オンラインで展開されているものは
ほぼ日本国内では違法ではあるので
手を出さないように気を付けないといけません!!!

カジノが出来たら
もっともっとシミュレーションしましょう。

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

今回は水曜日のダウンタウン企画
電気椅子ゲームのシミュレーションです。


解説動画はこちら

 

電気椅子ゲームとは

水曜日のダウンタウンのネタ
結構色々やっていますが
今回は電気椅子ゲームの
シミュレーションです。

ゲームのルールはこうです。


1~12などの番号が書かれたイス
プレイヤー二人は交互に以下を行う
相手が座るイスを予想し、電流を仕掛ける
電気イスでなければ得点獲得し、イスは撤去される
電流イスに座ると得点没収され、イスはそのまま

最終的にポイントが高い方が勝利
3回電流を喰らうか、40点先取されたら負け

このルールでシミュレーション用の
コードを作ってみました。
import random

headers = ["ターン数", "選択プレイヤー", "選択数字", "電気プレイヤー", "仕掛数字", "結果", "P1得点", "P2得点", "P1電気回数", "P2電気回数", "残り椅子"]

class ElectricChairGame:
    def __init__(self):
        self.chairs = {i: i for i in range(1, 13)}
        self.player_scores = [0, 0]
        self.electric_fails = [0, 0]
        self.current_player = 0
        self.electrified_chair = None

    def choose_chair(self, chair_number):
        # 選択した数字と電気椅子の数字が一致する場合のみ電気椅子確定
        if chair_number == self.electrified_chair:
            self.electric_fails[self.current_player] += 1
            self.player_scores[self.current_player] = 0
            return "電気椅子"
        else:
            points = self.chairs[chair_number]
            self.player_scores[self.current_player] += points
            del self.chairs[chair_number]
            return "得点獲得"

    def set_electric(self, chair_number):
        self.electrified_chair = chair_number
        return True

    def check_game_end(self):
        message = ["", ""]
        for player in range(2):
            if self.electric_fails[player] >= 3:
                winner = 2 if player == 0 else 1
                message = [winner, "電気椅子3回"]
                return True, message
            if self.player_scores[player] >= 40:
                winner = player + 1
                message = [winner, "スコア40"]
                return True, message

        if len(self.chairs) == 1:
            winner = 1 if self.player_scores[0] > self.player_scores[1] else 2
            message = [winner, "スコア差"]
            return True, message

        return False, message

def simulate_game(echo=False):
    game = ElectricChairGame()
    turn,selecting_player = 1,1
    results = []
    while True:
        # 電気椅子設置
        electric_player = 2 if selecting_player == 1 else 1
        electric_choice = random.choice(list(game.chairs.keys()))
        game.set_electric(electric_choice)

        # 椅子選択
        chair_choice = random.choice(list(game.chairs.keys()))
        game.current_player = selecting_player - 1
        result = game.choose_chair(chair_choice)

        # ログ出力
        log_line = [
            turn,
            selecting_player,
            chair_choice,
            electric_player,
            electric_choice,
            result,
            game.player_scores[0],
            game.player_scores[1],
            game.electric_fails[0],
            game.electric_fails[1],
            sorted(list(game.chairs.keys()))
        ]
        results.append(log_line)
        # ゲーム終了判定
        game_end, message = game.check_game_end()
        if game_end:
          if message[0]==1:
              win_score = game.player_scores[0]
          else:
              win_score = game.player_scores[1]
          message+=[turn ,
            win_score,
            game.player_scores[0],
            game.player_scores[1],
            game.electric_fails[0],
            game.electric_fails[1],
            len(list(game.chairs.keys()))]
          if echo:
            print(", ".join(headers))
            for log_line in results:
              print(log_line)
            print(f"ゲーム終了: 勝利プレイヤー : {message[0]}, 勝因 : {message[1]}")
          return message, results

        # プレイヤー交代
        selecting_player = 2 if selecting_player == 1 else 1
        turn += 1

ゲームを実行する場合はこうです
message, log_line = simulate_game(True)
ターン数, 選択プレイヤー, 選択数字, 電気プレイヤー,
仕掛数字, 結果, P1得点, P2得点, P1電気回数, P2電気回数, 残り椅子
[1, 1, 2, 2, 3, '得点獲得', 2, 0, 0, 0, [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]
[2, 2, 11, 1, 5, '得点獲得', 2, 11, 0, 0, [1, 3, 4, 5, 6, 7, 8, 9, 10, 12]]
[3, 1, 6, 2, 7, '得点獲得', 8, 11, 0, 0, [1, 3, 4, 5, 7, 8, 9, 10, 12]]
[4, 2, 1, 1, 8, '得点獲得', 8, 12, 0, 0, [3, 4, 5, 7, 8, 9, 10, 12]]
[5, 1, 3, 2, 10, '得点獲得', 11, 12, 0, 0, [4, 5, 7, 8, 9, 10, 12]]
[6, 2, 12, 1, 7, '得点獲得', 11, 24, 0, 0, [4, 5, 7, 8, 9, 10]]
[7, 1, 8, 2, 4, '得点獲得', 19, 24, 0, 0, [4, 5, 7, 9, 10]]
[8, 2, 9, 1, 7, '得点獲得', 19, 33, 0, 0, [4, 5, 7, 10]]
[9, 1, 10, 2, 7, '得点獲得', 29, 33, 0, 0, [4, 5, 7]]
[10, 2, 7, 1, 5, '得点獲得', 29, 40, 0, 0, [4, 5]]
ゲーム終了: 勝利プレイヤー : 2, 勝因 : スコア40





これで1万回やる場合は
こんな感じのコードで
データフレームにできます。
game_result, game_los = [], []
for i in range(10000):
  message, log_line = simulate_game()
  game_result.append([i+1] + message)
  for log in log_line:
    game_los.append([i+1] + log)

import pandas as pd
cols = ["ゲーム数", "勝利プレイヤー", "勝因","ターン数","獲得点数","P1得点", "P2得点", "P1電気回数", "P2電気回数", "残椅子個数"]
game_result_df = pd.DataFrame(game_result, columns=cols)
game_log_df = pd.DataFrame(game_los, columns=["ゲーム数"] + headers)

game_result_df.head()


シミュレーションの結果は
ぜひ動画を見てみてくださいませ


今回は電気椅子ゲームのシミュレーションコードについてでした
それでは!!

今回は、犬の名前ランキングの
TOP10の名前の犬に
出会えるまで帰れないロケの
検証シミュレーションです。


解説動画はこちら


 
動画作ってた時は思い出せなかったですが
「スクール革命」という番組で
芸人の「ちゃんぴょんず」が
このロケをしてました。

犬の名前ランキング
トップ10の犬に全部出会うというロケです。

街を歩く犬を散歩している人に声をかけて
何匹出会ったら、全部出会えるのか
プログラムで検証してみましょう。


犬の名前ランキング

まずは犬の名前ランキングです

毎年アンケート調査されている様で
そこのデータを引用します。

import pandas as pd
import numpy as np

df = pd.read_html("https://www.anicom-sompo.co.jp/news-release/2023/20231019/")[0]
df

順位 名前 頭数
0 1位(1) ムギ 1264
1 2位(2) ココ 1091
2 3位(4) ソラ 812
3 4位(3) モカ 786
4 5位(5) マロン 732
5 6位(6) レオ 714
6 7位(8) モコ 676
7 8位(13) ラテ 642
8 9位(7) モモ 641
9 10位(12) チョコ 631


犬の名前ランキングは
こんな感じになっている様です。


シミュレーション

犬は全体で何頭いるか不明なので
TOP10が全体の20%だと仮定します。

まずはこのTOP10をデータ化します。
top_data = []
for i, row in df[["名前","頭数"]].iterrows():
    name = row["名前"]
    num = row["頭数"]
    top_data += [name] * num

print(len(top_data))
7989

残りの80%はハズレですね。
top_num = df["頭数"].sum()
h_num = top_num * 4
h_data = ["ハズレ"] * h_num
print(len(h_data))
31956

data = top_data + h_data
print(len(data))
39945


全体で4万頭ほどのデータになります。

ランダムでシャッフルしてあげると
こんな感じのデータになります。
tmp = data.copy()
np.random.shuffle(tmp)
print(tmp[0:10])
['ハズレ', 'ハズレ', 'ムギ', 'モモ', 'ハズレ', 'ハズレ', 'ムギ', 'ハズレ', 'ハズレ', 'ハズレ']


あとはTOP10の名前に
全部出会えるまで会うだけです。

シミュレーションするプログラムは
こんな感じです。
res = {n:0 for n in df["名前"].values}
count = 0
for name in tmp:
    count+=1
    if "ハズレ"!=name:
        res[name]+=1
        # TOP10全部会ってるか判定
        if all([t>0 for t in res.values()]):
            break
print(count)
210

あとは1回の試行では結果がばらつくので
1万回ほど試行してあげれば
どれくらいの回数で見つかるのかが
分かるはずです。

# 回数チェック関数
def check_count(tmp):
    res = {n:0 for n in df["名前"].values}
    count = 0
    for name in tmp:
        count+=1
        if "ハズレ"!=name:
            res[name]+=1
            # TOP10全部会ってるか判定
            if all([t>0 for t in res.values()]):
                break
    return count

calc_data = []
for i in range(10000):
    tmp = data.copy()
    np.random.shuffle(tmp)
    c = check_count(tmp)
    calc_data.append(c)

calc_df = pd.DataFrame(calc_data,columns=["count"])
calc_df.hist(bins=100)
output_23_1


結果はこんな感じになりました。

大体平均160回くらいで
出会いきれる事になりそうです。

運が良いと数十頭
運が悪いと数百頭

そんな結果でした。

このように、テレビのロケであっても
おおよそのシミュレーションが出来るので

事前にシミュレーションしておくと
諸々の調整がスムーズに行く気がします。

プログラミングでの効率化が
オススメです!!!!

今回はテレビ番組の
ロケをシミュレーションしてみました。

それでは。

今回は神経衰弱を一人で行う
シミュレーションプログラムを考えてみました


解説動画はこちら



神経衰弱

今回は神経衰弱です

トランプで行うゲームのひとつで
カードを伏せた状態で混ぜ
プレイヤーは好きな2枚を
その場で表に向けます

2枚が同じ数字であれば
それらを得ることができ
もう一度プレイできます

2枚が異なる数の場合
カードを元通りに伏せて
次のプレイヤーの順番に

すべてのカードが取られるまで行い
取ったカードの枚数が多い
プレイヤーの勝ちになります


今回は友達がいないので
一人で神経衰弱をしてみる事を考えてみます

何ターンあれば全部空けられるのか?
仮想のカードを作って
シミュレーションしてみましょう


考え方

最初はデッキを用意して
52枚をシャッフルします
# 52枚のカードのデッキ
deck = [i for i in range(1,14)] * 4

import numpy as np

# シャッフルする
np.random.shuffle(deck)
print(deck)
[1, 11, 7, 6, 11, ・・・]

こんな感じで52枚分のデータを用意します


ターン時の行動を考えてみましょう

こんな感じでしょうか?

カードを1枚開ける
  知ってる数字があればそれを開ける
カードをもう1枚開ける
  同じ数字なら取る 次の行動へ
  違う数字ならターンプラス1
2枚ペアが判明していたらそれを開ける

ここら辺の行動を関数にまとめて
ゲームできる様にしてみました


ゲーム関数

import numpy as np
import collections

# under : カードを覚えておくための変数

# 2個以上分かったカードがあるかどうか
def check_2(under):
    c = collections.Counter(under.values())    
    for i in range(1,14):
        if c[i]>=2:
            return i
    return 0

# 2個のペアのインデックスを返す
def get_2pair(under , num):
    pair = [k for  k , v in under.items() if v == num]
    return pair

# ペアカードを取る
def get_card(under , pair):
    for i in pair:
        under[i] = 99
    return under

# 開けていないカードを1枚選択する
def open_1card(under):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==0]
    return np.random.choice(cand)

# 数字が判明しているかをチェック
def under_check(under,num,select):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==num and k!=select]
    if len(cand)>0:
        return True
    else:
        return False
    
# 数字が判明しているものを選択する
def open_under_card(under,num,select):
    # 候補 0:未開、1-13:開封済、99:取得済
    cand = [k for k,v in under.items() if v==num and k!=select]
    return np.random.choice(cand)

# ログのプリント
def logs(turn , gets , sheets , num):
    text = 'ターン : {0:02} , 取得済み : {1:02} , {2}枚目 , 数字:{3:02}'.format(turn , gets , sheets , num)
    print(text)
# 神経衰弱ゲームをシミュレーションする
def game():
    
    # デッキを作ってシャッフルする
    deck = [i for i in range(1,14)] * 4
    np.random.shuffle(deck)
    
    # 回数と開けたカードの記憶
    turn , gets , under = 1, 0 , {i:0 for i in range(52)}
    
    # ゲーム開始
    for j in range(100):
        # 上がりの判定
        if all([i==99 for i in under.values()]):
            print('上がり , turn : {0:02}'.format(turn))
            break
        
        #2枚開いたのがあればそれを消す
        pair_num = check_2(under)
        if pair_num > 0:
            print('判明済み有り : {0:2}'.format(pair_num))
            # ペアを開きに変える
            pair = get_2pair(under , pair_num)
            under = get_card(under , pair)
            gets +=1
            print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(pair[0] , pair[1] , pair_num))

        else:
            #1枚めくる
            select_one = open_1card(under)
            # 開けたカードの数字
            num_one = deck[select_one]
            # 数字を覚えておく
            under[select_one] = num_one
            logs(turn , gets , 1 , num_one)
            
            #数字が分かっているものがあればそれを取る
            if under_check(under,num_one,select_one):
                target = open_under_card(under , num_one , select_one)
                under[select_one] , under[target] = 99 , 99
                print('判明済み有り : {0:2}'.format(num_one))
                logs(turn , gets , 2 , num_one)
                gets +=1
                print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(select_one , target , num_one))

            else:
                #分かってない場合はもう一枚めくる
                select_2nd = open_1card(under)
                num_2nd = deck[select_2nd]
                # 数字を覚えておく
                under[select_2nd] = num_2nd               
                logs(turn , gets , 2 , num_2nd)
   
                #同じ数字ならカードを取る
                if num_one==num_2nd:
                    under[select_one] , under[select_2nd] = 99 , 99
                    gets +=1
                    print('get card : {0:02},{1:02} , 数字 : {2:02}'.format(select_one,select_2nd,num_one))
                else:
                    #違う数字ならターンプラス1
                    turn +=1
                    print('ターン終了')
                    
    return turn
    
game()

ターン : 01 , 取得済み : 00 , 1枚目 , 数字:04
ターン : 01 , 取得済み : 00 , 2枚目 , 数字:02
ターン終了
ターン : 02 , 取得済み : 00 , 1枚目 , 数字:07
ターン : 02 , 取得済み : 00 , 2枚目 , 数字:05
ターン終了
ターン : 03 , 取得済み : 00 , 1枚目 , 数字:09
ターン : 03 , 取得済み : 00 , 2枚目 , 数字:02
ターン終了
判明済み有り :  2
get card : 08,51 , 数字 : 02
ターン : 04 , 取得済み : 01 , 1枚目 , 数字:10
ターン : 04 , 取得済み : 01 , 2枚目 , 数字:09
ターン終了
判明済み有り :  9
get card : 29,34 , 数字 : 09
ターン : 05 , 取得済み : 02 , 1枚目 , 数字:07
判明済み有り :  7
ターン : 05 , 取得済み : 02 , 2枚目 , 数字:07
get card : 06,22 , 数字 : 07
ターン : 05 , 取得済み : 03 , 1枚目 , 数字:13
ターン : 05 , 取得済み : 03 , 2枚目 , 数字:03
ターン終了
ターン : 06 , 取得済み : 03 , 1枚目 , 数字:08
ターン : 06 , 取得済み : 03 , 2枚目 , 数字:08
get card : 37,31 , 数字 : 08
ターン : 06 , 取得済み : 04 , 1枚目 , 数字:01
ターン : 06 , 取得済み : 04 , 2枚目 , 数字:08
ターン終了
ターン : 07 , 取得済み : 04 , 1枚目 , 数字:06
ターン : 07 , 取得済み : 04 , 2枚目 , 数字:07
ターン終了
ターン : 08 , 取得済み : 04 , 1枚目 , 数字:12
ターン : 08 , 取得済み : 04 , 2枚目 , 数字:05
ターン終了
判明済み有り :  5
get card : 09,48 , 数字 : 05
ターン : 09 , 取得済み : 05 , 1枚目 , 数字:12
判明済み有り : 12
ターン : 09 , 取得済み : 05 , 2枚目 , 数字:12
get card : 28,38 , 数字 : 12
ターン : 09 , 取得済み : 06 , 1枚目 , 数字:08
判明済み有り :  8
ターン : 09 , 取得済み : 06 , 2枚目 , 数字:08
get card : 19,46 , 数字 : 08
ターン : 09 , 取得済み : 07 , 1枚目 , 数字:03
判明済み有り :  3
ターン : 09 , 取得済み : 07 , 2枚目 , 数字:03
get card : 03,26 , 数字 : 03
ターン : 09 , 取得済み : 08 , 1枚目 , 数字:04
判明済み有り :  4
ターン : 09 , 取得済み : 08 , 2枚目 , 数字:04
get card : 30,21 , 数字 : 04
ターン : 09 , 取得済み : 09 , 1枚目 , 数字:13
判明済み有り : 13
ターン : 09 , 取得済み : 09 , 2枚目 , 数字:13
get card : 13,25 , 数字 : 13
ターン : 09 , 取得済み : 10 , 1枚目 , 数字:05
ターン : 09 , 取得済み : 10 , 2枚目 , 数字:05
get card : 17,18 , 数字 : 05
ターン : 09 , 取得済み : 11 , 1枚目 , 数字:01
判明済み有り :  1
ターン : 09 , 取得済み : 11 , 2枚目 , 数字:01
get card : 01,23 , 数字 : 01
ターン : 09 , 取得済み : 12 , 1枚目 , 数字:11
ターン : 09 , 取得済み : 12 , 2枚目 , 数字:04
ターン終了
ターン : 10 , 取得済み : 12 , 1枚目 , 数字:01
ターン : 10 , 取得済み : 12 , 2枚目 , 数字:13
ターン終了
ターン : 11 , 取得済み : 12 , 1枚目 , 数字:09
ターン : 11 , 取得済み : 12 , 2枚目 , 数字:11
ターン終了
判明済み有り : 11
get card : 11,15 , 数字 : 11
ターン : 12 , 取得済み : 13 , 1枚目 , 数字:03
ターン : 12 , 取得済み : 13 , 2枚目 , 数字:01
ターン終了
判明済み有り :  1
get card : 35,45 , 数字 : 01
ターン : 13 , 取得済み : 14 , 1枚目 , 数字:02
ターン : 13 , 取得済み : 14 , 2枚目 , 数字:10
ターン終了
判明済み有り : 10
get card : 20,24 , 数字 : 10
ターン : 14 , 取得済み : 15 , 1枚目 , 数字:06
判明済み有り :  6
ターン : 14 , 取得済み : 15 , 2枚目 , 数字:06
get card : 10,49 , 数字 : 06
ターン : 14 , 取得済み : 16 , 1枚目 , 数字:12
ターン : 14 , 取得済み : 16 , 2枚目 , 数字:11
ターン終了
ターン : 15 , 取得済み : 16 , 1枚目 , 数字:10
ターン : 15 , 取得済み : 16 , 2枚目 , 数字:03
ターン終了
判明済み有り :  3
get card : 12,32 , 数字 : 03
ターン : 16 , 取得済み : 17 , 1枚目 , 数字:04
判明済み有り :  4
ターン : 16 , 取得済み : 17 , 2枚目 , 数字:04
get card : 39,14 , 数字 : 04
ターン : 16 , 取得済み : 18 , 1枚目 , 数字:12
判明済み有り : 12
ターン : 16 , 取得済み : 18 , 2枚目 , 数字:12
get card : 40,50 , 数字 : 12
ターン : 16 , 取得済み : 19 , 1枚目 , 数字:11
判明済み有り : 11
ターン : 16 , 取得済み : 19 , 2枚目 , 数字:11
get card : 41,47 , 数字 : 11
ターン : 16 , 取得済み : 20 , 1枚目 , 数字:07
判明済み有り :  7
ターン : 16 , 取得済み : 20 , 2枚目 , 数字:07
get card : 43,05 , 数字 : 07
ターン : 16 , 取得済み : 21 , 1枚目 , 数字:06
ターン : 16 , 取得済み : 21 , 2枚目 , 数字:10
ターン終了
判明済み有り : 10
get card : 42,44 , 数字 : 10
ターン : 17 , 取得済み : 22 , 1枚目 , 数字:09
判明済み有り :  9
ターン : 17 , 取得済み : 22 , 2枚目 , 数字:09
get card : 27,16 , 数字 : 09
ターン : 17 , 取得済み : 23 , 1枚目 , 数字:13
判明済み有り : 13
ターン : 17 , 取得済み : 23 , 2枚目 , 数字:13
get card : 02,07 , 数字 : 13
ターン : 17 , 取得済み : 24 , 1枚目 , 数字:02
判明済み有り :  2
ターン : 17 , 取得済み : 24 , 2枚目 , 数字:02
get card : 00,33 , 数字 : 02
ターン : 17 , 取得済み : 25 , 1枚目 , 数字:06
判明済み有り :  6
ターン : 17 , 取得済み : 25 , 2枚目 , 数字:06
get card : 36,04 , 数字 : 06
上がり , turn : 17

こんな感じで26ペアを取れた時点で
ゲームは終了です

多分合ってるとは
思うんですけどねー

大したデバッグはしていないので
間違いがあったらごめんなさいね



1000回でシミュレーション

最後にこれを1000回繰り返して
何手で上がれたかのシミュレーションです

ログを抜いたgame2関数を用意して
1000回実行した際の
結果をまとめてみました

calc = {}
for i in range(1000):
    key = game2()
    if key in calc:
        calc[key]+=1
    else:
        calc[key] =1
for k,v in sorted(calc.items()):
    print('ターン数 : {0:02} , {1:03}回'.format(k,v))
ターン数 : 11 , 001回
ターン数 : 13 , 004回
ターン数 : 14 , 041回
ターン数 : 15 , 149回
ターン数 : 16 , 266回
ターン数 : 17 , 295回
ターン数 : 18 , 168回
ターン数 : 19 , 059回
ターン数 : 20 , 016回
ターン数 : 21 , 001回

これでみると
大体17回もあれば
上がれるみたいですねー

ほぼほぼ20回以内に
上がれているので
記憶力があれば
20ターンもかからないようです

今回は神経衰弱を
シミュレーションしてみました

それでは



このページのトップヘ