プログラミング言語/Ruby/コードリーディング/しりとりゲーム
class ManualPlayer < Player include Rands def answer(before) def before_input private def read_answer(before, prompt, err_msg)
公開されているインタフェース(メソッド)はanswerとbefore_inputである。 要件上、人間が負けた場合はその際の回答を聞きたいので、AutoPlayerの publicメソッドがanswerだけなのだけに比べてbefore_inputを用意した。
def answer(before) before = char_rand unless before @wd = read_answer(before, "[#{before[-1, 1]}...] > ", "エラー: もう一度入力してください") @gm.twice?(@wd) ? nil : @wd end
beforeに渡されているのはGameMasterの@beforeであるが、 それは始めnilに初期化されている。 つまり一回目の回答、前の言葉をしりとりすることができない場合を、 beforeがnilのときとしている。
RandsモジュールからMix-inされたchar_randは、"a"から"z"の間の長さ1の 文字列をランダムに返す。"a"という言葉をしりとりすればaから始まる言葉 になるのでこれで要件を満たす。
そして、そのbeforeを根拠に何やらread_answerと、おそらくプレイヤーである 人間に回答を要求し、答えた単語を@wdに保持する。
既出の回答を人間がすると負けなので、@gm(自らのGameMaster)にtwice?を 問い合わせて、既出だったらnil=回答不能を返す。
def before_input @wd end
人間の最新の回答は@wdに保持されているので、それを返す。
def read_answer(before, prompt, err_msg) line = nil loop do $stderr.print prompt line = $stdin.gets or raise Interrupt line.strip! break if /\A#{before[-1, 1]}\w*/ =~ line && @dictionary.include?(line) $stderr.puts err_msg end line end
RubyにはGNU Readlineライブラリを利用するインタフェースであるreadline.soという 拡張ライブラリが標準で用意されているが、今回はいろいろ不都合があったので 使用を見送る。
このメソッドの本筋はloop内の処理だが、始めにlineにnilを代入しているのは GameMaster#log_searchの説明で語ったローカルスコープ対策である。
標準入力によって人間にしりとりの回答を要求するが、EOF(UNIXならCTRL+C, WindowsならCTRL+Z)を送られるとInterrupt例外を挙げる。 これはrubyがスクリプトを実行中にCTRL+Cされると挙げるものだ。
そして、その入力から、改行含め前後の余計な空白をString#strip!で剥ぎ取っている。
break if /\A#{before[-1, 1]}\w*/ =~ line && @dictionary.include?(line)
loopは渡されたブロック内の処理を無限に繰り返すメソッドである。そのブロックの中で ループを終わらせるのはここだけである。
String#[idx, len]はそのStringオブジェクトのidxバイト目からlenの長さの部分を返す。 idxが負の場合、文字列の終端からの勘定となる。 つまりbefore[-1, 1]は、beforeの最後の一文字ということだ。
例えばbeforeが"apple"ならば、リスト3-5の正規表現は式展開をうけて{{br}} /\Ae\w*/となり、eから始まる単語にマッチする。
このマッチが成功すればしりとりが正しく成されているということであり、 さらに辞書にちゃんと書いてある単語だったら回答としてオッケーなので 入力の要求を切り上げて終わり、というわけだ。