まずは,単語表を作る.Unixには /usr/share/dict/words(ただし,センター のUnixサーバでは/usr/dict/words)というファイルがあって,代表的な単語の 綴りが以下のようにABC順に並んでいる.
Aarhus Aaron Ababa aback abaft abandon abandoned abandoning abandonment abandons abaseしかし,これを問題に使うのは,
// 入出力関係のクラスを使うため, java.io.* をimport
import java.io.*;
// Randomクラスを使うため, java.util.* をimport
import java.util.*;
class SelWords {
// 途中で IOException 例外が発生することがあるため,throws IOException
// と記述する.
static void selectWords() throws IOException{
FileReader fr=null;
try{
// まずは /usr/share/dict/words を開いてみる
fr=new FileReader("/usr/share/dict/words");
}
catch(FileNotFoundException e){
// 失敗したら /usr/dict/words を開いてみる
fr=new FileReader("/usr/dict/words");
}
// 更に,BufferedReaderを作成.変数 br に代入.
BufferedReader br=new BufferedReader(fr);
// 行入力した結果の単語を入れる変数 s の宣言
String s;
// 乱数の種になる Random クラスのインスタンスの生成
Random r=new Random();
// break が出現するまで無限ループ
for(;;){
// 入力用 BufferedReaderであるbrから一行入力.ファイルの終端に
// 来たときは null となるので,ループから抜ける.
if((s=br.readLine())==null)
break;
// 読み込んだ単語の長さが8文字以上で,
if(s.length()>7 &&
// 0番目の(最初の)文字が,英小文字(lower case)で
Character.isLowerCase(s.charAt(0)) &&
// 整数の乱数を1つ取ってきたものがたまたま20で割りきれた場合は,
r.nextInt(20)==0)
// そのl単語を出力する
System.out.println(s);
}
// 入力ファイルを close する.
br.close();
}
// IOExceptionは本体の try-catch文 で捕まえてしまうので,
// throws IOExxceptionは不要
public static void main(String args[]){
try {
selectWords();
} catch (IOException e){
System.out.println(e);
}
}
}
上のプログラムをコンパイルして,
java SelWord > wordsと実行すると,wordsというファイルに1000単語程度が選択されて残る.
プログラム中に出てくるRandomは パッケージ java.util に含まれるクラスで,擬似乱数系 列を生成する.
Random r=new Random();として,インスタンスを作ってから,r.nextInt(n) を呼び出すと,0-(n-1)の範囲の 整数の擬似乱数を生成する.
Character.isLowerCase(s.charAt(0))は,s.charAt(0) で文字列sの0番目(最初の)文字を取っていて, Characterの isLowerCase()を呼び出すことによって,英小文字であ ることをチェックしている.
上で作られたファイルが /home/ktanaka/java/words だと仮定して, Hangmanのプログラムを書いてみる.クラス名は Hangman とする.まずは,擬 似乱数を使って適当な単語を1つ取ってくるメソッドgetRandomWordを書いてみ る.
簡単に書くために,一度,行数を数えてから乱数で何行目を正解にするかを決 める.そのあと,余分な行を読み飛ばしてから,一行を読んで記憶して返す. 関連するデータフィールド,メソッドは以下のようになる.
// 先ほど作った辞書ファイルのファイル名の宣言
static String dictFileName="/home/ktanaka/java/words";
String getRandomWord() throws IOException{
BufferedReader br=new BufferedReader(new FileReader(dictFileName));
int lineCount;
for(lineCount=0;;lineCount++){
if(br.readLine()==null)
break;
}
br.close();
Random r=new Random();
int selectedLine=Math.abs(r.nextInt()) % lineCount;
br=new BufferedReader(new FileReader(dictFileName));
for(lineCount=0;lineCount< selectedLine;lineCount++){
br.readLine();
}
String result=br.readLine();
br.close();
return result;
}
Hangman のプログラムを作る際に,Hangmanクラスのインスタンスを作るかど
うかで設計が分かれるが,ここでは作るという方針でやってみる.mainは,
Hangman クラスのインスタンスを作って,startGameを呼び出すだけにする.
public static void main(String args[]){
Hangman hangman=new Hangman();
hangman.startGame();
}
インスタンスのデータフィールドとして,正解の文字列 result, キーボード
入力のストリームsi, これまでに試した文字をまとめて文字列にした guessed
などを作る.
String result; BufferedReader si; String guessed;他に,有用なメソッドとして以下のようなものも定義する.
import java.io.*;
import java.util.*;
class Hangman {
static String dictFileName="/home/ktanaka/java/words";
String result;
// キーボードから入力するための BufferedReader クラスの変数si の宣言
BufferedReader si;
// これまでに,guess した文字の列
String guessed;
String getRandomWord() throws IOException{
BufferedReader br=new BufferedReader(new FileReader(dictFileName));
int lineCount;
for(lineCount=0;;lineCount++){
if(br.readLine()==null)
break;
}
br.close();
Random r=new Random();
int selectedLine=Math.abs(r.nextInt()) % lineCount;
br=new BufferedReader(new FileReader(dictFileName));
for(lineCount=0;lineCount< selectedLine;lineCount++){
br.readLine();
}
String result=br.readLine();
br.close();
return result;
}
// Hangman クラスのコンストラクタ
Hangman(){
try {
// キーボード入力用の BufferedReader の作成
si=new BufferedReader(new InputStreamReader(System.in));
// 解答の作成
result=getRandomWord();
} catch (IOException e){
// 例外が起きたときは,例外の内容を表示して,
System.out.println(e);
// プログラムを終了する.
System.exit(1);
}
// まだ何も guess していないので,空文字列を入れておく
guessed="";
}
// ある文字 c が,guessed に含まれているかどうかを判定するインスタンスメソッド.
boolean isGuessed(char c){
// Stringクラスのメソッド indexOf は,文字が文字列に含まれないと
// きは -1 を返す.
if(guessed.indexOf(c)!= -1) return true;
else return false;
}
// guessed に文字 c を加える.
void guess(char c){
// String.valueOf(c)で文字1文字からなる文字列ができるので,
// +=で文字列の最後に付け加えることができる.
guessed+=String.valueOf(c);
}
// result中の文字すべてが,guessed に入っているかどうか?
boolean isAllGuessed(){
int i;
// resultの長さまでの間
for(i=0;i< result.length();i++){
// resultの i 番目の文字が,まだ guess されていない場合は
if(!isGuessed(result.charAt(i)))
// false(偽) を返す
return false;
}
// すべての文字が guessed中に含まれているので true を返す.
return true;
}
// result の中で,guess されていない文字は「_」に置き換えた文字列を作る
String resultToString(){
int i;
char c;
// 結果は文字列 r にためていく
String r="";
// resultの長さまでの間
for(i=0;i< result.length();i++){
// resultの i 番目の文字が,まだ guess されている時は
if(isGuessed(c=result.charAt(i))){
// その文字をr に付け加え
r+=String.valueOf(c);
}
else{
// そうでないときは「_」をr に付け加え
r+="_";
}
}
// 最後に r を返す.
return r;
}
// 失敗したした回数が failCountの時に,表示すべき絞首台の文字列
String failString(int failCount){
// 回数によって変わる部分を文字列の配列にしてある.見易いように
// 文字列は行の切目で折り返してある.
String [] failStr={
" | \n"+
" | \n"+
" | \n"+
" | \n",
" | O \n"+
" | \n"+
" | \n"+
" | \n",
" | O \n"+
" | | \n"+
" | \n"+
" | \n",
" | O \n"+
" | | \n"+
" | | \n"+
" | \n",
" | O \n"+
" | | \n"+
" | | \n"+
" | / \n",
" | O \n"+
" | | \n"+
" | | \n"+
" | / \\\n",
" | O \n"+
" | /| \n"+
" | | \n"+
" | / \\\n",
" | O \n"+
" | /|\\\n"+
" | | \n"+
" | / \\\n"};
// failCountによって,不変な部分を連結して返す.
return
" _____\n"+
" | |\n"+
failStr[failCount]+
" __|_____\n"+
" | |___\n"+
" |_________|";
}
// ゲームの本体
void startGame(){
int failCount=0;
char c;
String s="";
// 失敗が7回にならない間
while(failCount<7){
// 絞首台の表示
System.out.println(failString(failCount));
// 正解のうち guess した部分だけの表示
System.out.println(resultToString());
// 入力促進用のプロンプトの表示
System.out.print(" Guessed "+guessed+" : Guess? ");
System.out.flush();
try{
// 標準入力siから一行入力して変数 s に入れる.
s=si.readLine();
} catch(Exception e){
System.out.println(e);
System.exit(1);
}
// 文字列の長さ1でなかったり,小文字でなかったり,すでに
// 解答済の文字だったときには
if(s.length()!=1 || !Character.isLowerCase(c=s.charAt(0)) ||
isGuessed(c)){
// 失敗の数は増やさず,エラーを表示してループの最初に戻る.
System.out.println("Guess an unguessed lowercase character");
continue;
}
// guessed に1加える
guess(c);
// resultの中に文字 c が出てこなかった場合
if(result.indexOf(c)== -1){
// failCountを増やす.
failCount++;
}
// もしも,result中のすべての文字が guess された時は
else if(isAllGuessed()){
// 正解を表示して終了
System.out.println("Congraturation ");
System.out.println(resultToString());
System.exit(0);
}
}
// 7回失敗した時は,絞首台を表示して,正解も表示
System.out.println(failString(failCount));
System.out.println("The word is "+result);
}
public static void main(String args[]){
Hangman hangman=new Hangman();
hangman.startGame();
}
}
failStringメソッドのfailStrの定義のところで,「\\」というのがいくつ
も出てくる.これは,文字列中の「\」はエスケープ文字として特別な意味を
持つ,たとえば「\n」は改行を表すので,「\」自体を表すために「\\」と2つ
重ねているということである.