今回は年末の飲み会の余興で
よく用いられる「ビンゴゲーム」
にまつわるものを検証してみました。

解説動画はこちら



ビンゴゲームとは

知らない方は少ないと思いますが
飲み会の余興などで行われる
安易なパーティーゲームの一種です。

ルールや遊び方は
5行x5列のマス目に
1 - 75までの数字が書かれた紙が配られ

ゲームマスターが数字を選び
手持ちの紙の数値が合えば穴を開ける
縦・横・斜めで5マス分の穴が空いたらクリア
というものです。

真ん中のマス目はFreeポケットになっていて
最初から開けられる物も有ります。

そんなビンゴゲームのアルアルを
色々考えてみました。


1. ビンゴ用紙を作ってみる

プログラム上で75個の数値から
25個を選んで、並べてあげれば
ビンゴ用紙(風の出力)の完成です。

import random

numbers = [i for i in range(1,76)]

# ランダムで数値を25個取得する
select = random.sample(numbers,25)
print(select)
print()

for i,n in enumerate(select):
    if i%5==0:
        print()
    print(n,end="\t")
[23, 24, 4, 64, 71, 29, 19, 6, 58, 70, 33, 68,
13, 25, 50, 12, 63, 11, 73, 22, 20, 36, 32, 51, 2]

23 24 4 64 71
29 19 6 58 70
33 68 13 25 50
12 63 11 73 22
20 36 32 51 2

import numpy as np
import pandas as pd

df = pd.DataFrame(np.reshape(select,[5, 5]))
print(df.to_string(index=False,header=False,col_space=8))
pandasで綺麗に描画するとこんなコードです。


もっとたくさん作りたい人向けには
画像化するコードをお送りします。
import random
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt

font = ImageFont.truetype('Arial.ttf', 32)
numbers = [i for i in range(1,76)]

# 作る個数を指定する
n = 2
for i in range(n):
    select = random.sample(numbers,25)
    im = Image.new('RGB', (500, 500), "silver")
    draw = ImageDraw.Draw(im)
    count = 0
    for x in range(5):
      for y in range(5):
        x0,x1,y0,y1 = x*100, x*100+100, y*100, y*100+100
        draw.rectangle((x0, y0, x1, y1), fill="white", outline="black", width=4)
        if count!=12:
            draw.text((x0+32, y0+32), "{0:02}".format(select[count]), (0, 0, 0), font=font, align='center')
        else:
            draw.text((x0+32, y0+32), "●", (0, 0, 0), font=font, align='center')
        count+=1
    plt.imshow(im)
    plt.axis("off")
    plt.show()
bingooo

こんな感じで真ん中フリーポケットの
ランダム数字がかかれた用紙が出来るので
あとはそれをプリントすれば良いですね。

お金をかけたく無い人には
丁度良いかもしれません。



2.ビンゴゲームにおける色々な数字

2-1.25マスの数値の並びの組み合わせ

75 個の数字から
25 個取り出して並べる順列の総数は
こんな感じの数式で表せます。

s
プログラムでは組み合わせや
順列の数を求める事ができます。

import math

n = 75
k = 25

# p = n! / (n - k)!
p = math.perm(n, k)

print(p)
print("{:,}".format(p))
815712000579973729423615859451974909952000000
815,712,000,579,973,729,423,615,859,451,974,909,952,000,000


とてつも無い桁になりますね
ビンゴカードを全通りプリントするのは
辞めた方が良さそうです。


2-2.何回くらいでビンゴになるのか


順列数は多すぎるので全通りは実験出来ません。

代わりにビンゴシミュレーターで
100万回試行してみました。
# ビンゴの場合True , ビンゴでない場合はFalse
def check_bingo(b):
    # 横
    for i in range(5):
        if sum(b[i*5:i*5+5])==0:
            return True
    # 縦
    for i in range(5):
        b1,b2,b3,b4,b5 = b[i],b[i+5],b[i+10],b[i+15],b[i+20]
        if sum([b1,b2,b3,b4,b5])==0:
            return True
    # 斜め
    d1 = [b[d*6] for d in range(5)]
    d2 = [b[d*4] for d in range(1,6)]
    if sum(d1)==0 or sum(d2)==0:
        return True
    return False

