# Word Search (#107)

The three rules of Ruby Q.:

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 Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
message,
if you can.

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

by Daniel F.

Today’s quiz would’ve been most useful in elementary school, where over
half of
the homework assignments were word search puzzles. The concept of these
puzzles
is simple enough that an elementary school student could understand it:
given a
box of letters, find a line containing the letters of a specified word
in order.

For example, find the words ruby, dan, rocks, and matz in the following
text:

``````U E W R T R B H C D
C X G Z U W R Y E R
R O C K S B A U C U
S F K F M T Y S G E
Y S O O U N M Z I M
T C G P R T I D A N
H Z G H Q G W T U V
H Q M N D X Z B S T
N T C L A T N B C E
Y B U R P Z U X M S
``````

The correct answer in the correct output format:

``````+ + + R + + + + + +
+ + + + U + + + + +
R O C K S B + + + +
+ + + + + + Y + + +
+ + + + + + + + + M
+ + + + + + + D A N
+ + + + + + + T + +
+ + + + + + Z + + +
+ + + + + + + + + +
+ + + + + + + + + +
``````

Notice that the words can go backwards and diagonally, and can intersect
one
another. Searching is case insensitive.

The word search solver should accept input entered by the user after
running the
program, i.e., not exclusively through STDIN or a file, by entering the
puzzle
line by line, pressing return after each line. A blank line indicates
the end of
the puzzle and the start of the comma separated words to find. The
following
example shows how a user would enter the above puzzle, with descriptive
text
from the program removed.

``````\$ ./wordsearch.rb
UEWRTRBHCD
CXGZUWRYER
ROCKSBAUCU
SFKFMTYSGE
YSOOUNMZIM
TCGPRTIDAN
HZGHQGWTUV
HQMNDXZBST
NTCLATNBCE
YBURPZUXMS

Ruby, rocks, DAN, matZ
``````

Now, by itself, this quiz is fairly simple, so I offer an additional
challenge.
Write a beautiful, extensible, and easily-modifiable program without
looking at
the extra credit before starting. When you’re done, try implementing
extra
credit options using less than 5 or 6 (reasonable) lines of code.

``````* An output format superior to the one given. The output format given
should remain the default unless both formats don't differ on a
textual basis. That should sound cryptic until pondered, I can't
give too much away!
* Allow for "snaking" of answers, in other words, the letters
composing a word don't have to be in a straight line.
* An option to give a hint, i.e., "The word ruby traverses the bottom
* Decide what to do with accented letters.
* Allow for wildcard letters.
``````

Ruby Q. wrote:

N T C L A T N B C E

• T + +
• Z + + +

Shouldn’t that be:

``````+ + + R + + + + + +
+ + + + U + + + + +
R O C K S B + + + +
+ + + + + + Y + + +
+ + + + + + + + + M
+ + + + + + + D A N
+ + + + + + + T + +
+ + + + + + Z + + +
+ + + + + + + + + +
Y B U R + + + + + +
``````

?

What should happen if the same word occurs more than once in the puzzle?

On Dec 29, 2006, at 11:45 AM, Phrogz wrote:

H Z G H Q G W T U V

• M
R O C K S B + + + +
• Y + + +
• M
• D A N
• T + +
• Z + + +

Y B U R + + + + + +

?

Good catch. I agree that the second match should be shown.

James Edward G. II

Hi,

nice quiz … Not too time intensive

On Dec 29, 2006, at 5:06 PM, Ruby Q. wrote:

Today’s quiz would’ve been most useful in elementary school, where
over half of
the homework assignments were word search puzzles. The concept of
these puzzles
is simple enough that an elementary school student could understand
it: given a
box of letters, find a line containing the letters of a specified
word in order.

I attached my solution. See test_ruby_quiz. Please be gentle

• An output format superior to the one given. The output format given
should remain the default unless both formats don’t differ on a
textual basis. That should sound cryptic until pondered, I can’t
give too much away!
I like the current format.
• Allow for “snaking” of answers, in other words, the letters
composing a word don’t have to be in a straight line.
It allows for snaking, see “test_snake”, and also for matches that
leave the field on one side and comes back on another, e.g. YRUB. See
test_overflow.
• An option to give a hint, i.e., “The word ruby traverses the bottom
nah.
• Decide what to do with accented letters.
Didn’t implement that either.
• Allow for wildcard letters.
Didn’t go all the way like using regexp, but “*” is allowed as a
wild card character.

