まずは,単語表を作る.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.*; public 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は10/21 課題問題にも出たように, パッケージ 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.*; public 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 クラスのコンストラクタ public 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 に含まれているかどうかを判定する動的メソッド. public boolean isGuessed(char c){ // Stringクラスのメソッド indexOf は,文字が文字列に含まれないと // きは -1 を返す. if(guessed.indexOf(c)!= -1) return true; else return false; } // guessed に文字 c を加える. public void guess(char c){ // String.valueOf(c)で文字1文字からなる文字列ができるので, // +=で文字列の最後に付け加えることができる. guessed+=String.valueOf(c); } // result中の文字すべてが,guessed に入っているかどうか? public 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 されていない文字は「_」に置き換えた文字列を作る public 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つ 重ねているということである.