神ゲーの1つである、ジャンケンをプログラミングで再現することで、プログラミングの最初から終わりまでの一連の流れをステップごとに追って説明していきます。それによりプログラミングとはなんだろうということを考察する内容です。
ジャンケンは分かりやすいゲームではありますが、それをちゃんとプログラミングするとなると、初心者にとってそれほど簡単ではありません。
完成したプログラムはこんな感じです。コンソールアプリになります。コンソールはとっつきにくいかもしれませんが、馴れてしまえば純粋にコードにだけ集中できるので練習にはふさわしいフォーマットだと思います。
文字だけとは言え、最低限のジャンケンとしての体裁は整っています。こんなんでも、ちゃんとするにはそれなりに考えないとだめですからね。逆に人間はコレだけのことを『なんとなく』できるわけで人間もなかなかやるじゃん!という気になります。
以前に書いた"プログラミングにおける5つの手順"に沿って、順を追って各工程ごとに書いていくことで、プログラミングという作業の流れというものも考えていきたいと思います。 それも本稿における重要な狙いです。
どんな構造なのか?
その前に、まず最初に概略について。
たかがジャンケンですが、コンソールアプリとはいえ150行ほどになりました。コードは一応載せますが、分かりやすさのためにも先にプログラムの全体図を示したいと思います。 一枚の図にやるべきことをまとめています。
この図では、おおよその流れと各々の動作とそのための解決策をざっくりと書き表しています。ある意味では、リアルなジャンケンもこの図のような処理をしていることになります。いろんな機能を加えようとすれば、いくらでもできるとは思いますが(例えば、アスキーアートで手の形を表す、など)とりあえず最低限の機能のみのプログラムにしました。では5つの手順に沿って、考えていきたいと思います。
要件定義
まずはどんな設計、ルールにするかを考えます。(第一工程)
- プログラム上のNPC とジャンケン勝負。
- ベーシックに先に三回勝った方が勝ち。
シンプルにコレだけになります。せめてwindowsアプリにしてグラフィカルにやった方がいいとは思いますが、プログラミングすることが目的だし、めんどくさいのでコンソール・アプリでの開発としました。
ファクタリング
次の第二工程は、"じゃんけん"というものの分析になります。
ジャンケンは一対一、あるいは複数人が同時に『手の形』を出し合い、その優劣によって勝敗を決めるゲームです。手の形はそれぞれグー、チョキ、パーでいわゆる三すくみの関係(絶対的な強者がいない)になっています。
同じ手が出た場合は、『あいこ』となり、勝敗つかずのやり直しになります。
道具の選定 其の一
分析も終わったので、その結果に基づき、ジャンケンをプログラム上で再現するために必要な道具を考えます。
まずは互いの勝敗数を数えるための変数が必要です。単純な数を数えるだけなのでintタイプが二個あれば十分です。
int playerScore = enemyScore = 0;
次は重要な『手の形』をどう表現するか?そして、二つの手をどう比較するかの問題です。プログラミングですので、つまりは数字に置き換わるわけですが、数字自体に意味はなく、自動的に振り分けられるような番号であり、プログラム中は変化することは無いので、(定数)つまenum(列挙体)がちょうど良いということになります。
という風にかけます。数字を直接的に割り振ることもできますが、そうすると人間側がいちいち「1はなんだっけ?」という風に、その都度思い出さないといけなくなる可読性の低いコードになってしまいます。なので、列挙体を使って言葉に置き換えた方がより分かりやすいコードになります。
そして、勝敗の判定方法です。
まずはプレイヤーとNPCの手の形を記憶するオブジェクトが必要です。
HandForm playerHand, enemyHand
このように列挙体変数を作り、そこにそれぞれデータを格納します。
この変数をintにキャスト変換し、その数字を使ってswitch文で勝負の判定を行います。それについては、後ほど説明します。
道具の選定 其の二
次は実際の勝負を何回行うか?という問題です。つまりループ処理になります。
今回のゲームはどちらかが勝つまで続きます。ここで問題なのが、じゃんけんはあいこがあるので、何回勝負が続くかわからないということです。
つまり、どちらかが三回勝つまで勝負は延々と続くことになります。
回数が決まってない、条件がtrueな限り処理が実行される、ということでwhile文がこの場合に適しています。条件は・・・
"どちらの勝ち星も共に2以下の限り"
とします。
while( playScore < 3 && enemyScore < 3 ){
//ジャンケン勝負の処理
}
上記のように、プレイヤーの勝ちが3より小さい、"かつ" 敵の勝ち星も3より少ない、という風に書き表せます。これはつまり、どちらが3回勝った時点で"false"偽となるのでループは終了するということになります。
道具の選定 其の三
ところで、敵の手はどうやって決める?という問題がありました。普通に考えればランダムに出すのがゲームとしては親切でしょう。
C#のライブラリにはRandomクラスがあるので、それをつかって1から3までの乱数を出します。それによって、列挙体から手を選ぶという方式になります。
Random rnd = new Random();
rndはインスタンスです。手の形に振り分けられた数字は1から3なので、rnd.Next(1, 4)とします。これにより、1から3までの整数がランダムに生成されます。
で、今回入力された整数にしたがって、手の形を返す関数を作りました。それに乱数を入力します。
enemyHand = DecisionHand(rnd.Next(1, 4));
これでランダムにNPCの手を決めたことになります。
ロジックを組み上げる
概略図で示したようなロジックを考える作業です。コードを見ながらの方が説明しやすいので、コードは随時示していきながら、どういう風にロジックを組み上げていくかを考えていきます。コードの冒頭はコチラです。enumを最初に書いてます。
まずは、敵の手を決めます。それについては具体的な内容は先に書いたとおりです。
次にプレイヤーの手を決めます。数字キーを押してもらい、そこから整数を取得して、手を決定するための関数に入力します。
それがその関数です。HandForm列挙体を返します。(行数からも分かるとおり、一番下に記述しています。)switch文で入力された数字に基づいて対応した手の形を返す、という構造です。
ちなみに数字のキーボードに振り分けられた数字は1のキーで49、2のキーで50ということになっているので、数値化する際は48を引かないとダメです。
その後は、実際に手の形を互いが見せ合って勝負するのですが、何もせずに画面上に表示されると、入力した瞬間結果が見えるのでジャンケンらしくなりません。
『じゃん!けん!ぽん!』というコールとリズムがなによりもジャンケンらしさを生みます。なのでThread.Sleepを使って、リズムを表現しています。コール部を再現するためのものが、上部写真のしたの方のコードになります。
(Thread.Sleepを使うにはusing System.Threadが必要です。)
その後、瞬時に互いの手を表示させますが、色が着いていた方が見やすいので、Console.ForegoudundColor = ConsoleColor.Redという風に文字の色を変えます。
見やすいインターフェイスというのは、実際のUnityでゲームを作る上でも大事なわけで、こういう細かな気配りの重要性を改めて感じます。
そして、いよいよ勝負の判定です。
二人の場合、その手の組み合わせは9通りになります。なので見易さからいってもswitich文を二つ使って羅列するのがいいのかなと判断しました。
ここはもう愚直に書いているという感じで、本当ならもう少し短く書ける工夫があるのかなと思いますが、ひとまず。結果の判定文とスコアの処理をしています。アイコの場合は、スコアは動かさず無効試合となります。
ここまでがいわゆる1ゲームの流れで、while文の中身になります。前述したようにどちらかが三回勝つまではこの流れが延々と繰り返されます。最初に貼ったGIFは自分が上手いことストレート勝ちしていますが、普通はけっこう続きます。
ループを抜けたら、最終的な勝者を判断して、勝者を称えます。
ループを抜けた時点では、どちらかのスコアが3を超えたという判断しか行っていません。なのでプレイヤーが勝ったかどうかを判断し、勝者の宣言をします。
コードを書く!
実際のプログラミングの5つの手順の最後は、実際にコードを書く!ということになりますが、前項にてコードがあった方が分かりやすいのでそっちで出しちゃってますので、ここは省略します。
要はコードを書くということは、コレまで書いてきたような作業を経て、初めて取り掛かれることであり、つまりはプログラミングという作業全体のうちの一部に過ぎないと言うことです。
これは本ブログでは度々書いていることですが、今回考察したプログラミングはどういう作業で、どんなことを考えないといけないのか?ということは市販の本にはほぼ書かれていないことです。なんでなんですかね?やっぱちゃんと説明するのが大変だからでしょうか?自分を含めた初心者が望む説明ってこういうことだと思うんですが。
拡張性について
というわけで、じゃんけんプログラムを作ってみました。がやろうと思えば、いろんな拡張機能が追加できそうです。
例えば、今回のNPCは完全ランダムですが、プレイヤーが入力した後に自分の手を決めるという仕組みにすれば絶対勝てないゲームに出来ます。(ずるい!)
それは現実的ではないですが、ほぼ完全ランダムゆえそれに気づくと確立論的な戦術をプレイヤーが立てやすくなってしまうので、場合によっては手の出し方を少し偏らせることでプレイヤーを惑わす、そんなAIも作れるかもしれません。
まずそもそも文字だけじゃつまらん!という場合にはGUIアプリを作った方がいいですし、じゃんけんと言えども試せることはいろいろある!ということになります。
まとめ!
以上でジャンケンを題材にプログラミングの一連の作業、流れを考察してみる!ということをやってみました。やっぱ丁寧にやるとなると、ジャンケンですらかなりの文量になりますね。でも、実際こうした記事を書いてみると、プログラミングをするにはどういう風に考えないといけないかを改めて考えることが出来てよかったです。