This is my solution.
My first thought was to make a FormattedCrossword and a Crossword class
and to let the latter have a #format method which creates a
FormattedCrossword from its contents. But that would have led to much
data duplication and complication, so I used a single Crossword class.
The hearts of my solution are Crossword#calculate_layout and
Crossword#display. Crossword#calculate_layout finds the locations of
the numbers and clears any filled squares at the border (by calling the
recursive method Crossword#empty_filled_square).
This quiz took me only about 20 minutes, though it had some parts which
first seemed easy but then weren't... that was really fun. :)
Markus Koenig
----
#!/usr/bin/env ruby
def Array.make2d(h, w, value)
# make a two-dimensional Array initialized with value
line = [value] * w
arr = Array.new
h.times do
arr.push line.dup
end
return arr
end
class Crossword
# --instance vars--
# @h: height
# @w: width
# @board: square types (2d array)
# true: filled inner square
# false: non-filled inner square
# nil: previously filled outer square
# @numbers: square number (2d array)
# Integer: this number
# nil: no number
# output options
CBORDER = '#'
CFREE = ' '
def Crossword.parse
# parse ARGF
arr = [[]]
y = 0
until ARGF.eof
case ARGF.getc
when ?_, ?-
arr[y].push false
when ?X, ?x
arr[y].push true
when ?\n
if arr[y].length != 0
arr.push []
y += 1
end
end
end
h = (arr[y].length == 0) ? y : y + 1
w = arr.map{|subarr| subarr.length}.max
cw = Crossword.new(h, w)
h.times do |y|
w.times do |x|
cw.board[y][x] = true if arr[y][x] != false
end
end
return cw
end
def initialize(h, w)
@h = h
@w = w
@board = Array.make2d(h, w, false)
end
attr_reader :board
def empty_filled_square(y, x)
# recursively replace a filled area with nils
return if y < 0 or x < 0
return if y >= @h or x >= @w
return if @board[y][x] != true
@board[y][x] = nil
empty_filled_square y-1, x
empty_filled_square y+1, x
empty_filled_square y, x-1
empty_filled_square y, x+1
end
private :empty_filled_square
def calculate_layout
# calculate @numbers and empty outer filled squares
@numbers = Array.make2d(@h, @w, nil)
current = 1
@h.times do |y|
@w.times do |x|
next if is_filled(y, x)
if (is_filled(y-1, x) and not is_filled(y+1, x)) \
or (is_filled(y, x-1) and not is_filled(y, x+1))
@numbers[y][x] = current
current += 1
end
end
end
@h.times do |y|
empty_filled_square y, 0
empty_filled_square y, @w - 1
end
@w.times do |x|
empty_filled_square 0, x
empty_filled_square @h - 1, x
end
end
private :calculate_layout
def is_filled(y, x)
return true if y < 0 or x < 0 or y >= @h or x >= @w
@board[y][x]
end
def has_hborder(y, x)
if y == 0
@board[0][x] != nil
elsif y == @h
@board[y-1][x] != nil
else
@board[y-1][x] != nil or @board[y][x] != nil
end
end
def has_vborder(y, x)
if x == 0
@board[y][0] != nil
elsif x == @w
@board[y][x-1] != nil
else
@board[y][x-1] != nil or @board[y][x] != nil
end
end
def has_node(y, x)
return true if y != 0 and has_vborder(y-1, x)
return true if x != 0 and has_hborder(y, x-1)
return true if y != @h and has_vborder(y, x)
return true if x != @w and has_hborder(y, x)
return false
end
private :is_filled, :has_hborder, :has_vborder, :has_node
def display
calculate_layout
print(has_node(0, 0) ? CBORDER : CFREE)
@w.times do |x|
print((has_hborder(0, x) ? CBORDER : CFREE) * 4)
print(has_node(0, x+1) ? CBORDER : CFREE)
end
puts
@h.times do |y|
print(has_vborder(y, 0) ? CBORDER : CFREE)
@w.times do |x|
if is_filled(y, x)
print CBORDER * 4
elsif @numbers[y][x]
nm = @numbers[y][x].to_s
print nm
print CFREE * (4 - nm.length)
else
print CFREE * 4
end
print(has_vborder(y, x+1) ? CBORDER : CFREE)
end
puts
print(has_vborder(y, 0) ? CBORDER : CFREE)
@w.times do |x|
print((is_filled(y, x) ? CBORDER : CFREE) * 4)
print(has_vborder(y, x+1) ? CBORDER : CFREE)
end
puts
print(has_node(y+1, 0) ? CBORDER : CFREE)
@w.times do |x|
print((has_hborder(y+1, x) ? CBORDER : CFREE) * 4)
print(has_node(y+1, x+1) ? CBORDER : CFREE)
end
puts
end
end
end
# this is the "main" program
cw = Crossword.parse
cw.display