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

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

突然ですが、Pythonの勉強を始めようと思います。現時点でPythonについては知識ゼロ、他の言語はCとJavaとPHPとJavaScriptならまぁヒトナミには使えます。

で、早速図書館で借りてきた入門書が…絶望的にツマラン。文法の説明が延々と続いてサンプルがほとんどなし。最初の章で挫折しました(苦笑)

というわけで僕は言語を勉強するときは、『とにかく作りたいプログラムを作ってみる』ことにしています。まずはテキストベースの簡単なゲームを作ってみました。

プログラム言語自体をほぼ知らない人はちゃんと文法書を読んだ方がいいですが、既にCやJavaなどがある程度判る人はこのように勉強した方が判りやすいと思います。

マスターマインドをつくってみる

まずは定番CUIゲーム、マスターマインドを作ってみます。

マスターマインドのルール

出題者は、0~9から異なる4つの数字を選んで並べたものを”正解”とします(たとえば『0246』)

解答者は、この”正解”を推測し”解答”します。

出題者は、解答者の”解答”を確認して、
・数字と桁が正しいものの数を”ヒット(hit)”
・数字は使われているが桁が正しくないものの数を”ブロー(blow)”
の2つの値をヒントとして解答者に与えます。
(たとえば正解『0246』に対して推測『0123』だった場合はhit=1、blow=1となります)

これを繰り返して解答者が”正解”を当てるゲームです。

解答者が”解答”する回数を制限したり、相互に出題し合ってどちらが先に”正解”にたどり着くかを競ったり、というバリエーションもありますが、今回は”正解”にたどり着くまでの回答数を表示するようにしました。

早速、全ソースコード

入門書を初めて開いて1時間で、この程度のプログラムなら作れてしまいました。クラスや関数定義など一切使わず、大昔のBASIC時代のような作りです。

import random

seed=["0","1","2","3","4","5","6","7","8","9"]          # リストの定義。リストは他言語の配列に相当する。要素の型は固定されない。
random.seed()                                           # 乱数の初期化
for c in range(10):                                     # PythonのforはPHPなどのforeachに相当する。range(10)で要素数10のイテラブルオブジェクトを生成し、その数だけループさせている
    i = random.randrange(0, 10, 1)                      # このあたり、要するに10個の要素の中から重複なく4つを順序つきで選ぶ定番アルゴリズム
    j = random.randrange(0, 10, 1)
    seed[i], seed[j] = seed[j], seed[i]                 # Pythonではこれで変数の値をswapできる。3つ以上でも可
correct = seed[0:4]                                     # リストの一部を取り出す。0:4は『インデックス0から4要素』の意味

count = 0
while True:
    answer = list(input("解答を入力して下さい:"))        # list()は文字列を1文字ずつバラしてリストに変換する
    if len(answer) != 4:                                # len()はリストの長さ(要素数)を返す
        print("入力は4桁にして下さい")
        continue                                        # continueの意味はJavaと同じ
    count += 1
    hit = blow = 0
    for i in range(4):                                  # range(4)は 0,1,2,3 を要素とするイテラブルオブジェクトを作るので、これで0~3をカウントできる
        for j in range(4):
            if correct[i] == answer[j]:
                if i == j:
                    hit += 1
                else:
                    blow += 1

    print("HIT:" + str(hit) + ",blow:" + str(blow))
    if hit == 4:
        print(str(count) + "回で正解しました")
        break

コードの説明

冒頭部分

import random

importはモジュール(他のPythonプログラムなど)を参照します。モジュール内の変数や関数は

モジュール名.変数

モジュール名.関数

の形で利用することができます。『モジュール名.』が必要なところがJavaと異なります。

”正解”の生成

seed=["0","1","2","3","4","5","6","7","8","9"] 
random.seed()
for c in range(10):
    i = random.randrange(0, 10, 1)
    j = random.randrange(0, 10, 1)
    seed[i], seed[j] = seed[j], seed[i]
correct = seed[0:4]
”正解”生成のアルゴリズム

最初に、4桁の”正解”を作ります。この部分では以下のような処理を行っています。

(1) 0~9の数字を1列に並べる
(2) そのうち2文字をランダムに選び、場所を入れ替える
(3) (2)を10回くりかえす
(4) ランダムな順番になった数字の、最初の4つを取り出す

では、各文を見ていきましょう。

リスト

まず、0~9を順番に並べたリストを作ります。リストとはPythonのデータ構造の1つで、複数の要素を1列に並べたものです。Javaなどの配列のようなものですね。