# リストの中に数値がある場合はインデックスを、ない場合は-1を返す
def find_index(b,x):
    return b.index(x) if x in b else -1

# 何ターンでビンゴしたかをカウントする
def bingo_count(bingo,numbers):
    count=0
    for num in numbers:
        index = find_index(bingo,num)
        if index!=-1:
            bingo[index] = 0
        count+=1
        if check_bingo(bingo):
            return count
        else:
            continue
    return count

import random
numbers = [i for i in range(1,76)]
random.shuffle(numbers)

n = 1000000
calc = {k:0 for k in range(1,76)}
for i in range(n):
    # ランダムで数値を25個取得してビンゴ用紙を作る
    bingo = random.sample(numbers,25)
    # 真ん中は開ける(数値0は穴開き)
    bingo[12] = 0
    ans = bingo_count(bingo,numbers)
    calc[ans]+=1

# 結果
print("総回数 : {0}回".format(n))
tmp = []
for k,v in calc.items():
    r = v / n * 100
    print("{0:02} 回目, : {1:06}回 , {2:.05}%".format(k,v,r))
    tmp.append([k,v,r])

実行するのにすごく時間が掛かります。
これを描画してみましょう。
# 描画
import matplotlib.pyplot as plt
import pandas as pd

df_b = pd.DataFrame(tmp,columns=["num","count","rate"])
df_b["cumsum"] = df_b["rate"].cumsum()

fig, ax1 = plt.subplots(figsize=(16,8))
ax2 = ax1.twinx()
ax1.bar(df_b["num"],df_b["rate"])
ax2.plot(df_b["cumsum"],color="red")
plt.show()
kakuritu


だいたい
16回くらいやれば、10%くらいの人がビンゴ
41回くらいやれば、半分くらいの人がビンゴ
49回くらいやれば、80%くらいの人がビンゴ
になるようです。




2-3.ビンゴにならずに開けられる数の最大数


1つづつ穴を開けていくと
穴の組み合わせが多すぎるため大変です。

ビンゴ用紙に25個穴が空いた状態から穴を塞いで
ビンゴにならないかどうかを判定して
測定する形にしました。
# 結果のプリント用
def print_bingo(bingo):
    for i,b in enumerate(bingo):
        if i%5==0:
            print()
        print(b,end="\t")
    print()

# ビンゴでない場合True , ビンゴの場合はFalse
def check_bingo(b):
    # 横
    for i in range(5):
        if sum(b[i*5:i*5+5])==5:
            return False
    # 縦
    for i in range(5):
        b1,b2,b3,b4,b5 = b[i],b[i+5],b[i+10],b[i+15],b[i+20]
        if sum([b1,b2,b3,b4,b5])==5:
            return False
    # 斜め
    d1 = [b[d*6] for d in range(5)]
    d2 = [b[d*4] for d in range(1,6)]
    if sum(d1)==5 or sum(d2)==5:
        return False
    return True

count = 0
all_count = 0
seq = [i for i in range(0,25)]

combi = list(itertools.combinations(seq,5))
for c in combi:
    all_count +=1

    # ビンゴを用意(1==穴あき)
    bingo = [1] * 25

    # 穴なしに戻す(0==穴なし)
    for i in c:
        bingo[i]=0

    # ビンゴでないかどうかの判定
    if check_bingo(bingo):
        print(c)
        print_bingo(bingo)
        print()
        count +=1

print(count,all_count)
(0, 6, 12, 18, 24)

0 1 1 1 1
1 0 1 1 1
1 1 0 1 1
1 1 1 0 1
1 1 1 1 0

・・・・
48 53130

結果として
穴が20個まではビンゴにならずに
開けられるパターンが存在します。

そんなに穴が開いた状態になった人
いるんでしょうかねえ

逆にめちゃくちゃ運が悪い方を
決めることが出来ます。

シミュレーション結果を用いれば
どれくらいでプレゼントが捌けるのか
計算しやすくなると思いますので
幹事の方は逆算してみると
捗るかも知れません。

今回はビンゴゲームについての
数値検証でした。

それでは。