Drawing Celtic Knotwork 6
Now the program does what I want it to. But if I hand this script off to somebody else and say "This program will make knotwork panels of any size," it'll be maybe 30 seconds before they ask how to set the size. "Go in and edit the code" won't cut it. Let's haul out our trusty OptParse library again.
require 'RMagick'
require 'optparse'
include Magick
# 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 = []
(0..8).each {
row = []
(0..8).each { 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
def initialize(rows, columns)
@tile_size = 9
@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...@tile_size).each { |tile_x|
x = pixel_origin_x + tile_x
(0...@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)
@row_size = rows + 2
@col_size = columns + 2
@grid = Grid.new(@row_size, @col_size)
# Set the top row
@grid.set_tile(0, 0, @@top_left)
(1...@col_size-1).each do |i|
@grid.set_tile(0, i, @@top)
end
@grid.set_tile(0, @col_size-1, @@topright)
# Set the center rows.
(1...@row_size-1).each do |i|
@grid.set_tile(i, 0, @@left)
(1...@col_size-1).each do |j|
@grid.set_tile(i, j, @@center)
end
@grid.set_tile(i, @col_size-1, @@right)
end
# Set the bottom row
@grid.set_tile(@row_size-1, 0, @@bot_left)
(1...@col_size-1).each do |i|
@grid.set_tile(@row_size-1, i, @@bottom)
end
@grid.set_tile(@row_size-1, @col_size-1, @@botright)
end
def to_aa()
return @grid.to_s
end
def to_image()
filename = "panel-#\{@row_size}x#\{@col_size}.png"
max_x = 9 * @row_size
max_y = 9 * @col_size
image = Image.new(max_x, max_y) { self.background_color = "white" }
(0...max_y).each do |y|
(0...max_x).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
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()
$ ruby knotworkpanel.rb
![]()
$ ruby knotworkpanel.rb --rows 2
![]()
Me from the future says "See? This is what I was talking about. You say '2 rows', and you get a 4×4 square? How do you think people are going to react? Man, I need more coffee."
Ignoring me from the future for today, let's see what happens when we try to make a nice big KnotworkPanel.
$ ruby knotworkpanel.rb --rows 98 --columns 73
Hmm ... took a few seconds this time. If I cared about
performance, I might go in and see where this could
be
tightened up. I don't care about performance today,
though, just results. And the results aren't
too bad.
