[QUIZ] Turtle Graphics (#104)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Morton Goldberg

[Editor's Note: You can download the files for this quiz at:

  http://rubyquiz.com/turtle.zip

--JEG2]

  Turtle Graphics
  ===============

Turtle graphics is a form of computer graphics based on the ideas of turtle
geometry, a formulation of local (coordinate-free) geometry. As a brief
introduction to turtle graphics, I quote from [1]:

  Imagine that you have control of a little creature called a turtle
  that exists in a mathematical plane or, better yet, on a computer
  display screen. The turtle can respond to a few simple commands:
  FORWARD moves the turtle in the direction it is facing some
  number of units. RIGHT rotates it clockwise in its place some
  number of degrees. BACK and LEFT cause the opposite movements. ...
  The turtle can leave a trace of the places it has been: [its
  movements] can cause lines to appear on the screen. This is
  controlled by the commands PENUP and PENDOWN. When the pen is
  down, the turtle draws lines.

For example, the turtle commands to draw a square, 100 units on a side, can be
written (in a Ruby-ized form) as:

  pen_down
  4.times { forward 100; right 90 }

For more information, see [2] and [3].

This quiz is a bit different from most. If the usual Ruby quiz can be likened to
an essay exam, this one is a fill-in-the-blanks test. I'm supplying you with a
complete turtle graphics package, except -- to give you something to do -- I've
removed the method bodies from the key file, lib/turtle.rb. Your job is to
repair the damage I've done and make the package work again.

  Turtle Commands
  ===============

There are quite a few turtle commands, but that doesn't mean you have to write a
lot of code to solve this quiz. Most of the commands can be implemented in a
couple of lines. It took me a lot longer to write a description of the commands
than it did for me to implement and test all of them.

I use the following format to describe turtle commands:

  long_name | short_name <arg>
     description ...
     Example: ...

All turtle commands take either one argument or none, and not all turtle
commands have both a long name and a short name.

  Required Commands
  -----------------

These commands are required in the sense that they are needed to reproduce the
sample designs. Actually, you could get away without implementing 'back' and
'left', but implementing them is far easier than trying to write turtle code
without them.

  pen_up | pu
     Raises the turtle's pen. The turtle doesn't draw (lay down a visible
     track) when its pen is up.
  
  pen_down | pd
     Lowers the turtle's pen. The turtle draws (lays down a visible track)
     when its pen is down.
   
  forward | fd <distance>
     Moves the turtle forwards in the direction it is facing.
     Example: forward(100) advances the turtle by 100 steps.
  
  back | bk <distance>
     Moves the turtle backwards along its line of motion.
     back <distance> == forward -<distance>
     Example: back(100) backs up the turtle by 100 steps.
  
  right | rt <angle>
     Turns the turtle clockwise by <angle> degrees.
     Example: right(90) turns the turtle clockwise by a right angle.
  
  left | lt <angle>
     Turns the turtle counterclockwise by <angle> degrees.
     left <angle> == right -<angle>
     Example: left(45) turns the turtle counterclockwise by 45 degrees.

  Traditional Commands
  --------------------

These commands are not needed to reproduce any of the sample designs, but they
are found in all implementations of turtle graphics that I know of.

  home
     Places the turtle at the origin, facing north, with its pen up. The
     turtle does not draw when it goes home.
  
  clear
     Homes the turtle and empties out it's track. Sending a turtle a clear
     message essentially reinitializes it.
  
  xy
     Reports the turtle's location.
     Example: Suppose the turtle is 10 turtle steps north and 15 turtle steps
     west of the origin, then xy will return [-15.0, 10.0].
  
  set_xy | xy= <point>
     Places the turtle at <point>. The turtle does not draw when this command
     is executed, not even if its pen is down. Returns <point>.
     Example: Suppose the turtle is at [10.0, 20.0], then self.xy = [50, 80]
     moves the turtle to [50.0, 80.0], but no line will drawn between the [10,
     20] and [50, 80].
  
  heading
     Reports the direction in which the turtle is facing. Heading is measured
     in degrees, clockwise from north.
     Example: Suppose the turtle is at the origin facing the point [100, 200],
     then heading will return 26.565 (approximately).
  
  heading= | set_h <angle>
     Sets the turtle's heading to <angle>. <angle> should be given in degrees,
     measured clockwise from north. Returns <angle>.
     Example: After self.heading = 135 (or set_h(135) which is easier to
     write), the turtle will be facing southeast.
  
  pen_up? | pu?
     Reports true if the turtle's pen is up and false otherwise.
  
  pen_down? | pd?
     Reports true if the turtle's pen is down and false otherwise.

  Optional Commands
  -----------------

These commands are only found in some implementations of turtle graphics. When
they are implemented, they make the turtle capable of doing global (coordinate)
geometry in addition to local (coordinate-free) geometry.

I used one of these commands, go, to draw the mandala design (see
designs/mandala.tiff and samples/mandala.rb). If you choose not to implement the
optional commands, you might try writing a turtle program for drawing the
mandala design without using go. But, believe me, it is much easier to implement
go than to write such a program.

  go <point>
     Moves the turtle to <point>.
     Example: Suppose the turtle is home (at the origin facing north). After
     go([100, 200]), the turtle will be located at [100.0, 200.0] but will
     still be facing north. If its pen was down, it will have drawn a line
     from [0, 0] to [100, 200].
  
  toward | face <point>
     Turns the turtle to face <point>.
     Example: Suppose the turtle is at the origin. After toward([100, 200]),
     its heading will be 26.565 (approximately).
  
  distance | dist <point>
     Reports the distance between the turtle and <point>.
     Example: Suppose the turtle is at the origin, then distance([400, 300])
     will return 500.0 (approximately).

  Interfacing to the Turtle Graphics Viewer
  =========================================

Implementing turtle graphics without being able to view what the turtle draws
isn't much fun, so I'm providing a simple turtle graphics viewer. To interface
with the viewer, turtle instances must respond to the message track by returning
an array which the viewer can use to generate a line drawing.

The viewer expects the array returned by track to take the following form:

  track ::= [segment, segment, ...] # drawing data
  segment ::= [point, point, ...] # points to be joined by line segments
  point ::= [x, y] # pair of floats
  
  Example: [[[0.0, 0.0], [200.0, 200.0]], [[200.0, 0.0], [0.0, 200.0]]]

This represents an X located in the upper-right quadrant of the viewer; i.e.,
two line segments, one running from the center of the viewer up to its
upper-right corner and the other running from the center of the top edge down to
the center of the right edge.

[Editor's Note: I added a script to dump your turtle graphics output to PPM
image files, for those that don't have TK up and running. It works identically
to Morton's turtle_viewer.rb, save that it writes output to a PPM image file in
the current directory. For example, to output the included tree image, use
`ruby turtle_ppm_writer.rb samples/tree.rb`. --JEG2]

  Unit Tests
  ==========

I'm including the unit tests which I developed to test turtle commands. For the
purposes of the quiz, you can ignore tests/turtle_view_test.rb. But I hope you
will find the other test suite, tests/turtle_test.rb, helpful. It tests every
one of the turtle commands described above as well as argument checking by the
commands. Don't hesitate to modify any of the unit tests to meet the needs of
your quiz solution.

  References
  ==========

  [1] Abelson, H. & A. diSessa, "Turtle Geometry", MIT Press, 1981.
  [2] Harvey, B., "Computer Science Logo Style", Chapter 10.
      http://www.cs.berkeley.edu/~bh/pdf/v1ch10.pdf
  [3] Wikipedia, http://en.wikipedia.org/wiki/LOGO_programming_language

3 cheers, great quiz!

Ironically, I very recently implemented a turtle graphics system using
GTK and Ruby.

Here are some images that came from it to whet your appetite:
http://www.danceliquid.com/images/LS/

It used cairo for the anti-aliased line drawing.

Whee!
-Harold

···

On 12/1/06, Ruby Quiz <james@grayproductions.net> wrote:

by Morton Goldberg

        Turtle Graphics
        ===============

Hello,
  Does the above work for everyone?

(~/turtle_graphics) > ruby turtle_ppm_writer.rb samples/tree.rb
turtle_ppm_writer.rb:47:in `run_code': undefined method `each' for
nil:NilClass (NoMethodError)
        from turtle_ppm_writer.rb:27:in `initialize'
        from turtle_ppm_writer.rb:75:in `new'
        from turtle_ppm_writer.rb:75

ruby 1.8.5 (2006-08-25) [powerpc-darwin8]

  Kurt

···

On 12/1/06, Ruby Quiz <james@grayproductions.net> wrote:

[Editor's Note: I added a script to dump your turtle graphics output to PPM
image files, for those that don't have TK up and running. It works identically
to Morton's turtle_viewer.rb, save that it writes output to a PPM image file in
the current directory. For example, to output the included tree image, use
`ruby turtle_ppm_writer.rb samples/tree.rb`. --JEG2]

Here is my straight-to-the-point answer:

class Turtle
   include Math # turtles understand math methods
   DEG = Math::PI / 180.0

   attr_accessor :track
   alias run instance_eval

   def initialize
     clear
   end

   attr_reader :xy, :heading

   # Place the turtle at [x, y]. The turtle does not draw when it
changes
   # position.
   def xy=(coords)
      raise ArgumentError if !coords.is_a?(Array) ||
                             coords.size != 2 ||
                             coords.any? { |c| !c.is_a?(Numeric) }
      @xy = coords
   end

   # Set the turtle's heading to <degrees>.
   def heading=(degrees)
      raise ArgumentError if !degrees.is_a?(Numeric)
      set_heading(degrees)
   end

   # Raise the turtle's pen. If the pen is up, the turtle will not
draw;
   # i.e., it will cease to lay a track until a pen_down command is
given.
   def pen_up
      @pen_down = false
   end

   # Lower the turtle's pen. If the pen is down, the turtle will draw;
   # i.e., it will lay a track until a pen_up command is given.
   def pen_down
      @pen_down = true
   end

   # Is the pen up?
   def pen_up?
      !@pen_down
   end

   # Is the pen down?
   def pen_down?
      @pen_down
   end

   # Places the turtle at the origin, facing north, with its pen up.
   # The turtle does not draw when it goes home.
   def home
      pen_up
      @xy = [0,0]
      @heading = 0
   end

   # Homes the turtle and empties out it's track.
   def clear
      home
      @track =
   end

   # Turn right through the angle <degrees>.
   def right(degrees)
     set_heading(@heading + degrees)
   end

   # Turn left through the angle <degrees>.
   def left(degrees)
     set_heading(@heading - degrees)
   end

   # Move forward by <steps> turtle steps.
   def forward(steps)
     dx, dy = calc_delta(steps)
     go [ @xy[0] + dx, @xy[1] + dy ]
   end

   # Move backward by <steps> turtle steps.
   def back(steps)
     dx, dy = calc_delta(steps)
     go [ @xy[0] - dx, @xy[1] - dy ]
   end

   # Move to the given point.
   def go(pt)
     track << [ @xy, pt ] if pen_down?
     @xy = pt
   end

   # Turn to face the given point.
   def toward(pt)
     @heading = atan(pt[0].to_f / pt[1].to_f) / DEG
   end

   # Return the distance between the turtle and the given point.
   def distance(pt)
     sqrt((@xy[0] - pt[0]) ** 2 + (@xy[1] - pt[1]) ** 2)
   end

   # Traditional abbreviations for turtle commands.
   alias fd forward
   alias bk back
   alias rt right
   alias lt left
   alias pu pen_up
   alias pd pen_down
   alias pu? pen_up?
   alias pd? pen_down?
   alias set_h heading=
   alias set_xy xy=
   alias face toward
   alias dist distance

   private
   def set_heading(degrees)
     @heading = degrees % 360
   end

   def calc_delta(steps)
     [ sin(heading * DEG) * steps,
       cos(heading * DEG) * steps ]
   end
end

Ruby Quiz wrote:

···

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Morton Goldberg

[Editor's Note: You can download the files for this quiz at:

  http://rubyquiz.com/turtle.zip

--JEG2]

  Turtle Graphics
  ===============

Turtle graphics is a form of computer graphics based on the ideas of turtle
geometry, a formulation of local (coordinate-free) geometry. As a brief
introduction to turtle graphics, I quote from [1]:

  Imagine that you have control of a little creature called a turtle
  that exists in a mathematical plane or, better yet, on a computer
  display screen. The turtle can respond to a few simple commands:
  FORWARD moves the turtle in the direction it is facing some
  number of units. RIGHT rotates it clockwise in its place some
  number of degrees. BACK and LEFT cause the opposite movements. ...
  The turtle can leave a trace of the places it has been: [its
  movements] can cause lines to appear on the screen. This is
  controlled by the commands PENUP and PENDOWN. When the pen is
  down, the turtle draws lines.

For example, the turtle commands to draw a square, 100 units on a side, can be
written (in a Ruby-ized form) as:

  pen_down
  4.times { forward 100; right 90 }

For more information, see [2] and [3].

This quiz is a bit different from most. If the usual Ruby quiz can be likened to
an essay exam, this one is a fill-in-the-blanks test. I'm supplying you with a
complete turtle graphics package, except -- to give you something to do -- I've
removed the method bodies from the key file, lib/turtle.rb. Your job is to
repair the damage I've done and make the package work again.

  Turtle Commands
  ===============

There are quite a few turtle commands, but that doesn't mean you have to write a
lot of code to solve this quiz. Most of the commands can be implemented in a
couple of lines. It took me a lot longer to write a description of the commands
than it did for me to implement and test all of them.

I use the following format to describe turtle commands:

  long_name | short_name <arg>
     description ...
     Example: ...

All turtle commands take either one argument or none, and not all turtle
commands have both a long name and a short name.

  Required Commands
  -----------------

These commands are required in the sense that they are needed to reproduce the
sample designs. Actually, you could get away without implementing 'back' and
'left', but implementing them is far easier than trying to write turtle code
without them.

  pen_up | pu
     Raises the turtle's pen. The turtle doesn't draw (lay down a visible
     track) when its pen is up.

  pen_down | pd
     Lowers the turtle's pen. The turtle draws (lays down a visible track)
     when its pen is down.

  forward | fd <distance>
     Moves the turtle forwards in the direction it is facing.
     Example: forward(100) advances the turtle by 100 steps.

  back | bk <distance>
     Moves the turtle backwards along its line of motion.
     back <distance> == forward -<distance>
     Example: back(100) backs up the turtle by 100 steps.

  right | rt <angle>
     Turns the turtle clockwise by <angle> degrees.
     Example: right(90) turns the turtle clockwise by a right angle.

  left | lt <angle>
     Turns the turtle counterclockwise by <angle> degrees.
     left <angle> == right -<angle>
     Example: left(45) turns the turtle counterclockwise by 45 degrees.

  Traditional Commands
  --------------------

These commands are not needed to reproduce any of the sample designs, but they
are found in all implementations of turtle graphics that I know of.

  home
     Places the turtle at the origin, facing north, with its pen up. The
     turtle does not draw when it goes home.

  clear
     Homes the turtle and empties out it's track. Sending a turtle a clear
     message essentially reinitializes it.

  xy
     Reports the turtle's location.
     Example: Suppose the turtle is 10 turtle steps north and 15 turtle steps
     west of the origin, then xy will return [-15.0, 10.0].

  set_xy | xy= <point>
     Places the turtle at <point>. The turtle does not draw when this command
     is executed, not even if its pen is down. Returns <point>.
     Example: Suppose the turtle is at [10.0, 20.0], then self.xy = [50, 80]
     moves the turtle to [50.0, 80.0], but no line will drawn between the [10,
     20] and [50, 80].

  heading
     Reports the direction in which the turtle is facing. Heading is measured
     in degrees, clockwise from north.
     Example: Suppose the turtle is at the origin facing the point [100, 200],
     then heading will return 26.565 (approximately).

  heading= | set_h <angle>
     Sets the turtle's heading to <angle>. <angle> should be given in degrees,
     measured clockwise from north. Returns <angle>.
     Example: After self.heading = 135 (or set_h(135) which is easier to
     write), the turtle will be facing southeast.

  pen_up? | pu?
     Reports true if the turtle's pen is up and false otherwise.

  pen_down? | pd?
     Reports true if the turtle's pen is down and false otherwise.

  Optional Commands
  -----------------

These commands are only found in some implementations of turtle graphics. When
they are implemented, they make the turtle capable of doing global (coordinate)
geometry in addition to local (coordinate-free) geometry.

I used one of these commands, go, to draw the mandala design (see
designs/mandala.tiff and samples/mandala.rb). If you choose not to implement the
optional commands, you might try writing a turtle program for drawing the
mandala design without using go. But, believe me, it is much easier to implement
go than to write such a program.

  go <point>
     Moves the turtle to <point>.
     Example: Suppose the turtle is home (at the origin facing north). After
     go([100, 200]), the turtle will be located at [100.0, 200.0] but will
     still be facing north. If its pen was down, it will have drawn a line
     from [0, 0] to [100, 200].

  toward | face <point>
     Turns the turtle to face <point>.
     Example: Suppose the turtle is at the origin. After toward([100, 200]),
     its heading will be 26.565 (approximately).

  distance | dist <point>
     Reports the distance between the turtle and <point>.
     Example: Suppose the turtle is at the origin, then distance([400, 300])
     will return 500.0 (approximately).

  Interfacing to the Turtle Graphics Viewer
  =========================================

Implementing turtle graphics without being able to view what the turtle draws
isn't much fun, so I'm providing a simple turtle graphics viewer. To interface
with the viewer, turtle instances must respond to the message track by returning
an array which the viewer can use to generate a line drawing.

The viewer expects the array returned by track to take the following form:

  track ::= [segment, segment, ...] # drawing data
  segment ::= [point, point, ...] # points to be joined by line segments
  point ::= [x, y] # pair of floats

  Example: [[[0.0, 0.0], [200.0, 200.0]], [[200.0, 0.0], [0.0, 200.0]]]

This represents an X located in the upper-right quadrant of the viewer; i.e.,
two line segments, one running from the center of the viewer up to its
upper-right corner and the other running from the center of the top edge down to
the center of the right edge.

[Editor's Note: I added a script to dump your turtle graphics output to PPM
image files, for those that don't have TK up and running. It works identically
to Morton's turtle_viewer.rb, save that it writes output to a PPM image file in
the current directory. For example, to output the included tree image, use
`ruby turtle_ppm_writer.rb samples/tree.rb`. --JEG2]

  Unit Tests
  ==========

I'm including the unit tests which I developed to test turtle commands. For the
purposes of the quiz, you can ignore tests/turtle_view_test.rb. But I hope you
will find the other test suite, tests/turtle_test.rb, helpful. It tests every
one of the turtle commands described above as well as argument checking by the
commands. Don't hesitate to modify any of the unit tests to meet the needs of
your quiz solution.

  References
  ==========

  [1] Abelson, H. & A. diSessa, "Turtle Geometry", MIT Press, 1981.
  [2] Harvey, B., "Computer Science Logo Style", Chapter 10.
      http://www.cs.berkeley.edu/~bh/pdf/v1ch10.pdf
  [3] Wikipedia, Logo (programming language) - Wikipedia

My solution, which does pass your updated test_coord_cmnds, Morton...

require "matrix"

class Turtle
   include Math # turtles understand math methods
   DEG = Math::PI / 180.0
   ORIGIN = [0.0, 0.0]
   NORTH = 0.0

   attr_accessor :track
   alias run instance_eval

   def initialize
      clear
   end

   attr_reader :xy, :heading

   # Place the turtle at [x, y]. The turtle does not draw when it changes
   # position.
   def xy=(pt)
      validate_point(pt)
      if pen_up?
         @xy = pt
      else
         pen_up
         @xy = pt
         pen_down
      end
      @xy
   end

   # Set the turtle's heading to <degrees>.
   def heading=(degrees)
      validate_angle(degrees)
      @heading = degrees % 360
   end

   # Raise the turtle's pen. If the pen is up, the turtle will not draw;
   # i.e., it will cease to lay a track until a pen_down command is given.
   def pen_up
      @segment = nil
   end

   # Lower the turtle's pen. If the pen is down, the turtle will draw;
   # i.e., it will lay a track until a pen_up command is given.
   def pen_down
      if pen_up?
         @segment = [@xy.dup]
         @track << @segment
      end
   end

   # Is the pen up?
   def pen_up?
      not @segment
   end

   # Is the pen down?
   def pen_down?
      not pen_up?
   end

   # Places the turtle at the origin, facing north, with its pen up.
   # The turtle does not draw when it goes home.
   def home
      pen_up
      @xy, @heading = ORIGIN, NORTH
   end

   # Homes the turtle and empties out it's track.
   def clear
      home
      @track = []
   end

   # Turn right through the angle <degrees>.
   def right(degrees)
      validate_angle(degrees)
      self.heading += degrees
   end

   # Turn left through the angle <degrees>.
   def left(degrees)
      validate_angle(degrees)
      self.heading -= degrees
   end

   # Move forward by <steps> turtle steps.
   def forward(steps)
      validate_dist(steps)
      go offset(steps)
   end

   # Move backward by <steps> turtle steps.
   def back(steps)
      validate_dist(steps)
      go offset(-steps)
   end

   # Move to the given point.
   def go(pt)
      validate_point(pt)
      @xy = pt
      @segment << @xy if pen_down?
   end

   # Turn to face the given point.
   def toward(pt)
      validate_point(pt)
      d = delta(pt)
      self.heading = atan2(d[0], d[1]) / DEG
   end

   # Return the distance between the turtle and the given point.
   def distance(pt)
      validate_point(pt)
      delta(pt).r
   end

   # Traditional abbreviations for turtle commands.
   alias fd forward
   alias bk back
   alias rt right
   alias lt left
   alias pu pen_up
   alias pd pen_down
   alias pu? pen_up?
   alias pd? pen_down?
   alias set_h heading=
   alias set_xy xy=
   alias face toward
   alias dist distance

   # Given a heading, build a unit vector in that direction.
   def facing
      rd = @heading * DEG
      Vector[ sin(rd), cos(rd) ]
   end

   # Offset the current position in the direction of the current
   # heading by the specified distance.
   def offset(dist)
      (Vector[*@xy] + (facing * dist)).to_a
   end

   # Build a delta vector to the specified point.
   def delta(pt)
      (Vector[*pt] - Vector[*@xy])
   end

   def validate_point(pt)
      raise ArgumentError unless pt.is_a?(Array)
      raise ArgumentError unless pt.size == 2
      pt.each { |x| validate_dist(x) }
   end

   def validate_angle(deg)
      raise ArgumentError unless deg.is_a?(Numeric)
   end

   def validate_dist(dist)
      raise ArgumentError unless dist.is_a?(Numeric)
   end

   private :facing
   private :offset
   private :delta
   private :validate_point
   private :validate_angle
   private :validate_dist
end

Quiz 104 -- Solution

···

====================

Here is what turtle.rb looked like before I messed with it to produce Quiz 104.

<code>
# An implementation of Turtle Procedure Notation (TPN) as described in
# H. Abelson & A. diSessa, "Turtle Geometry", MIT Press, 1981.
#
# Turtles navigate by traditional geographic coordinates: X-axis pointing
# east, Y-axis pointing north, and angles measured clockwise from the
# Y-axis (north) in degrees.

class Turtle
    include Math
    DEG = Math::PI / 180.0
    ORIGIN = [0.0, 0.0]

    alias run instance_eval
    attr_accessor :track
    attr_reader :xy, :heading

    def degree
       DEG
    end

    ###
    # Turtle primitives
    ###
</code>

I explicitly define a writer for @xy to get the Logo-like argument checking that I wanted. Also, I decided to maintain @xy as an array of floats to minimize the accumulation of position errors in long tracks.

<code>
    # Place the turtle at [x, y]. The turtle does not draw when it changes
    # position.
    def xy=(coords)
       if coords.size != 2
          raise(ArgumentError, "turtle needs two coordinates")
       end
       x, y = coords
       must_be_number(x, 'x-coordinate')
       must_be_number(y, 'y-coordinate')
       @xy = x.to_f, y.to_f
    end
</code>

Similarly, I explicitly define a writer for @heading. But it's not just for argument checking: I also use it to constrain @heading to the interval [0.0, 360.0).

<code>
    # Set the turtle's heading to <degrees>.
    def heading=(degrees)
       must_be_number(degrees, 'heading')
       @heading = degrees.to_f
       case
       when @heading >= 360.0
          @heading -= 360.0 while @heading >= 360.0
       when @heading < 0.0
          @heading += 360.0 while @heading < 0.0
       end
       @heading
    end

    # Raise the turtle's pen. If the pen is up, the turtle will not draw;
    # i.e., it will cease to lay a track until a pen_down command is given.
    def pen_up
       @pen = :up
    end
</code>

When the pen goes down, a new track segment must be added. Initially, the segment contains only a single point. If the pen goes up before another point is added to the segment, the segment ends up with just one point. Such singleton segments are skipped when the track is processed by in the view.

<code>
    # Lower the turtle's pen. If the pen is down, the turtle will draw;
    # i.e., it will lay a track until a pen_up command is given.
    def pen_down
       @pen = :down
       @track << [@xy]
    end

    # Is the pen up?
    def pen_up?
       @pen == :up
    end

    # Is the pen down?
    def pen_down?
       @pen == :down
    end

    ###
    # Turtle commands
    ###

    # Place the turtle at the origin, facing north, with its pen up.
    # The turtle does not draw when it goes home.
    def home
       pen_up
       self.xy = ORIGIN
       self.heading = 0.0
    end

    # Home the turtle and empty out it's track.
    def clear
       home
       self.track = []
    end

    alias initialize clear

    # Turn right through the angle <degrees>.
    def right(degrees)
       must_be_number(degrees, 'turn')
       self.heading = heading + degrees.to_f
    end

    # Turn left through the angle <degrees>.
    def left(degrees)
       right(-degrees)
    end
</code>

This is one of two places in the code where it actually has to do some trigonometry -- Turtle#toward below is the other.

<code>
    # Move forward by <steps> turtle steps.
    def forward(steps)
       must_be_number(steps, 'distance')
       angle = heading * DEG
       x, y = xy
       self.xy = [x + steps * sin(angle), y + steps * cos(angle)]
       track.last << xy if pen_down?
    end

    # Move backward by <steps> turtle steps.
    def back(steps)
       forward(-steps)
    end

    # Move to the given point.
    def go(pt)
       self.xy = pt
       track.last << xy if pen_down?
    end
</code>

In Turtle#toward, the expression atan2(y2 - y1, x2 - x1) computes the slope angle of the line between pt and xy. Math#atan2 is better here than Math#atan because atan2 handles the four quadrant cases automatically. Once the slope angle is known, it is easily converted into a heading.

<code>
    # Turn to face the given point.
    def toward(pt)
       x2, y2 = pt
       must_be_number(x2, 'pt.x')
       must_be_number(y2, 'pt.y')
       x1, y1 = xy
       set_h(90.0 - atan2(y2 - y1, x2 - x1) / DEG)
    end
</code>

Turtle#distance is easy to implement providing one remembers the existence of Math#hypot.

<code>
    # Return the distance between the turtle and the given point.
    def distance(pt)
       x2, y2 = pt
       must_be_number(x2, 'pt.x')
       must_be_number(y2, 'pt.y')
       x1, y1 = xy
       hypot(x2 - x1, y2 - y1)
    end

    # Traditional abbreviations for turtle commands.
    alias fd forward
    alias bk back
    alias rt right
    alias lt left
    alias pu pen_up
    alias pd pen_down
    alias pu? pen_up?
    alias pd? pen_down?
    alias set_h heading=
    alias set_xy xy=
    alias face toward
    alias dist distance

private

    # Raise an exception if <val> is not a number.
    def must_be_number(val, name)
       if !val.respond_to?(:to_f)
          raise(ArgumentError, "#{name} must be a number")
       end
    end
end
</code>

Now that you've seen the code, let me discuss some of the implementation decisions I made.

The first issue I had to deal with was how to reconcile the way turtles measure angles with the way Ruby/Math measures angles. Turtles, you recall, (following the conventions of geography/navigation) measure angles clockwise from north in degrees, while the Math module (following mathematical conventions) measures angles counterclockwise from east in radians. Since the Turtle class includes Math, there are advantages to following mathematical conventions when maintaining the turtle's orientation internal to the class, However, influenced by Logo, I chose to use the navigator's notion of angle and to reconcile turtle angles to Math angles each time I actually did some trig.

I also considered overriding the trig functions with methods that would accept angles in degrees as their arguments. In the end, I decided not to, but I still find myself thinking, from time to time, that I should go back to the code and do it.

The next issue I settled was: what, if any, argument checking should I do? I settled on accepting any argument that responds to to_f, raising ArgumentError for those that don't, and providing Logo-like error messages. The private method Turtle#must_be_number takes care of this.

The last major issue was: how should I maintain the turtle's state? That is, what instance variables should the class have? My choices were:

@xy turtle location
@heading turtle orientation
@pen pen state (up or down)
@track array needed to interface with Ruby/Tk

One last remark. Over the years I have built up a good-sized collection of Logo turtle graphics programs. One of reasons I wanted a Ruby turtle graphics capability was to convert this collection to Ruby. I had the feeling that Ruby would prove to be a better Logo than Logo. Well, I've performed the conversion and I'm convinced I was right: the Ruby versions of the Logo programs are simpler, easier to understand, and often considerably shorter than their Logo counterparts.

Regards, Morton

Hi folks,

Just for fun I implemented a quick and dirty version of
turtle_viewer.rb using Java/Swing. It must be run using JRuby 0.9.1.

Just put the file alongside turtle_viewer.rb and call:
jruby jturtle_viewer.rb

Here it is:
# jturtle_viewer.rb

require 'java'
require "lib/turtle"

class TurtleView
   DEFAULT_FRAME = [[-200.0, 200.0], [200.0, -200.0]]

   attr_accessor :frame

   def initialize(turtle, canvas, frame=DEFAULT_FRAME)
      @turtle = turtle
      @canvas = canvas
      @frame = frame
      @turtles =
   end

   def handle_map_event(w, h)
      top_lf, btm_rt = frame
      x0, y0 = top_lf
      x1, y1 = btm_rt
      @x_xform = make_xform(x0, x1, w)
      @y_xform = make_xform(y0, y1, h)
   end

   def draw
      g = @canvas.graphics
      @turtle.track.each do |seqment|
         if seqment.size > 1
            pts = seqment.collect { |pt| transform(pt) }
            g.drawLine(pts[0][0], pts[0][1], pts[1][0], pts[1][1])
         end
      end
   end

   def transform(turtle_pt)
      x, y = turtle_pt
      [@x_xform.call(x), @y_xform.call(y)]
   end

private

   def make_xform(u_min, u_max, v_max)
      lambda { |u| v_max * (u - u_min) / (u_max - u_min) }
   end

end

JFrame = javax.swing.JFrame
JPanel = javax.swing.JPanel
Dimension = java.awt.Dimension
BorderLayout = java.awt.BorderLayout

class TurtleViewer
   def initialize(code)
      @code = code

      root = JFrame.new "Turtle Graphics Viewer"
      @canvas = JPanel.new
      root.get_content_pane.add @canvas, BorderLayout::CENTER
      root.set_default_close_operation(JFrame::EXIT_ON_CLOSE)
      root.set_preferred_size Dimension.new(440, 440)
      root.set_resizable false
      root.pack
      root.set_visible true
      run_code
   end

   def run_code
      turtle = Turtle.new
      view = TurtleView.new(turtle, @canvas)
      view.handle_map_event(@canvas.width,
                            @canvas.height)
      turtle.run(@code)
      view.draw
   end
end

# Commands to be run if no command line argument is given.
CIRCLE_DESIGN = <<CODE
def circle
   pd; 90.times { fd 6; rt 4 }; pu
end
18.times { circle; rt 20 }
CODE

if ARGV.size > 0
   code = open(ARGV[0]) { |f| f.read }
else
   code = CIRCLE_DESIGN
end
TurtleViewer.new(code)

Ruby Quiz wrote:

···

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Morton Goldberg

[Editor's Note: You can download the files for this quiz at:

  http://rubyquiz.com/turtle.zip

--JEG2]

  Turtle Graphics
  ===============

Turtle graphics is a form of computer graphics based on the ideas of turtle
geometry, a formulation of local (coordinate-free) geometry. As a brief
introduction to turtle graphics, I quote from [1]:

  Imagine that you have control of a little creature called a turtle
  that exists in a mathematical plane or, better yet, on a computer
  display screen. The turtle can respond to a few simple commands:
  FORWARD moves the turtle in the direction it is facing some
  number of units. RIGHT rotates it clockwise in its place some
  number of degrees. BACK and LEFT cause the opposite movements. ...
  The turtle can leave a trace of the places it has been: [its
  movements] can cause lines to appear on the screen. This is
  controlled by the commands PENUP and PENDOWN. When the pen is
  down, the turtle draws lines.

For example, the turtle commands to draw a square, 100 units on a side, can be
written (in a Ruby-ized form) as:

  pen_down
  4.times { forward 100; right 90 }

For more information, see [2] and [3].

This quiz is a bit different from most. If the usual Ruby quiz can be likened to
an essay exam, this one is a fill-in-the-blanks test. I'm supplying you with a
complete turtle graphics package, except -- to give you something to do -- I've
removed the method bodies from the key file, lib/turtle.rb. Your job is to
repair the damage I've done and make the package work again.

  Turtle Commands
  ===============

There are quite a few turtle commands, but that doesn't mean you have to write a
lot of code to solve this quiz. Most of the commands can be implemented in a
couple of lines. It took me a lot longer to write a description of the commands
than it did for me to implement and test all of them.

I use the following format to describe turtle commands:

  long_name | short_name <arg>
     description ...
     Example: ...

All turtle commands take either one argument or none, and not all turtle
commands have both a long name and a short name.

  Required Commands
  -----------------

These commands are required in the sense that they are needed to reproduce the
sample designs. Actually, you could get away without implementing 'back' and
'left', but implementing them is far easier than trying to write turtle code
without them.

  pen_up | pu
     Raises the turtle's pen. The turtle doesn't draw (lay down a visible
     track) when its pen is up.

  pen_down | pd
     Lowers the turtle's pen. The turtle draws (lays down a visible track)
     when its pen is down.

  forward | fd <distance>
     Moves the turtle forwards in the direction it is facing.
     Example: forward(100) advances the turtle by 100 steps.

  back | bk <distance>
     Moves the turtle backwards along its line of motion.
     back <distance> == forward -<distance>
     Example: back(100) backs up the turtle by 100 steps.

  right | rt <angle>
     Turns the turtle clockwise by <angle> degrees.
     Example: right(90) turns the turtle clockwise by a right angle.

  left | lt <angle>
     Turns the turtle counterclockwise by <angle> degrees.
     left <angle> == right -<angle>
     Example: left(45) turns the turtle counterclockwise by 45 degrees.

  Traditional Commands
  --------------------

These commands are not needed to reproduce any of the sample designs, but they
are found in all implementations of turtle graphics that I know of.

  home
     Places the turtle at the origin, facing north, with its pen up. The
     turtle does not draw when it goes home.

  clear
     Homes the turtle and empties out it's track. Sending a turtle a clear
     message essentially reinitializes it.

  xy
     Reports the turtle's location.
     Example: Suppose the turtle is 10 turtle steps north and 15 turtle steps
     west of the origin, then xy will return [-15.0, 10.0].

  set_xy | xy= <point>
     Places the turtle at <point>. The turtle does not draw when this command
     is executed, not even if its pen is down. Returns <point>.
     Example: Suppose the turtle is at [10.0, 20.0], then self.xy = [50, 80]
     moves the turtle to [50.0, 80.0], but no line will drawn between the [10,
     20] and [50, 80].

  heading
     Reports the direction in which the turtle is facing. Heading is measured
     in degrees, clockwise from north.
     Example: Suppose the turtle is at the origin facing the point [100, 200],
     then heading will return 26.565 (approximately).

  heading= | set_h <angle>
     Sets the turtle's heading to <angle>. <angle> should be given in degrees,
     measured clockwise from north. Returns <angle>.
     Example: After self.heading = 135 (or set_h(135) which is easier to
     write), the turtle will be facing southeast.

  pen_up? | pu?
     Reports true if the turtle's pen is up and false otherwise.

  pen_down? | pd?
     Reports true if the turtle's pen is down and false otherwise.

  Optional Commands
  -----------------

These commands are only found in some implementations of turtle graphics. When
they are implemented, they make the turtle capable of doing global (coordinate)
geometry in addition to local (coordinate-free) geometry.

I used one of these commands, go, to draw the mandala design (see
designs/mandala.tiff and samples/mandala.rb). If you choose not to implement the
optional commands, you might try writing a turtle program for drawing the
mandala design without using go. But, believe me, it is much easier to implement
go than to write such a program.

  go <point>
     Moves the turtle to <point>.
     Example: Suppose the turtle is home (at the origin facing north). After
     go([100, 200]), the turtle will be located at [100.0, 200.0] but will
     still be facing north. If its pen was down, it will have drawn a line
     from [0, 0] to [100, 200].

  toward | face <point>
     Turns the turtle to face <point>.
     Example: Suppose the turtle is at the origin. After toward([100, 200]),
     its heading will be 26.565 (approximately).

  distance | dist <point>
     Reports the distance between the turtle and <point>.
     Example: Suppose the turtle is at the origin, then distance([400, 300])
     will return 500.0 (approximately).

  Interfacing to the Turtle Graphics Viewer
  =========================================

Implementing turtle graphics without being able to view what the turtle draws
isn't much fun, so I'm providing a simple turtle graphics viewer. To interface
with the viewer, turtle instances must respond to the message track by returning
an array which the viewer can use to generate a line drawing.

The viewer expects the array returned by track to take the following form:

  track ::= [segment, segment, ...] # drawing data
  segment ::= [point, point, ...] # points to be joined by line segments
  point ::= [x, y] # pair of floats

  Example: [[[0.0, 0.0], [200.0, 200.0]], [[200.0, 0.0], [0.0, 200.0]]]

This represents an X located in the upper-right quadrant of the viewer; i.e.,
two line segments, one running from the center of the viewer up to its
upper-right corner and the other running from the center of the top edge down to
the center of the right edge.

[Editor's Note: I added a script to dump your turtle graphics output to PPM
image files, for those that don't have TK up and running. It works identically
to Morton's turtle_viewer.rb, save that it writes output to a PPM image file in
the current directory. For example, to output the included tree image, use
`ruby turtle_ppm_writer.rb samples/tree.rb`. --JEG2]

  Unit Tests
  ==========

I'm including the unit tests which I developed to test turtle commands. For the
purposes of the quiz, you can ignore tests/turtle_view_test.rb. But I hope you
will find the other test suite, tests/turtle_test.rb, helpful. It tests every
one of the turtle commands described above as well as argument checking by the
commands. Don't hesitate to modify any of the unit tests to meet the needs of
your quiz solution.

  References
  ==========

  [1] Abelson, H. & A. diSessa, "Turtle Geometry", MIT Press, 1981.
  [2] Harvey, B., "Computer Science Logo Style", Chapter 10.
      http://www.cs.berkeley.edu/~bh/pdf/v1ch10.pdf
  [3] Wikipedia, Logo (programming language) - Wikipedia

I tried to use the new pure Ruby PNG library to get anti-aliased line drawing in the quiz files, but that sucker is a bit broken. :wink:

James Edward Gray II

···

On Dec 1, 2006, at 9:42 AM, Harold Hausman wrote:

It used cairo for the anti-aliased line drawing.

Making it work is the quiz. You need to finish the turtle.rb file so a track() is returned to draw.

James Edward Gray II

···

On Dec 1, 2006, at 10:50 AM, Kurt Hindenburg wrote:

On 12/1/06, Ruby Quiz <james@grayproductions.net> wrote:

[Editor's Note: I added a script to dump your turtle graphics output to PPM
image files, for those that don't have TK up and running. It works identically
to Morton's turtle_viewer.rb, save that it writes output to a PPM image file in
the current directory. For example, to output the included tree image, use
`ruby turtle_ppm_writer.rb samples/tree.rb`. --JEG2]

Hello,
Does the above work for everyone?

(~/turtle_graphics) > ruby turtle_ppm_writer.rb samples/tree.rb
turtle_ppm_writer.rb:47:in `run_code': undefined method `each' for
nil:NilClass (NoMethodError)
       from turtle_ppm_writer.rb:27:in `initialize'
       from turtle_ppm_writer.rb:75:in `new'
       from turtle_ppm_writer.rb:75

ruby 1.8.5 (2006-08-25) [powerpc-darwin8]

Ironically, I very recently implemented a turtle graphics system using
GTK and Ruby.

Why ironic? I's say you have a head start on solving the quiz. I look forward to seeing your submission.

Here are some images that came from it to whet your appetite:
http://www.danceliquid.com/images/LS/

It used cairo for the anti-aliased line drawing.

Nice images. The design you call Penrose, I call Hilbert. That and several other classic turtle designs (both the turtle code and the graphic output) are included with the quiz for participants to test against their Turtle class. The turtle code is in the samples folder and the graphics (.tiff files) are in the designs folder. Also, compare tree.tiff to your tree.png -- fairly similar.

Regards, Morton

···

On Dec 1, 2006, at 10:42 AM, Harold Hausman wrote:

Here is my straight-to-the-point answer:

Your solution passes all the unit tests I supplied and is certainly good enough to reproduce all the sample designs. So you have good reason to think it's completely correct. However, one of the optional methods has a problem.

   # Turn to face the given point.
   def toward(pt)
     @heading = atan(pt[0].to_f / pt[1].to_f) / DEG
   end

This won't work in all four quadrants.

I apologize for not providing tests good enough to detect the problem. Here is one that will test all four quadrants.

<code>
    # Test go, toward, and distance.
    # Verify heading measures angles clockwise from north.
    def test_coord_cmnds
       nne = [100, 173]
       @turtle.go nne
       x, y = @turtle.xy
       assert_equal(nne, [x.round, y.round])
       @turtle.home
       @turtle.run { pd; face nne; fd 200 }
       assert_equal(30, @turtle.heading.round)
       assert_equal([[[0, 0], nne]], snap(@turtle.track))
       sse = [100, -173]
       @turtle.home
       @turtle.run { face sse; fd 200 }
       assert_equal(150, @turtle.heading.round)
       ssw = [-100, -173]
       @turtle.home
       @turtle.run { face ssw; fd 200 }
       assert_equal(210, @turtle.heading.round)
       nnw = [-100, 173]
       @turtle.home
       @turtle.run { face nnw; fd 200 }
       assert_equal(330, @turtle.heading.round)
       @turtle.home
       assert_equal(500, @turtle.dist([400, 300]).round)
    end
</code>

Regards, Morton

···

On Dec 3, 2006, at 8:30 PM, Dema wrote:

Yes, and it passes all my other unit tests, too. Well done.

Regards, Morton

···

On Dec 4, 2006, at 7:46 AM, Matthew Moss wrote:

My solution, which does pass your updated test_coord_cmnds, Morton...

Morton Goldberg wrote:

/ ...

       case
       when @heading >= 360.0
          @heading -= 360.0 while @heading >= 360.0

@heading %= 360

       when @heading < 0.0
          @heading += 360.0 while @heading < 0.0

@heading %= 360 # same solution

In fact, now that I think about it, the entire block:

   case
   when @heading &gt;= 360\.0
      @heading \-= 360\.0 while @heading &gt;= 360\.0
   when @heading &lt; 0\.0
      @heading \+= 360\.0 while @heading &lt; 0\.0
   end

can be replaced with:

@heading %= 360

For a net improvement in execution speed and readability.

I hope this doesn't come off as golfing, apparently a popular pastime here.

···

--
Paul Lutus
http://www.arachnoid.com

Ha! I did the exact same thing the day they announced that PNG lib.
There is some kind of bug in their implementation of that tricksy line
drawing algorithm they're using. I spent about 20 minutes trying to
unravel it and then quit.

I found it kind of ironic that they chose some super speedy line
drawing routine meant to save cycles when writing directly to video
ram in mode 13h from the early ninety's. Sitting right next to a pure
ruby implementation of png, compression and all, the contrast was
stark.

(:,
-Harold

···

On 12/2/06, James Edward Gray II <james@grayproductions.net> wrote:

On Dec 1, 2006, at 9:42 AM, Harold Hausman wrote:

> It used cairo for the anti-aliased line drawing.

I tried to use the new pure Ruby PNG library to get anti-aliased line
drawing in the quiz files, but that sucker is a bit broken. :wink:

James Edward Gray II

> Ironically, I very recently implemented a turtle graphics system using
> GTK and Ruby.

Why ironic? I's say you have a head start on solving the quiz. I look
forward to seeing your submission.

I guess 'coincidentally' would have been a better word. :stuck_out_tongue:

Unfortunately, I have scant time for Ruby Quiz. The one (possibly
two?) submission I made was a total fluke. :((

> Here are some images that came from it to whet your appetite:
> http://www.danceliquid.com/images/LS/
>
> It used cairo for the anti-aliased line drawing.

Nice images. The design you call Penrose, I call Hilbert. That and
several other classic turtle designs (both the turtle code and the
graphic output) are included with the quiz for participants to test
against their Turtle class. The turtle code is in the samples folder
and the graphics (.tiff files) are in the designs folder. Also,
compare tree.tiff to your tree.png -- fairly similar.

My implementation includes an L-system exploration library which can
feed instructions directly to the pen. (which is abstracted from the
turtle in my code)

The turtle (which is implemented using eval in a very straight forward
manner), and the L-system library, can both produce strings of
instructions for the pen. (the pen is capable of both cairo and
gtk::drawingarea drawing)

I pulled most of the L-systems I was drawing from fractint, actually,
because the my L-system library is nearly interface compatible with
it. So credit goes out to the crazy wizards who contributed L-systems
to fractint.

There's even the option of having the turtle generate a string of
instructions, which can then be further iterated on by the L-systems,
an option that has yet to be properly exploited.

With that said, the code is a dirty, voracious, sub-optimal, tightly
coupled, borderline embarrassing, pile of hack. (As most of my one
weekend personal projects are.) But maybe it would be interesting to
you or others.

I've put it online, feel free to grab it:
http://www.danceliquid.com/docs/turtlegraphics.zip

But no cheating on the quiz with it... :wink:

Regards,
-Harold

···

On 12/2/06, Morton Goldberg <m_goldberg@ameritech.net> wrote:

On Dec 1, 2006, at 10:42 AM, Harold Hausman wrote:

In a bout of "why can't graphics be easy like it was on my BBC", I proposed as ASCII art Turtle graphics ruby quiz :slight_smile: I knocked up a quick implementation (invoking code reuse (woo!) of some bits I had left over from the earlier dungeon quiz), but the graphics were so cheesy, I didn't want to inflict them on the community.

···

On 1 Dec 2006, at 16:07, James Edward Gray II wrote:

On Dec 1, 2006, at 9:42 AM, Harold Hausman wrote:

It used cairo for the anti-aliased line drawing.

I tried to use the new pure Ruby PNG library to get anti-aliased line drawing in the quiz files, but that sucker is a bit broken. :wink:

Very nice. I didn't think to use mod for the angle correction either, but
that is definitely the more elegant solution.

--Tyler Prete

···

On 12/4/06, Paul Lutus <nospam@nosite.zzz> wrote:

Morton Goldberg wrote:

/ ...

> case
> when @heading >= 360.0
> @heading -= 360.0 while @heading >= 360.0

@heading %= 360

> when @heading < 0.0
> @heading += 360.0 while @heading < 0.0

@heading %= 360 # same solution

In fact, now that I think about it, the entire block:

  case
  when @heading >= 360.0
  @heading -= 360.0 while @heading >= 360.0
  when @heading < 0.0
  @heading += 360.0 while @heading < 0.0
  end

can be replaced with:

@heading %= 360

For a net improvement in execution speed and readability.

I hope this doesn't come off as golfing, apparently a popular pastime
here.

--
Paul Lutus
http://www.arachnoid.com

Good catch. I had forgotten that %= existed.

Regards, Morton

···

On Dec 4, 2006, at 2:20 PM, Paul Lutus wrote:

Morton Goldberg wrote:

/ ...

       case
       when @heading >= 360.0
          @heading -= 360.0 while @heading >= 360.0

@heading %= 360

       when @heading < 0.0
          @heading += 360.0 while @heading < 0.0

@heading %= 360 # same solution

In fact, now that I think about it, the entire block:

       case
       when @heading >= 360.0
          @heading -= 360.0 while @heading >= 360.0
       when @heading < 0.0
          @heading += 360.0 while @heading < 0.0
       end

can be replaced with:

@heading %= 360

Thanks for pointing that out. I don't even know how all the sample
drawings were right with that huge bug in the code.

Here is the corrected version that passes your updated tests (sorry, it
took me so long to reply).

Seeing the other solutions I feel that mine is probably not the best,
but perhaps the most concise one. I tried to be very "economic" on the
line count.

(Please James, could you update my solution link on the rubyquiz site?)

class Turtle
  include Math # turtles understand math methods
  DEG = Math::PI / 180.0

  attr_accessor :track
  alias run instance_eval

  def initialize
    clear
  end

  attr_reader :xy, :heading

  # Place the turtle at [x, y]. The turtle does not draw when it
changes
  # position.
  def xy=(coords)
    raise ArgumentError if !coords.is_a?(Array) ||
                          coords.size != 2 ||
                          coords.any? { |c| !c.is_a?(Numeric) }
    @xy = coords
  end

  # Set the turtle's heading to <degrees>.
  def heading=(degrees)
    raise ArgumentError if !degrees.is_a?(Numeric)
    set_heading(degrees)
  end

  # Raise the turtle's pen. If the pen is up, the turtle will not draw;
  # i.e., it will cease to lay a track until a pen_down command is
given.
  def pen_up
    @pen_down = false
  end

  # Lower the turtle's pen. If the pen is down, the turtle will draw;
  # i.e., it will lay a track until a pen_up command is given.
  def pen_down
    @pen_down = true
  end

  # Is the pen up?
  def pen_up?
    !@pen_down
  end

  # Is the pen down?
  def pen_down?
    @pen_down
  end

  # Places the turtle at the origin, facing north, with its pen up.
  # The turtle does not draw when it goes home.
  def home
    pen_up
    @xy = [0,0]
    @heading = 0
  end

  # Homes the turtle and empties out it's track.
  def clear
    home
    @track =
  end

  # Turn right through the angle <degrees>.
  def right(degrees)
    set_heading(@heading + degrees)
  end

  # Turn left through the angle <degrees>.
  def left(degrees)
    set_heading(@heading - degrees)
  end

  # Move forward by <steps> turtle steps.
  def forward(steps)
    dx, dy = calc_delta(steps)
    go [ @xy[0] + dx, @xy[1] + dy ]
  end

  # Move backward by <steps> turtle steps.
  def back(steps)
    dx, dy = calc_delta(steps)
    go [ @xy[0] - dx, @xy[1] - dy ]
  end

  # Move to the given point.
  def go(pt)
    track << [ @xy, pt ] if pen_down?
    @xy = pt
  end

  # Turn to face the given point.
  def toward(pt)
    set_heading 90.0 - atan2(pt[1] - @xy[1], pt[0] - @xy[0]) / DEG
  end

  # Return the distance between the turtle and the given point.
  def distance(pt)
    sqrt((@xy[0] - pt[0]) ** 2 + (@xy[1] - pt[1]) ** 2)
  end

  # Traditional abbreviations for turtle commands.
  alias fd forward
  alias bk back
  alias rt right
  alias lt left
  alias pu pen_up
  alias pd pen_down
  alias pu? pen_up?
  alias pd? pen_down?
  alias set_h heading=
  alias set_xy xy=
  alias face toward
  alias dist distance

  private
  def set_heading(degrees)
    @heading = degrees % 360
  end

  def calc_delta(steps)
    [ sin(heading * DEG) * steps,
      cos(heading * DEG) * steps ]
  end
end

Morton Goldberg wrote:

···

On Dec 3, 2006, at 8:30 PM, Dema wrote:

> Here is my straight-to-the-point answer:

Your solution passes all the unit tests I supplied and is certainly
good enough to reproduce all the sample designs. So you have good
reason to think it's completely correct. However, one of the optional
methods has a problem.

> # Turn to face the given point.
> def toward(pt)
> @heading = atan(pt[0].to_f / pt[1].to_f) / DEG
> end

This won't work in all four quadrants.

I apologize for not providing tests good enough to detect the
problem. Here is one that will test all four quadrants.

<code>
    # Test go, toward, and distance.
    # Verify heading measures angles clockwise from north.
    def test_coord_cmnds
       nne = [100, 173]
       @turtle.go nne
       x, y = @turtle.xy
       assert_equal(nne, [x.round, y.round])
       @turtle.home
       @turtle.run { pd; face nne; fd 200 }
       assert_equal(30, @turtle.heading.round)
       assert_equal([[[0, 0], nne]], snap(@turtle.track))
       sse = [100, -173]
       @turtle.home
       @turtle.run { face sse; fd 200 }
       assert_equal(150, @turtle.heading.round)
       ssw = [-100, -173]
       @turtle.home
       @turtle.run { face ssw; fd 200 }
       assert_equal(210, @turtle.heading.round)
       nnw = [-100, 173]
       @turtle.home
       @turtle.run { face nnw; fd 200 }
       assert_equal(330, @turtle.heading.round)
       @turtle.home
       assert_equal(500, @turtle.dist([400, 300]).round)
    end
</code>

Regards, Morton

"Harold Hausman" <hhausman@gmail.com> writes:

Ha! I did the exact same thing the day they announced that PNG lib.
There is some kind of bug in their implementation of that tricksy line
drawing algorithm they're using. I spent about 20 minutes trying to
unravel it and then quit.

I found it kind of ironic that they chose some super speedy line
drawing routine meant to save cycles when writing directly to video
ram in mode 13h from the early ninety's. Sitting right next to a pure
ruby implementation of png, compression and all, the contrast was
stark.

To be fair, they don't do the compression in pure ruby - they call
Zlib, so the really computationally intensive bit isn't in ruby.

Also, the fast line-drawing algorithm used for writing to that old
320x200 VGA mode wasn't what they're using here. Here, they're doing
*anti-aliased* lines, which no one would ever think of trying when you
only have 256 colors available and the pixels are going to be visibly
squares no matter what you do.

That old line drawing algorithm I could just pull out of a book on my
shelf; what they're doing here is enough different that it's very
tough to disentangle.

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.map{|i|i}[1] )
       puts "s=%q(#{s})",s.map{|i|i}[1]