Drawing Celtic Knotwork 2

The simplest element of our description is the Tile. I decided that a Tile would be a two dimensional chunk of characters that would let you set or get any point in that space. Remember that this isn't the only way we could have done things. You could also describe the lines and curves in the tile, or the colors, transparency, and whatever else the crazy kids are coming up with these days. This is my first drawing program, though, and I want to keep it a simple as I can. So I'm going with the bitmap idea. The tile images in the Sloss book are provided in different sizes. Let's go with 9×9. It's small and manageable, without being too small to see.

# 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()
    @pixels = []
    (0..8).each { 
      row = []
      (0..8).each { row << nil }
      @pixels << row
    }
  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
  
#####
# Test code
#####
  
require 'test/unit'
  
class TC_Tile < Test::Unit::TestCase
  def setup
    @@tile = Tile.new()
  end
  
  def test_pixels
    assert_equal(nil, @@tile.is_set?(0, 0), 
      "By default, any pixel in a Tile is blank")
    assert(@@tile.set(0, 0), 
      "Use Tile#set(row, col) to set a pixel at coordinates (row, col)")
    assert(@@tile.is_set?(0, 0), 
      "A pixel (row, col) is set after Tile#set(row, col) has been called")
    assert(@@tile.unset(0, 0), 
      "Use Tile#unset(row, col) to clear a pixel at coordinates (row, col)")
    assert_equal(nil, @@tile.is_set?(0, 0), 
      "An unset pixel has no set value")
    @@tile.set(1, 1)
    assert_equal(nil, @@tile.is_set?(0, 0), 
      "Setting one pixel has no effect on other pixels in a Tile")
    assert(@@tile.is_set?(1, 1), 
      "Tile remembers the set status of each pixel in its confines.")
    source_string =<<HERE
      x . . . . . . . x
      . x . . . . . x .
      . . x . . . x . .
      . . . x . x . . .
      . . . . x . . . .
      . . . x . x . . .
      . . x . . . x . .
      . x . . . . . x .
      x . . . . . . . x
HERE
    source_string.gsub!(/^\s+/m, '')
    assert(@@tile.set_from_string(source_string),
      "You can use ASCII art strings to set the pixels in a Tile")
    assert(@@tile.is_set?(0, 0))
    assert(@@tile.is_set?(1, 0))
    assert_equal('x', @@tile.at(0, 0),
      "A Tile remembers the value assigned, if given, during " \
      "Tile#set(row, col, val)")
  end
end

I'm told this is Test Driven Development, where you write the tests for your code as you are writing the code itself. A little before the code itself, actually. TDD is useful for any non-trivial programming task. You have the tests there right from the beginning to describe what your classes are supposed to be able to do. Because TDD is based on lots of tiny changes being applied rapidly over time, I decided it would be tedious to describe that process to you at each little step. Instead, we stop and take a snapshot as we get each major stage accomplished. Like that code sample up there. It's really where I'm at right about now. See, there's the class definition, a couple of very basic accessors, and the ability to set all the pixels of a Tile at once from a String.

Now that the Tile is pretty much doing everything I want it to, let's move on to the Grid.