Ruby Quiz 10

Here is my solution to Ruby Quiz 10: Crosswords. Jump down to the solution or the test data.

Back to my Ruby Quiz solutions page.

Solution

#! /usr/bin/env ruby

class Cell

    WIDTH = 6
    HEIGHT = 4

    attr_accessor :neighbors        # Hash, key=:left, :right, :top, :bottom

    def Cell.from_char(c)
        return c == '_' ? WhiteCell.new : BlackCell.new
    end

    def initialize
        @neighbors = {}
    end

    def print_border_at(row, col, paper)
        x = col * (Cell::WIDTH - 1)
        y = row * (Cell::HEIGHT - 1)

        # corners
        paper[y][x] =
            paper[y][x+Cell::WIDTH-1] =
            paper[y+Cell::HEIGHT-1][x] =
            paper[y+Cell::HEIGHT-1][x+Cell::WIDTH-1] = '+'

        # top and bottom
        (Cell::WIDTH-2).times { | j | paper[y][x+1+j] = '-' }
        (Cell::WIDTH-2).times { | j | paper[y+Cell::HEIGHT-1][x+1+j] = '-' }

        # sides
        (Cell::HEIGHT - 2).times { | i |
            paper[y+1+i][x] = '|'
            paper[y+1+i][x+Cell::WIDTH-1] = '|'
        }
    end

end

class WhiteCell < Cell
    attr_accessor :clue_number

    def black?
        return false
    end

    def needs_clue_number?
        return ((@neighbors[:left].nil? || @neighbors[:left].black?) &&
                !@neighbors[:right].nil? && !@neighbors[:right].black?) ||
               ((@neighbors[:top].nil? || @neighbors[:top].black?) &&
                !@neighbors[:bottom].nil? && !@neighbors[:bottom].black?)
    end

    def print_at(row, col, paper)
        print_border_at(row, col, paper)

        if @clue_number
            x = col * (Cell::WIDTH - 1)
            y = row * (Cell::HEIGHT - 1)
            s = @clue_number.to_s
            s.split(//).each_with_index { | char, i |
                paper[y+1][x+1+i] = char
            }
        end
    end
end

class BlackCell < Cell

    def initialize
        super
        @fill_char = '#'
    end

    def black?
        return true
    end

    def hidden?
        return @hidden
    end

    def needs_clue_number?
        return false
    end

    def hide
        return if hidden?
        @hidden = true
        @fill_char = ' '
        @neighbors.each_value { | cell | cell.hide if cell && cell.black? }
    end

    def print_at(row, col, paper)
        return if hidden?

        print_border_at(row, col, paper)

        # fill center
        x = col * (Cell::WIDTH - 1)
        y = row * (Cell::HEIGHT - 1)
         (Cell::HEIGHT-2).times { | i |
            (Cell::WIDTH-2).times { | j | paper[y+1+i][x+1+j] = '#' }
        }
    end

    def print_scanline(i)
        print @fill_char * (Cell::WIDTH - 1)
    end

    def print_bottom
        print_scanline(0)
    end

    def print_right_wall
        print @fill_char
    end
end

class Puzzle

    def initialize(io)
        read_picture(io)
        introduce_neighbors()
        hide_outer_black()
        assign_clue_numbers()
    end

    def read_picture(io)
        @cells = []
        io.each_with_index { | line, i |
            line = line.chomp.gsub(/[^x_]/i, '')
            @cells[i] = []
            line.split(//).each { | c | @cells[i] << Cell.from_char(c) }
        }
    end

    def introduce_neighbors
        @cells.each_with_index { | row, i |
            row.each_with_index { | cell, j |
                cell.neighbors[:left] = @cells[i][j-1] unless j == 0
                cell.neighbors[:right] = @cells[i][j+1]
                cell.neighbors[:top] = @cells[i-1][j] unless i == 0
                cell.neighbors[:bottom] = @cells[i+1][j] if @cells[i+1]
            }
        }
    end

    def hide_outer_black
        @cells.first.each { | cell | cell.hide if cell.black? }
        @cells.last.each { | cell | cell.hide if cell.black? }
        @cells.each { | row |
            row.first.hide if row.first.black?
            row.last.hide if row.last.black?
        }
    end

    def assign_clue_numbers
        clue_number = 1
        @cells.each_with_index { | row, i |
            row.each_with_index { | cell, j |
                if cell.needs_clue_number?
                    cell.clue_number = clue_number
                    clue_number += 1
                end
            }
        }
    end

    def print
        paper = blank_paper
        @cells.each_with_index { | row, row_num |
            row.each_with_index { | cell, col_num |
                cell.print_at(row_num, col_num, paper)
            }
        }
        paper.each { | scanline | puts scanline.join }
    end

    def blank_paper
        width = @cells[0].size * (Cell::WIDTH - 1) + 10
        height = @cells.size * (Cell::HEIGHT - 1) + 10
        paper = []
        height.times { paper << (' ' * width).split(//) }
        return paper
    end

end

Puzzle.new(ARGF).print

Test Data

X _ _ _ _ X X
_ _ X _ _ _ _
_ _ _ _ X _ _
_ X _ _ X X X
_ _ _ X _ _ _
X _ _ _ _ _ X