【if編】Java言語で『じゃんけんゲーム』を作る
Java言語でプログラミングの勉強をしていくと、「じゃんけんゲーム」を作るというサンプルや課題がでてきます。
どのようにして作ればよいのか、その考え方を説明します。なお、今回は「if文」を利用してサンプルプログラムを作ります。
開発環境は、Eclise(Pleiades)を利用しています。
Eclipseのインストール方法については以下の記事を参考にしてください。
まず、この記事は以下のような人を対象としています。
対象者・Java言語でじゃんけんゲームの作り方を知りたい人
・if文を使ってじゃんけんゲームを作りたい人
・メソッドを使ってじゃんけんゲームを作りたい人
この記事を読むと、次のようなことが理解できるようになります。
この記事を読むとできること・Java言語でじゃんけんゲームをつくることができる
・Java言語のif文の使い方が理解できる
Java言語でじゃんけんゲームを作る課題が出たのですが、どのようにして作ればよいのかわかりません。
色々な作り方があるのですが、今回は「if文」を利用するプログラムの作り方を説明します。
「if文」を利用するプログラムということは、他の方法でも作れるのですか?
他にも色々な作り方があります。
それは、また別の機会で説明します。
それから、メソッドを使ってプログラムを作っていきますので、メソッドの使い方が分からない人も勉強になりますよ。
メソッドの書き方を詳しく紹介している記事を追加しました。ぜひ参照してください。
メソッドを定義する時の「static修飾子」については以下の記事で解説しています。
なお、switchh文を利用してじゃんけんゲームを作る以下の記事も参照してみて下さい。
また、配列を利用したじゃんけんゲームの解説は以下の記事を参照してください。
じゃんけんゲームの仕様
今回作成するじゃんけんゲームは以下のような仕様(ルール)とします。
- グーは「0」、チョキは「1」、パーは「2」で表現する
- ユーザのじゃんけんの手は、キーボードから入力してもらう
- 「0」「1」「2」以外のデータが入力された時は、再入力させる
- コンピュータ側のじゃんけんの手は、乱数で自動的に求める
- ユーザの判定結果(勝ち、負け、引き分け)を画面に表示する
- メソッドを利用して作成する
なお、これらのルールはプログラムの3部構成で考えています。
プログラムの3部構成については、以下の記事を参照してください。
https://ict-skillup.com/java/106/
作成するメソッドの設計
仕様を元に、作成する各種メソッドを以下のようにします。
機能 | メソッド名 | 引数 | 戻り値 |
ユーザ側のじゃんけんの手を求める | getUser() | なし | 整数(0/1/2) |
コンピュータ側のじゃんけんの手を求める | getPc() | なし | 整数(0/1/2) |
ユーザの勝敗を判定する | judgeJanken() | ユーザの手(整数)、
コンピュータの手(整数) |
文字列 |
結果を表示する | showResult() | ユーザの手(整数)、
コンピュータの手(整数)、 判定結果(文字列) |
なし |
main()メソッドの内容(途中)
決定したメソッドをまずは、main()内に記述します。記述した内容は以下の通りです。
なお、まだ各メソッドの詳細内容を記述していないので、コンパイルエラーとなっていますが、気にする必要はありません。
※ この記事の最後には全てのコードを記載しています。
ユーザの手を取得:getUser()メソッド
では、まずユーザの手をキーボードから入力するメソッドを説明します。
キーボードからデータを入力させる場合、「Scannerクラス」を利用します。
Scannerクラスによるキーボード入力
キーボードからデータを入力する場合、Scannerクラスのコンストラクタに「System.in」を指定してインスタンスを作成します。この「System.in」は「標準入力」を意味し、デフォルトでは「キーボード」に該当します。
なお、「System.in」を具体的なファイル名に変更すると、指定したフィルからデータを読み込むことができます。
Scannerクラスに用意されている主なメソッド
キーボードからデータを入力する際に利用する主なメソッドは以下の通りです。
メソッド名 | 機能 | 戻り値 |
hasNextInt() | 次のトークンがint型ならtrueを返す | true/false |
hasNextDouble() | 次のトークンがdouble型ならtrueを返す | true/false |
hasNext() | 次のトークンがあればtrueを返す | true/false |
nextInt() | 次のトークンをint型として読み込む | データ(int型) |
nextDouble() | 次のトークンをdouble型として読み込む | データ(double型) |
next() | 次のトークンをString型として読み込む | データ(String型) |
nextLine() | 1行全てをString型として読み込む | データ(String型) |
トークンとは?
キーボードからデータを入力する場合、すぐにプログラムには読み込まれません。一度、バッファと呼ばれるメモリに保存されます。その際、空白(スペース)やタブを区切り文字として、入力された1行を分割します。その分割されたデータを「トークン」と呼びます。
例外処理
読み込んだデータを保存する変数のデータ型とトークンを読み込むメソッドの戻り値が異なる場合、例外(エラー)が発生します。したがって、try-catchによる例外処理か必要となります。
例えば、int型の変数にnextDouble()の値を代入しようとすると、データ型が異なるので例外が発生します。
例外処理の記述が面倒な場合は、hasNext系のメソッドを利用すれば、if文で代用することができます。例外処理の記述が不得意な人はこちらのメソッドを利用すると良いでしょう。
今回は例外処理を使わず、hasNext系のメソッドとif文でデータ型が正しいかどうかをチェックしています。
getUser()メソッドの記述内容
ユーザのじゃんけんの手をキーボードから入力してもらうメソッドの記述内容は以下のようになります。
無限ループ
プログラムを作成する場合、「ユーザがこちらの想定していなことをするかもしれない」という前提でプログラムを作る必要があります。「正しいデータが入力される」前提で作ってしまうと、もしユーザがプログラマが想定していなかった操作をした場合、エラーが発生し、ユーザの画面には無数の英語で表現されたエラーメッセージが表示されます。
ユーザはプログラムを壊してしまったかも? と混乱するかもしれません。ユーザにとって使いやすい安心なプログラムを作る責任がプログラマにはあります。
ユーザは1回で正しいデータを入力するかは不明です。何度も間違えるかも知れません。このような操作に対応するためには通常「無限ループ」を利用します。
正しいデータが入力されたかどうかをチェックし、正しい場合は無限ループを終了し、間違えている場合は無限ループを繰り返します。
読み込もうとしているトークンが整数かどうかのチェック
33行目のif文は「読み込もうとしているトークンが整数かどうか」をチェックしています。注意しなければならないのは、この「hasNextInt()」メソッドは実際にはトークンを読み込みません。単なる整数かどうかをチェックするだけです。
整数の場合、実際にトークンを変数に読み込んでいるのは、35行目の「nextInt()」メソッドになります。
読み込まんだ整数が不適切な値かどうかのチェック
整数のデータを読み込んだ場合、使いたいデータは「0,1,2」だけになります。したがって、これら以外のデータだった場合、再入力させる必要があります。
37行目で不適切なデータかどうかをチェックし、不適切な場合は40行目の「continue」を実行します。
「continue」はそれ以降の処理を無視し、繰り返しの先頭(27行目)に戻ります。つまり、再度データ入力を行わせます。
適切な値の場合は呼出し元に返す
37行目で不適切なデータかどうかをチェックし、適切な場合は43行が実行されます。return文は指定した変数の値をメソッドの呼び出しもとに返します。
getUser()メソッドはmain()メソッド内の8行目「int user=getUser()」で実行されているので、return文で返されたデータは「getUser()」部分に置き換えられ、その後変数userに代入されます。
※ メソッドを記述した結果、import文が追加され、実際の行番号は変化しています。
読み込もうとしているトークンが整数以外の場合
33行目の「hasNextInt()」の結果が「false」の場合、45行目以降が実行されます。
整数以外のデータなので、じゃんけんの手としては使用できません。したがって、再度入力を行わせます。この時、ただ単に次のデータの入力を実行させると、バッファの中に直前に入力している整数以外のデータが残ったままになり、次の「hasNextInt()」の結果も「false」になってしまいます。
つまり、キーボードから新しいデータの入力が全くできず、永遠にエラーメッセージ(47行目)と次の手の入力を促すメッセージ(29行目、30行目)が表示され、プログラムが止まらなくなってしまいます。
これを防ぐために不適切なデータをバッファから取り除く必要があります。49行目ではバッファ内に残っているトークンを文字列として読み込んでいます。
通常、読み込んだデータは変数に代入するようにプログラムを記述しますが、49行目では読み込んだデータを「=」で変数に代入していません。
このように読み込んだデータを変数に代入していない場合、読み込んだデータは消えてなくなります。
この結果、バッファ内には何もデータが残っていないので、次のデータ入力を行うことができます。
Scannerクラスをclose()するかどうか
Scannerクラスのインスタンスを作成すると、Elipseは「リソース・リーク:’stdin’は閉じられることはありません」という注意文を発します。コンパイルエラーではないので、気にする必要はありません。
「System.in」は「標準入力」を意味し、通常「キーボード」を指し示します。自分でオープン(用意)したリソースは自分でクローズ(メモリの開放)するのが鉄則ですが、標準入力を明示的にクローズしてしまうと、最悪キーボードが使えなくなってしまいます。
したがって、一般的には「標準入力」を自分でクローズする必要ありません。注意文が気になるかもしれませんが、気にしないように。
コンピュータの手を取得:getPc()メソッドの記述
コンピュータのじゃんけんの手を乱数で生成するメソッドの記述内容は以下のようになります。
getPc()メソッドの記述内容
0,1,2のどれかを乱数で求める
Java言語で乱数を求める方法はいくつかあります。しかし、一番簡単な方法はRandomクラスを利用する方法です。
まず、Random()クラスのコンストラクタを作成します(57行目)。
乱数は「nextInt()」メソッドで求めることができます。このメソッドの引数に、求めたい乱数の種類を指定します。今回のサンプルでは「3」を指定しています。乱数は「0から始まる整数」なので、「3」を指定すると、「0から始まる3種類の整数」を意味し、「0か1か2のどれか」となります。
分かりづらければ、「指定した数-1」までの範囲内で乱数を1つ求めます。「3」の場合、「0から2(3-1)までの範囲内」となるので、「0か1か2のどれか」となります。
getPc()メソッドはmain()メソッド内の11行目「int pc=getPc()」で実行されているので、return文で返されたデータは「getPc()」部分に置き換えられ、その後変数pcに代入されます。
※ メソッドを記述した結果、import文が追加され、実際の行番号は変化しています。
勝敗の判定:judgeJanken()メソッドの記述
では、次にじゃんけんの判定を行うjudgeJanken()メソッドの内容を考えてみましょう。
プログラムを記述する前に、じゃんけんの判定を実際に日本語で表現してみると以下の通りとなります。
①.もし、ユーザが0(グー)で、なおかつコンピュータが0(グー)なら、「あいこ」
②.もし、ユーザが0(グー)で、なおかつコンピュータが1(チョキ)なら、「勝ち」
③.もし、ユーザが0(グー)で、なおかつコンピュータが2(パー)なら、「負け」
④.もし、ユーザが1(チョキ)で、なおかつコンピュータが0(グー)なら、「負け」
⑤.もし、ユーザが1(チョキ)で、なおかつコンピュータが1(チョキ)なら、「あいこ」
⑥.もし、ユーザが1(チョキ)で、なおかつコンピュータが2(パー)なら、「勝ち」
⑦.もし、ユーザが2(パー)で、なおかつコンピュータが0(グー)なら、「勝ち」
⑧.もし、ユーザが2(パー)で、なおかつコンピュータが1(チョキ)なら、「負け」
⑨.もし、ユーザが2(パー)で、なおかつコンピュータが2(パー)なら、「あいこ」
この9つのパターンを全てプログラムで記述すれば良いわけです。
複数の条件をまとめる「論理演算子」
勝敗判定のパターン(①~⑨)のうち、同じ結果になる条件を1つのif文にまとめると、入力するプログラムの行数が多少減ります。
「勝ち」のパターンは、②、⑥、⑦
「負け」のパターンは、③、④、⑧
「あいこ」のパターンは、①、⑤、⑨
複数の条件をまとめる場合に利用する論理演算子は「&&」と「||」の2種類があります。
「&&」は「論理積」と呼ばれ、日本語では「~かつ~」で表現され、複数の条件が同時に成り立つ時だけ「真」を返します。
「||」は「論理和」と呼ばれ、日本語では「~または~」で表現され、複数の条件のうちどれか一つでも成り立てば「真」を返します。
judgeJanken()メソッドの記述内容
以上のことを踏まえて、勝敗判定の記述内容は以下の通りとなります。
65行目が「勝ち」のパターン、67行目が「負け」のパターン、69行目がそれ以外(「あいこ」)のパターンをそれぞれ判定しています。
上で説明した①~⑨の判定内容と比べてみて下さい。
※ メソッドを記述した結果、import文が追加され、実際の行番号は変化しています。
結果の表示:showResult()メソッドの記述
以上で全ての処理が終了したので、結果を表示するメソッドを記述していきます。
まず、じゃんけんの手を「グー、チョキ、パー」という文字列で表示したいので、それらをString型の配列に保存しておきます。
配列の添え字は「0」から始まるので、添え字「0」には「グー」、添え字「1」には「チョキ」、添え字「2」には「パー」の文字列を保存します。ちょうど、添え字とじゃんけんの手を表す値が一致するようにします。
あとは、メソッドを呼び出す時にユーザの手とコンピュータの手が引数として渡されているので、その値を配列の添え字に指定すると、それぞれの手が文字列として取得できます。
結果を表示するだけなのに、何故わざわざメソッドに分ける必要があるのか、不思議に思う人がいるかも知れません。
main()メソッド内に結果を表示するプログラムを記述しても問題ありません。しかし、プログラムを記述する場合、修正しやすいプログラムかどうかを意識してください。
今回は結果を画面に表示するのですが、「結果はファイルに保存する」と仕様が変更された場合、もしmain()メソッド内に直接記述していれば、main()メソッドの内のコードを変更しなければなりません。
main()メソッドはシステム全体に影響を与える重要なメソッドです。そのメソッド内のプログラムを頻繁に変更しなければならない場合、間違えて関係ない部分を書き換えてしまうかも知れません。
しかし、メソッドとして記述しておけば、変更するのは具体的な処理内容を記述しているメソッド内だけで、main()メソッド内は一切手を加える必要がありません。間違えて関係ない部分を書き換える危険性がゼロになります。
メソッドに分けて記述することは、メンテナンス性を向上させる利点があります。
※ メソッドを記述した結果、import文が追加され、実際の行番号は変化しています。
全コード
以下に全コードを記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package mygame; import java.util.Random; import java.util.Scanner; public class JankenIf { public static void main(String[] args) { // ユーザの手をキーボードから入力してもらう int user = getUser(); // コンピュータの手を乱数で作成する int pc = getPc(); // 勝敗の判定を行う String result = judgeJanken(user,pc); // 結果を表示する showResult(user,pc,result); } public static int getUser() { // キーボード入力の準備 Scanner stdin = new Scanner(System.in); // 無限ループ while(true) { // メッセージの表示 System.out.println("あなたのじゃんけんの手を入力して下さい"); System.out.print("(グー:0,チョキ:1、パー:2) --> "); // 入力されたデータが整数かどうかのチェック if(stdin.hasNextInt()) { // 入力されたデータを整数として読み込む int number = stdin.nextInt(); // 整数でも有効なのは「0,1,2」のみ if(number<=-1 || number >=3) { // 範囲外は無効なデータなのでやり直し System.out.println("【エラー】入力できるのは「0~2」です"); continue; }else { // 0,1,2の場合、メソッドの結果として返す return number; } }else { // 整数以外の場合、無効なデータなのでやり直し System.out.println("【エラー】入力できるのは整数だけです"); // 不要なトークンをバッファから取り除く stdin.next(); } } } public static int getPc() { // 乱数の準備 Random rand = new Random(); // 0,1,2のどれかを求め、メソッドの結果として返す return rand.nextInt(3); } public static String judgeJanken(int user,int pc) { String result=""; // 判定結果を保存する if((user==0 && pc==1) || (user==1 && pc==2) || (user==2 && pc==0)) { result="勝ち"; }else if( (user==0 && pc==2) || (user==1 && pc==0) || (user==2 && pc==1)) { result="まけ"; }else { result="あいこ"; } // 勝敗結果を返す return result; } public static void showResult(int user,int pc,String result) { // じゃんけんの手を配列で定義 String[] janken= {"グー","チョキ","パー"}; // 結果の表示 System.out.println("あなたの手:"+janken[user]+",コンピュータの手:"+janken[pc]); System.out.println("結果:"+result); } } |
実行結果
実際に今回のプログラムを実行した結果は以下の通りです。
入力チェックは次の5パターンです。
①.不適切なデータ(-1以下)
②.不適切なデータ(3以上)
③.整数以外のデータ(小数点を含む数値)
④.整数以外のデータ(文字列)
⑤.適切なデータ(0、1、2)のどれか
きちんと対応できていることが、確認できます。
まとめ
今回は、Java言語で「if文」を使って『じゃんけんゲーム』の作り方について説明しました。
私たちは日常、よくじゃんけんをしていますが、そのじゃんけんをプログラムで作ろうとすると、初心者の人は戸惑うかも知れません。
戸惑う大きな理由は、じゃんけんの判定を論理的にきちんと表現できないためです。何気なくおこなっているじゃんけんの判定には、明確なルールが存在します。そのルールをもれなく記述することがプログラムでは必要です。
初心者の人は、すぐにプログラムを入力しようとせず、一度プログラム内で行いたいことを文章で表現してみると良いでしょう。これは、「アルゴリズムを考え、フローチャートで表現する」ことになります。
フローチャートやアルゴリズムについては、以下の記事を参考にしてください。
・じゃんけんの勝敗ルールを全て洗い出す
・勝敗ルールをグループ化し、if文の条件として論理演算子を使って指定する
・プログラムはなるべく、メソッドに分けて記述する
じゃんけんプログラムの作り方は理解できました?
勝敗を判定する部分が曖昧だったのですが、きちんと洗い出すことが大事だと分かりました。
いきなり、プログラムを記述せず、まずはきちんと考えることが大事ですよ。
これから、じっくり考えるようにします。
ちなみにif文以外を使った場合はどのようなプログラムになるのですか?
if文と似ている「switch文」や配列を使って記述することができますよ。配列を使うとプログラムは非常に短くなります。
switchや配列を使ったサンプルの解説もぜひお願いします!!
クラスの作り方については以下の記事で詳しく解説しています。
クラスの継承については、以下の記事で詳しく説明しています。合わせて確認してみて下さい。