しりとり

Last-modified: 2018-04-28 (土) 19:29:57
 

言葉を覚えさせていくために、しりとりを利用しようと考えました。
(とりあえずは、単語だけ。意味を覚えこませるのは次の段階で。)

 

しりとりのルールは次の通り

  1. 最初は「しりとり」の「り」から始める。
  2. 当然、始まりの文字が前の言葉の最後の文字と違っていれば負け。
  3. もちろん、「ん」で終わる言葉を使っても負け。
  4. 同じ言葉を2回使うと負け。
  5. ローカルルールとして、(当面)使うのはひらがなに限る、最後が長音(ー)は省略する。
     

まずは寂しく一人しりとり

いきなり対話的しりとりは難しいので、一人だけで楽しむしりとりを作ってみる。

 

shiritori.rb

puts "はじめは「しりとり」の「り」"
tail = "り"
wordsbank = []
while true
  word = gets.chomp
  wordsbank.each do |w|
    if w == word
      puts "その言葉はもう出てます。あなたの負けです。"
      break
    end
  end
  if word[-1] == "ん"
    puts "最後の言葉が「ん」です。あなたの負けです。"
    break
  elsif word[0] == tail
    puts word
    tail = word[-1]
    wordsbank.push(word)
  else
    puts tail + "で始まる言葉ではありません。あなたの負けです。"
    break
  end
end

PCが相手してくれるしりとり1

何となく一人しりとりがうまく行ったので、今度はPCが相手してくれるしりとりを作ってみる。

  1. 操作者は、言葉を直接入力する。
  2. PCはあらかじめ用意した言葉のリスト(配列)から、使える言葉を見つけて返す。一度使った言葉はリストから外す。言葉が見つからない場合は降参する。
  3. 答えた言葉が正しいか間違っているかの判定は、操作者でもPCでも同じ。
    と言うことなので、それぞれについてメソッドにする。
     
    shiritori_2.rb
    ##人間側の設定
    def human_play
      return gets.chomp
    end
## pc側の設定
pc_wordsbank = ["ごりら","ぱんだ","ごま","めだか"]
wordsbank=[]
def pc_play(pc_wordsbank,tail)
  pc_wordsbank.each do |w|
    if w[0] == tail
      puts w
      pc_wordsbank.delete(w)
      return w
    end
  end
  puts "ごめんなさい。「"+tail+"」で始まる言葉を思い付きません。私の負けです。"
end
##審判
def judge(wordsbank,word,tail)
    wordsbank.each do |w|
      if w == word
        puts "その言葉はもう出てます。あなたの負けです。"
        break
      end
    end
    if word[-1] == "ん"
      puts "最後の言葉が「ん」です。あなたの負けです。"
    elsif word[0] == tail
      wordsbank.push(word)
      return word[-1]
    else
      puts tail + "で始まる言葉ではありません。あなたの負けです。"
    end
end
##試合開始
puts "はじめは「しりとり」の「り」"
tail = "り"
while true
  word=human_play
  tail=judge(wordsbank,word,tail)
  puts "次は"+tail+"で始まる言葉"
  word=pc_play(pc_wordsbank,tail)
  tail=judge(wordsbank,word,tail)
  puts "次は"+tail+"で始まる言葉"
end

一応動くようだ。何やらエラーが出るが。

 

PCが相手してくれるしりとり2

しりとりで出てきた言葉を別ファイルに保存して、次の試合でPC側が使えるようにしてみました。
実際にやってみると、少しずつ語彙が増えて、しりとりが続くようになります(ただし、そのためには、あえて負けてあげる心の広さも必要です。)。

 

なお、このプログラムを実行するに当たって、あらかじめ同じフォルダに、「wordsbank.txt」と言うファイル(中身は空で構わない。)を作っておく必要があります。

 

