[QUIZ] Word Blender (#108)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

  [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
  [2]: http://www.gamehouse.com/

Is the goal to get the most points or to get to the highest round? Do you
get points based on the number of letters used (as in Upwords) or do you get
points based on the obscurity of the letter to be used (as in scrabble)?

···

On 1/5/07, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby Talk follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2]
and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and
is
tasked with unscrambling those letters into as many words as possible. If
the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone.
Effectively,
this means that you must generate a list of three- to six-letter words
that can
all be constructed from the same six letters. This list must contain at
least
one six-letter word.

Bonus points for building a completely functional game!

       [1]: http://games.yahoo.com/games/texttwist.html (just one example,
java)
       [2]: http://www.gamehouse.com/

I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file. Does anyone
have links to decent word/dictionary files? Or perhaps does Mac OS X
come with one?

It just so happened that I was learning ruby last summer and wrote a program
to cheat for me at TextTwist. Needless to say the game got boring really
fast, but it was neat writing the program. I've modified it a bit to instead
play the game, but I'm a fairly new user and would appreciate any feedback,
both ruby related and general programming.

To run this you'll need a dictionary file of words, each on a new line,
called wordlist.txt and you need to require 'dict' and run Dict.reduce(3,6)
before you run the program. This will create a reduced dictionary file of
only words of lengths between 3 and 6. I didn't make this part of my
program's general execution as I figured doing this once was enough and I
could do it manually.

It's got a simple text interface that looks sort of like the TextTwist gui
layout on Yahoo. Sort of. The dictionary I use, which I did not attach
because its size (3 megs, I believe it was the full Scrabble dictionary)
makes for a much harder game than the Yahoo TextTwist as some of the words
are really obscure.

Also, I have the problem that when running this on Windows it doesn't allow
me to manipulate files with Ruby, giving me a Permission Denied error on
File.open. Any idea why this might be? This is if I try to run Dict.reduce(3,6)
for example.

texttwistGame.rb (1.85 KB)

dict.rb (2.09 KB)

Here it is. Scoring works like this: 10 points per (tile used)^2. 100
bonus points for using all 6 tiles. 5 wrong answers in a row ends the game.
I made a dictionary file of 3-6 letter words using crop.rb. Any feedback is
appreciated, both from a stylistic and general programming perspective. If
there's a way to rubify (not a word!) anything, also let me know.

crop.rb (272 Bytes)

quiz108.rb (1.35 KB)

Here is my solution.

-Chunyun

#== Synopsis
#This is the solution to Ruby Quiz #108 described on
http://www.rubyquiz.com/quiz108.html\.

···

