みなくんの日記

やる気が皆無で自由気ままに生きてる人のブログ

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その4

今まで作ってきたパーツを組み立て、メイン関数を作ってプログラムを完成させる総集編です!ここまで来れば、後は楽ちんですね!

今回の目標

  • [Chapter.4]「4Numbers」を完成させる
  • [Chapter.EX] タイマーの実装

部品(プログラム)を組み立てる→メイン関数を作る

過去3回分までで、以下のようなことをやってきましたね。

  • 正解の4桁の数字の生成
  • ユーザに4桁の数字を入力してもらう処理の実装
  • 正解桁とユーザの解答桁との比較

今回作成するゲームは、これらの機能を使うわけです。ということは、今までやってきたのは例えるならば「自動車の部品作り」であり、今回行う作業は「自動車の組み立て」なんですね。今まで苦労して作って来たものを良い感じに組み合わせ、ゲームとして完成させてしまいましょう!
ただし、ただ作った部品をくっつける…だけではうまくいきません。部品を部品として使えるようにしてあげる必要があります。
その準備は実は今までの回でやっていたのですが、気付いていましたか?

クラス・メソッド→オブジェクト指向

今まで作成してきたプログラムはすべて、クラスを作成していましたよね。
第2回のユーザ入力プログラムをもう一度見返してみましょう。(クラスが分からん…という人は調べてみてください)

class input_number:
    def input_num(self):
        while True: #4桁の数字かGive upが入力されるまでループ処理
            try:
                num=input("正解だと思う正の数字4桁を入力してください:")
                if len(num)==4: #4桁であった場合
                    check=int(num) #それが文字列であった場合、ここでValueErrorに飛ばす
                    if num[0]=="-":
                        raise ValueError #入っていたらそれはエラーとして処理
                    return num #何もなければユーザの入力を返り値にする
                else: #4桁以上の数字であった場合
                    raise ValueError
            except ValueError: #文字列が入力された場合(今回は4桁以上の数字もこのエラー処理になる)
                if num=="Give up": #Give upと入力された場合
                    print("諦めます。また挑戦してくださいね!")
                    exit()
                else: #他の文字列もしくは4桁以上の数字が入力された場合
                    print("4桁の正の数字かGive upのみを入力してください。")
    def start_init(self):
        input("開始するにはEnterキーを押してください...")

ここでは、input_numberクラスを作成して、その中でユーザの入力を受け付けるinput_num()と、「開始するにはEnterキーを押してください…」と表示してEnterキーの入力を促すstart_init()メソッドを定義しています。
このように、クラスの中に処理を定義することでユーザ入力を受け付けたければinput_num()メソッドを使えばよいなど、新しいプログラムに組み込む際に組み込む側の人が詳細な処理内容を理解することなく部品として使えるようになるんですね。
このように操作手順よりも操作対象に重点を置いた考え方を「オブジェクト指向」と言います。
私もこの考え方を学んだのは最近のことで、説明に間違いがあるかもしれないので…もっとよく知りたい人は「オブジェクト指向」で調べてみてください!(丸投げ)

長々と説明しましたが、今まで作成してきたプログラムはそれぞれ
* 正解桁生成処理→ create_randomクラス
* ユーザ入力処理→ input_numberクラス
* 正解桁とユーザ桁比較処理→compare_numberクラス
と定義し、使える状態にしています。あとはこれを利用して、メイン関数を作っていきましょう!

部品を組み合わせてゲームを作る(メイン関数の作成)

ではここからメイン関数を作成して、完成させていきます。
先ほどまで述べてきたように、今まで作ったものを組み合わせて…ドーンと作ります。作ったものが以下の通り。
なお、「デバッグ用」とコメントされたものはあくまで自分がテストとして使うものであり、実際のプログラムコードには使わないため予めコメントアウトしています。コメントアウトしなかった場合、正解桁が表示されてしまうので…ゲームにならないです。

import random_number #ランダム生成クラス
import input_user2 #ユーザー入力クラス
import compare #正解との比較クラス
import time #timeモジュール