shiritori_3.rb

 #### PC用の言葉の呼出と保存
 ## いきなりPCが負けるのも可哀想なので、初めから与えておく言葉。
 pc_wordsbank = ["ごりら","ぱんだ","ごま","めだか","かばん","ごりら"]
 wordsbank=[]
 ## 単語を覚えこませている外部ファイルを開いて、単語を読み込む。
 File.open("wordsbank.txt","r") do |text|
   text.each_line do |line|
     pc_wordsbank.push(line.chomp)
   end
 end
 #### 出てきた言葉を保存するメソッド
 ## 外部ファイルの単語を配列に入れる。
 def store_words(wordsbank)
   File.open("wordsbank.txt","r") do |text|
     text.each_line do |line|
       wordsbank.push(line.chomp)
     end
   end
   ## 同じ単語を重複して保存しないように、外部ファイルの中を一度空にする。
   File.open("wordsbank.txt","w") do |text|
   end
   ## 配列から重複する単語を削除の上、外部ファイルに保存する。
   wordsbank.uniq!
   wordsbank.each do |w|
     File.open("wordsbank.txt","a") do |text|
       text.puts w
     end
   end
   exit
 end
 ####人間側の設定(キーボードから入力した文字を返すだけ。)
 def human_play
   return gets.chomp
 end
 #### pc側の設定(手持ちのリストから、該当する単語を探し出す。)
 def pc_play(pc_wordsbank,tail,wordsbank)
   pc_wordsbank.each do |w|
     if w[0] == tail
       puts w
       pc_wordsbank.delete(w)
       return w
     end
   end
   puts "ごめんなさい。「" + tail + "」で始まる言葉を思い付きません。私の負けです。"
   store_words(wordsbank)
   exit
 end
 ####審判
 def judge(wordsbank,word,tail)
 ## すでに使われた単語か判断。
   wordsbank.each do |w|
     if w == word
       puts "その言葉はもう出てます。あなたの負けです。"
       store_words(wordsbank)
     end
   end
   ## 「ん」で終わっていないか判断
   if word[-1] == "ん"
     puts "最後の言葉が「ん」です。あなたの負けです。"
     wordsbank.push(word)
     store_words(wordsbank)
   ## 前の単語の最後の文字で始まっていれば正常処理
   elsif word[0] == tail
     wordsbank.push(word)
     puts "次は「" + word[-1] + "」で始まる言葉"
     return word[-1]
   ## 前の言葉の最後の言葉と違う言葉で始まっていれば負け判定
   else
     puts tail + "で始まる言葉ではありません。あなたの負けです。"
     wordsbank.push(word)
     store_words(wordsbank)
   end
 end
 #### 試合開始
 puts "はじめは「しりとり」の「り」"
 tail = "り"
 while true
   word=human_play
   tail=judge(wordsbank,word,tail)
   word=pc_play(pc_wordsbank,tail,wordsbank)
   tail=judge(wordsbank,word,tail)
 end
 

気づいたこと

  • 毎回「り」から始まる言葉を思いつくのは大変なので、最初は任意の言葉で始められるようにした方がいいかも知れません。
     puts "はじめは「しりとり」の「り」"
     tail = "り"
 while true
   word=human_play
   tail=judge(wordsbank,word,tail)
   word=pc_play(pc_wordsbank,tail,wordsbank)
   tail=judge(wordsbank,word,tail)
 end

 word=gets.chomp
 tail=word[-1]
 while true
   word=pc_play(pc_wordsbank,tail,wordsbank)
   tail=judge(wordsbank,word,tail)
   word=human_play
   tail=judge(wordsbank,word,tail)
 end

にすれば良いように思います。

 
  • pc_wordsbank配列の順番をランダムにすると多少変化が出てくると思います。
     

YAMLを利用してみた

