From: Adam Shelly Date: 2008-04-16T08:53:23+09:00 Subject: Re: [QUIZ] Word Search Generator (#159, again) On 4/11/08, Matthew Moss wrote: > The three rules of Ruby Quiz 2: > Your task for this week's quiz is to write a Ruby program that > generates word search puzzles, given a list of words and the desired > width and height for the puzzle. Here's my solution - it's mostly brute force, but it does attempt to place the first letter of a new word on an existing letter in the grid. If it gets stuck it will remove some placed words and try again. It usually fits the list of programming words into a 20x20 grid, but sometimes it takes a long time to get there. -Adam --- Directions = [[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]] Mark = ['|', '\\', '-', '/', '|', '\\', '-', '/'] #$ShowProgress = true class CrossWord attr_accessor :word, :place, :dir def initialize w @word=w @dir=0 @place=nil end def unplaced? @place==nil end end class CharGrid def initialize cols,rows @w=cols @h=rows @g="."*(@w*@h) @ref_count = Array.new(@w*@h){0} @iterations = 0 end #fills the grid with words def fill words iterations = 0 @words = words.map{|w| CrossWord.new(w)} words_todo = @words.select{|w|w.unplaced?} until words_todo.empty? words_todo.each{|cw| place cw } words_todo = @words.select{|w|w.unplaced?}.sort_by{rand} if (iterations+=1) %(@w+@h)==0 #if we are getting stuck, try removing some words puts "#{togo = words_todo.size} to go..." words_done = (@words-words_todo).sort_by{rand} (togo*2).times{|i| words_todo<< remove(words_done[i]) if words_done[i]} end end end #returns the obfuscated grid def to_s puz = @g.dup puz.size.times{|i|puz[i]=('a'..'z').to_a[rand(26)] if puz[i]==?.} sln=[] newwidth=(2*@w-1) @h.times{|r| sln<= @g.size && [0,1,7].include?(dir)) or (pos%@w == 0 && (5..7).include?(dir)) or (pos%@w+1 == @w && (1..4).include?(dir)) return nil end pos+Directions[dir][0]*@w+Directions[dir][1] end #adds word at index in direction def add cw, idx, dir puts "+#{cw.word}" if $ShowProgress cw.dir = dir pt = cw.place = idx cw.word.each_byte{|chr| @g[pt]=chr @ref_count[pt]+=1 pt=nextp(pt,dir) } return [idx,dir] end #removes word from grid def remove cw p "-#{cw.word}" if $ShowProgress pt = cw.place cw.word.each_byte{|chr| @g[pt]=?. if 0== (@ref_count[pt]-=1) pt=nextp(pt,cw.dir) } cw.place=nil cw end #finds random start for word begining with matchchar def randStart matchChar start = rand(@w*@h) start= (start+1)%(@w*@h) until (@g[start]==?. || @g[start]==matchChar) start end def randDir rand(8) end end if __FILE__ == $0 gridsize = ARGV[1].split('x').map{|v|v.to_i} g = CharGrid.new *gridsize words=File.open(ARGV[0],"r").read.split.sort_by{|s|s.length}.reverse puts "unsolvable!" or exit if (words[0].size>gridsize.max) g.fill words puts g.pack File.open("search.txt","w"){|f|f.puts g.to_s} File.open("solution.txt","w"){|f|f.puts g.solution} end