[要素0, 要素1, 要素2,…]

と記述します。

PythonではJavaのようなnew演算子による生成は不要です。また変数の宣言も不要なので、リストの用意は1行で済みます。

for文

forは繰り返しのための制御構文ですが、CやJavaのfor文というよりもPHPなどのforeach文に近い動作をします。

for 変数 in イテラブルオブジェクト:
  処理

の記述で使用します。イテラブルオブジェクトとは『複数の要素が1列に並んだオブジェクト』全般のことで、前項のリストもその一種です。for文はイテラブルオブジェクトの先頭から要素を1つずつとりだし、それを変数に代入して繰り返し動作をします。

for などの制御構造のブロックは、CやJavaのように { と } で囲むのではなく、インデントによって表されます。 : は他言語の { のような使われ方ですが、 } に相当する文字はありません。連続した、同じ深さのインデントの部分が同じブロックであることを表します。

for c in range(10):

この部分では『range(10)』で要素数10のrangeオブジェクト(rangeオブジェクトもイテラブルオブジェクトの一種)を生成しているので、10回繰り返し処理が行われます。

乱数

random.seed() メソッドは、乱数を初期化します。引数を省略すると自動的にシステムクロックなどで初期化するので、通常は引数なしのままで使うのが良いでしょう。

random.randrange() 関数は、乱数を生成します。

random.randrange(start, stop, step)

の書式で使用します。start以上・stop未満・step刻みの乱数を生成します。

random.randrange(0, 10, 1)

この部分のように 0, 10, 1 を指定すると、0以上・10未満・1刻みの整数値(要するに0,1,2,3,4,5,6,7,8,9のいずれか)をランダムに出力します。

リストの要素へのアクセス

リストの各要素へは、

リスト[インデックス]

の書式でアクセスできます。
CやJavaの配列と同様、インデックスは0から要素数-1までの値の整数値です。

値の入れ替え

Pythonでは、

seed[i], seed[j] = seed[j], seed[i]

この部分では seed[i] と seed[j] の値を入れ替えています。
CやJavaにはない記法ですが、簡潔で便利ですね。

リストの範囲取得

リストの一部を取り出す場合は

リスト[開始インデックス:取り出す要素数]

と記述します。

correct = seed[0:4]

この部分では、seedの要素0番目から4要素を取り出してcorrectに代入しています。
このとき、correctにはseedの要素0番目から4要素のコピーが代入されるので、correctに対して内容を変更する操作をしてもseedには影響を与えません。

解答者からの入力

count = 0
while True:
    answer = list(input("解答を入力して下さい:"))
    if len(answer) != 4:
        print("入力は4桁にして下さい")
        continue
    count += 1
入力部分の処理内容

この部分では、

(1) まず、解答回数を表す変数 count を 0 に初期化
(2) (3)以降をずっと繰り返す
(3) コンソールから4桁の数字文字列の入力を受け付け、1文字毎にばらしてanswerに代入
(4) もし入力文字数が4文字以外だったら、エラーメッセージを表示して再度入力に戻る
(5) (4)が不成立(入力が4文字)だったら、解答回数を1増やす

という処理を行っています。
(繰り返し処理の内容はこの後も続いています)

では、各文について見ていきましょう。

while文

whileはCやJavaと同様、繰り返し処理を行います。

while 条件式:
  処理

と記述します。CやJavaと異なり、条件式に( )は不要です。

for文と同様、while文そのものの最後に : を記述し、その後のインデントされた部分が繰り返し処理されるブロックとなります。 } に相当する記述はありません。

while True:

この部分では、条件式に True (Tだけ大文字)と記述しているので、永久に繰り返します。

コンソール入力

コンソール入力には、input()関数を使用します。

input(メッセージ文字列)

とすると、メッセージ文字列を表示(改行しない)で入力待ちとなります。
入力された文字列が input() 関数の返却値となります。

リスト生成

list()関数は、リストを生成する関数です。いろいろな使い方がありますが、

list(文字列)

とすると文字列を1文字ずつバラバラにしたリストを生成します。

answer = list(input("解答を入力して下さい:"))

この部分では、input()関数とlist()関数を組み合わせて、解答者から入力された4桁の文字列を1文字ずつバラバラにしてリストを生成し、answerに代入しています。

if文

if文はCやJavaと同様、条件分岐を表します。

if 条件式:
  処理

