# Forum: Ruby The Golden Fibonacci Ratio (#69)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
on 2006-03-05 19:59
```# Author: Shane Emmons
#
# Quiz 69: The Golden Fibonacci Ratio.
#
# I decided for this quiz to try and use the curses library to make
# screen placement easier. I think it turned out well, though I am sure
# there are more clever ways of achieving the same results. Press the
# 'enter' key to advance through each successive iteration of the
# algorithm. Five is the optimal number of times to iterate for
standard
# consoles.
#
# usage: ruby quiz69.rb [optional: number of times to add sqr]

require 'curses'

class Square

attr_writer :height, :width

def initialize( screen, length = 1, width = 1 )
@screen, @height, @width = screen, length, width
end

def max_side
@height >= @width ? @height : @width
end

@screen.setpos( 0, 0 )
( 0 .. @width + 1 ).each { @screen.addstr( '#' ) }
( 1 .. @height ).each do |height|
@screen.setpos( height, 0 )
@screen.setpos( height, @width + 1 )
end
@screen.setpos( @height + 1, 0 )
( 0 .. @width + 1 ).each { @screen.addstr( '#' ) }
@screen.getch
end

end

sqr = Square.new( Curses::init_screen )

times = ARGV[ 0 ] || 5
( 1 .. times.to_i ).each do |x|

sqr.width  += sqr.max_side + 1
else
sqr.height += sqr.max_side + 1
end

end```
on 2006-03-05 20:30
```I've never done OpenGL before. So after looking a some examples here's
what I came up with. I did this pretty quick so there are many things
that ought to be fixed (like the mess of global variables), but it gets
the job done. Like the curses submission, pressing enter repeatedly
will add the next iteration. I don't have automatic resizing yet so
after you press enter, to get it to look right you have to resize the
window. Maybe I'll think about that a bit later (suggestions are
welcome on how resizing should be done). Anyways thanks for the quiz!

-----Jay Anderson

require 'opengl'
require 'glut'

class Array
def rotate!
push shift
end
def rotate
self.dup.rotate!
end
end

key_func = lambda do |key,x,y|
case key
when ?Q, ?q
exit 0
when ?\r, ?\n
side = \$width>\$height ? \$width : \$height
sq_x, sq_y = \$x, \$y
when :right
sq_x = \$x + \$width
\$width += side
when :left
sq_x = \$x - side
\$x = sq_x
\$width += side
when :top
sq_y = \$y + \$height
\$height += side
when :bottom
sq_y = \$y - side
\$y = sq_y
\$height += side
end
\$squares << {
:side => side,
:x => sq_x,
:y => sq_y,
:color => [rand, rand, rand]
}

end
GLUT.PostRedisplay
end

reshape_func = lambda do |w,h|
\$screen_width = w
\$screen_height = h
range = (\$width>\$height ? \$width : \$height)
border = range * 0.05
left, right = \$x-border, \$x+range+border
bottom, top = \$y-border, \$y+range+border
aspect = w.to_f/h.to_f
if w <= h then
bottom /= aspect
top /= aspect
else
left *= aspect
right *= aspect
end
GL.Viewport(0, 0, w, h)
GL.MatrixMode GL::PROJECTION
GLU::Ortho2D(left, right, bottom, top)
GL.MatrixMode GL::MODELVIEW
end

display_func = lambda do
GL.Clear GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT

\$squares.each do |s|
GL.Translate(s[:x], s[:y], 0.0)
side = s[:side]
GL.Color(*s[:color])
GL.Vertex(0.0, 0.0)
GL.Vertex(side, 0.0)
GL.Vertex(side, side)
GL.Vertex(0.0, side)
GL.End
GL.Translate(-s[:x], -s[:y], 0.0)
end

GL.Flush
end

GLUT.Init
GLUT.InitDisplayMode GLUT::SINGLE | GLUT::RGB | GLUT::DEPTH
GLUT.CreateWindow "Fibonacci"
GL.FrontFace GL::CW
GL.Enable(GL::DEPTH_TEST)

\$screen_width = GLUT.Get(GLUT::WINDOW_WIDTH)
\$screen_height = GLUT.Get(GLUT::WINDOW_HEIGHT)
\$squares = []
\$squares << {
:side => 1.0,
:x => 0.0,
:y => 0.0,
:color => [rand, rand, rand]
}
\$width = 1.0
\$height = 1.0
\$x = 0.0
\$y = 0.0
\$add_to = [:right, :bottom, :left, :top]

GLUT.KeyboardFunc key_func
GLUT.ReshapeFunc reshape_func
GLUT.DisplayFunc display_func

GLUT.MainLoop```
on 2006-03-05 21:00
```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, :opacity => 0.5) }
end
end

class Square < SeedSquare
include Memoize

def initialize(parent)
@parent = parent
@grandparent = @parent.parent

# without memoization this implementation
wouldn't be feasible
[:parent, :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
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::Path.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, :opacity => 0.5) }

path = ["M#{0},#{0}", "A#{width},#{height} 0
0,0 #{width},#{height}"]
group << SVG::Path.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

def initialize(parent)
@parent = parent
@grandparent = @parent.parent

# without memoization this implementation
wouldn't be feasible
[:level, :parent, :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
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::Path.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```
on 2006-03-09 07:09
```I fixed a few things and added mouse callbacks (click drag for zoom,
shift-click-drag for panning). I'd still like to figure out how to make
it more OO, but at least my understanding of OpenGL is better.

-----Jay Anderson

usr/bin/ruby

require 'opengl'
require 'glut'

class Array
def rotate!
push shift
end
def rotate
self.dup.rotate!
end
end

mouse_func = lambda do |button,state,x,y|
shift_down = (GLUT.GetModifiers & GLUT::ACTIVE_SHIFT) != 0
case button
when GLUT::LEFT_BUTTON
case state
when GLUT::UP
\$mode = :none
when GLUT::DOWN
if shift_down then
\$mode = :pan
else
\$mode = :zoom
end
\$mouse_x = x
\$mouse_y = y
end
when 3 #scroll up
if state == GLUT::DOWN then
\$zoom *= 1.25
GLUT.PostRedisplay
end
when 4 #scroll down
if state == GLUT::DOWN then
\$zoom /= 1.25
GLUT.PostRedisplay
end
end
end

motion_func = lambda do |x,y|
case \$mode
when :zoom
\$zoom *= 1.0 + (y-\$mouse_y)/100.0
when :pan
\$pan_x += (x-\$mouse_x)*\$units_per_pixel
\$pan_y += (\$mouse_y-y)*\$units_per_pixel
when :rotate
end
if \$mode != :none then
\$mouse_x = x
\$mouse_y = y
GLUT.PostRedisplay
end
end

key_func = lambda do |key,x,y|
case key
when ?Q, ?q
GLUT.DestroyWindow(\$window);
exit 0
when ?-
\$zoom /= 2.0
when ?+
\$zoom *= 2.0
when ?\r, ?\n
side = \$width>\$height ? \$width : \$height
sq_x, sq_y = \$box_x, \$box_y
when :right
sq_x = \$box_x + \$width
\$width += side
when :left
sq_x = \$box_x - side
\$box_x = sq_x
\$width += side
when :top
sq_y = \$box_y + \$height
\$height += side
when :bottom
sq_y = \$box_y - side
\$box_y = sq_y
\$height += side
end
\$squares << {
:side => side,
:x => sq_x,
:y => sq_y,
:color => [rand, rand, rand]
}

end
GLUT.PostRedisplay
end

reshape_func = lambda do |w,h|
h = 1 if h == 0
\$screen_width = w
\$screen_height = h
GL.Viewport(0, 0, w, h)
GL.MatrixMode GL::PROJECTION
GLU.Perspective(45.0, w.to_f/h.to_f, 0.1, 100.0);
GL.MatrixMode GL::MODELVIEW
end

display_func = lambda do
GL.Clear GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT

GL.Translate(\$pan_x, \$pan_y, -6.0)
GL.Scale(\$zoom, \$zoom, 0.0)
\$squares.each do |s|
GL.Translate(s[:x], s[:y], 0.0)
side = s[:side]
GL.Color(*s[:color])
GL.Vertex(0.0, 0.0)
GL.Vertex(side, 0.0)
GL.Vertex(side, side)
GL.Vertex(0.0, side)
GL.End
GL.Translate(-s[:x], -s[:y], 0.0)
end

GLUT.SwapBuffers;
end

GLUT.Init
GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGBA | GLUT::DEPTH)

\$screen_width = 640
\$screen_height = 480
\$units_per_pixel = 1.0/100.0 #TODO: should be determined by screen size
GLUT.InitWindowSize(\$screen_width, \$screen_height)
GLUT.InitWindowPosition(0, 0)

\$window = GLUT.CreateWindow "Fibonacci"

GLUT.KeyboardFunc key_func
GLUT.ReshapeFunc reshape_func
GLUT.DisplayFunc display_func
GLUT.MotionFunc motion_func
GLUT.MouseFunc mouse_func
#GLUT.IdleFunc display_func

GL.ClearColor(0.0, 0.0, 0.0, 0.0)
GL.ClearDepth(1.0)
GL.DepthFunc(GL::LESS)
GL.Enable(GL::DEPTH_TEST)

\$squares = []
\$squares << {
:side => 1.0,
:x => 0.0,
:y => 0.0,
:color => [rand, rand, rand]
}
\$width = 1.0
\$height = 1.0
\$box_x = 0.0
\$box_y = 0.0
\$pan_x = 0.0
\$pan_y = 0.0
\$zoom = 1.0
\$mode = :none
\$add_to = [:right, :bottom, :left, :top]

GLUT.MainLoop```
This topic is locked and can not be replied to.