短期集中:PythonでCUIゲームを作る その2

Python
この記事は約11分で読めます。

もうすこしPythonの基本的な文法を勉強します。ネタは前回と同じマスターマインドですが、今度は攻守を逆転して、人間が決めた4桁の数字をコンピュータにあてさせてみましょう。

マスターマインド解法プログラムを作る

解法のアルゴリズム

AI…というほど高級なことはやっていません。機械的な処理で正解を得ることが出来ます。

原理は以下の通りです。

(1) 0~9のうち異なる4つの数字を並べ替えてできるパターンすべてのリスト、”正解候補リスト”を用意する
(2) ”正解候補リスト”から1つを選んで”解答”とし、表示する
(3) hitとblowを入力させる
(4) もしhit==4ならば正解に到達したので解答回数を表示して終了する
(5) ”正解候補リスト”の全要素と”解答”を比較してそれぞれhitとblowを求め、入力されたhit・blowの値と一致するものだけを残して新たな”正解候補リスト”を作る
(6) もし”正解候補リスト”が空になった場合、人間がhitとblowの入力を誤ったとして終了する
(7) (2)~(6)を繰り返す

早速、全ソースコード

今回もけっこうやっつけで作ってしまいました。前回に比べてコメント少なめです。

def init():
    list = []                                                       # 空のリスト
    for i in range(10):
        for j in range(10):
            if i==j: continue
            for k in range(10):
                if i==k or j==k: continue
                for l in range(10):
                    if i==l or j==l or k==l: continue
                    list.append([str(i),str(j),str(k),str(l)])      # リストに要素を追加
    return list

def hitblow(answer, correct):
    hit = blow = 0
    for i in range(4):
        for j in range(4):
            if correct[i] == answer[j]:
                if i == j:
                    hit += 1
                else:
                    blow += 1
    return hit, blow

list = init()
count = 0
while True:
    count += 1
    print("その数は、"+("".join(list[0]))+"ですか?")
    hit = int(input("hit="))
    blow = int(input("blow="))
    if hit==4:
        print(str(count)+"回で正解しました")
        break
    newlist = []
    for answer in list:
        hit0, blow0 = hitblow(answer, list[0])
        if hit0==hit and blow0==blow:
            newlist.append(answer)
    list = newlist
    if len(list)==0:
        print("ヒントが間違っていた可能性があります")
        break

コードの説明

正解候補リストの作成

def init():
    list = []                                                     
    for i in range(10):
        for j in range(10):
            if i==j: continue
            for k in range(10):
                if i==k or j==k: continue
                for l in range(10):
                    if i==l or j==l or k==l: continue
                    list.append([str(i),str(j),str(k),str(l)])    
    return list

ます、0~9のうち異なる4つの数字を並べ替えてできるパターンすべてのリスト、”正解候補リスト”を生成します。アルゴリズムは簡単で、

0~9 の4重のループを作り、どの桁も値が重複しない場合はその4つを組にしてリストに追加する

ということです。

関数定義

今回は、『正解候補リスト作成』および『hit・blowの判定』は間数とし、独立させました。Pythonの関数は、とりあえずCの関数やJavaのメソッドと似たようなものだと思っていて差し支えなさそうです。

関数の定義は、

def 関数名(引数):
  関数の処理

と記述します。forなどと同様、関数の処理内容は : からインデントされた範囲です。
CやJavaと異なり、返却値の型は記述しません。

def init():

は、引数なしの関数 init() を定義しています。

ローカル変数

関数の中で代入された変数は、すべて関数の中だけで参照できるローカル変数になります。

    list = []       

ここで代入した変数list は、init()関数の中でしか参照できません。ちなみに右辺の [] は空(要素なし)のリストを表します。

if文 その3
            if i==j: continue

if文で条件成立時に実行する文が1つしかない場合などは、このように1行で記述することもできます。

リストに要素を追加

リストに要素を追加するには、append()関数を使用します。

追加先のリスト.append(追加する要素)

の書式で使用します。

                    list.append([str(i),str(j),str(k),str(l)])    

この例では、変数 list に [str(i),str(j),str(k),str(l)] という要素を追加しています。
すぐ判るように、要素自体がリスト(『4つの数字を並べ替えてできるパターン』の1つ)になっています。このように、リストを別のリストの要素にすることができます。Javaの多次元配列もこのようなイメージですね。

str()関数で i, j, k, l を文字列化してからリストに入れているのは、後でコンソールから入力された文字列と比較するためです。

これにより、正解候補リスト

[ [“0″,”1″,”2″,”3”], [“0″,”1″,”2″,”4”], [“0″,”1″,”2″,”5”], … ]

