プログラミング言語/Ruby/コードリーディング/しりとりゲーム

AutoPlayerクラス

リスト4-1 AutoPlayerインタフェース

class AutoPlayer < Player
  include Rands
  def answer(before)
private
  def think_answer(before)

スーパークラスを同じくPlayerとするManualPlayerクラスよりもシンプルだ。 公開されているメソッドはanswerしかない。

リスト4-2 AutoPlayer#answer

 def answer(before)
   if before
     while wd = think_answer(before)
       if @gm.twice? wd
         @dictionary.delete_if{|x| x == wd}
       else
         break
       end
     end
     wd
   else
     array_rand(@dictionary)
   end
 end

インデントのネストがわりと深めだ。

"初回の回答"="beforeがnil"はManualPlayerの項で語ったとおりで、 最初のインデントネストのif-elseはその分岐だ。 要件どおり、初回の回答者を任された場合、自身の辞書からarray_randで ひとつ見繕って返す。

そうでない場合、think_answerで回答を導き出すようだ。

answerの返り値は、wdまたはarray_randの返り値である。

リスト4-3 think_answer

 def think_answer(before)
   @dictionary.detect{|x| /\A#{before[-1, 1]}\w*/ === x}
 end

Array#detectメソッド(ComparableモジュールのMix-in)は、 配列の要素に渡されたブロックを試していって、結果が真になる 最初の要素を返す。どれも真にならなければnilだ。

think_answerはほぼこれのラップだ。しりとりできている単語かnilを返す。

リスト4-4 AutoPlayer#answerの一部

     while wd = think_answer(before)
       if @gm.twice? wd
         @dictionary.delete_if{|x| x == wd}
       else
         break
       end
     end
     wd

このwhileループはthink_answerがnilを返すまで続く。 think_answerがnilを返すときというのは、辞書にしりとりできる 単語がない場合だ。つまり回答不能である。 wdにもthink_answerの返り値としてnilが代入されているはずなので、 answerはnilを返す。

そして、whileの中では自らのGameMasterに考え出した回答が既出かどうか 問い合わせる。twice?が偽、つまり既出ではない有効な回答ならば ループを終える。wdがその回答として返される。

既出の回答である場合は、それはもう使えない単語なので辞書から消してしまう。 Array#delete_ifは、要素ごとにブロックを試し、結果が真ならばその要素を削ってしまう 破壊的メソッドである。

相手が優秀な回答者ならば、AutoPlayerの辞書から次々単語が消されていき、 やがてthink_answer(の中のArray#detect)が失敗してnilを返すだろう。