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