「wordsbank.txt」はただのテキストファイルですが、今後他で活用することも考え、YAMLファイルに保存するようにしてみました。
なお、YAML を利用するには、最初に require "yaml" と宣言しておく必要があります。
shiritiri_4.rb

 require "yaml"
 wordsbank=[]
 #### PC用の言葉の呼出と保存
 ## いきなりPCが負けるのも可哀想なので、初めから与えておく言葉。
 pc_wordsbank = ["ごりら","ぱんだ","ごま","めだか","かばん","ごりら"]
 #### wordsbank.yamlを一旦読み込み、配列pc_wordsbankに格納
 #### ただし、最初はwordsbank.yamlがないので、例外処理を行う
 begin
   load_wordsbank=YAML.load_file"wordsbank.yaml"
   load_wordsbank.each do |i|
     pc_wordsbank.push(i)
   end
 ## wordsbank.yamlがないとき(中身が空のとき)の処理(と言っても何もしない)
 rescue
 end
 #### 最後にwordsbank.yamlに(再)保存するために一時保管
 temp_wordsbank=pc_wordsbank
 #### 出てきた言葉を保存するメソッド
 def store_words(temp_wordsbank,wordsbank)
   temp_wordsbank.each do |i|
     wordsbank.push(i)
   end
   wordsbank.uniq!
   YAML.dump(wordsbank,File.open("wordsbank.yaml", "w"))
   exit
 end
 ####人間側の設定(キーボードから入力した文字を返すだけ。)
 def human_play
   print "俺> "
   return gets.chomp
 end
 #### pc側の設定(手持ちのリストから、該当する単語を探し出す。)
 def pc_play(pc_wordsbank,tail,temp_wordsbank,wordsbank)
   pc_wordsbank.each do |w|
     if w[0] == tail
       puts "嫁> " + w
       pc_wordsbank.delete(w)
       return w
     end
   end
   puts "嫁> ごめんなさい。「" + tail + "」で始まる言葉を思い付きません。私の負けです。"
   store_words(temp_wordsbank,wordsbank)
   exit
 end
 ####審判
 def judge(temp_wordsbank,wordsbank,word,tail)
 ## すでに使われた単語か判断。
   wordsbank.each do |w|
     if w == word
       puts "その言葉はもう出てます。あなたの負けです。"
       store_words(temp_wordsbank,wordsbank)
     end
   end
   ## 「ん」で終わっていないか判断
   if word[-1] == "ん"
     puts "最後の言葉が「ん」です。あなたの負けです。"
     wordsbank.push(word)
     store_words(temp_wordsbank,wordsbank)
   ## 前の単語の最後の文字で始まっていれば正常処理
   elsif word[0] == tail
     wordsbank.push(word)
     puts "次は「" + word[-1] + "」で始まる言葉"
     return word[-1]
   ## 前の言葉の最後の言葉と違う言葉で始まっていれば負け判定
   else
     puts tail + "で始まる言葉ではありません。あなたの負けです。"
     wordsbank.push(word)
     store_words(temp_wordsbank,wordsbank)
   end
 end
 #### 試合開始
 puts "はじめはあなたからどうぞ"
 word=human_play
 tail=word[0]
 while true
   tail=judge(temp_wordsbank,wordsbank,word,tail)
   word=pc_play(pc_wordsbank,tail,temp_wordsbank,wordsbank)
   tail=judge(temp_wordsbank,wordsbank,word,tail)
   word=human_play
 end
 

最初の方で

 begin
   load_wordsbank=YAML.load_file"wordsbank.yaml"
   load_wordsbank.each do |i|
     pc_wordsbank.push(i)
   end
 ## wordsbank.yamlがないとき(中身が空のとき)の処理(と言っても何もしない)
 rescue
 end

とbeginで始まる処理が書いてあります。
これは、プログラムの始めの方でwordsbank.yamlを配列に読み込む処理をしているのですが、初めてこのプログラムを実行するときには、まだwordsbank.yamlというファイルがないため、そのままではエラーで終了してしまいます。
そのため、例外処理で、もしまだwordsbank.yamlがない場合には無視して先に進むようにしてあります。

 

