William James wrote:
> Short:
> 
> D=IO.read('dict').split($/).grep(/^.{#{$*[0].size}}$/)
> def rate(new,old,goal,l)
>   t=0; v=0; new.size.times{|i|t+=1 if new[i]!=old[i]
>     v+=1 if new[i]==goal[i] }
>   [ 1==t && nil==l.index(new), v ]
> end
> def m(a,b,l)
>   l << a.dup
>   if a==b then p l; exit; end
>   D.inject([]){|w,x| y,v = rate(x,a,b,l)
>     w << [v,x] if y ; w
>   }.sort.reverse_each{|v,x| m(x,b,l) }
> end
> m($*[0], $*[1], [])

Using Dominik's nice idea i boiled it down to:
(the dictionary is the optional third parameter, no -d)

-----------------------------------------------------------
dict, len = Hash.new{|h,k|h[k] = []}, ARGV[0].size
IO.foreach(ARGV[2] || 'words.txt') do |w| w.chomp!
   if w.size != len then next else s = w.dup end
   (0...w.size).each{|i|s[i]=?.; dict[s] << w; s[i]=w[i]}
end
t, known = {ARGV[1] => 0}, {}
while !known.merge!(t).include?(ARGV[0])
   t = t.keys.inject({}){|h, w|(0...w.size).each{|i|
     s=w.dup; s[i]=?.; dict[s].each{|l|h[l] = w if !known[l]}};h}
   warn 'no way!' or exit if t.empty?
end
puts w = ARGV[0]; puts w while (w = known[w]) != 0
-----------------------------------------------------------

It's still quite fast.

BTW:
Is there a nice way to construct a hash, like #map does?
like:
array = (1..10).map{|i| i}
hash = (1..10).map_to_hash{|i| i => i*2}

(i always use inject in a 'not so nice' way)

cheers

Simon