From: James Edward Gray II Date: 2007-07-12T03:47:03+09:00 Subject: Re: [QUIZ][SOLUTION] Hangman (#130) A second version of my code. Minor changes mainly to support me testing it. I tried different guessing algorithms, but nothing beat the original strategy with my dictionary. First, this is a tiny words.rb lib used by both the guesser and test scripts: #!/usr/bin/env ruby -wKU WORDS_CASH_FILE = "words.cache" if File.exist? WORDS_CASH_FILE WORDS = File.open(WORDS_CASH_FILE) { |file| Marshal.load(file) } else WORDS = File.open( ARGV.find { |arg| arg =~ /\A[^-]/ } || "/usr/share/dict/words" ) do |dict| dict.inject(Hash.new) do |all, word| all.update(word.delete("^A-Za-z").downcase => true) end.keys end File.open(WORDS_CASH_FILE, "w") { |file| Marshal.dump(WORDS, file) } end __END__ Next, my guesser script which works the same as yesterday's version but is optimized here and there for testing: #!/usr/bin/env ruby -wKU puts "One moment..." puts require "words" choices = WORDS guesses = Array.new loop do puts guesses.empty? ? "Please enter a word pattern (_ _ _ _ for example):" : "Please update your pattern according to my guess (_ i _ _ for example):" $stdout.flush pattern = $stdin.gets.to_s.delete("^A-Za-z_") if (guesses - pattern.delete("_").split("")).size > 5 and pattern.include? "_" puts "I'm out of guesses. You win." elsif not pattern.include? "_" puts "I guessed your word. Pretty smart, huh?" else choices = choices.grep(/\A#{pattern.tr("_", ".")}\Z/i) odds = Hash.new(0) choices.each do |word| word.split("").each do |l| next if guesses.include? l odds[l] += word.count(l) end end guess = odds.max { |(_, c1), (_, c2)| c1 <=> c2 }.first rescue nil guesses << guess puts "I guess the letter '#{guess}'." puts next end puts if ARGV.include? "--loop" choices = WORDS guesses = Array.new else break end end __END__ Finally, this is my test script: #!/usr/bin/env ruby -wKU require "words" results = Hash.new(0) at_exit do results[:total] = results[:right] + results[:wrong] puts puts "Words: #{results[:total]}" puts "Guessed: #{results[:right]}" puts "Missed: #{results[:wrong]}" printf "Accuracy: %.2f%%\n", results[:right] / results [:total].to_f * 100 puts end trap("INT") { exit } IO.popen( File.join(File.dirname(__FILE__), "hangman.rb --loop"), "r+" ) do |hangman| WORDS.each do |word| pattern = word.tr("a-z", "_") loop do input = String.new hangman.each do |line| input << line break if input =~ /^(?:I'm out|I guessed)|:\Z/ end if input =~ /^I'm out/ puts "It missed '#{word}'." results[:wrong] += 1 break elsif input =~ /^I guessed/ puts "It guessed '#{word}'." results[:right] += 1 break elsif input =~ /^I guess the letter '(.)'/ guess = $1 word.split("").each_with_index do |letter, i| pattern[i, 1] = letter if letter == guess end end hangman.puts pattern end end end __END__ James Edward Gray II