Go to Previous Page Go to Contents Go to Java Page Go to Next Page
Second Step of Java
 
 

そして、もう一度...

 
  忘れられたチェック  
 

できあがった Mastermind で遊んでいる時に、間違って入力する桁数を間違えたことはないですか。前節ではユーザの回答の入力に対して、「数字であるか」と「同じ数字が使われていないか」という 2 種類のチェックを行っていました。

ところが、入力するときに桁数を間違えてしまうと

C:\java\mastermind>java Mastermind
Let's Play Mastermind.
1回目のトライ
数字を入力してください
>12345
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at Solver.getAnswer(Solver.java:62)
        at Mastermind.playGame(Mastermind.java:24)
        at Mastermind.main(Mastermind.java:100)

C:\java\mastermind>

このように例外が発生してプログラムが終了してしまいます。この例外は配列の大きさ以上の要素にアクセスしたときに発生します。桁数は多くても少なくてもいけないので、正しい桁数の数字が入力されたかどうかのチェックを行わないといけないということです。

さっそく、ソースを書き換えていきましょう。ユーザからの入力の処理を行うのは Solver クラスのオブジェクトですが、Solver オブジェクトは桁数に関する情報をもっていません。どうすれば、桁数が合っているかどうかを確かめられるのでしょうか。

桁数は Mastermind クラスの定数なので、Mastermind クラスに聞きにいくことにしましょう。このために Mastermind クラスにひとつ関数 getAnswerFigure を追加しましょう。

    public static int getAnswerFigure(){
        return answerFigure;
    }

単に桁数を帰すだけですが、変数 answerFigure はstatic な変数なので、Mastermind オブジェクトに問い合わせる必要はありません。そこで、getAnswerFigure 関数も static にしてみました。

まず回答を入れる配列の定義の部分を getAnswerFigure 関数を使用して書きかえてみます。

    public Solver(){
        answer = new int[Mastermind.getAnswerFigure()];
    }

また、getAnswer 関数の桁数チェックの部分を示します。

    while(true){
        try{
            String answerText = reader.readLine();
 
            // 桁数のチェック
            if(answerText.length() != Mastermind.getAnswerFigure()){
                System.out.println("回答は" + Mastermind.getAnswerFigure() + "桁です");
                System.out.println("もう一度入力してください");
                System.out.print(">");
                continue;
            }

入力を受け取るとまず桁数のチェックをします。文字列の長さは length 関数で調べることができます。これと getAnswerFigure 関数の戻り値が等しいか比較して、異なっていればメッセージを出力して、continue 文でもう一度 while ループに戻ります。

面白いことに、数字を全角で入力しても下に示したように、正しく処理を行うことができます。String#length 関数は文字が半角か全角であるにかかわらず、正しく文字数を帰すようになっています。

C:\java\mastermind>java Mastermind
Let's Play Mastermind.
1回目のトライ
数字を入力してください
>1234
Blow : 1 Hit : 0
2回目のトライ
4桁の数字を入力してください
>

また、Integer#parseInt 関数も全角数字でも正しく int 型に変換することができるため、正しく処理が行われていくのです。

 

 
  マジックナンバを探せ  
 

プログラム中にある特定の数字が埋め込んでしまうことはありませんか。たとえば、Mastermind クラスの playGame 関数の中で赤字で示してあるような数字です。

    public void playGame(){
        System.out.println("Let's Play Mastermind.");
        for(int i = 0 ; i < 10 ; i++){
            System.out.println((i+1) + "回目のトライ");
            int[] answer = solver.getAnswer();
 
            int blow = countBlow(answer);
            int hit = countHit(answer);
            solver.setBlowAndHit(blow, hit);
 
            boolean result = checkAnswer(hit);
            solver.setResult(result);
            if(result){
                break;
            }
        }
 
        System.out.print("正解は ");
        for(int i = 0 ; i < answerFigure ; i++){
            System.out.print(correctAnswer[i]);
        }
        System.out.println(" でした");
    }

このように意味のある数字をソース中に直接書いてしまったものをマジックナンバといいます。マジックナンバが放置されていると、そこはバグの温床になってしまいます。

上の playGame 関数ではマスターマインドの回答できる回数をマジックナンバにしています。回数を 10 回から 15 回に変更するときは、直接 10 を 15 に変更すれば可能です。しかし、この回答できる回数がプログラムの他の部分にも書かれていたらどうでしょうか。1 個所であれば変更することは簡単ですが、複数の場合すべてを同時に変更しなくてはならないくなってしまいます。人間がやることですから、1 箇所ぐらい忘れてしまうかもしれません。それがバグになってしまうわけです。

したがって、マジックナンバは極力減らすようにしたほうが、バグが少なくなります。減らすためには定数を使うのが一般的です。たとえば、上の例では、まず maxLoopNumber という定数で回答できる回数を定義してしまいます。

    // 繰り返しの回数
    private static final int maxLoopNumber = 10;

そして、マジックナンバを使っていた部分はこの定数を使うように変更します。

    public void playGame(){
        System.out.println("Let's Play Mastermind.");
        for(int i = 0 ; i < maxLoopNumber ; i++){
            System.out.println((i+1) + "回目のトライ");
            int[] answer = solver.getAnswer();
 
            int blow = countBlow(answer);
            int hit = countHit(answer);
            solver.setBlowAndHit(blow, hit);
 
            boolean result = checkAnswer(hit);
            solver.setResult(result);
            if(result){
                break;
            }
        }
 
        System.out.print("正解は ");
        for(int i = 0 ; i < answerFigure ; i++){
            System.out.print(correctAnswer[i]);
        }
        System.out.println(" でした");
    }

このようにすると、回答できる回数を変更したいときは、定数の定義を変更するだけで、他の部分を変更する必要がありません。バグになる可能性はそれだけ減ったことになります。

 

 
  そしてクラスは  
 

クラスの定義は最終的には次のようになりました。変更した部分は赤字にしてあります。

Mastermind クラス

Mastermind のゲームを管理するクラス
正解の生成や回答者からの回答を問い合わせを行い、Blow と Hit を算出する

属性 private int[] correctAnswer 正解となる数字
要素は正解の桁の数字
private static final int answerFigure 正解の桁数
private static final int maxLoopNumber 回答できる回数
メンバ関数 public void playGame() Matermind を開始して、ゲームを行う
private void makeCorrectAnswer() 正解を作成する
private int countBlow(int[] answer) 回答から Blow をカウントする
private int countHit(int[] answer) 回答から Hit をカウントする
private boolean checkAnswer(int hit) 正解かどうかを調べる
public int getAnswerFigure() 正解の桁数を帰す

Solver クラス

Mastermind ゲームの回答者
問題作成者に対し、回答を行う。

属性 private int[] answer 回答
各桁の数字が配列の要素になる
private int blow Blow の数
private int hit Hit の数
private boolean result 回答が正解かどうかを示すフラグ
メンバ関数 public int getAnswer() 回答を行う
public void setBlowAndHit(int blow, int hit) Blow 数と Hit 数の設定を行う
public void setResult(boolean result) 正解かどうかを示す

説明はしませんでしたが、実装の部分で実行の効率を上げるために何箇所か変更を加えています。どこが変更されたのか比べてみて、なぜ変更したか考えてみるのもいいかもしれません。

 

 
 

作成したソースファイルとコンパイルを行ったクラスファイルはここでダウンロードできます mastermind2.zip

(Sep. 2000)

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page