GameEngineを作る
まずは遊べるものができました。しかし、これをいきなりGUIのプログラムにするのは難しそうです。そこでルーチンを整理し、部品としてクラスを1つ作り、再利用しやすくします。名前は少し大仰ですが「GameEngine」としました。
要素を抽出
では、「CodeBreakerFirst」の要素を抽出し、メソッドやフィールドにしていきましょう。抽出すると言っても、私達は既に1度擬似コードを書いてますね。擬似コードであがった機能をそれぞれメソッドに、「CodeBreakerFirst」で使ったローカル変数をフィールドに置き換えてみればうまくいくのではないでしょうか? 試してみましょう。
フィールドを作る
フィールドとして必要なものを考えてみます。
- 解答用の配列
- 入力用の配列
- ヒット数の格納用変数
- ブロー数の格納用変数
- タイトル、ルールの格納用変数
- 解答数の保持用変数
- 乱数幅の保持用変数
といったあたりでしょうか。名称や型は、「CodeBreakerFirst」の物を踏襲する形にします。チャレンジ数を格納する変数については、今回は「GameEngine」ではなく、それを呼び出す側に管理を任せることにします。フィールドは、すべてprivateとし、getter/setterを設定します。ただし、setterについては、必要最低限のものにします。
メソッドを作る
「CodeBreakerFirst」のルーチンから、メソッドを作っていきます。Eclipseのリファクタリング機能の中にメソッドの生成のコマンドがあるので、とりあえず大枠を作るのに利用します。
- 解答製作用メソッド
makeAnswers - 入力受付用メソッド
inputAnswer - 判定処理用メソッド
judge
この3つが主な機能となります。ほかにgetter/setterがありますが、紹介は省略します。
コンストラクタを作る
GameEngineのコンストラクタでは、初回の解答の作成と各変数の初期化を行います。そのためコンストラクタを明示して記述する必要があります。また、将来の拡張のことも考え、コンストラクタは2つ作成します。違いは解答数を引数として与えるかどうかです。当初のルール通り3つの数字で行うならデフォルトコンストラクタを、数字を変更して初期化を行うならもう1つの引数を持つコンストラクタを使用します。
例外クラスを作る
CodeBreakerFirstでは、入力が異常だった場合、ルーチンで対応したわけですが、GameEngineとして外出しをするにあたって、それではちょっと困ります。せっかくJavaには例外という仕組みがあるわけですから、これを利用してみます。エラーメッセージの伝達に使うだけでも、結構便利になります。クラスの名称は「InputException」とします。
実際の作成
実際に「CodeBreakerSecond」と、「InputException」を作成します。実物は「net.sougetu.second」パッケージ内にあります。
package net.sougetu.second; /** * net.sougetu.first.CodeBreakerFirstクラスの各要素を * メソッドにしたもの。 * * @author ono */ public class GameEngine { /** タイトル */ private String title = "*** CodeBreaker ***";// タイトル /** ルール説明 */ private String rule = "隠された3つの数字をあてます。\n" + "1つの数字は1から6の間です。\n" + "3つの答えの中に同じ数字はありません。\n" + "入力した数字の、" + "位置と数字が当たってたらヒット、\n" + "数字だけあってたらブローとカウントします。\n" + "全部当てたら(3つともヒットになったら)" + "終了です\n\n"; /** 解答を格納する配列 */ private int[] answer; /** 入力した内容を保持する配列 */ private int[] input; /** 答えの数字の数。将来的に拡張する事を考慮して作成 */ private int numberOfAnswers = 3; /** 乱数の幅。この場合、1から6までとなる */ private int widthOfRandom = 6; /** ヒットの数 */ private int hit = 0; /** ブローの数 */ private int blow = 0; /** * デフォルトコンストラクタ。 * numberOfAnswersがデフォルト値の3のまま運用する場合 */ public GameEngine() { answer = new int[numberOfAnswers]; input = new int[numberOfAnswers]; makeAnswers(); } /** * numberOfAnswersを変更し、 * 解答の数字の数を3以外にしたい場合に使う */ public GameEngine(int numberOfAnswers) { this.numberOfAnswers = numberOfAnswers; answer = new int[numberOfAnswers]; input = new int[numberOfAnswers]; makeAnswers(); } /** * 答えを作成する。 * 答えの数が変更されることも考え、numberOfAnswersと照合する。 */ public void makeAnswers() { if (answer.length != numberOfAnswers) { answer = new int[numberOfAnswers]; input = new int[numberOfAnswers]; } for (int i = 0; i < answer.length; i++) { //自分より前の要素にかぶるやつがないか確かめる。 //あったらもう1回random boolean flag = false; answer[i] = (int) (Math.random() * widthOfRandom + 1); do { flag = false; for (int j = i - 1; j >= 0; j--) { if (answer[i] == answer[j]) { flag = true; answer[i] = (int) (Math.random() * widthOfRandom + 1); } } } while (flag == true); } } /** * 要素を指定して答えを入力する。indexは0から。 * * @param index * @param answer * @throws Exception */ public void inputAnswer(int index, int answer) throws InputException { if (index > -1 && index < numberOfAnswers) { if (answer > 0 && answer <= widthOfRandom) { input[index] = answer; } else { throw new InputException("入力が答えの範囲外です"); } } else { throw new InputException("入力する場所を間違っています"); } } public void inputAnswer(int index, String string) throws InputException { int answer; try{ answer = Integer.parseInt(string); }catch(NumberFormatException e){ throw new InputException("入力が答えの範囲外です"); } inputAnswer(index, answer); } /** * inputとanswerを突き合わせる。 * すべてhitならtrue、それ以外はfalse。 * * @return すべてhitならtrue、それ以外はfalse。 */ public boolean judge() { //答え判断 hit = 0; blow = 0; for (int i = 0; i < answer.length; i++) { for (int j = 0; j < answer.length; j++) { if (i == j && input[i] == answer[j]) { hit++; } else if (input[i] == answer[j]) { blow++; } } } return (hit == numberOfAnswers); } public int getNumberOfAnswers() { return numberOfAnswers; } public int getWidthOfRandom() { return widthOfRandom; } public int[] getAnswer() { return answer; } public int getBlow() { return blow; } public int getHit() { return hit; } public int[] getInput() { return input; } public void setInput(int[] input) throws InputException { for (int i = 0; i < input.length; i++) { inputAnswer(i, input[i]); } } public String getRule() { return rule; } public void setRule(String rule) { this.rule = rule; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
package net.sougetu.second; /** * @author ono */ public class InputException extends Exception { public InputException(String str) { super(str); } }
GameEngineを使って書き直す
今度は、先に作成したCodeBreakerFirstを、GameEngineを使う形に書き直します。名前は「CodeBreakerSecond」です。「net.sougetu.second」パッケージにあります。
「GameEngine」を作った後、すぐにGUIにしてしまいたいと思うかもしれません。しかし、ここで既存のものを置き換えて不具合を確認しておかないと、GUIに移って不具合が起きたとき、GUIとGameEngineのどちらで起きている不具合なのか、原因の切り分けをやりにくくなってしまいます。
作成に慣れている人の場合、GameEngineを作成する段階でどのような不具合が起きるのか、どのように置き換えていけばいいのか想定することができます。しかし、初心者の場合、そういう想定を行うことは非常に困難です。そのため、退屈なようでも一歩一歩確実に歩を進めることが重要です。
package net.sougetu.second; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author ono */ public class CodeBreakerSecond { /** * @param args */ public static void main(String[] args) { GameEngine ge = new GameEngine(); /* * countは何回目のチャレンジかを数えている。 */ int count = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println(ge.getTitle()); System.out.println(ge.getRule()); //ランダムな答えを作成する ge.makeAnswers(); while (true) { count++; System.out.println("*** "+count + "回目 ***"); //インプット for (int i = 0; i < ge.getNumberOfAnswers(); i++) { System.out.print( (i + 1) + "個目 : "); try { ge.inputAnswer(i,br.readLine()) ; } catch(InputException e){ System.err.println(e.getMessage()); i--; } catch (IOException e) { System.err.println("もう一度入力してください"); i--; } } //答え判断 boolean end=ge.judge(); //終了判断 System.out.println("ヒット" + ge.getHit() + " ブロー" + ge.getBlow()); if (end) { System.out.println("おめでとー"); break; }else{ System.out.println(); } } } }
ゲームロジックをGameEngineとして部品化したことで、見た目が多少すっきりし、メンテナンスもしやすくなったのではないかと思います。次はGUIにとりかかります。