#
#== Usage
# text_twist.rb dictionary_file
#
#== Author
# Chunyun Zhao(chunyun.zhao@gmail.com)
#
class Dictionary
  MIN_LEN, MAX_LEN = 3, 6
  attr_reader :max_letters
  def initialize(dict_file, min=MIN_LEN, max=MAX_LEN)
    @min_len = min
    @max_len = max
    @words = Hash.new {|hash,key|hash[key]=}
    File.foreach(dict_file) {|word|add_word(word.strip)}
    @max_letters = @words.keys.select {|key| key.size==@max_len}
  end
  def word_list(letters)
    words=
    permutate(letters).select {|letters|
      letters.size.between? @min_len, @max_len
    }.uniq.each {|key|
      words += @words[key]
    }
    words.sort_by {|word| word.size}
  end
  private
  def add_word(word)
    if (@min_len..@max_len)===word.size && word=~/^[a-z]+$/i
      word.downcase!
      @words[word.split(//).sort] << word
    end
  end
  def permutate(letters)
    _letters = letters.dup
    result =
    while letter = _letters.shift
      permutate(_letters).each do |perm|
        result << [letter] + perm
      end
      result << [letter]
    end
    result
  end
end

Task = Struct.new(:letters, :words)

class GameUi
  def initialize(dict)
    @dictionary = dict
    @history_tasks =
    @rounds = 1
    @score = 0
  end

  def next_task
    letters = @dictionary.max_letters[rand(@dictionary.max_letters.size)]
    retry if @history_tasks.include?(letters)
    task = Task.new(letters, @dictionary.word_list(letters))
    @history_tasks << task
    task
  end

  def run_task
    @task = next_task
    @found =
    @cleared = false
    puts "\nRound #{@rounds}. Letters: #{@task.letters*', '}. Hint: number
of matching words: #{@task.words.size}"
    while !(word=ask("Enter your word:")).empty?
      if @found.include? word
        puts "Word already found!"
      elsif @task.words.include? word
        @found << word
        @score += word.size
        puts "Good job! You scored #{word.size} points!"
        if word.size == @task.letters.size
          @cleared = true
          puts "\nBingo! Round #@rounds cleared. You found #{@found.size}
word#{'s' if @found.size > 1}. "
          break
        end
      else
        puts "Wrong word!"
      end
    end
    puts "Missed words: #{(@task.words-@found)*', '}."
    @cleared
  end

  def run
    while run_task
      answer = ask("\nProceed to next round?")
      break if answer !~ /^y/i
      @rounds += 1
    end
    puts "\nYou've cleared #{cleared=@cleared?@rounds:@rounds-1} round#{'s'
if cleared > 1}, and your total score is #{@score}."
  end

  def ask question
    print question, " (Hit enter to exit)=> "
    gets.strip
  end
end
if __FILE__ == $0
  if ARGV.size != 1
    puts "Usage: #{File.basename(__FILE__)} dictionary_file"
    exit
  end
  GameUi.new(Dictionary.new(ARGV.shift)).run
end
__END__

On 1/5/07, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby Talk follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2]
and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and
is
tasked with unscrambling those letters into as many words as possible. If
the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone.
Effectively,
this means that you must generate a list of three- to six-letter words
that can
all be constructed from the same six letters. This list must contain at
least
one six-letter word.

Bonus points for building a completely functional game!

        [1]: http://games.yahoo.com/games/texttwist.html (just one
example, java)
        [2]: http://www.gamehouse.com/

Here is my solution. Takes the filename of the dictionary to use as the
first argument and optionally a word to solve for as the second
argument. If no second argument is provided, you play the game.

···

--

