0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

プログラミング上達講座3:メガテンのコードブレイカー

Posted at

プログラミング上達講座の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

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?