COOLNAMEHERE

Drawing Celtic Knotwork 7

Okay, it's done! That is to say, it does all the things I want it to for now. There's a lot more stuff that I would like to do with this program, but it's important to know when to stop and take a breath. Let's just go back and clean up the code a little bit. Not actually change any functionality or user interface, so Me From The Future is just going to have to wait. I only want to make it easier to read the code that I have already written.

require 'RMagick'
require 'optparse'
  
include Magick
  
TILE_SIZE = 9
  
# I am a single small section of a knotwork image. I know about my 
# dimensions, and can describe myself on a pixel-by-pixel basis.
class Tile
  
  def initialize(str = nil)
    @pixels = []
    1.upto(TILE_SIZE) { 
      row = []
      1.upto(TILE_SIZE) { row << nil }
      @pixels << row
    }
    
    if str then
      set_from_string(str)
    end
  end
  
  def at(x, y)
    return @pixels[x][y]
  end
  alias is_set? at
  
  def set(x, y, value=true)
    @pixels[x][y] = value
  end
  
  def unset(x, y)
    @pixels[x][y] = nil
    
    return true
  end
  
  def set_from_string(str)
    str.split(''\n'').each_with_index do |line, row|
      line.split(' ').each_with_index do |pixel, col|
        set(row, col, pixel)
      end
    end
  end
  
  def to_s
    str = ''''
    @pixels.each { |row|
      str += ''|''
      row.each { |pixel| 
        pixel ||= '' ''
        str += ''#\{pixel}|'' 
      }
      str += ''\n''
    }
    return str
  end
end
  
# I am an arranged collection of Tiles. I know how to add and remove
# Tiles along a 2-d grid, and can also present myself as if I were a single
# large Tile.
class Grid
  
  attr_reader :rows, :columns
  
  def initialize(rows, columns)
    @tile_size = TILE_SIZE
    @rows      = rows
    @columns   = columns
    @pixels    = Array.new(rows*@tile_size) { |i|
      Array.new(columns*@tile_size)
    }
  end
  
  def set_tile(row, column, tile)
    if row >= @rows or column >= @columns then
      raise ArgumentError, \
      ''set_tile at #\{row}, #\{column} outside of Grid area '' \
      ''(#\{@rows}, #\{@columns})''
    end
    
    pixel_origin_x = row * @tile_size
    pixel_origin_y = column * @tile_size
    (0...&#064;tile_size).each { |tile_x|
      x = pixel_origin_x + tile_x
      (0...&#064;tile_size).each { |tile_y|
        y = pixel_origin_y + tile_y
        @pixels[x][y] = tile.at(tile_x, tile_y)
      }
    }
  end
  
  def at(row, column)
    return @pixels[row][column]
  end
  
  def to_s
    str = ''''
    @pixels.each { |row|
      str += row.join(' ')
      str += ''\n''
    }
    
    return str
  end
  
end
  