Answer=random_number.create_random() #正解桁生成クラスのインスタンス化
Ans=Answer.create_num() #正解桁生成
#print(Ans) #デバッグ用
user=input_user2.input_number() #ユーザー入力クラスのインスタンス化
compare=compare.compare_number() #比較クラスのインスタンス化
user.start_init() #始めるには…
End=False #End変数の初期化
Start = time.time() #計測開始
while End==False: #継続条件:EndがFalseである間 終了条件:EndがTrue(Falseでなくなる)のとき
    Num=user.input_num()
    #print(Num,Ans) #デバッグ用
    End=compare.cmp_num(Num,str(Ans)) 
Stop = time.time() #計測終了
print("正解するまでにかかった時間:{0}秒".format(round(Stop-Start,2)))
print("また挑戦してくださいね!")

多分正解桁生成プログラムの次ぐらいにコード数が少ないプログラムなのでは…?
パッと見たとき、「これだけでゲームできるのかよ!」と思ってしまうほどですね。
コードを見てもらえばわかるように、importで今まで作成したパーツを読み込んできて、それを適宜必要なタイミングで利用している…といった感じです。何をしているのか、何となくわかるのではないでしょうか。
でもただ部品を並べただけではなく、少し手間を加えたり追加機能を付けたりしているので順に説明していくことにしましょう。

クラスの呼び出しとインスタンス

まずメイン関数の1~4行目に書いてある「import」ですが、これはモジュールや他プログラムを呼び出すのに使用しています。
4行目に関しては後々使うことになるtimeモジュールです。現在時刻の取得などに使える、便利なモノです。それ以外の3行はそれぞれのプログラムを呼び出している感じですね。
メイン関数でほかのプログラムを呼び出す際はこれでいいのですが、注意すべきことが一つ。
それは、メインプログラムとimportで呼び出したプログラムは同じディレクトリ内に入っている必要があるということです。
設定したら違うディレクトリでも行けたかもしれませんが…同一ディレクトリ内ですることをお勧めします(何となく面倒そうなので)。
そして、importで呼び出した後に以下のコードが載っていると思います。

Answer=random_number.create_random() #正解桁生成クラスのインスタンス化
…
user=input_user2.input_number() #ユーザー入力クラスのインスタンス化
compare=compare.compare_number() #比較クラスのインスタンス化

これがインスタンスです。 クラスは言わば「仕様書」であり、インスタンスは「実体」になります。今回のコードでいえば「user」という名前のインスタンス(実体/部品)を、inout_user2.pyに記載されているinput_numberというクラス(仕様書)をもとに作成した、といった意味になります。
今回は3つのプログラムを組み込んで一つのプログラムにするので、インスタンスは3つ、それぞれ違うものが必要になります。よって、Answerとuserとcompareというインスタンスを用意しているんですね。

プログラムの利用…手順通りに使っていく

インスタンスの作成によりパーツができたので、あとは利用していくだけになります。
使い方は以下のような感じ。

Ans=Answer.create_num() #正解桁生成

何と言いますか…私たちが無意識に「print("Hello, Python world!")」と打って画面上に"Hello, Python world!"を表示させているときと同じ感覚です。これは正解桁を生成してくれるメソッドで、変数に代入させれば正解桁を得られる…という仕様さえ知っていれば、これは簡単にかけると思います。
あのような書き方で、正解桁生成、スタートコール、ユーザ入力処理、正解比較…を行っていきます。
スタートコール担当は"user.start_init()"です。これを実行すれば「開始するには…」という文章が出てEnterキーの入力を促します。
ユーザ入力処理担当は"user.input_num()"です。これを実行し、返り値を変数に代入させることでユーザが入力した4桁が得られます。
正解比較担当は"compare.cmp_num(Num,str(Ans))"です。引数としてユーザ入力桁のNumと正解桁をString型に変換したstr(Ans)を与え、返り値を変数に代入させることでTrueかFalseを得ることができます。
この順番で並べるだけで、基本的にはゲームが成立するのですが…。
ユーザ入力処理と正解比較処理はクリアするまでループさせる必要があり、ループ判定条件として正解比較処理の返り値であるTrueとFalseが使えるので、この2処理をwhileループで挟んでいるわけなんですね。

while End==False: #継続条件:EndがFalseである間 終了条件:EndがTrue(Falseでなくなる)のとき
    Num=user.input_num()
    #print(Num,Ans) #デバッグ用
    End=compare.cmp_num(Num,str(Ans)) 

