Here's my attempt at the quiz. Instead of producing a text output I
tried myself at SVG. The first version I came up with didn't use any
special SVG commands and takes care of finding out the positions for
each square by itself. After seeing the Postscript solution I looked in
the SVG specs for something similar.. the result is the second version,
which seems a bit more unclear for human beings but makes the algorithm
much easier (no more "case @side")
So here they are..
First version with explicit placement of squares:
require 'rubygems'
require 'svg/svg'
require 'memoize'
module GoldenRectangle
COLORS = ['red', 'green', 'blue', 'yellow']
SEED_POS = {:x => 0, :y => 0}
class PreSeedSquare
def height
0
end
def x
SEED_POS[:x]
end
def y
SEED_POS[:y]
end
def color
COLORS[0]
end
def parent
@parent
end
end
class SeedSquare < PreSeedSquare
def initialize
@parent = PreSeedSquare.new
end
def fib(n)
a, b = 1, 1
n.times { a, b = b, a + b }
a
end
def size
fib(level)
end
def level
0
end
def width
size
end
alias_method :height, :width
def side
:top
end
def color
COLORS[1]
end
def as_spiral
["M#{x},#{y + height}", "A#{width},#{height} 0
0,1 #{x + width},#{y}"]
end
def as_squares
[svg_square]
end
def svg_square
col = self.color
SVG::Rect.new(x, y, width, height) { self.style
= SVG::Style.new
fill => col,
pacity => 0.5) }
end
end
class Square < SeedSquare
include Memoize
attr_reader
arent
def initialize(parent)
@parent = parent
@grandparent = @parent.parent
# without memoization this implementation
wouldn't be feasible
[
arent, :color, :size, :side, :x, :y].each
{|f| memoize f }
end
class << self
alias_method :attach_to, :new
end
def as_squares
@parent.as_squares << svg_square
end
def as_spiral
@parent.as_spiral << "A#{width},#{height} 0 0,1
" + spiral_point.join(",")
end
def color
if @grandparent.parent.nil?
COLORS[2]
elsif
@grandparent.parent.parent.nil?
COLORS[3]
else
(COLORS -
[@parent.parent.parent.parent.color, @parent.parent.parent.color,
@parent.color]).first
end
end
def level
@parent.level + 1
end
def side
case @parent.side
when :right
:bottom
when :bottom
:left
when :left
:top
when :top
:right
end
end
def x
case side
when :right
@parent.x + @parent.width
when :bottom
@grandparent.x
when :left
@parent.x - width
when :top
@parent.x
end
end
def y
case side
when :left
@grandparent.y
when :bottom
@parent.y + @parent.height
when :top
@parent.y - height
when :right
@parent.y
end
end
def spiral_point
case side
when :right
[x + width, y + height]
when :left
[x, y]
when :top
[x + width, y]
when :bottom
[x, y + height]
end
end
end
end
def fixpoint(start, limit, &blk)
limit.times do
start = blk.call(start)
end
start
end
iterations = ARGV.shift || 17
# the golden rectangle is the fixpoint of the function that attaches a
square
# to a golden-rectangle-approximation
# we start with a 1x1 seed
the_golden_rectangle = fixpoint(GoldenRectangle::SeedSquare.new,
iterations) do |rectangle|
GoldenRectangle::Square.attach_to rectangle
end
svg = SVG.new('1024', '768', '-550 -300 1024 768')
svg << the_golden_rectangle.as_squares
svg << SVG:
ath.new(the_golden_rectangle.as_spiral) {
self.style = SVG::Style.new
fill => 'none', :stroke => '#000',
:stroke_width => 1, :stroke_opacity => 1.0)
}
puts svg.to_s
###############
Second solution involving rotate & translate
require 'rubygems'
require 'svg/svg'
require 'memoize'
module GoldenRectangle
COLORS = ['red', 'green', 'blue', 'yellow']
SEED_POS = {:x => 0, :y => 0}
class PreSeedSquare
def height
0
end
def x
SEED_POS[:x]
end
def y
SEED_POS[:y]
end
def color
COLORS[0]
end
def parent
@parent
end
end
class SeedSquare < PreSeedSquare
def initialize
@parent = PreSeedSquare.new
end
def fibo(n)
a, b = 1, 1
n.times { a, b = b, a + b}
a
end
def size
fibo(level)
end
def width
size
end
def level
0
end
alias_method :height, :width
def color
COLORS[1]
end
def as_spiral
["M#{x},#{y + height}", "A#{width},#{height} 0
0,1 #{x + width},#{y}"]
end
def as_squares
svg_square
end
def svg_square
col = self.color
group = SVG::Group.new
group.transform = "rotate(90)
translate(#{-width},#{-height})"
group.id = level
group << SVG::Rect.new(0, 0, width, height) {
self.style = SVG::Style.new
fill => col,
pacity => 0.5) }
path = ["M#{0},#{0}", "A#{width},#{height} 0
0,0 #{width},#{height}"]
group << SVG:
ath.new(path) {
self.style = SVG::Style.new
fill =>
'none', :stroke => '#000', :stroke_width => 1, :stroke_opacity => 1.0)
}
group
end
end
class Square < SeedSquare
include Memoize
attr_reader
arent
def initialize(parent)
@parent = parent
@grandparent = @parent.parent
# without memoization this implementation
wouldn't be feasible
[:level,
arent, :color, :size].each {|f|
memoize f }
end
class << self
alias_method :attach_to, :new
end
def as_squares
g1 = @parent.as_squares
g2 = svg_square
g2 << g1
g2
end
def color
if @grandparent.parent.nil?
COLORS[2]
elsif
@grandparent.parent.parent.nil?
COLORS[3]
else
(COLORS -
[@parent.parent.parent.parent.color, @parent.parent.parent.color,
@parent.color]).first
end
end
def level
@parent.level + 1
end
end
end
def fixpoint(start, limit, &blk)
limit.times do
start = blk.call(start)
end
start
end
iterations = ARGV.shift || 17
# the golden rectangle is the fixpoint of the function that attaches a
square
# to a golden-rectangle-approximation
# we start with a 1x1 seed
the_golden_rectangle = fixpoint(GoldenRectangle::SeedSquare.new,
iterations) do |rectangle|
GoldenRectangle::Square.attach_to rectangle
end
svg = SVG.new('1024', '768', '2500 -1100 1024 768')
svg << the_golden_rectangle.as_squares
svg << SVG:
ath.new(the_golden_rectangle.as_spiral) {
self.style = SVG::Style.new
fill => 'none', :stroke => '#000',
:stroke_width => 1, :stroke_opacity => 1.0)
}
puts svg.to_s