MOO は, Hit & Blow, Cow & Bull などさまざまな名で呼ばれ, 親しまれて きた数当てゲームである. ゲームのルールは以下のようになっている.
人間同士でプレイするのも楽しいが,その場合は相手が必要であるし,bull 数とcow数を間違えて答えられたりするとゲームそのものが成り立たなくなっ てしまう.そこで,コンピュータに出題者をさせるプログラムを作成してみる.
| コンピュータに解答させるプログラムはそれはそれで面白い.かなり単純な プログラムでも人間のエキスパートレベルに達するし,工夫をすると更に良く なる.なお,平均質問数を最小にする戦略は計算されている. |
また,繰り返しゲームをプレイするプログラムを作るか,1回だけゲームをプ レイするプログラムにするかが問題になる.プログラムの作成しやすさを考え て,1回だけゲームをプレイするプログラムにしてみよう.
ktanaka@ux019> java Kadai1105 Guess a Moo number 入力を促す1行(メッセージは任意を表示) 0123 4桁の数字 1:1B1C メッセージ内部に 「数字」「大文字B」「数字」「大文字C」 という文字列(すべていわゆる半角)を含む 0245 2:0B0C 6137 3:1B2C 3178 4:2B1C 3179 5:2B0C 3168 6:2B2C 3186 7:4B0C 正解の時も4B0C を表示する Correct answer in 7
プログラム全体の流れは以下のようになるはずである.
// Moo数を表すクラス
class MooNumber{
}
まずは,MOO数をどのようなインスタンス変数で保持するか考える.10000以
下の整数でも表せるし,MOO積を高速に計算するためには,どの数が使われて
いるかというビットの配列を使うなどのテクニックもあるが,今回のプログラ
ムは速度を使わないので,4桁の10進数に対応した配列で表現して良い.コン
ストラクタの中で配列の実体を確保しても良いが,インスタンス変数の初期化
でおこなっても良い.
// Moo数を表すクラス
class MooNumber{
// 4桁の10進表現に対応した配列
int digits[]=new int[4];
}
コンストラクタは2種類用意する.まずは問題を出題する際に,乱数でMOO数
を作るためのもので,これは引数なしで呼ぶ.次は,ユーザの入力した文字列
からMOO数を作成する時で,これは文字列を引数として呼ぶ.まずは,乱数で
MOO数を作るための方法を見ていく.乱数を生成するには,Random
クラスを使う.このクラスのインスタンスを作成してから,nextInt(int)
メソッドを呼んで,0から指定した数までの一様乱数を作成することができる.
MOO数を作るには,0123456789という列を作ってから,0(番目の要素)と 0-9 (の範囲の乱数を添字とする tmpDigitsの要素)の入れ換え,1と1-9,2と 2-9, 3と3-9を入れ換えていく.以下のようになる.
// 乱数でのMOO数の生成
// 0123456789を並び替えて最初の4桁
MooNumber(){
int tmpDigits[]=new int[10];
int i;
// tmpDigitsに 0123456789を入れる
for(i=0;i< 10;i++){
tmpDigits[i]=i;
}
// 乱数を生成するためのクラス
Random r=new Random();
// 0 と 0-9(の範囲の乱数を添字とする tmpDigitsの要素)を,
// 1と1-9を, 2と2-9を,3と3-9を入れ換える
for(i=0;i< 4;i++){
int j=i+r.nextInt(10-i);
digits[i]=tmpDigits[j];
tmpDigits[j]=tmpDigits[i];
}
}
文字列を元にしたコンストラクタは実現が簡単だが,正しくない入力があっ
た時に,例外を返す必要がある.本来は Exceptionのサブクラスを作っておく
のが良いが,ここではさぼって,Exception のインスタンスを throw するよう
にする.
// 文字列から対応するMoo数を作る
MooNumber(String str) throws Exception{
if(str.length() != 4){
throw new Exception("Not a Moo Number");
}
// 同じ数字が2回使われないかどうかのチェック
boolean used[]=new boolean[10];
for(int i=0;i< 4;i++){
// strのi番目の文字が 0-9にあてはまるか?
int index="0123456789".indexOf(str.charAt(i));
// 0-9以外の文字か,index番目の数字が既に使われた時
if(index == -1 || used[index]){
throw new Exception("Not a Moo Number");
}
// index番目の数字が使われた
used[index]=true;
// インスタンス変数 digitのi桁目をindexにする.
digits[i]=index;
}
}
MooNumberクラスの重要なメソッドとして,別のMoo数とのMoo積を求めるとい
うことがある.MOO積を bull, cowのペアとして一度に扱うのも可能だが,こ
こでは bull を求める getBull, cowを求める getCowを別に定義する.
// 別のMoo数との間のbullを数える
int getBull(MooNumber m){
int bull=0;
for(int i=0;i< 4;i++)
if(digits[i]==m.digits[i])
bull++;
return bull;
}
// 別のMoo数との間のcowを数える
int getCow(MooNumber m){
int cow=0;
for(int i=0;i< 4;i++)
for(int j=0;j< 4;j++)
if(i!=j && digits[i]==m.digits[j]){
cow++;
break;
}
return cow;
}
デバッグ(プログラムのミスをチェックする)などのために,MooNumberクラス
のインスタンスを直接(見易く)表示すると嬉しい.そのためには,public
String toStringメソッドを定義しておくと良い.
// デバッグ用など表示に使う.
public String toString(){
return ""+digits[0]+""+digits[1]+""+digits[2]+""+digits[3];
}
// Randomクラスを使うために必要
import java.util.*;
// キーボード入力のために必要
import java.io.*;
// Moo数を表すクラス
class MooNumber{
// 4桁の10進表現に対応した配列
int digits[]=new int[4];
// 乱数でのMOO数の生成
// 0123456789を並び替えて最初の4桁
MooNumber(){
int tmpDigits[]=new int[10];
int i;
// tmpDigitsに 0123456789を入れる
for(i=0;i< 10;i++){
tmpDigits[i]=i;
}
// 乱数を生成するためのクラス
Random r=new Random();
// 0 と 0-9(の範囲の乱数を添字とする tmpDigitsの要素)を,
// 1と1-9を, 2と2-9を,3と3-9を入れ換える
for(i=0;i< 4;i++){
int j=i+r.nextInt(10-i);
digits[i]=tmpDigits[j];
tmpDigits[j]=tmpDigits[i];
}
}
// 文字列から対応するMoo数を作る
MooNumber(String str) throws Exception{
if(str.length() != 4){
throw new Exception("Not a Moo Number");
}
// 同じ数字が2回使われないかどうかのチェック
boolean used[]=new boolean[10];
for(int i=0;i< 4;i++){
// strのi番目の文字が 0-9にあてはまるか?
int index="0123456789".indexOf(str.charAt(i));
// 0-9以外の文字か,index番目の数字が既に使われた時
if(index == -1 || used[index]){
throw new Exception("Not a Moo Number");
}
// index番目の数字が使われた
used[index]=true;
// インスタンス変数 digitのi桁目をindexにする.
digits[i]=index;
}
}
// 別のMoo数との間のbullを数える
int getBull(MooNumber m){
int bull=0;
for(int i=0;i< 4;i++)
if(digits[i]==m.digits[i])
bull++;
return bull;
}
// 別のMoo数との間のcowを数える
int getCow(MooNumber m){
int cow=0;
for(int i=0;i< 4;i++)
for(int j=0;j< 4;j++)
if(i!=j && digits[i]==m.digits[j]){
cow++;
break;
}
return cow;
}
// デバッグ用など表示に使う.
public String toString(){
return ""+digits[0]+""+digits[1]+""+digits[2]+""+digits[3];
}
}
class Kadai1105{
public static void main(String [] args) throws Exception{
// この部分を埋めて下さい.
}
}
/home/ktanaka/bin/report1105 1を実行する.プログラムのコンパイル実行をチェックした上で,プログラムを コピーし,実行結果を記録する.なお,このプログラムを通っても正しくない プログラムはありうる.その場合は減点されることもある.
class MooTest {
public static void main(String[] args){
// 乱数でMooNumberを作る
MooNumber n1=new MooNumber();
// toString() を定義しているので、表示される。
System.out.println(n1);
// MooNumberを作成中にExceptionが発生する可能性があるので
// tryでくるむ
try{
// 文字列からMooNumberを作る。
// ここでExceptionが発生する可能性がある。
MooNumber n2=new MooNumber("0123");
System.out.println(n2);
// Bullを計算して表示
System.out.print(n1.getBull(n2)+"B");
// Cowを計算して表示
System.out.println(n1.getCow(n2)+"C");
}
catch(Exception e){
System.out.println("Moo Numberではありません。");
}
}
}
実行例は以下のようになる(乱数の関係で結果は毎回異なる)。
ktanaka@nc01913> java MooTest 5637 0123 0B1C