# I am a lovely Celtic knotwork panel. I know my dimensions, and can output 
# myself as ASCII art.
class KnotworkPanel
    @@top_left = Tile.new(%{. . . . x x x x x
                            . . . . x . . . .
                            . . . . x . . . .
                            . . . . x . . . x
                            . . . . x . . x .
                            . . . . x . . x .
                            . . . . . x . . x
                            . . . . . x . . x
                            . . . . . . x x .}.gsub(/^\s+/, ''))
  
  @@top      = Tile.new(%{x x . . . . . x x
                          . . x x . x x . .
                          . . . . x . . . .
                          x x . . . x . x x
                          . . x . . . x . .
                          . x . x . . . x .
                          x . . . x . . . x
                          . . . x . x . . x
                          . . x . . . x x .}.gsub(/^\s+/, '')
                        )
  
  @@topright = Tile.new(%{x x x x x . . . .
                          . . . . x . . . .
                          . . . . x . . . .
                          x . . . x . . . .
                          . x . . x . . . .
                          . x . . x . . . .
                          x . . x . . . . .
                          . . . x . . . . .
                          . . x . . . . . .}.gsub(/^\s+/, '')
                        )
  
  @@left     = Tile.new(%{. . . . . . x . .
                          . . . . . x . . .
                          . . . . . x . . .
                          . . . . x . . . x
                          . . . . x . . x .
                          . . . . x . . x .
                          . . . . . x . . x
                          . . . . . x . . x
                          . . . . . . x x .}.gsub(/^\s+/, '')
                        )
  
  @@center   = Tile.new(%{. . x . . . x . .
                          . x . x . x . . .
                          x . . . x . . . x
                          . x . . . x . x .
                          . . x . . . x . .
                          . x . x . . . x .
                          x . . . x . . . x
                          . . . x . x . x .
                          . . x . . . x . .}.gsub(/^\s+/, '')
                        )
  
  @@right    = Tile.new(%{. x x . . . . . .
                          x . . x . . . . .
                          x . . x . . . . .
                          . x . . x . . . .
                          . x . . x . . . .
                          . x . . x . . . .
                          x . . x . . . . .
                          . . . x . . . . .
                          . . x . . . . . .}.gsub(/^\s+/, '')
                        )
  
  @@bot_left = Tile.new(%{. . . . . . x . .
                          . . . . . x . . .
                          . . . . . x . . x
                          . . . . x . . x .
                          . . . . x . . x .
                          . . . . x . . . x
                          . . . . x . . . .
                          . . . . x . . . .
                          . . . . x x x x x}.gsub(/^\s+/, '')
                        )
  
  @@bottom   = Tile.new(%{. x x . . . x . .
                          x . . x . x . . .
                          x . . . x . . . x
                          . x . . . x . x .
                          . . x . . . x . .
                          x x . x . . . x x
                          . . . . x . . . .
                          . . x x . x x . .
                          x x . . . . . x x}.gsub(/^\s+/, '')
                        )
  
  @@botright = Tile.new(%{. x x . . . . . .
                          x . . x . . . . .
                          x . . x . . . . .
                          . x . . x . . . .
                          . x . . x . . . .
                          x . . . x . . . .
                          . . . . x . . . .
                          . . . . x . . . .
                          x x x x x . . . .}.gsub(/^\s+/, '')
                        )  
  
  def initialize(rows, columns=rows)
    # Make room for the border tiles
    row_size = rows + 2
    col_size = columns + 2
    last_row = row_size - 1
    last_col = col_size - 1
    
    @grid = Grid.new(row_size, col_size)
    
    # Set the top row
    @grid.set_tile(0, 0, @@top_left)
    (1...last_col).each do |i|
      @grid.set_tile(0, i, @@top)
    end
    @grid.set_tile(0, last_col, @@topright)
    
    # Set the center rows.
    (1...last_row).each do |i|
      @grid.set_tile(i, 0, @@left)
      (1...last_col).each do |j|
        @grid.set_tile(i, j, @@center)
      end
      @grid.set_tile(i, last_col, @@right)
    end
    
    # Set the bottom row
    @grid.set_tile(last_row, 0, @@bot_left)
    (1...last_col).each do |i|
      @grid.set_tile(last_row, i, @@bottom)
    end
    @grid.set_tile(last_row, last_col, @@botright)
  end
  
  def to_aa()
    return @grid.to_s
  end
  
  def to_image()
    
    filename = "panel-#\{@grid.rows}x#\{@grid.columns}.png"
    
    x_size = TILE_SIZE * @grid.rows
    y_size = TILE_SIZE * @grid.columns
    
    image = Image.new(x_size, y_size) { self.background_color = "white" }
    (0...y_size).each do |y|
      (0...x_size).each do |x|
        pixel = @grid.at(x, y)
        if pixel == ''x'' then
          image.pixel_color(x, y, ''black'')
        end
      end
    end
    image.write(filename)
    
  end
  
end
  
def main
  rows    = 1
  columns = 1
  
  opts = OptionParser.new do |opts|
    opts.banner  = ''Usage #\{$0} |opts|''
    opts.separator ''''
    opts.separator ''Specific Options''
    
    opts.on(''-r'', ''--rows [ROWS]'',
            ''Number of rows for this panel (default 1)'') do |r|
      rows = r.to_i
      columns = rows
    end
    
    opts.on(''-c'', ''--columns [COLUMNS]'',
            ''Number of columns for this panel (default ROWS)'') do |c|
      columns = c.to_i
    end
    
    opts.on_tail(''-h'', ''--help'',
                 ''Show this message'') do
      puts opts
      exit
    end
  end
  
  opts.parse!
  
  panel = KnotworkPanel.new(rows, columns)
  panel.to_image()
end
  
main()

Copyright 1999 - 2008 Brian Wisti

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 United States License.