class String
  def lettercount
    split(//).uniq.map{|c| [c, count(c)]}
  end
  def fisher_yates_shuffle
    a = self.dup
    (length-1).downto(0){|i|
      j = rand(i+1)
      a[i], a[j] = a[j], a[i] if i != j
    }
    a
  end
end

class Array
  def random_element
    self[rand(length)]
  end
end

class Dictionary
  def initialize(filename)
    @words =
    IO.foreach(filename) do |line|
      word = line.chomp
      @words << word.downcase if word.length.between?(3, 6)
    end
  end
  def blend(word)
    @words.select{|x|
      x.count(word.downcase) == x.length &&
      x.lettercount.all?{|c, n|
        n <= word.downcase.lettercount.assoc(c).last }
    }
  end
  def randomword
    @words.select{|x| x.length == 6}.random_element
  end
end

class WordBlender
  def initialize(dictionary)
    @dictionary = dictionary
  end
  def blend_to_s(word)
    word_blend = @dictionary.blend(word)
    puts "WordBlender: '#{word}' has #{word_blend.length} answers."
    puts
    max = -1
    word_blend.sort_by{|x| [x.length, x]}.each do |x|
      if x.length > max
        max = x.length
        puts "Words of length #{max}:"
      end
      puts " #{x}"
    end
  end
  def play
    puts "Welcome to WordBlender! (enter a blank line to quit)"
    puts
    round = 0
    points = 0
    continue = true
    while continue do
      points = points + 10 * round
      round = round + 1
      word = @dictionary.randomword
      word_blend = @dictionary.blend(word)
      word_shuffled = word.fisher_yates_shuffle
      puts "Round: #{round} - Blend: '#{word_shuffled}' - Total Score:
#{points}"
      current_word = ""
      current_words =
      current_continue = true
      while continue && current_continue do
        current_word = STDIN.gets.chomp.downcase
        if current_word == ""
          puts
          puts "Final Word: '#{word}' - Final Score: #{points}"
          continue = false
        elsif current_words.include?(current_word)
          puts "'#{current_word}' already used."
        elsif word_blend.include?(current_word)
          current_words << current_word
          points = points + current_word.length * current_word.length
          current_continue = (current_word.length < word.length)
        elsif current_word.count(word) == current_word.length
          puts "'#{current_word}' not in dictionary."
        else
          puts "'#{current_word}' not found in '#{word_shuffled}'."
        end
      end
    end
  end
end

if ARGV.size == 0
  puts "Usage: wordblender.rb <filename> - play WordBlender with the
specified dictionary"
  puts "Usage: wordblender.rb <filename> <word> - show all blends for
the word using the dictionary"
elsif ARGV.size == 1
  WordBlender.new(Dictionary.new(ARGV[0])).play
elsif ARGV.size >= 2
  WordBlender.new(Dictionary.new(ARGV[0])).blend_to_s(ARGV[1])
end

On Jan 5, 8:05 am, Ruby Quiz <j...@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

        [1]:http://games.yahoo.com/games/texttwist.html\(just one example, java)
        [2]:http://www.gamehouse.com/

I made two this time. One plays a game and one just picks the word.

Here's the one that just picks a word. It runs very quickly, about .155 or .2 seconds.
#! /usr/bin/ruby -w

class Array
  def rand_elem
    self[rand(size)]
  end
end

# Open and read the dictionary.
dict = IO.read("/usr/share/dict/words")

# Pick a random word with 6 letters.
baseWord = dict.scan(/^[a-z]{6}$/).rand_elem

# Find words that use the same letters
selectedWords = dict.scan(/^[#{baseWord}]{3,6}$/)

# Display the words.
puts baseWord + ":\n\t" + selectedWords.join("\n\t")

And here's the one that plays a game. Basically, it gives you the letters and you enter the 5 best words you can think of. The score is the length of the word ** 3 added to a running total, but subtracted if the word isn't valid (already said or not in the list).

#! /usr/bin/ruby -w

class Array
  def rand_elem
    self[rand(size)]
  end
  
  def english_join
    self[0...-1].join(', ') + ', and ' + self[-1]
  end
end

class String
  def letters
    unless $DEBUG
      split(//).uniq.sort_by{rand}
    else
      split(//)
    end
  end
end

class Game
  @@dict = nil
  
  def initialize
    # Open and read the dictionary.
    @@dict ||= IO.read("/usr/share/dict/words")
    
    @points = 0
    @round = 1
  end

  def play
    # Pick a random word with 6 letters.
    baseWord = @@dict.scan(/^[a-z]{6}$/).rand_elem

    # Find words that use the same letters
    selectedWords = @@dict.scan(/^[#{baseWord}]{3,6}$/)
    
    # Initialize word list & continue var.
    guessed =
    continue = false
    
    # Display banner
    puts "",
      "Round #{@round}:",
      "Enter the 5 longest words you can make from the letters #{baseWord.letters.english_join}.",
      "Invalid and repeated words count towards the 5 words but subtract points.",
      ""
    
    # Gather all the points, calculate the score, and see if the player should go to the next round.
    5.times do
      print "#{@points}\t"
      word = gets.chomp.downcase
      if !guessed.include?(word) && selectedWords.include?(word)
        @points += word.length ** 3
        guessed << word
        continue = true if word.length == 6
      else
        @points -= word.length ** 3
      end
    end
    
    # Go on to the next round or lose.
    if continue
      @round += 1
      play
    else
      puts "Sorry, you didn't get a 6 letter word. You got #{@points} points, however."
    end
  end
end

Game.new.play

Ruby Quiz wrote:

wordjumblefast.rb (396 Bytes)

wordjumblegame.rb (1.47 KB)

···

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

This solution simply chooses a six-letter word (or allows the user to
choose one) and then displays a list of words that can be composed with
a subset of the letters (size >= 3). Here is output from a sample run:

ape
lap
pal
pea
sap
sea
see
else
leap
pale
peas
peel
sale
seal
slap
sleep
asleep
please

Eric

···

------------
Interested in Ruby training with a well-reviewed instructor and
training materials? www.LearnRuby.com

# Given an array of letters, a whole/partial word built up so far, and
# a hash, adds to the hash all permutations of subsets built from the
# partial word and the array of letters. If a block is given it acts
# as a filter since the words must produce a true result when submitted
# to the block in order to be added to the hash.
def permute(letters, word, possible_words, &filter_block)
  possible_words[word] = true if filter_block.nil? ||
filter_block.call(word)
  return if letters.empty?

  letters.each_with_index do |letter, i|
    (new_letters = letters.dup).delete_at(i)
    permute(new_letters, word + letter, possible_words, &filter_block)
  end
end

# Verify that a filename was provided as the first argument and that
# it is a readable file
if ARGV[0].nil?
  $stderr.puts("Usage: #{$0} dictionary-file [word]")
  exit 1
elsif ! File.file?(ARGV[0]) || ! File.readable?(ARGV[0])
  $stderr.puts("Error: \"#{ARGV[0]}\" is not a readable file.")
  exit 2
end

# Build list of all six-letter words from dictionary file
words6 = Array.new
open(ARGV[0], "r") do |f|
  f.each_line { |w| words6 << w if w.chomp! =~ /^[a-z]{6}$/ }
end

# Determine whether a random six-letter word is chosen or the user
# specifies one.
if ARGV[1]
  # user attempted to specify a word; check its validity
  if words6.include?(ARGV[1])
    word = ARGV[1]
  else
    $stderr.puts("Error: \"#{ARGV[1]}\" is not a known six-letter
word.")
    exit 3
  end
else
  word = words6[rand(words6.size)] # choose a random word
end

# Generate a hash of all three- to six-letter permutations using the
# letters of the chosen six-letter word. Note: most will not be valid
# words.
possible_words = Hash.new
permute(word.split(""), "", possible_words) { |w| w.length >= 3 }

# Generate a list of all valid words that are also permutations of
# subsets of the chosen six-letter word. This is done by
# re-reading the word file and testing each word against the
# possible permutations.
actual_words = Array.new
open(ARGV[0], "r") do |f|
  f.each_line { |w| actual_words << w if possible_words[w.chomp!] }
end

# Display the resulting actual words sorted first by length and then
# alphabetically.
puts actual_words.sort_by { |w| [w.length, w] }

Here is my solution. I'm not too good with regular expressions, so the
word selection script is a little bit slow.

#!/usr/bin/env ruby

···

#
# Author: Dan Manges - http://www.dcmanges.com
# Ruby Quiz #108 - Ruby Quiz - Word Blender (#108)

class WordList
  include Enumerable

  attr_accessor :file
  attr_reader :filters

  def initialize(file = nil)
    @file, @filters = file,
  end

  def each
    File.open(@file, "r") do |file|
      while line = file.gets
       yield apply_filters(line.chomp)
      end
    end
  end

  protected

  def apply_filters(word)
    @filters.inject(word) do |word, filter|
      filter.call(word)
    end
  end

end

# Module to select words based on length and letter composition.
module WordFinder
  # Finds words of length +size+ which can be composed with the letters
in +base_word+
  def find_words(size, base_word)
    letter_counts = base_word.split(//).inject(Hash.new(0)) {

hash,letter| hash[letter] += 1; hash }

    regexp = Regexp.new("^" + letter_counts.map { |letter,count|
"#{letter}{0,#{count}}"}.sort.join + "$")
    select { |word| word.to_s.size == size && word.split(//).sort.join
=~ regexp }
  end

  # Finds a random word of the given size
  def random_word_of_size(size)
    words = find_words(size, (('a'..'z').to_a * 3).join)
    words[rand(words.size)]
  end
end

WordList.send(:include, WordFinder)

# Dictionary file from: http://wordlist.sourceforge.net/
# Download alt12dicts-4.tar.gz (SCOWL (and friends))
@wordlist = WordList.new("/Users/dan/Desktop/alt12dicts/2of12full.txt")
# This particular wordlist has an offset
@wordlist.filters << lambda { |word| word[17..-1] }
# Skip proper names, contractions, etc.
@wordlist.filters << lambda { |word| word =~ /^[a-z]+$/ ? word : "" }

module WordBlender
  class Round
    def initialize(wordlist, word_size = (3..6))
      @wordlist = wordlist
      @min_size, @max_size = word_size.first, word_size.last
      @qualified = false
      @hits = Hash.new { |h,k| h[k] = }
      load_words
    end

    def qualified?
      @qualified
    end

    def guess?(word)
      word = word.to_s.strip
      dup?(word) || qualify?(word) || hit?(word) || :miss
    end

    def letters
      @base.split(//).sort
    end

    def status
      result =
      @min_size.upto(@max_size) do |size|
        result << [size, @hits[size].size, @words[size].size]
      end
      result.map { |data| "#{data[0]} letters: #{data[1]} of
#{data[2]}"}.join(", ")
    end

    protected

    def dup?(word)
      :dup if @hits[word.size].include?(word)
    end

    def qualify?(word)
      if @words[word.size].include?(word) and word.size == @max_size
        @hits[word.size] << word
        @qualified = true
        :qualify
      end
    end

    def hit?(word)
      if @words[word.size].include?(word)
        @hits[word.size] << word
        :hit
      end
    end

    def load_base_word
      @base = @wordlist.random_word_of_size(@max_size)
    end

    def load_words
      @words = Hash.new()
      load_base_word
      @min_size.upto(@max_size) do |size|
        @words[size] = @wordlist.find_words(size, @base)
      end
    end
  end

  class Game
    def initialize(wordlist)
      @wordlist = wordlist
      reset
    end

    def start!
      help
      start_round
      print 'guess> '
      while input = gets
        input = input.strip
        break if input == ".quit"
        if input[0,1] == "." && respond_to?(input[1..-1])
          send(input[1..-1])
          print 'guess> '
          next
        end
        result = @round.guess?(input)
        puts case result
          when :miss
            "Wrong!"
          when :dup
            "Already guessed that!"
          when :hit
            "You got it!"
          when :qualify
            "You got it! And you qualify for the next round!"
        end + " " + input
        status unless result == :miss
        print 'guess> '
      end
      puts "Goodbye!"
    end
    alias :play! :start!

    protected

    def letters
      puts "Available Letters: " + @round.letters.sort_by {rand}.join(',
')
    end

    def next
      if @round.qualified?
        start_round
      else
        puts "You have not yet qualified for the next round!"
      end
    end

    def help
      puts <<-END_HELP
      When prompted, either enter a word or a command.
      The following commands are available:
        .quit => quits the game
        .help => display this help
        .next => goes to the next round (if qualified)
        .letters => display available letters
        .status => show the current status of this round
      END_HELP
    end

    def reset
      @round_number = 0
    end

    def start_round
      @round_number += 1
      @round = Round.new(@wordlist)
      puts "Beginning Round #{@round_number}!"
      letters
    end

    def status
      puts @round.status
    end
  end
end

@blender = WordBlender::Game.new(@wordlist)
@blender.play!

--
Posted via http://www.ruby-forum.com/\.

My game will play almost like the one you can play at yahoo without score. Has
cheating and giving-up commands and draws possible solutions on the screen.
Run it with wordblender.rb <dictfile>.

martin

# wordblender.rb

···

#
# Usage: wordblender.rb [dictfile]
#

class String
# Checks if string can be build out of these characters.
#
# "hello".build_outof?("llohe") => true
# "world".build_outof?("dlrowl") => true
def build_outof?(other)
   return false if self.length > other.length
   o = other.clone
   self.each_byte do |c|
      return false unless o.include?(c.chr)
      o[o.index(c.chr)] = 0
   end
   true
end

# Shuffle a word.
#
# "hello".shuffle => "oellh"
def shuffle
   return self.scan(/./).sort_by{rand}.to_s
end
end

class WordBlenderGame

  attr_reader :words

  # limits for words for the game
  MINCHARACTERS = 3
  MAXCHARACTERS = 6
  
  # time limit per game
  TIME_PER_GAME = 90
  
  # how to display the board
  DISPLAY_COLUMNS = 5

  # read the dictionary from a file.
  # we also keep words with length of MAXCHARACTERS to find
  # good initial letters quickly.
  def initialize(dictionary)
    @words, @maxwords = ,
    File.open(dictionary).each do |line|
      l = line.strip.downcase
      @words << l if (l.length >= MINCHARACTERS && l.length <= MAXCHARACTERS)
      @maxwords << l if l.length == MAXCHARACTERS
    end
  end

  # this generates a bunch of letters to play with and looks up words
  # that can be build by them from the dictionary ("candidates").
  def prepare_game()
    @letters = @maxwords[rand(@maxwords.size-1)].shuffle
    @candidates =
    @words.each { |w| @candidates << w if w.build_outof?(@letters) }
    @candidates = @candidates.uniq # this fixed duplicated entries
    @candidates = @candidates.sort {|x,y| x.length <=> y.length }
    @found_candidates = @candidates.collect { false }
  end

  #
  # This is to display the candidates to the screen. Draws it into columns
  # and returns a string.
  #
  def get_board(solution=false, title="Words to find")
     result = "" ; i = 0
     sempty = ' '*(DISPLAY_COLUMNS*(MAXCHARACTERS+2))
     s = String.new(sempty)
     result << title << ":\n"
              
     @found_candidates.each_index do |idx|
         f = @found_candidates[idx] || solution
         s[i.modulo(DISPLAY_COLUMNS)*(MAXCHARACTERS+2)] =
f ? "[#{@candidates[idx]}]" : "["+(' '*@candidates[idx].length)+"]"
         if i.modulo(DISPLAY_COLUMNS) == DISPLAY_COLUMNS-1 then
           result << (s + "\n")
           s = String.new(sempty)
         end
         i+=1
     end
     result << s if s.include?('[')
     result << "\n"
  end
  
  # This plays one round of the game, returns true if won
  def play
    self.prepare_game
    message = "Press RETURN to shuffle the letters, '!' to give up, '?' to
cheat."
    
    # start the time.
    @time = TIME_PER_GAME
    timer = Thread.new { while true do @time-=1; sleep 1 end }
    
    # game loop
    while @found_candidates.include?(false) do
       
       # print board and other stuff
       puts get_board
       puts
       puts "Time: " + @time.to_s
       puts "Msg: " + message if message != ''
       puts "Use: " + @letters
       print "Try: "

       # get user's guess and handle it
       $stdout.flush
       s = STDIN.gets.downcase.strip

       if @time <= 0 then
         puts "Time's up!"
         break
       end
       
       if s == "" then
         @letters = @letters.shuffle
         message = "Letters shuffled!"
         next
       end
    
       break if s == '!'
      
       if s == '?' then
         puts get_board(true)
         message = "Cheater!"
         next
       end
         
       if !s.build_outof?(@letters) then
         message = "Invalid word!"
         next
       end
         
       if @candidates.include?(s) then
         @found_candidates[@candidates.index(s)] = true
         message = "#{s} Found!"
       else
         message = "#{s} not listed!"
       end
    end

    Thread.kill(timer)
    
    # print solution
    puts get_board(true, "Solution is")
    
    # Check if player found a word with all characters
    @found_candidates.each_index do |idx|
      return true if @found_candidates[idx] && @candidates[idx].length ==
MAXCHARACTERS
    end
    false
  end
end

print "Loading game...";$stdout.flush
game = WordBlenderGame.new(ARGV[1] || '/usr/share/dict/words')
puts "#{game.words.size} words found."

while game.play do
  puts "You won, press any key to play next round."
  gets
end

puts "Game over!"

On Friday 05 January 2007 13:05, Ruby Quiz wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until 48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone on Ruby Talk follow the discussion. Please reply to the original
quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If
the player can use all six letters in a word, they proceed to the next
round.

Your task is to build the back-end engine to run a TextTwist clone.
Effectively, this means that you must generate a list of three- to
six-letter words that can all be constructed from the same six letters.
This list must contain at least one six-letter word.

Bonus points for building a completely functional game!

  [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
  [2]: http://www.gamehouse.com/

Here's my submission. There are two programs: the first builds a set
of puzzles from a decent word list I found on line. Files like
/usr/dict/words have too many obscure words. The second is a (very)
simplistic game interface.

A "puzzle" is just a string of six or more 3-6 letter words, sorted by
length. The output of the first program is just one line per puzzle,
with the words separated by colons. The words are ROT13 encoded. The
puzzles.rb program generates 905 different puzzles.

Bob Showalter

---------- puzzles.rb ----------

# puzzles.rb
# generate puzzles for use by wordblend.rb program

···

#
# usage: ruby puzzles.rb >puzzles.dat

require 'open-uri'
require 'yaml'

# these urls point to text files with lists of 2000 commonest
# English word "families", including plurals and other forms.
# this ends up generating reasonably good puzzles.
URIS = %w{
  http://www1.harenet.ne.jp/~waring/vocab/wordlists/full1000.txt
  http://www1.harenet.ne.jp/~waring/vocab/wordlists/full2000.txt
}

# minimum number of words necessary to form a puzzle
MIN_SIZE = 6

# define some helper functions
class String

  # returns string with characters in sorted order
  def sort
    split(//).sort.join
  end

  # returns true if s is a subword of the string. both
  # the string and s must be sorted!
  def subword?(s)
    i = j = 0
    while j < s.length
      i += 1 while i < length and self[i] != s[j]
      i < length or return false
      j += 1
      i += 1
    end
    true
  end

end

# grab the 3-6 letter words from word lists. sort each word by
# character (e.g. 'test' becomes 'estt'), and then accumulate
STDERR.puts "Fetching words..."
words = Hash.new {|h,k| h[k] = []}
URIS.each do |uri|
  open(uri) do |f|
    f.read.split.select {|w| w.length >= 3 and w.length <= 6}.each do |word|
      word.upcase!
      sword = word.sort
      words[sword] << word
    end
  end
end

# find puzzles by looking at which sorted words are contained in
# other six-character sorted words.
STDERR.puts "Finding puzzles..."
n = 0
words.keys.select {|w| w.length == 6}.each do |key|
  puzzle = words.select {|ssub, subs| key.subword?(ssub)}.collect {|a|
a.last}.flatten.sort_by {|w| "#{w.length}#{w}"}
  next if puzzle.size < MIN_SIZE
  puts puzzle.join(':')
end

---------- wordblend.rb ----------

# wordblend.rb
# simplistic Word Blend puzzle game
# uses puzzles.dat file created by separate puzzles.rb program

class String
  def rot13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end
end

class Puzzle

  attr_reader :words, :letters, :board

  def self.pick
    @@puzzles ||= IO.readlines('puzzles.dat')
    new(@@puzzles[rand(@@puzzles.size)].chomp.rot13.split(':'))
  end

  def initialize(words)
    @words = words
    scramble
    @board = words.collect {|w| w.gsub(/./, '-')}
  end

  def scramble
    @letters = words.last.split(//).sort_by {rand}.join
    scramble if words.include? @letters
  end

  def help
    puts "Enter 'Q' to give up, 'S' to scramble letters"
  end

  def play
    help
    turn while board != words
    puts board
  end

  def turn
    puts board
    puts
    puts letters
    while true
      print "? "
      guess = gets.strip.upcase
      if guess == ''
        help
        redo
      end
      if guess == 'S'
        scramble
        puts letters
        redo
      end
      @board = words.dup if guess == 'Q'
      i = words.index(guess) and board[i] = guess
      break
    end
  end

end

# play a random game
p = Puzzle.pick
p.play

require 'rubygems'
require 'facets/core/enumerable/permutation'
require 'set'

#usage: texttwist [word]
# specifying a word will use /usr/share/dict/words to solve a TextTwist
# problem. If no word is specified, a random word will be selected, to
# generate an round of texttwist.

#load dictionary
matcher=Set.new
allwords=Array.new
open("/usr/share/dict/words") do |f|
   f.each do |line|
      line.chomp!
      next if line !~ /^[a-z]+$/
      matcher << line if line.length<=6
      allwords << line if line.length==6
   end
end

#generate subwords of a word
word=ARGV[0] || allwords[rand(allwords.length)]
thiswordmatcher=Set.new
word.split(//).each_permutation do |perm|
   perm=perm.join
   (3..6).each do |len|
      candidate=perm[0,len]
      if matcher.include?(candidate)
   thiswordmatcher << candidate
      end
   end
end

#output
puts word
puts "======"
thiswordmatcher.each do |subword|
   puts subword
end

···

On Fri, 05 Jan 2007 22:05:52 +0900, Ruby Quiz wrote:

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

  [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
  [2]: http://www.gamehouse.com/

--
Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/

Ruby Quiz wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ben Bleything

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2] and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and is
tasked with unscrambling those letters into as many words as possible. If the
player can use all six letters in a word, they proceed to the next round.

Your task is to build the back-end engine to run a TextTwist clone. Effectively,
this means that you must generate a list of three- to six-letter words that can
all be constructed from the same six letters. This list must contain at least
one six-letter word.

Bonus points for building a completely functional game!

  [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
  [2]: http://www.gamehouse.com/

class String
  def chars
    split("")
  end
  def sorted
    chars.sort.join
  end
end

# Generate combinations.
def comb array, n, str = "", &blk
  0.upto(array.size - n){|i|
    if 1 == n
      yield str + array[i]
    else
      comb array[i+1..-1], n-1, str+array[i], &blk
    end
  }
end

word_groups = Hash.new {}
shorts = Hash.new {}
while word = gets do
  next unless (word=word.downcase.delete('^a-z')).size.between?(3,6)
  if 6 == word.size
    word_groups[word.sorted] += [ word ]
  else
    shorts[word.sorted] += [ word ]
  end
end

word_groups.each_key{|key|
  3.upto(5){|n|
    combinations =
    comb( key.chars, n ){|s| combinations << s}
    combinations.uniq.each{|s| word_groups[key] += shorts[s] }}}

This just solves the find-all-subwords problem:

target = ARGV[0]
dict = ARGV[1] || 'sowpods'

reduced = target.split(//).sort.uniq.join
primes = [2, 3, 5, 7, 11, 13]
factors = []
reduced.split(//).each_with_index {|e, i|
  factors[e[0]] = primes[i]
}

target_num = 1
target.each_byte {|i| target_num *= factors[i]}

IO.foreach(dict) {|word|
  word.chomp!
  next unless (word =~ /^[#{reduced}]+$/) &&
    (word.length < 7) && (word.length > 2)
  p = 1
  word.each_byte {|i| p *= factors[i]}
  puts word if target_num % p == 0
}

My solution doesn't track "points" per say, just the rounds really. So for my code, it's getting to the highest round.

Feel free to add some scoring though. I would probably score based on words found, with bigger words earning more points.

James Edward Gray II

···

On Jan 5, 2007, at 7:53 AM, Jason Mayer wrote:

Is the goal to get the most points or to get to the highest round? Do you
get points based on the number of letters used (as in Upwords) or do you get
points based on the obscurity of the letter to be used (as in scrabble)?

I'm using the ones here: http://wordlist.sourceforge.net/
I don't know how good they are, but they're ok for this particular quiz IMHO

···

On Friday 05 January 2007 17:53, Matthew Moss wrote:

I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file. Does anyone
have links to decent word/dictionary files? Or perhaps does Mac OS X
come with one?

Sure: /usr/share/dict/words

James Edward Gray II

···

On Jan 5, 2007, at 9:53 AM, Matthew Moss wrote:

Or perhaps does Mac OS X come with one?

Official scrabble list of accepted 6 letter words:
http://www.math.utoronto.ca/jjchew/scrabble/lists/ospd-only-6.html

···

On 1/5/07, Matthew Moss <matthew.moss.coder@gmail.com> wrote:

I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file. Does anyone
have links to decent word/dictionary files? Or perhaps does Mac OS X
come with one?

This is my first ruby program that actually does all it's supposed to do. It
doesn't have a lot of style, but hopefully my next programs will look better.
The wordlist is from wordlist.sourceforge.net and the first part of the
program reads the word.lst file and puts all the words in an array.

Hope it does what it's supposed to do, as i couldn't see the texttwist on
yahoo.
I'm also attaching the Unnoficial Jargon File Wordlist word.lst file.

-Ionuţ Arţărişi

texttwist.rb (2.3 KB)

word.lst (12.1 KB)