I build the full display with double borders and then remove
them.
Clark
---
#!/usr/bin/env ruby
class GraphicBlock
attr_reader :arr
def initialize(arr)
@arr = arr
end
def add_right(other)
result = @arr.zip(other.arr).collect do |line|
line.join("")
end
GraphicBlock.new(result)
end
def add_below(other)
GraphicBlock.new(@arr + other.arr)
end
def transpose
result = @arr.collect { |row| row.split(//) }.
transpose.collect { |line| line.join("") }
GraphicBlock.new(result)
end
def collapse_column_borders(cell_width)
result = @arr.each do |line|
(line.size/cell_width).downto(1) do |i|
border = (i*cell_width) - 1
line[border, 2] = line[border, 2].include?("#") ? "#" : " "
end
end
GraphicBlock.new(result)
end
def collapse_row_borders(cell_height)
transpose.collapse_column_borders(cell_height).transpose
end
def to_s
@arr.join("\n")
end
end
class Crossword
attr_reader :cell_width, :cell_height
def initialize(filename, cell_width = 6, cell_height = 4)
@cell_width = cell_width
@cell_height = cell_height
@puzzle = Array.new
IO.foreach(filename) do |line|
line = line.chomp.gsub(/ /, '')
@puzzle << (row = Array.new)
line.split('').each do |char|
if (char == "X")
row << :filled
else
row << :letter
end
end
end
remove_filled_border
number_puzzle
end
def cell(i, j)
if (i < 0 || i >= @puzzle.size ||
j < 0 || j >= @puzzle[i].size)
nil
else
@puzzle[i][j]
end
end
def above(i, j) cell(i-1, j) end
def below(i, j) cell(i+1, j) end
def left(i, j) cell(i, j-1) end
def right(i, j) cell(i, j+1) end
def adjacent(i, j)
[above(i, j), below(i, j), left(i, j), right(i, j)]
end
def unused_cell?(item)
(item == nil) || (item == :filled)
end
def letter_cell?(item)
!unused_cell?(item)
end
def puzzle_visit
@puzzle.each_with_index do |row, i|
row.each_with_index do |item, j|
yield(item, i, j)
end
end
end
def remove_filled_border
begin
changed = false
puzzle_visit do |item, i, j|
if (item == :filled && adjacent(i, j).include?(nil))
@puzzle[i][j] = nil
changed = true
end
end
end while (changed)
end
def number_puzzle
count = 1
puzzle_visit do |item, i, j|
if (letter_cell?(item) &&
((unused_cell?(above(i, j)) && letter_cell?(below(i, j))) ||
(unused_cell?(left(i, j)) && letter_cell?(right(i, j)))))
@puzzle[i][j] = count
count += 1
end
end
end
def graphics(item)
arr = Array.new
if (item == :filled)
cell_height.times { |i| arr << "#" * cell_width }
elsif (item == :letter)
arr << "#" * cell_width
(cell_height-2).times { |i|
arr << "#" + " " * (cell_width-2) + "#"
}
arr << "#" * cell_width
elsif (item != nil && item.integer?)
arr << "#" * cell_width
arr << sprintf("#%-*d#", cell_width-2, item)
(cell_height-3).times { |i|
arr << "#" + " " * (cell_width-2) + "#"
}
arr << "#" * cell_width
else
cell_height.times { |i| arr << " " * cell_width }
end
GraphicBlock.new(arr)
end
def draw_raw
@puzzle.collect do |row|
row.collect { |cell| graphics(cell) }.inject do |row_picture, g|
row_picture.add_right(g)
end
end.inject do |full_picture, row_picture|
full_picture.add_below(row_picture)
end
end
def draw
draw_raw.collapse_column_borders(cell_width).
collapse_row_borders(cell_height)
end
end
if __FILE__ == $0
puzzle = Crossword.new(ARGV.first)
print puzzle.draw.to_s + "\n"
end