Funny enough, my Sudoku solution took less lines than this one …

Cheers,
Mariano

ï¿¼ï¿¼

Oh, and btw. wrt the “overflow” mechanism the actual quiz solution
looks like this:

+++R++++++
++++++++++
ROC+++++++
++K+++++++
+S+++++++M
+++++++DAN
+++++++T++
++++++Z+++
++++++++++
YBU+++++++

Note, that I don’t try to find multiple solutions.

Cheers,
Mariano

Here is my solution for the word search quiz. It’s faily simple and
stupid and
doesn’t do any of the extras, except it accepts a ‘?’ wildcard.

But then it also finds words that break the board boundaries and
continue on
the other side… call it bug of feature , so this:

\$ ruby wordsearch.rb
UEWRTRBHCB
CXGZUWRYUR
ROCKSBARCU
SFKFMTYSGE
YSOOUNMZIM
TCGPRTIDAN
HZGHQGWTUV
HQMNDXZBST
NTCLATNBCE
YBURPZUXMS

Ruby, rocks, DAN, matZ

… gives you:

+++R+++++B
++++U+++U+
ROCKSB+R++
++++++Y+++
+++++++++M
+++++++DAN
+++++++T++
++++++Z+++
++++++++++
YBUR++++++

Happy new year,
Martin

# create a each_chr method

class String
def each_chr
self.each_byte { |b| yield b.chr }
end
end

class Puz
DIRECTIONS = [[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]]

# word_list - array for words to look for

def initialize(puzzle, word_list)
@puzzle = puzzle
@word_list = word_list

# get dimention of the board

@x,@y = @puzzle[0].length-1, @puzzle.length-1

# create an empty result board

@result = Array.new(@y+1)
0.upto(@y) { |i| @result[i] = String.new("+"*(@x+1)) }
end

protected

# return cursor to the next position in direction

def gonext(x,y,d)
x+=d[0]; y+=d[1]
x = 0 if x > @x; x = @x if x < 0
y = 0 if y > @y; y = @y if y < 0
[x,y]
end

# writes a word into the @result container

def write_result(word, x,y, direction)
word.each_chr do |c|
@result[y][x] = c
x,y = gonext(x,y, direction)
end
end

# yields all possible cursor positions of the board

def each_position
0.upto(@y) { |y| 0.upto(@x) { |x| yield x,y }}
end

def char_match(char, x, y)
return true if char == ‘?’ # allow wildcard ‘?’
@puzzle[y][x].chr == char
end

# finds a given word on a position in a specific direction

def find_in_direction(word, x, y, d)
word.each_chr do |c|
return false if !self.char_match(c, x, y)
x,y = gonext(x,y,d)
end
true
end

# finds a word on a position

def find(word, x, y)
DIRECTIONS.each do |d|
write_result(word, x,y,d) if find_in_direction(word, x,y, d)
end
end

public

def resolve
@word_list.each do |word|
each_position { |x,y| find(word, x, y) }
end
return (@result.join("\n"))
end
end

board = []
while true do
inp = STDIN.gets.strip
if inp.length > 0 then board << inp else break end
end

puts Puz.new(board, STDIN.gets.split(’,’).collect{ |w|
w.strip.upcase}).resolve

Here is my solution. It is my first Ruby program. I was able to get two
of the extra credit with virtually no work. I had snaking already
because I generated the snaked answers before eliminating the ones that
weren’t a straight line. I had an alternative output format because I
was using it to debug my code as I went along before I wrote the code
to generate the answer grid. Using the sample input set, here is my
output:

+++R+R++++
++++U+++++
ROCKSB++++
++K+++Y+++
+S+++++++M
+++++++DAN
+++++++T++
+++ND+Z+++
++++A+++++
YBUR++++++

RUBY
(3,0)(4,1)(5,2)(6,3)
(5,0)(4,1)(5,2)(6,3)
(3,9)(2,9)(1,9)(0,9)
ROCKS
(0,2)(1,2)(2,2)(3,2)(4,2)
(0,2)(1,2)(2,2)(2,3)(1,4)
DAN
(7,5)(8,5)(9,5)
(4,7)(4,8)(3,7)
MATZ
(9,4)(8,5)(7,6)(6,7)