やはり if文そのものの最後に : を記述し、条件が成立したら処理する内容はインデントで表します。
while文と同様、条件式には ( ) は不要です。

比較演算子

比較演算子は、CやJavaと似ています。

a == baとbが等しい
a != baとbが等しくない
a < baはbより小さい
a > baはbより大きい
a <= baはbより小さいか、または等しい
a >= baはbより大きいか、または等しい
a is baとbは同じオブジェクトである
リストの要素数

len()関数は、リストの要素数を取得します。

    if len(answer) != 4:

この部分は、『もしanswerの要素数が4ではないならば』という意味です。

print()関数

print()関数は、コンソールに文字列を出力します。

print(文字列)

の書式で使用します。文字列の出力後は改行します。Javaでいえば System.out.print ではなく System.out.println の動作です。

continue文

continue文は、CやJavaと同様、繰り返し処理の『その回』を中断します。ループ全体を中止するのではありません。
たとえば10回繰り返す処理の2回目の処理の途中でcontinue文を実行すると、2回目の処理のつづきを止めて、3回目の処理が開始されます。

インクリメント・復号代入演算子

Pythonには ++ (インクリメント)演算子や ーー(デクリメント)演算子がありません。

a += 1

は、CやJavaと同様に

a = a + 1

の意味で、復号代入演算子と言います。

hitとblowの判定

    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

hitとblowの判定手順

この部分では、以下のような処理を行っています。
前提として、correctには”正解”、answerには”解答者の解答”が、1桁ずつバラバラにしたリストとして格納されています。

(1) 変数hitとblowを0に初期化
(2) 変数 i をカウンタとして、0~3 の範囲で(3)を繰り返す
(3) 変数 j をカウンタとして、0~3 の範囲で(4)を繰り返す
(4) correct[i] と answer[i] が等しい場合(つまり正しい数字が使われている場合)
 (4-1) i == j ならば(桁も数字も正しい場合は)、hit を1つ増やす
 (4-2) i == j でないならば(数字は正しいが桁が違う場合は)、blow を1つ増やす

では、各文を見ていきましょう。

代入式

CやJavaと同様、

変数1 = 変数2 = … = 変数n = 値

という書式で、変数1~変数nのすべてに同じ値を代入することができます。

hit = blow = 0

この部分では、変数 hit と 変数 blow をともに0にしています。

for文 その2

range(4) は 0,1,2,3 の4つの要素を持つイテラブルオブジェクト(rangeオブジェクト)なので、

for i in range(4):

とすると、変数 i に 0, 1, 2, 3 を順次代入しながら処理を繰り返します。

if文 その2

CやJavaと同様、if文には『条件が成立しなかった場合』を表すelse節を記述することができます。

if 条件式:
  条件式が成立した場合の処理
else:
  条件式が成立しなかった場合の処理

という書式です。elseの後にも : が必要です。また処理ブロックをインデントで表すのも同じです。

制御構文の重ね合わせ

この例のとおり、『for文の中にまたfor文』や『if文の中にまたif文』というように、制御構文を何重にも重ねて記述することができます。

結果表示と終了判定

    print("HIT:" + str(hit) + ",blow:" + str(blow))
    if hit == 4:
        print(str(count) + "回で正解しました")
        break

この部分では、解答者の入力に対してhitとblowを表示、もし”正解”と完全一致(hitが4)していた場合はそれまでの解答入力回数を表示してゲームを終了します。

では各文を見ていきましょう。

文字列の連結

PythonでもJavaと同様、+演算子で文字列を連結することができます。
しかし文字列型以外の値を自動的に文字列に変換してくれないようで、

print("HIT:" + hit + ",blow:" + blow)

とするとエラーになってしまいます。そこで数値が入っている変数 hit と blow は str() 関数で明示的に文字列化しています。

print(str(count) + "回で正解しました")

この部分も同様で、count を str() 関数で文字列化してから+演算子で連結しています。

break文

break文は、繰り返し処理そのものを中止します。たとえば10回繰り返す処理の2回目でbreakを実行すると、2回目の続きや3回目以降の処理を一切行わず、繰り返し処理全体を終了します。

このプログラムは、全ソースコード11行目の while True: 以降すべてが1つのwhileループなので、hit==4が成立した後のbreakでプログラムが終了します。

というわけで。

これでマスターマインドの解説を終わります。CやJavaを知っている人なら、これだけでなんとなくPythonのプログラムを組めるようになるのではないでしょうか?

プログラムを書くより、解説を書く方が数十倍の時間がかかっています…。

コメント

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