が作成されます。

return文

関数から呼び出し元に値を返却するには、return文を使用します。

書式は、他の多くの言語と同様に

return 返却値

です。関数定義のdef文で返却値の型を書いていないので、return文の後にはどんな型のデータでも記述できます。return文を書かなくても文法エラーにはなりません(呼び出し元で返却値を参照していてさえエラーになりません)。

    return list

listはinit()関数内のローカル変数なので、返却値として呼び出し元に返さないと関数の処理終了時に内容が失われてしまいます。

hitとblowの判定

def hitblow(answer, correct):
    hit = blow = 0
    for i in range(4):
        for j in range(4):
            if correct[i] == answer[j]:
                if i == j:
                    hit += 1
                else:
                    blow += 1
    return hit, blow

hitとblowの判定処理は、一度の”解答”のたびに最大数百回呼び出されるので、関数化しました。処理内容は前回のものとまったく同じです。

関数の引数
def hitblow(answer, correct):

関数名はhitblowとしました。引数を2つとります。前回のプログラム(人間のプレイヤが解答者)に倣って answerが”解答”、correctが”正解”ですが、今回のプログラムでは若干意味が異なります(後述)

複数の値の返却
    return hit, blow

Pythonでは、これで複数の値の返却ができます。便利です。

コンピュータが解答し、hitとblowを入力させる

list = init()
count = 0
while True:
    count += 1
    print("その数は、"+("".join(list[0]))+"ですか?")
    hit = int(input("hit="))
    blow = int(input("blow="))
    if hit==4:
        print(str(count)+"回で正解しました")
        break

ここから先がメインルーチンです。この部分は、最初の『解法のアルゴリズム』の(1)~(4)です。

関数の呼び出し

def文で定義した関数も、もともと存在する関数と同じように

list = init()

のように呼び出すことができます。左辺の変数 list は、init()関数定義内にあったローカル変数listとは同名ですが別の変数です。

リストの結合

リストの要素を結合して1つの文字列にするには、join()関数を使用します。書式は、

join(リスト)

です。

    print("その数は、"+("".join(list[0]))+"ですか?")

ここでは、『解法のアルゴリズム』の『(2)”正解候補リスト”から1つを選んで”解答”とし、表示する』を実行しています。具体的には、”正解候補リスト”の先頭(インデックス0番)の要素を結合して文字列とし、画面に表示します。

『1つ選ぶ』と言っても常に0番目を表示するので、最初は必ず “0123” を表示し、それ以降も”正解”が同じならまったく同じ順序で”解答”します。ここをランダムにした方が人間っぽくて面白いかもしれません。余力のある方は改造してみて下さい。

整数化

数字の文字列を整数に変換するにはint()関数を使用します。書式は

int(数字の文字列)

です。

    hit = int(input("hit="))
    blow = int(input("blow="))

コンソールから入力されたhitとblowの値を入力させ、それぞれ整数に変換してから変数hit、変数blowに格納しています。整数化するのは、後でhitblowの返却値と比較するためです。

入力されたhitとblowから、正解候補リストを絞り込む

    newlist = []
    for answer in list:
        hit0, blow0 = hitblow(answer, list[0])
        if hit0==hit and blow0==blow:
            newlist.append(answer)
    list = newlist

この部分は、『解法のアルゴリズム』の『(5) ”正解候補リスト”の全要素と”解答”を比較してそれぞれhitとblowを求め、入力されたhit・blowの値と一致するものだけを残して新たな”正解候補リスト”を作る』と対応しています。

選んだ”解答”と、”正解候補リスト”のすべての要素を比較して、hitとblowがコンソールから入力された値と一致する要素だけ残した新しい”正解候補リスト”を作成しています。

関数から複数の値を受け取る
        hit0, blow0 = hitblow(answer, list[0])

この部分は、hitblow()関数内の

    return hit, blow

この部分と対応して、1回の関数呼び出してhitとblowの2つの値を取得しています。

リストが空になったらエラーとして終了

    if len(list)==0:
        print("ヒントが間違っていた可能性があります")
        break

この部分は、『解法のアルゴリズム』の『 (6) もし”正解候補リスト”が空になった場合、人間がhitとblowの入力を誤ったとして終了する』と対応しています。

文法的に新しいことはないので特に解説は要らないと思います。

まあなんとか。

関数の定義の仕方も憶えたので、一昔前のC言語の入門書に載っていたようなCUIプログラムはひととおりPythonに移植できるようになりました。

次はWebプログラムに手を出してみましょうか…。

コメント

タイトルとURLをコピーしました