The coordinates in the output set are 0-based x,y originating from the
upper-left corner.

-Ben

require “enumerator”

class Point
def initialize(x, y)
@x = x
@y = y
end
def to_s
“(#{@x},#{@y})”
end
((@x - p.x).abs <= 1) & ((@y - p.y).abs <= 1) & !(self == p)
end
def -§
Point.new(@x - p.x, @y - p.y)
end
def ==§
(@x == p.x) & (@y == p.y)
end
end

class Array
def diff
end
def same?
return false if length < 1
return true if length == 1
end
end

def findletter(puzzle, c)
locations = []
puzzle.each_with_index do |line, y|
line.split(//).each_with_index do |letter, x|
locations << Point.new(x, y) if letter == c
end
end
return locations
end

def getletters(puzzle, term)
term.split(//).map{|c| findletter(puzzle, c)}
end

def mixarrays(arr)
return [] if (arr.empty?)
return arr.first.zip if (arr.length == 1)

temp = []
tail = arr.slice(1, arr.length-1)
mixarrays(tail).each do |y|
temp << [x] + y
end
end
return temp
end

def connectedword(word)
return false if word.length < 1
return true if word.length == 1
end

def showpoints(term, points)
puts term
points.each {|x| print x, “\n” }
end

answer = puzzle.map {|line| line.gsub(/./, ‘+’)}
points.flatten.each do |p|
end
end

puzzle = []
while (line = gets.chomp) != ‘’
puzzle << line
end
terms = gets.chomp.upcase.split(/\s*,\s*/)

terms_words = terms.map{|term|
[term, mixarrays(getletters(puzzle, term))]}

terms_connectedwords = terms_words.map{|term, words|
[term, words.select {|word| connectedword(word)}]}

terms_samediffconnectedwords = terms_connectedwords.map{|term, words|
[term, words.select {|word| word.diff.same?}]}

puts

puts
answerkey.each {|term, words| showpoints(term, words) }

#! /usr/bin/env ruby

# Glen Pankow 12/29/06

#----------------------------------------------------------------------------

matrix-type

or

be

transformations

the quiz

# extra credit problems, but what the hey.

class String

``````#
# upcase_trim
#
# Return a copy of the current string with a non-letter characters
``````

trimmed
# and all lower case letters converted to upper case.
#
def upcase_trim
upcase.gsub(/[^A-Z]/, ‘’)
end

``````#
# replicate_match(strs, replication_str)
#
# If any of the simple Strings in the Array <strs> is found in the
``````

current
# string (possibly in multiple places and possibly overlapping), the
String
# <replication_str> is updated with the matching string in the
corresponding
# location. may also be a single String object.
#
# For example, ‘abdcbcbcb’.replicate_match([‘cbc’, ‘ab’],
‘---------’)
# updates the ‘---------’ to ‘ab-cbcbc-’.
#
def replicate_match(strs, replication_str)
strs = [ strs ] unless (strs.is_a?(Array))
strs.each do |str|
str_len = str.length
next if (length < str_len)
offset, last_offset = 0, length - str_len
while (offset <= last_offset)
if str_pos = index(str, offset)
replication_str[str_pos, str_len] = str
offset = str_pos
end
offset += 1
end
end
end

``````#
# spacey_str = string.space_out
#
# Return a copy of the current string with space characters inserted
# between its characters (plus an initial space character).
#
def space_out
gsub(/(.)/, ' \1')
end
``````

end

class WordSearch

``````#
# word_search = WordSearch.new
#
# Initialize and return an empty word search puzzle object.
#
def initialize
@text_lines = [ ]
end

#
#
# Add the String <line> to the current word search puzzle object.
#
@text_lines << line.upcase_trim
end

#
# word_search.solve(*words)
#
# Solve the current word search object for the words <words>.  The
``````

solution
# is returned, which is an Array of Strings in the same shape as the
# original puzzle, where the solution word letters are kept intact,
but the
# non-solution word letters replaced with the character ‘+’.
#
# We tackle this problem by doing simple string matches of
over
# repeated transformations of the puzzle text lines:
#
# ABCD hflip DCBA diag D hflip D undiag DHL
# EFGH -----> HGFE ----> CH -----> HC ------> CGK
# IJKL LKJI BGL LGB BFJ
# (L->R) (R->L) AFK KFA AEI
# ^ EJ JE (T->B)
# | I I |
# | undiag (TL->BR) (BR->TL) | hflip
# | v
# A hflip A diag AEI hflip IEA vflip LHD
# BE <------ EB <----- BFJ <----- JFB <----- KGC
# CFI IFC CGK KGC JFB
# DGJ JGD DHL LHD IEA
# HK KH (B->T)
# L L
# (TR->BL) (BL->TR)
#
# Other types of transformations (such as straight transpose) would
be
# easier (by simply undoing some transformation steps), but would
require
# more steps.
#
def solve(*words)
words = words.collect { |word| word.upcase_trim }

``````    #
# Make the various transformations, checking for matches along
``````

the
# way.
#
normalize ; replicate_match(words) # match L->R
flip_horizontal ; replicate_match(words) # match R->L
diagonalize ; replicate_match(words) # match
TL->BR
flip_horizontal ; replicate_match(words) # match
BR->TL
undiagonalize(true) ; replicate_match(words) # match T->B
flip_horizontal ; replicate_match(words) # match B->T
flip_vertical ; flip_horizontal
diagonalize ; replicate_match(words) # match
BL->TR
flip_horizontal ; replicate_match(words) # match
TR->BL
undiagonalize(false)

``````    #
# And return the solution.
#
@sltn_lines
end
``````

protected

``````#
# word_search.normalize
#
# Undiagonalizing is somewhat tricky, as we need to recover its
``````

original
# (or transposed) shape. Set the internal state of this object for
# suitable shape information.
#
# Also, (un)diagonalizing will be screwed up if this shape is not a
nice,
# full rectangle. Pad it if necessary. And, clear out the solution
array
# (and give it the same shape).
#
def normalize
@height = @text_lines.size
@width = 0
@sltn_lines = [ ]
@text_lines.each do |line|
len = line.length
@width = len if (len > @width)
@sltn_lines << ‘+’ * len
end
(0…@text_lines.size).each do |i|
@text_lines[i] << ‘+’
@sltn_lines[i] << ‘+’
end
end
end

``````#
# word_search.flip_horizontal()
#
# Flip all the lines of the current word search puzzle object
``````

horizontally.
#
# (Note: this and all similar methods should more appropriately be
named
# in their bang (!) forms, but I don’t do that for this quiz, nor do
I do
# other normal things here like returning self.)
#
def flip_horizontal
(0…@text_lines.size).each do |i|
@text_lines[i].reverse!
@sltn_lines[i].reverse!
end
end

``````#
# word_search.flip_vertical()
#
# Flip all the lines of the current word search puzzle object
``````

vertically.
#
def flip_vertical
@text_lines.reverse!
@sltn_lines.reverse!
end

``````#
# word_search.diagonalize()
#
# Convert the lines of the current word search puzzle object to a
``````

kind of
# diagonalized form.
#
# Note that here I don’t presize the arrays, and so use the ||=
trick
# (well, I suppose it’s possible to figure out how big to make the
arrays,
# but I didn’t bother doing that).
#
def diagonalize
text_lines = @text_lines ; @text_lines = [ ]
sltn_lines = @sltn_lines ; @sltn_lines = [ ]
text_lines.each_with_index do |line, i|
line.split(‘’).each_with_index do |char, j|
(@text_lines[i+j] ||= ‘’) << char
(@sltn_lines[i+j] ||= ‘’) << sltn_lines[i][j]
end
end
end

``````#
# word_search.undiagonalize(transposed)
#
# Convert the lines of the current word search puzzle object back
``````

into a
# rectangular form. Because we don’t do true matrix-like
manipulation (we
# work with simple strings) and thus lose any original indexing (via
simple
# string appending), we need original shape information in order to
do the
# reconstruction.
#
# But this is perhaps fortuitous, because we can in fact reconstruct
the
# lines into a transposed-like shape (saving us several
transformation
# steps).
#
def undiagonalize(transposed)
text_lines = @text_lines
@text_lines = Array.new(transposed ? @width : @height) {
String.new }
sltn_lines = @sltn_lines
@sltn_lines = Array.new(transposed ? @width : @height) {
String.new }
text_lines.each_with_index do |line, i|
if (transposed)
o = (i + 1 < @height)? 0 : i + 1 - @height
else
o = (i + 1 < @width)? 0 : i + 1 - @width
end
line.split(‘’).each_with_index do |char, j|
@text_lines[j+o] << char
@sltn_lines[j+o] << sltn_lines[i][j]
end
end
end

``````#
# word_search.replicate_match(words)
#
# Update the solution lines of the current word search puzzle object
``````

with
# any matches of the word Strings .
#
def replicate_match(words)
@text_lines.each_with_index do |line, i|
line.replicate_match(words, @sltn_lines[i])
end
end
end

# Go for it!

puzzle = WordSearch.new
infile = ((ARGV.size == 0) || (ARGV[0] == ‘-’))? \$stdin :
File.open(ARGV[0])
loop do
line = infile.gets
break if line =~ /^\s*\$/
end
words = infile.gets.strip.split(/\s+/)
print "\nAnd the solution is:\n “,
puzzle.solve(*words).collect { |line| line.space_out }.join(”\n "),
“\n”

Here is my solution. It supports ? and * as wildcard letters.

-Chunyun

Sample input:

U E W R T R B H C D
C X U Z U W R Y E R
R O C K S B A U C U
Y F K F M T Y S G E
Y S O O U N M E I M
T C G P R T I D A N
Y Z G H Q G W T U R
H B M N D X Z B U T
N T U L A T N X C E
Y B U R P Z Y X M S

Ru?y Year ro*s DAN matZ

Output:

• R + + + + + +
• U + U + + + + +
R O C K S B + + + +
Y + + + + + Y + + +
• E + M
• D A N
Y + + + + + + T + R
• B + + + + Z + U +
• U + + + + X + +
Y B U R + + Y + + +

Code:
#== Synopsis
#This is the solution to Ruby Q. #107 described on
Ruby Quiz - Word Search (#107).

#== Usage

#== Author

# Chunyun Z.([email protected])

class WordSearch

def initialize(box)
@box = box
@height = @box.size
@width = @box[0].size
end

def search_words(words)
@found_coords=[]
regexize_words(words)
each_line do |line_coords|
line_str = get_line_str(line_coords)
words.each do |word|
if line_str=~/#{word}/i
offset = \$~.offset(0)
@found_coords |= line_coords[offset[0]…offset[1]]
end
end
end
end

def display_words
for x in 0…@height
for y in 0…@width
@found_coords.include?([x,y])? print(@box[x][y]):print(‘+’)
print ’ ’
end
puts
end
end

private
#Generates all possible lines(represented as arrays of coordinates)
from
@box , and
#calls the block with the line coordinates.
def each_line
vertical_line_proc = lambda {|x, y| [x+1, y]}
horizonal_line_proc = lambda {|x, y| [x, y+1]}
backward_diagonal_line_proc = lambda {|x, y| [x+1, y+1]}
forward_diagonal_line_proc = lambda {|x, y| [x+1, y-1]}

``````lines = []
#Genernates the lines starting with the top horizonal line
for y in 0...@width
lines << get_line_coords(0, y, &vertical_line_proc)
lines << get_line_coords(0, y, &backward_diagonal_line_proc)
lines << get_line_coords(0, y, &forward_diagonal_line_proc)
end
#Genernates the lines starting with the leftmost and rightmost
``````

vertical
lines
for x in 0…@height
lines << get_line_coords(x, 0, &horizonal_line_proc)
lines << get_line_coords(x, 0, &backward_diagonal_line_proc)
lines << get_line_coords(x, @width-1, &forward_diagonal_line_proc)
end
lines.each{|line_coords|yield line_coords; yield
line_coords.reverse}
end

#Generates the line starting with coordinate [x,y]. It calls the block
to
find the
#next position in the line. It can be used to generate snake lines if
necessary.
def get_line_coords(x, y)
line = [[x,y]]
loop do
next_x, next_y = yield x, y
@box[next_x] && @box[next_x][next_y] ? line << [next_x, next_y] :
break
x, y = next_x, next_y
end
line
end

#Gets the string represented by an array of coordinates.
def get_line_str(coords)
line_str = ‘’
coords.each{|x,y| line_str << @box[x][y]}
line_str
end

#Replaces ? and * with \w? and \w* in each word so that it could be
used
#as the regex to support wildcard letter matching.
def regexize_words(words)
words.each {|word|word.gsub!(/(?|*)/, ‘\w\1’)}
end
end

box = []
width = nil
while line=gets
break if line.strip.empty?
row = line.split
raise “The width of all the rows must be equal!” if !width.nil? and
width
!= row.size
width = row.size
box << row
end
raise “You need at least enter one row of letters!” if box.empty?
words = gets.split
raise “You need at least enter one word!” if words.empty?

ws = WordSearch.new(box)
ws.search_words(words)
ws.display_words

END

Here’s my submission. No extra credits.

# Bob S.

class WordSearch

class Board < Array

``````def to_s
collect {|s| s.split(//).join(' ')}.join("\n")
end
``````

end

# creates a new, empty solver

def initialize
@board = Board.new
@solution = Board.new
end

# resets the solution

def reset
@solution.clear
@board.each {|row| @solution << row.gsub(/./, ‘+’)}
end

uniform

# rectangular shape

def validate
@board.size > 0 or raise “Board has no rows”
@board.grep(/[^A-Z]/).empty? or raise “Board contains non-letters”
w = @board.collect {|row| row.size}.uniq
w.size == 1 or raise “Board rows are not all the same length”
w.first > 0 or raise “Board has no columns”
end

# parses the board by reading lines from io until a blank line (or

EOF)

def parse(io = ARGV)
@board.clear
while line = io.gets
line = line.strip.upcase
break if line == ‘’
@board << line
end
validate
reset
end

with

# all occurences.

def search(word)
found = 0
0.upto(board.size-1) do |y|
0.upto(board[y].size-1) do |x|
[-1, 0, 1].each do |dy|
[-1, 0, 1].each do |dx|
next if dx == 0 and dy == 0
found += 1 if search_for(word.strip.upcase, x, y, dx, dy)
end
end
end
end
found
end

# search for word in board starting at position (x,y) and moving in

direction

def search_for(word, x, y, dx, dy)
return false if x < 0 or x >= board.first.size or y < 0 or y >=
board.size
return false if board[y][x] != word[0]
prev = solution[y][x]
solution[y][x] = board[y][x]
return true if word.length <= 1
found = search_for(word[1,word.length-1], x + dx, y + dy, dx, dy)
solution[y][x] = prev unless found
found
end

# creates a new puzzle by parsing the board from io. see

WordSearch#parse
def self.parse(io = ARGF)
obj = new
obj.parse(io)
obj
end

def to_s
solution.to_s
end

end

# parse the board first

p = WordSearch.parse

# parse the words until a blank line is read

words = []
while line = ARGF.gets
line = line.strip.upcase
break if line == ‘’
words += line.gsub(’,’, ’ ').split
end

# submit each word and show how many times it was found

for word in words.sort.uniq
n = p.search(word)
puts word + ’ was ’ + (n == 0 ? ‘not found’ : n == 1 ? ‘found once’
: “found #{n} times”)
end

# show the solution

puts p

Ruby Q. wrote:

S F K F M T Y S G E

• U + + + + +
R O C K S B + + + +
• Y + + +
• M
• D A N
• T + +
• Z + + +

If you don’t want snaking, put “–straight” on the command line.

def write ary
ary.each{|c,row,col| \$out[row][col] = c }
end

def outside y, x
y<0 or y>=Board.size or x<0 or x>=Board.first.size
end

def snake letters, row, col, directions, placed
return if letters[0] != Board[row][col]
placed << [letters[0],row,col]
if letters.size == 1
write placed
return
end
directions.each{|dy,dx|
y = row + dy ; x = col + dx
next if outside( y, x )
snake letters[1…-1], y, x, directions, placed.dup
}
end

straight = ARGV.delete ‘–straight’

puts “Enter grid line by line, followed by blank line.”
Board = []
while (line = gets.strip.upcase) != “” do
Board << line
end

puts “Enter words separated by commas.”
words = gets.strip.upcase.split(/\s*,\s*/)

\$out = Board.map{|s| “+” * s.size}

all_directions = (-1…1).inject([]){|a,m| (-1…1).each{|n|
a<<[m,n]}; a}
all_directions.delete [0,0]

Board.each_index{|row|
Board[0].size.times{|col|
words.each{|word|
if straight
all_directions.each{|direction|
snake word, row, col, [direction], []
}
else
snake word, row, col, all_directions, []
end
}
}
}

puts “”, \$out.map{|s| s.split(’’).join(’ ') }