そして、whileループを抜ける=ユーザが正解したということで、最後に「正解です!」と文字を表示させてこのプログラムを終了するようにしています。これで、ゲームとして動作するようになりました!

追加機能「正解するまでにかかった時間」の実装

このままでも十分楽しいゲームだと思うのですが、やはりやり込み要素は欲しいところ。
というわけで、正解するまでにかかった時間を計測して表示させる機能をつけましょう。
そこで登場するのが先ほどimportで取り込んだtimeモジュールです。
「time.time()」とすることで現在時刻を取得することができます。これを用いて、ゲーム開始時刻をStart変数に格納、終了時刻をStop変数に格納して、この差を用いて正解するまでにかかった時間を求めて表示させます。

print("正解するまでにかかった時間:{0}秒".format(round(Stop-Start,2)))

解答にかかった時間=(終了時刻)-(開始時刻) で求められますが、あまりに細かい時間を表示させても意味がないので、無難に小数点第2位まで表示させるために四捨五入を行ってくれるround()関数を利用しています。
print()関数にformatが使われていますね。これは文字列に変数を組み込む際に使う形式です。print("文字列{0}",format(変数))とすることで、{0}内にformat()内の変数を組み込むことができます。

実際に動かしてみる

さあ、プログラムが完成しました!実際に起動して動作を試してみましょう。
プログラムを実行するには、以下のようにコマンドを打つ必要があります。

$ python3 main.py

python2系だと恐らく動きません。(3系で今までプログラムを作成してきたため)
で、遊んでみた結果がこちら。

開始するにはEnterキーを押してください...
正解だと思う正の数字4桁を入力してください:1234
HIT= 1 BLOW= 1
正解だと思う正の数字4桁を入力してください:1111
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:2222
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:333
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:3333
HIT= 1 BLOW= 0
正解だと思う正の数字4桁を入力してください:4111
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:1411
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:3111
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:1311
HIT= 1 BLOW= 0
正解だと思う正の数字4桁を入力してください:5678
HIT= 0 BLOW= 2
正解だと思う正の数字4桁を入力してください:5555
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:6666
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:7384
正解です!
正解するまでにかかった時間:66.03秒
また挑戦してくださいね!

難しい…!
しかし、特に問題もなく動いていることが分かりますね。
ちなみに、変なことばかりをするユーザになりきってもう一度遊んでみました…。

開始するにはEnterキーを押してください...aaaaaa
正解だと思う正の数字4桁を入力してください:dete
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:hoge
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:ギブアップ
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:GiGive up
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:111122
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:djawjdawlpdmwapdaw@da
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:1234
HIT= 1 BLOW= 1
正解だと思う正の数字4桁を入力してください:nwopdawpodnapodaw
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:amdwapampoa
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:Give up
諦めます。また挑戦してくださいね!

Enterキーを押せと言っているのに"aaaaa"と入力する、日本語も入れる、デタラメを入れる、数字が4桁以上…など、あらゆる動作を盛り込んでみましたがすべて弾かれていますね。問題なく例外処理ができている証拠です。

今後の課題(改善検討案)

このゲーム「4Numbers」を最大限に楽しむコツとしては、「1111」などをデタラメに入れて正解の4つの数字を当てて行くのではなく「いかに手数を少なく、かつ早く解答まで持っていけるか」にあります。
よって、今後検討すべき追加要素の一つとして「解答までの手数を表示する」というのも良いかもしれませんね!
さらに、現在はpython3で.pyファイルを指定してゲームを起動していますが、世の中のゲームというのは.「4Numbers.exe」といった実行形式のファイルになっているものです。なので実行形式にしてダブルクリックで起動できるようにする、というのも検討してみても良いかもしれません!

まとめ

  • 今まで作ったプログラムを組み合わせてゲームを作成できた!
  • ゲームのやり込み要素としてタイマー機能を実装することに成功した

今まで長々とやってきましたが、ここで【プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~】は終了になります。お疲れさまでした!最後までご覧いただいた皆様、ありがとうございました!
また、なんとかブログとして形に残せた自分を褒めたいと思います。よく頑張った、自分。

もしかしたらEX編で何かするかもしれません(改善検討案を実装する、など)。その時はどうぞよろしくお願いします!
以上、みなくんの「とにかく動け!プログラミング講座」でした!