Here is my solution. I didn't post the whole thing. This is the main
engine minus the Tk UI class. You can get the full solution
(dictionary, graphics and ruby source at
http://www.dalemartenson.com/files/rubytexttwist.tar.gz

--Dale Martenson


class TextTwist
  # Algorithm derived from online book by Robert Sedgewick and Kevin
Wayne.
  # It was origomally written in Java.
  #
  # References:
  # http://www.cs.princeton.edu/introcs/31datatype/
  # http://www.cs.princeton.edu/introcs/31datatype/TextTwist.java.html

  class Profile < Hash
    def initialize( word )
      super
      word.downcase.each_byte do |b|
        self[b.chr] = 0 unless self.has_key?(b.chr)
        self[b.chr] += 1
      end
    end

    def contains( p )
      p.each_pair do |k,v|
        return false unless self.has_key?(k)
        return false if self[k] < v
      end
      true
    end
  end

  attr_reader :word, :words

  def initialize( dictionary, word=nil )
    @dictionary = dictionary
    @dictionary.collect! {|x| x.downcase}
    @dictionary.sort!
    word.nil? ? restart : start( word )
  end

  def start( word )
    @word = word.downcase
    @profile = Profile.new( @word )
    @words = process
  end

  def process
    result = []

    @dictionary.each do |dw|
      next if dw.length < 3 || dw.length > @word.length
      result << dw if @profile.contains( Profile.new( dw ) )
    end

    result
  end

  def mix
    a = @word.split(//)
    1.upto(a.length) do
      i1 = rand(a.length)
      i2 = rand(a.length)
      t = a[i1]
      a[i1] = a[i2]
      a[i2] = t
    end
    a
  end

  def check( word )
    @words.include?( word.downcase )
  end

  def contains( word )
    @profile.contains( Profile.new( word ) )
  end

  def restart
    six_letter_words = []
    @dictionary.each do |w|
      six_letter_words << w if w.length == 6
    end

    start( six_letter_words[ rand(six_letter_words.size) ] )
  end
end

class TextTwistUI
# ... SNIP ...
end

# MAIN PROGRAM -- where the magic begins

# Using the crossword dictionary from "Moby Word Lists" by Grady Ward
(part of
# Project Gutenberg). I reduced the dictionary to only words that are 3
to 6
# characters in length.
#
# Note: This may not be the best dictionary for this game, but it
works. It
# does contain numerous obscure words which is both good and bad.
#
#     irb(main):001:0> f = File.open("crosswd.txt")
#     => #<File:crosswd.txt>
#     irb(main):002:0> of = File.open("3-6.txt","w+")
#     => #<File:3-6.txt>
#     irb(main):003:0> f.each_line do |line|
#     irb(main):004:1*   l = line.strip
#     irb(main):005:1>   if l.length >= 3 && l.length <= 6 then
#     irb(main):006:2*    of.puts( l )
#     irb(main):007:2>   end
#     irb(main):008:1> end
#     => #<File:crosswd.txt>
#     irb(main):009:0> of.close
#     => nil
#
# References:
# http://www.gutenberg.org/etext/3201

dictionary = []
f = File.open("3-6.txt")
dictionary = f.read.split

tt = TextTwist.new( dictionary )

TextTwistUI.new( tt )
Tk.mainloop