2018/12/19
クリスマス、忘年会シーズンですが、この時期イベントでビンゴゲームが行われることも多いかと思います。
先日、結婚式二次会でオリジナルのビンゴを作ってやってみたところ、作り方を教えて欲しいという声をいくつか頂いたので、その作り方と、表題の疑問『オリジナルビンゴをつくると、プレイ時間は調節可能か?』について考えていきたいと思います。
オリジナルビンゴカードを作る
ビンゴのシミュレーション
結論
オリジナルビンゴを作ろうと思ったそもそものモチベーションは、一般的な数字の5x5のビンゴではなく、何か特徴的なものをビンゴのネタに使いたく、ひらがなを使いたいという所から始まりました。同じひらがなを使ったゲームに『ひらがなポーカー』というものがあり、縦横で単語や文章になったりする偶然も狙っています。
さて、そのようなビンゴは存在しないので作ろうということになるのですが、数字の入っていない5x5のビンゴカードなんてあるのかと探したらありました、メルカリに。
書き込み式の3x3のビンゴカード(名前ビンゴなどに使われる)はいくつかamazonや楽天で売られていますが、5x5の穴を開けるタイプの無地のものはここしか見当たらず。
ちなみに、これ以外ですと、市販の数字が入ったビンゴカードの裏を使う、という若干微妙な手段はあります。
こちらは、人数分のそれぞれ違う配置のカードが必要ですので、pythonで生成することにしました。
import numpy as np
import sys
import time
from PIL import Image,ImageOps,ImageDraw,ImageFont
BOARD_NUM = 140
BOARD_W = 880
BOARD_H = 1100
KANA_SIZE = 80
KANA_DISTANCE = 170
KANA_CENTER_Y = 660
KANA_NUM = 46
BINGO_SIZE = 5
CHARA_CENTER_Y = 130
CHAEA_SIZE = 180
CROP = 5
KANA_LIST = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん".decode("utf-8")
def main(argv):
kana = []
result = {}
chara = Image.open("./chara.png")#せっかくなので上部にロゴ画像を入れました。
charaW,charaH = chara.size
charaW = int(1. * CHAEA_SIZE * charaW / charaH)
charaH = CHAEA_SIZE
for i in range(KANA_NUM):
#imagesフォルダの中に、1.png〜46.pngの画像を用意しておきます。
kana.append([i, Image.open("images/"+str(i+1)+".png") ])
result[i]=0
checkList=[]
saveNpy = np.zeros((BOARD_NUM,BINGO_SIZE,BINGO_SIZE))
for i in range(BOARD_NUM):
img = Image.new("RGB",(BOARD_W,BOARD_H),(255,255,255))
np.random.shuffle(kana)
check = ""
for j in range(BINGO_SIZE):
for k in range(BINGO_SIZE):
pasteX = BOARD_W / 2 + KANA_DISTANCE * (j - 2) - KANA_SIZE / 2
pasteY = KANA_CENTER_Y + KANA_DISTANCE * (k - 2) - KANA_SIZE / 2
img.paste(kana[j+k*BINGO_SIZE][1].resize((KANA_SIZE,KANA_SIZE)),(pasteX,pasteY))
result[kana[j+k*BINGO_SIZE][0]] += 1
saveNpy[i,k,j] = kana[j+k*BINGO_SIZE][0]
check+= str(kana[j+k*BINGO_SIZE][0])
check+= ':'
print check
print saveNpy[i]
if check in checkList:
print "error"
#念の為、たまたま同じ配列のカードができていないかはチェックしています。
quit()
else:
checkList.append(check)
img.paste(chara.resize((charaW,charaH)).crop((CROP,CROP,charaW-CROP,charaH-CROP)),(CROP+BOARD_W/2-charaW/2, CROP+CHARA_CENTER_Y-charaH/2))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("フォントファイル.ttf", 32)
draw.text((50,50), "No."+str(i+1), fill="#000",font=font)
img.save("./output/"+str(i)+".png")
np.save("save.npy",saveNpy)#あとでシミュレーション用に配列を保存
return
if __name__ == "__main__":
main(sys.argv)
年賀状を印刷する要領で、家庭用のプリンターを使います。
プリンターがなかったので良い機会なので買いました。
印刷ズレもなく140枚あっというまにできました!
さて、カードができたのでビンゴを回すアプリもつくっていきましょう。
こちらは面倒であれば、ひらがなを振った紙を箱に入れて引いていくのでも良いでしょう。
以上のことができると、とてもスムーズです。
参考までに、openframeworksで以上の機能を実装したものを上げておきます。
oFBingoMachine(Github)
さて、これでビンゴの準備は万端ですが、オリジナル故に、実際にゲームとして成り立つのか気になります。
特に、ひらがなの場合、一般的な数字ビンゴよりも75->46と引ける数が少ないので途中でビンゴになる人が大量発生してしまうとやっかいです。
以下のコードを実行して、1〜75までの数字、中央のFREEあり、参加者140人でゲームを10000回やった場合の、
ターンが進むに従ってビンゴを達成する人数の、平均ヒストグラムを計算します。
import numpy as np
BOARD_NUM = 140
KANA_NUM = 75
BINGO_SIZE = 5
TEST_NUM = 10
USE_FREE = True
def main():
#board = np.uint8(np.load("先ほど保存したカードの情報.npy"))
board = np.uint8(np.load("save75.npy"))
key = range(KANA_NUM)
averageTimes = []
totalTimesToFinishHist = np.zeros((KANA_NUM),dtype="int")
for itr in range(TEST_NUM):
np.random.shuffle(key)
print key
state = np.zeros((BOARD_NUM,BINGO_SIZE,BINGO_SIZE),dtype="uint8")
if USE_FREE:
state[:,2,2]=1
finishedPlayerNum = np.zeros((KANA_NUM),dtype="uint8")
finishedFlag = np.zeros((BOARD_NUM),dtype="uint8")
for i,j in enumerate(key):
for k in range(BOARD_NUM):
state[k],ret = check(board[k],state[k],j)
if ret>0:
if finishedFlag[k] == 0:
finishedFlag[k] = i+1
finishedPlayerNum[i]+=1
print "When each player finished ?"
print finishedFlag
print "Average times to finish :",np.average(finishedFlag)
averageTimes.append(np.average(finishedFlag))
print "Times to finish histgram"
print finishedPlayerNum
totalTimesToFinishHist += finishedPlayerNum
print "==========================="
print "Times to finish histgram"
print totalTimesToFinishHist * 1. / TEST_NUM
print "Average times to finish :",np.average(averageTimes)
def check(ary,state,key):
ret = np.nonzero(ary == key)
if len(ret[0]) > 0:
state[ret[0][0],ret[1][0]] = 1
return state,checkBingo(state)
else:
return state,checkBingo(state)
def checkBingo(state):
score = 0
if min(state[0,:]) == 1:score+=1
if min(state[1,:]) == 1:score+=1
if min(state[2,:]) == 1:score+=1
if min(state[3,:]) == 1:score+=1
if min(state[4,:]) == 1:score+=1
if min(state[:,0]) == 1:score+=1
if min(state[:,1]) == 1:score+=1
if min(state[:,2]) == 1:score+=1
if min(state[:,3]) == 1:score+=1
if min(state[:,4]) == 1:score+=1
if (state[0,0]+state[1,1]+state[2,2]+state[3,3]+state[4,4])==5:score+=1
if (state[4,0]+state[3,1]+state[2,2]+state[1,3]+state[0,4])==5:score+=1
return score
if __name__ == "__main__":
main()
さて、これが、ひらがな46文字だとどうなるでしょうか。とりあえずビンゴは出やすそうです。
KANA_NUM = 46と書き換えて再度実行します。
市販されているビンゴカードは、大抵真ん中の部分が最初から空いています。これによって少しビンゴになりやすくなっていると思うのですが、実際の所どうなのでしょうか。
それでは一斉に結果です。
ビンゴになるまでに必要な平均ターン数 (140人10000回の平均)
数字(1〜75) | ひらがな(46文字) | |
---|---|---|
FREEあり | 41.38回 | 25.59回 |
FREEなし | 43.54回 | 26.93回 |
FREEがあっても2回ぐらいしか変わらない!!
意外と影響は少ないんですね。
一方で、実際のビンゴでは最後までめくりきることはなく、先着順で一定数ビンゴが出たら終了してしまいます。それを踏まえて、累積でビンゴになった人をカウントしていくと、次のようなグラフになります。
今回、商品は15個だったため、15人ビンゴが出るまでのターン回数を数えると
数字(1〜75) | ひらがな(46文字) | |
---|---|---|
FREEあり | 18回 | 29回 |
FREEなし | 20回 | 31回 |
この時、同時にビンゴになる人は3〜4人ぐらい出てくる見込みです。じゃんけんですね。
数字のビンゴに比べて、約2/3の時間でゲームが終了することが分かりました。
今回、オリジナルビンゴを、外注せずに自前でやることで用紙3000円+プリンター6000円と1万円以内に収めることができました。出る種類が少ないため、通常のビンゴよりも2/3ぐらいの時間になってしまいましたが、時間が押すことを考えるとこれぐらいでも良いのかもしれません。最終的に「ひらがな、FREEなし」で、20回程引いて無事にゲームは終了しました。