From: James Edward Gray II Date: 2007-01-08T01:53:57+09:00 Subject: Re: [QUIZ] Word Blender (#108) Here's the solution I wrote while considering this quiz (it requires Unix): #!/usr/bin/env ruby -w require "io/wait" # game date cache CACHE_FILE = ".game_words" if File.exist? CACHE_FILE # load from cache word_list = File.open(CACHE_FILE) { |file| Marshal.load(file) } else # build word list # prepare data structure words_by_signature = Hash.new { |words, sig| words[sig] = Array.new } # read dictionary File.foreach(ARGV.shift || "/usr/share/dict/words") do |word| word.downcase! word.delete!("^a-z") next unless word.length.between? 3, 6 (words_by_signature[word.split("").sort.join] << word).uniq! end # prepare recursive signature search def choices(sig, seen = Hash.new { |all, cur| all[cur] = true; false }, &blk) sig.length.times do |i| shorter = sig[0...i] + sig[(i+1)...sig.length] unless seen[shorter] blk[shorter] choices(shorter, seen, &blk) unless shorter.length == 3 end end end # prepare game data structure word_list = Hash.new # build game choices words_by_signature.keys.grep(/\A.{6}\Z/) do |possible| word_list[possible] = words_by_signature[possible] choices(possible) do |shorter_signature| if words_by_signature.include? shorter_signature word_list[possible].push(*words_by_signature [shorter_signature]) end end end # cache for faster loads File.open(CACHE_FILE, "w") { |file| Marshal.dump(word_list, file) } end ### game interface (requires Unix) ### TERMINAL_STATE = `stty -g` system "stty raw -echo cbreak" at_exit { system "stty #{TERMINAL_STATE}" } clear = `clear` # a raw mode savvy puts def out(*args) print(*(args + ["\r\n"])) end # for easy selection words = word_list.keys rounds = 0 loop do # select letters letters = current = words[rand(words.size)] while word_list.include? letters letters = letters.split("").sort_by { rand }.join end letters.gsub!(/.(?=.)/, '\0 ') # round data advance = false matches = Array.new current_match = String.new start = Time.now message = nil last_update = start - 1 # round event loop until Time.now >= start + 2 * 60 # game display if last_update <= Time.now - 1 print clear out "Your letters: #{letters}" out " Time left: #{120 - (Time.now - start).round} seconds" out " Your words: #{matches.join(', ')}" out unless message.nil? out message out end print current_match $stdout.flush last_update = Time.now end # input handler if $stdin.ready? char = $stdin.getc case char when ?a..?z, ?A..?Z # read input current_match << char.chr.downcase message = nil last_update = start - 1 when ?\b, 127 # backspace/delete current_match = current_match[0..-2] message = nil last_update = start - 1 when ?\r, ?\n # test entered word if word_list[current].include? current_match matches << current_match matches = matches.sort_by { |word| [word.size, word] } if not advance and current_match.length == 6 advance = true message = "You will advance to the next round!" else message = nil end else message = "Unknown word." end current_match = String.new last_update = start - 1 end end end # round results print clear missed = word_list[current] - matches unless missed.empty? out "Other words using \"#{letters}:\"" out missed.sort_by { |word| [word.size, word] }.join(", ") out end if advance rounds += 1 out "You made #{matches.size} word#{'s' if matches.size != 1}, ", "including at least one six letter word. Nice work." out "Press any key to begin the next round." $stdin.getc else out "You made #{matches.size} word#{'s' if matches.size != 1}, ", "but failed to find a six letter word." break # end game end end # game results out "You completed #{rounds} round#{'s' if rounds != 1}. Thanks for playing." __END__ James Edward Gray II