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