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