[QUIZ.SOLUTION] The Golden Fibonacci Ratio

Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).

require 'matrix'

# Class helpers
class Array
   def tail
      self[1..-1]
   end
   def mod_fetch i
      self[i % size]
   end
   def each_pair
      each_with_index do |a, i|
         yield a, self[i+1] if i+1 < size
      end
   end
end

class Vector
   def x
      self[0]
   end

   def y
      self[1]
   end

   def len
      Math.sqrt(inner_product(self))
   end

   def rot90
      Vector[-y, x]
   end

   def rot45 # cheap rotate by 45 degrees
      Vector[x - y, x + y]
   end

   def to_s
      "#{x} #{y}"
   end
end

# Postscript class (what a hack!)
class PS
   def initialize(&block)
      @cmds = []
      instance_eval(&block) if block
   end

   def push(*args, &block)
      @cmds << args.join(' ')
      @cmds << instance_eval(&block) if block
   end

   def to_s
      @cmds.join("\n")
   end

   def page(&block)
      instance_eval(&block)
      push 'showpage'
   end

   def path(&block)
      push 'newpath'
      instance_eval(&block)
   end

   def gsave(&block)
      push 'gsave'
      instance_eval(&block)
      push 'grestore'
   end

   def method_missing(name, *args)
      push *args + [name]
   end
end

# Constants and helper funcs for building image data
Basis = [Vector[1, 0], Vector[0, -1], Vector[-1, 0], Vector[0, 1]]
Shade = [0.3, 0.5, 0.7]

def fibo(n)
   a, b = 1, 1
   n.times { a, b = b, a + b }
   a
end

def spiral(n)
   if n.zero?
      Vector[0, 0]
   else
      i = n - 1
      spiral(i) + Basis.mod_fetch(i) * fibo(i)
   end
end

# Build list of spiral coordinates
steps = (ARGV[0] || 11).to_i
coords = (0..steps).map { |i| spiral(i).rot45 }

# Calculate page/content dimensions, scale and center
inch = 72
margin = 0.5 * inch
pagew = 8.5 * inch
pageh = 11 * inch
contw = pagew - 2 * margin
conth = pageh - 2 * margin

xmin = coords.min { |a, b| a.x <=> b.x }.x
xmax = coords.max { |a, b| a.x <=> b.x }.x
ymin = coords.min { |a, b| a.y <=> b.y }.y
ymax = coords.max { |a, b| a.y <=> b.y }.y

scale = [contw / (xmax - xmin), conth / (ymax - ymin)].min

cx = (pagew - (xmax - xmin.abs) * scale) / 2
cy = (pageh - (ymax - ymin.abs) * scale) / 2

# Scale coords to fill page
coords.map! { |v| v * scale }

# Build Postscript image
doc = PS.new do
   def box a, b
      l, r = [a.x, b.x].min, [a.x, b.x].max
      b, t = [a.y, b.y].min, [a.y, b.y].max

      moveto l, b
      lineto r, b
      lineto r, t
      lineto l, t
      closepath
   end

   page do
      translate cx, cy

      i = 0
      coords.each_pair do |a, b|
         path do
            box a, b
            gsave do
               setgray Shade.mod_fetch(i += 1)
               fill
            end
            stroke
         end
      end

      setrgbcolor 0.8, 0.4, 0
      path do
         moveto coords.first
         angle = 180
         coords.each_pair do |a, b|
            d = (a + b) * 0.5
            d += (a - d).rot90
            arcn d, (d - a).len, angle, (angle -= 90)
         end
         stroke
      end
   end
end

puts doc

Very nice output!

Jim Freeze

···

On Mar 5, 2006, at 8:37 AM, Matthew Moss wrote:

Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).

Okay, I could not resist "doing it right". :wink:

puts <<EOF
100 100 translate

/s 400 def
/colors [ 0.7 0.5 0.3 ] def

/phi 5 sqrt 1 sub 2 div def

/box { /i exch def
  0 0 moveto
  i 0 lineto
  i i lineto
  0 i lineto
  closepath
} def

0 1 9 { %for
    colors exch 3 mod get setgray
    s box
    gsave fill grestore
    0 setgray stroke

    0.8 0.4 0 setrgbcolor
    0 s s 270 360 arc stroke

    s s translate
    90 rotate
    phi phi scale
    currentlinewidth 1 phi div mul setlinewidth % keep linewidth
} for

showpage
EOF

···

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

I hang my head in shame.

···

On 3/6/06, Christian Neukirchen <chneukirchen@gmail.com> wrote:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

> Here is my solution... Uses a cheap trick (a la method_missing) to
> allow inline Postscript as method calls (so not as postfix). The
> output is dumped to standard output to be redirected to a .ps file,
> which can be easily viewed on Mac OS X (and almost as easily on other
> platforms using ghostscript).

Okay, I could not resist "doing it right". :wink:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

···

On 3/6/06, Christian Neukirchen <chneukirchen@gmail.com> wrote:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

> Here is my solution... Uses a cheap trick (a la method_missing) to
> allow inline Postscript as method calls (so not as postfix). The
> output is dumped to standard output to be redirected to a .ps file,
> which can be easily viewed on Mac OS X (and almost as easily on other
> platforms using ghostscript).

Okay, I could not resist "doing it right". :wink:

I hang my head in shame.

The one who reads that code a bit closer probably will grin deeply...

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

Me guesses you refer to this:
/phi 5 sqrt 1 sub 2 div def

Cheater! :wink:

···

On Mar 6, 2006, at 10:49 AM, Christian Neukirchen wrote:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

On 3/6/06, Christian Neukirchen <chneukirchen@gmail.com> wrote:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on other
platforms using ghostscript).

Okay, I could not resist "doing it right". :wink:

I hang my head in shame.

The one who reads that code a bit closer probably will grin deeply...

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

Logan Capaldo <logancapaldo@gmail.com> writes:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

"Matthew Moss" <matthew.moss.coder@gmail.com> writes:

Here is my solution... Uses a cheap trick (a la method_missing) to
allow inline Postscript as method calls (so not as postfix). The
output is dumped to standard output to be redirected to a .ps file,
which can be easily viewed on Mac OS X (and almost as easily on
other
platforms using ghostscript).

Okay, I could not resist "doing it right". :wink:

I hang my head in shame.

The one who reads that code a bit closer probably will grin deeply...

--
Christian Neukirchen <chneukirchen@gmail.com> http://
chneukirchen.org

Me guesses you refer to this:
/phi 5 sqrt 1 sub 2 div def

Bingo! Mine is drawn outside-in. :wink: [That said, I was amazed how
much Postscript I still knew, I didn't touch it for quite some time.]

···

On Mar 6, 2006, at 10:49 AM, Christian Neukirchen wrote:

On 3/6/06, Christian Neukirchen <chneukirchen@gmail.com> wrote:

Cheater! :wink:

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org