さて、これでも一応動きますが、よく見ると、store_words、pc_play、judgeと言った複数のメソッドでwordsbankなど同じ名前の変数が何箇所も出てきて、何やらごちゃごちゃしてます。
たとえ同じ名前の変数であっても、別のメソッドで使われていれば、それば別の変数とみなされます。
しかし、このプログラムでは、同じ名前の変数はひとつのものを指しているので、色々と無駄が多すぎますね(まさにこのwikiのタイトル通りの状態に・・・)。

 

しりとりクラスを作成してみた

メソッドを実行させるときに実体が同じ変数をいちいち引数として明記するのは、余りに無駄だし、修正する度にエラーの原因になってしまいます。
このように共通して使う変数は「インスタンス変数」と言うものにすると良さそうです。そんなわけで、生まれて初めてインスタンス変数をいうものを使ってみました。ついでに生まれて初めてクラスを定義してみました。

 
 require "yaml"
 class Shiritori
   #### 初期化の際、各変数を指定する
   def initialize
     #### 初めからPCが答えられる言葉を用意してあげる。
     @pc_wordsbank = ["ごりら","ぱんだ","ごま","めだか","かばん","ごりら"]
     #### wordsbank.yamlを一旦読み込み、配列pc_wordsbankに格納
     #### ただし、最初はwordsbank.yamlがないので、例外処理を行う
     begin
       @load_wordsbank=YAML.load_file"wordsbank.yaml"
       @load_wordsbank.each do |i|
         @pc_wordsbank.push(i)
       end
     ## wordsbank.yamlがないとき(中身が空のとき)の処理(と言っても何もしない)
     rescue
     end
     ## 最後にwordsbank.yamlに(再)保存するために一時保管
     @temp_wordsbank=@pc_wordsbank
     ## wordsbankの空配列
     @wordsbank=[]
   end
   #### 出てきた言葉を保存するメソッド
   def store_words
     @temp_wordsbank.each do |i|
       @wordsbank.push(i)
     end
     @wordsbank.uniq!
     YAML.dump(@wordsbank,File.open("wordsbank.yaml", "w"))
     exit
   end
   ####人間側の設定(キーボードから入力した文字を返すだけ。)
   def human_play
     print "俺> "
     return gets.chomp
   end
   #### pc側の設定(手持ちのリストから、該当する単語を探し出す。)
   def pc_play
     @pc_wordsbank.each do |w|
       if w[0] == @tail
         puts "嫁> " + w
         @pc_wordsbank.delete(w)
         return w
       end
     end
     puts "嫁> ごめんなさい。「" + @tail + "」で始まる言葉を思い付きません。私の負けです。"
     @store_words
     exit
   end
   ####審判
   def judge
   ## すでに使われた単語か判断。
     @wordsbank.each do |w|
       if w == @word
         puts "その言葉はもう出てます。あなたの負けです。"
         store_words
       end
     end
     ## 「ん」で終わっていないか判断
     if @word[-1] == "ん"
       puts "最後の言葉が「ん」です。あなたの負けです。"
       @wordsbank.push(@word)
       store_words
     ## 前の単語の最後の文字で始まっていれば正常処理
     elsif @word[0] == @tail
       @wordsbank.push(@word)
       puts "次は「" + @word[-1] + "」で始まる言葉"
       return @word[-1]
     ## 前の言葉の最後の言葉と違う言葉で始まっていれば負け判定
     else
       puts @tail + "で始まる言葉ではありません。あなたの負けです。"
       @wordsbank.push(@word)
       store_words
     end
   end
   def play
     #### 試合開始
     puts "はじめはあなたからどうぞ"
     @word=human_play
     @tail=@word[0]
     while true
       @tail=judge
       @word=pc_play
       @tail=judge
       @word=human_play
     end
   end
 end
 #### 実際にオブジェクトを作ってプレイする
 shiritori=Shiritori.new
 shiritori.play

pc_playやjudgeなどメソッドにいちいち引数を書かなくて良くなったので、かなりスッキリしました。