プログラミング上達講座の3回目です。
メガテンのコードブレイカーを題材にして
プログラムを考えてみましょう。
解説動画はこちら
コードブレイカーとは
RPG「女神転生」シリーズのミニゲームコードブレーカー
正式名称は「Hit&Blow」という数当てゲームです。
その昔ヌメロン
という名前でテレビ番組やっていましたね。
ヌメロン
だとアイテムが使えたりがありましたが
今回はアイテムとか無しです。
ルール
各桁の数字(0-9までの数字)が全て違う、3桁の数字を当てるゲーム
予想の数字を入力して、桁ごとに下記を判定
正解と場所と数字が一致する(ヒット)
場所が違うがその数字が含まれる(ブロー)
1回答ごとにHitとBlowの数を言い、全て当たる(3H)まで行う。
例:
正解の数 957
1回目の予想「915」→ 「1H1B」
2回目の予想「234」→ 「0H0B」
3回目の予想「795」→ 「0H3B」
4回目の予想「957」→ 「3H0B」→当たり
初級編:コードブレイカーのチェック関数を作る
予想の数値と正解の数字を入力して
HitとBlowの数を返す関数check_call
を作ってみよう
# 引数:call=予想数字 , solve=正解数字
# 戻り値:(hit , blow)のタプル型(両方数値)
def check_call(call , solve):
処理
return (hit , blow)
例:
check_call('564','987') →(0,0)
check_call('564','687') →(0,1)
check_call('564','546') →(1,2)
check_call('564','564') →(3,0)
中級編:コードブレイカーのゲームを作ってみよう
3桁の数値をランダムで生成して正解数として
正解数を当てに行くミニゲームを作ってみよう。
inputで数値入力を受け付け
数値とhit , blowを出力し続ける。
3Hになるか、10回当てられなければゲーム終了。
ヒント:
まず正解の数字を1つ作る。
チェックする部分は 初級編の関数を利用する。
正解の数字と入力数字が合うまで
各回の予想とhit&blowの出力する
import random
import itertools
# 数字をチェックする
def check_call(call , solve):
# 処理を書く
return hit , blow
# ゲームの開始
# 処理を書く
上級編:正解の数字を導き出すアルゴリズム
正解の数字をあらかじめ決めておき、チェック関数の結果を用いて
正解の数字を予想するプログラムを作成してみよう。
ヒント:
チェック関数をうまく使うと、正解の候補を考える事が出来るよ!!
# 処理を書く
解答編
初級編の解答
# 予想と正解をチェックする関数
def check_call(call , solve):
hit , blow = 0,0
for i,n in enumerate(call):
if solve[i]==n:
hit+=1
elif n in solve:
blow +=1
return hit , blow
print(check_call('564','987'))
print(check_call('564','687'))
print(check_call('564','546'))
print(check_call('564','564'))
(0, 0)
(0, 1)
(1, 2)
(3, 0)
中級編の解答
まず最初に正解の数字を作る
(10個の数字を使った3桁でダブりのない数字)
10個の数字を用いた順列を作るには
itertools.permutations(seq, 3)
順列の中から1つ選ぶには
random.choice(リストなど)
繰り返しはWhile文かfor文を使い
hit==3か10回当てられない場合に抜ける。
import random
import itertools
# 数字をチェックする
def check_call(call , solve):
hit , blow = 0,0
for i,n in enumerate(call):
if solve[i]==n:
hit+=1
elif n in solve:
blow +=1
return hit , blow
# ゲームの開始
# 正解数値の生成
seq = (0,1,2,3,4,5,6,7,8,9)
nums = [str(a)+str(b)+str(c) for a,b,c in itertools.permutations(seq, 3)]
solve = random.choice(nums)
# 10回繰り返すか、正解で終了
count = 0
while True:
count+=1
call = input()
hit , blow = check_call(call , solve)
print(count , call , '{0}H{1}B'.format(hit , blow))
if hit==3:
print('You Win , Solve = {0}'.format(solve))
break
if count==10:
print('You Lose , Solve = {0}'.format(solve))
break
123
1 123 0H1B
789
2 789 0H1B
567
3 567 0H1B
712
4 712 0H1B
254
5 254 0H0B
710
6 710 1H1B
910
7 910 1H0B
817
8 817 0H1B
709
9 709 0H2B
098
10 098 0H1B
You Lose , Solve = 370
上級編の解答
チェック関数を使って予想数字から
正解候補の判定結果を全通り求めておく。
集合をうまく使うと正解候補を絞り込む事が出来る。
Pythonではset
が集合のデータ型。
積集合(intersection)を使うと
候補同士の重なる部分を抽出できる。
候補の重なりの中から次の予想を行い
繰り返すと、候補が絞られてゆく
サンプルコードはこちら
# 数字をチェックする
def check_call(call , solve):
hit , blow = 0,0
for i,n in enumerate(call):
if solve[i]==n:
hit+=1
elif n in solve:
blow +=1
return hit , blow
# 候補を絞り込むための組み合わせの辞書を作る
def all_combination_calc(call):
result_dict = {}
for i in list(itertools.permutations((0,1,2,3,4,5,6,7,8,9), 3)):
num = str(i[0])+str(i[1])+str(i[2])
hit , blow = check_call(call,num)
key = '{0}H{1}B'.format(hit , blow)
if key in result_dict:
tmp = result_dict[key]
tmp.append(num)
else:
tmp = [num]
result_dict[key] = tmp
return result_dict
# 正解の数字を解く
def solve_code_break(solve):
sets = set([])
call = random.choice([str(a)+str(b)+str(c) for a,b,c in itertools.permutations(seq, 3)])
hit , blow = check_call(call , solve)
key = '{0}H{1}B'.format(hit , blow)
print(call,key)
if call==solve:
print('End')
else:
while True:
conbination_dict = all_combination_calc(call)
s_choice = conbination_dict[key]
sets = set(s_choice) if len(sets) ==0 else sets.intersection(set(s_choice))
call = random.choice(list(sets))
s_hit , s_blow = check_call(call , solve)
key = '{0}H{1}B'.format(s_hit , s_blow)
print(call,key)
if call==solve:
break
print('End')
# 数字当てをする
seq = (0,1,2,3,4,5,6,7,8,9)
s_nums = [str(a)+str(b)+str(c) for a,b,c in itertools.permutations(seq, 3)]
solve = random.choice(s_nums)
print('Solve = ', solve)
solve_code_break(solve)
Solve = 736
936 2H0B
931 1H0B
906 1H0B
836 2H0B
236 2H0B
736 3H0B
End
せっかくなのでシミュレーションしてみる
1000回ほど試行して何回で解けるかシミュレーションしてみる
上級編のコードを改変して集計プログラムに直して試行する。
# 数字をチェックする
def check_call(call , solve):
hit , blow = 0,0
for i,n in enumerate(call):
if solve[i]==n:
hit+=1
elif n in solve:
blow +=1
return hit , blow
# 候補を絞り込むための組み合わせの辞書を作る
def all_combination_calc(call):
result_dict = {}
for i in list(itertools.permutations((0,1,2,3,4,5,6,7,8,9), 3)):
num = str(i[0])+str(i[1])+str(i[2])
hit , blow = check_call(call,num)
key = '{0}H{1}B'.format(hit , blow)
if key in result_dict:
tmp = result_dict[key]
tmp.append(num)
else:
tmp = [num]
result_dict[key] = tmp
return result_dict
# 正解の数字を解くシミュレーションをする
def code_break_sim():
seq = (0,1,2,3,4,5,6,7,8,9)
s_nums = [str(a)+str(b)+str(c) for a,b,c in itertools.permutations(seq, 3)]
solve = random.choice(s_nums)
term,sets = 1,set([])
call = random.choice([str(a)+str(b)+str(c) for a,b,c in itertools.permutations(seq, 3)])
hit , blow = check_call(call , solve)
key = '{0}H{1}B'.format(hit , blow)
if call==solve:
return term
else:
while True:
conbination_dict = all_combination_calc(call)
second_choice = conbination_dict[key]
sets = set(second_choice) if len(sets) ==0 else sets.intersection(set(second_choice))
call = random.choice(list(sets))
s_hit , s_blow = check_call(call , solve)
key = '{0}H{1}B'.format(s_hit , s_blow)
term+=1
if call==solve:
return term
calc_dict = {}
for i in range(1000):
key = code_break_sim()
if key in calc_dict:
calc_dict[key]+=1
else:
calc_dict[key]=1
for k,v in sorted(calc_dict.items()):
print(k,v)
1 2
2 10
3 63
4 196
5 335
6 277
7 86
8 28
9 3
だいたい5回くらいで解くことができそうです。
7回もあればほぼほぼ解き終わっていますね。
まとめ
コードブレイカー自体は女神転生のミニゲームなので
だいぶ前のゲームでしたが
プログラム的な考え方ができる
いい題材でした。
ヌメロン懐かしいですよね。
今なら無双できる気がします。
再放送するなら
呼んでくれないかなwww
それでは。
作者の情報
乙pyのHP:
http://www.otupy.net/
Youtube:
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMw
Twitter:
https://twitter.com/otupython