I’m going to move my standard thank you note right to the beginning of

this

summary, because it’s very important this time. Morton put in a lot of

work

prepping this problem so it would be Ruby Q. size and fun at the same

time.

He even nursed me through my additions. Thank you Morton! More thanks

to those

who fiddled with the problem, showing Morton how much we appreciate his

efforts.

Alright, let’s get to the solutions.

Solving this problem isn’t too tricky. The main issue is to have the

Turtle

track its state which consists of where it currently is, which way it is

facing,

and if the pen is currently up or down. Then you need to make the

methods that

alter this state functional. A surprising number of the methods have

trivial

implementations, but you do need a little trigonometry for some.

Let’s walk through Pete Y.'s turtle.rb file to see how a solution

comes

together. Here’s the start of the code:

```
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
# ...
```

The only line in there not provided by the quiz is the call to clear()

in

initialize(). We’ll look at what that does in just a moment, but first

let’s

talk a little about what the quiz gave us for free.

We’ve already decided a little trig is needed so the functions of the

Math

Module are included for us. Now those Math methods expect arguments in

radians,

but our Turtle is going to work with degrees. The conversion formula is

radians

= degrees * (PI / 180) and that’s exactly what the DEG constant sets up

for us.

Skipping down, we see that instance_eval() is given a new name, so we

can invoke

Turtle code more naturally. This tells us how our object will be used.

Because

user code is evaluated in the context of this object, it will have

access to all

the methods we are about to build and even the methods borrowed from

Math.

The rest of the code provides accessors to the elements of Turtle state

we

identified earlier. Since they are there, we might as well take the

hint and

tuck our instance data away in them. We still need to figure out how to

track

the pen’s up/down state though. Finally, The track() method provides

access to

the Turtle path we are to construct. The viewer will call this to

decide what

to render.

I’ll jump ahead in the code now, to show you that clear() method and

another

method it makes use of:

```
# ...
# Homes the turtle and empties out it's track.
def clear
@track = []
home
end
# Places the turtle at the origin, facing north, with its pen up.
# The turtle does not draw when it goes home.
def home
@heading = 0.0
@xy = [0.0, 0.0]
@pen_is_down = false
end
# ...
```

As you can see, clear() resets the Turtle to the beginning state (by

calling

home()) and clears any drawing that has been done. The constructor

called this

method to ensure all the state variables would be set before we run()

any code.

We can now see that pen state will be tracked via a boolean instance

variable as

well. Here are the methods that expose that to the user:

```
# ...
# 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_is_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_is_down = true
@track << [@xy]
end
# Is the pen up?
def pen_up?
!@pen_is_down
end
# Is the pen down?
def pen_down?
@pen_is_down
end
# ...
```

Most of those should be obvious implementations. The surprise, if any,

comes

from the fact that pen_down() puts a point on the track. This makes

sense

though, if you think about it. If you touch a pen to a piece of paper

you have

made a mark, even though you have not yet drawn a line. The Turtle

should

function the same way.

Here are the other setters for our Turtle’s state:

```
# ...
# Place the turtle at [x, y]. The turtle does not draw when it
```

changes

# position.

def xy=(coords)

raise ArgumentError unless is_point?(coords)

@xy = coords

end

```
# Set the turtle's heading to <degrees>.
def heading=(degrees)
raise ArgumentError unless degrees.is_a?(Numeric)
@heading = degrees % 360
end
# ...
```

These should be pretty straight-forward as well. I haven’t shown it

yet, but

is_point?() just validates that we received sensible parameters. Beyond

the

checks, these methods just make assignments, save that heading=()

restricts the

parameter to a value between 0 and 359.

We’ve got the state, so it’s time to get the Turtle moving. Let’s start

with

turns:

```
# ...
# Turn right through the angle <degrees>.
def right(degrees)
raise ArgumentError unless degrees.is_a?(Numeric)
@heading += degrees
@heading %= 360
end
# Turn left through the angle <degrees>.
def left(degrees)
right(-degrees)
end
# ...
```

The right() method is the workhorse here. It validates, adds the

requested

number of degrees, and trims the heading if we have passed 360. Pete

then

wisely reuses the code by defining left() in terms of a negative right()

turn.

Two for the price of one.

We can turn, so it’s time to mix in a little motion:

```
# ...
# Move forward by <steps> turtle steps.
def forward(steps)
raise ArgumentError unless steps.is_a?(Numeric)
@xy = [ @xy.first + sin(@heading * DEG) * steps,
@xy.last + cos(@heading * DEG) * steps ]
@track.last << @xy if @pen_is_down
end
# Move backward by <steps> turtle steps.
def back(steps)
forward(-steps)
end
# ...
```

Remember your trig? We have the angle (@heading) and the length of the

hypotenuse of a right triangle (steps). What we need are the lengths of

the

other two sides which would be the distance we moved along the X and Y

axes.

Note the use of DEG here to convert degrees to into the expected

radians.

Once you accept how forward() calculates the new location, drawing the

line is

almost a let down. The point where we were will already be on the

track, either

from a previous line draw or from a pen_down() call. Just adding the

new point

to that segment that contains the last point ensures that a line will be

drawn

to connect them.

Again, we see that back() is just a negative forward().

Here are the rest of the Turtle movement commands:

```
# ...
# Move to the given point.
def go(pt)
raise ArgumentError unless is_point?(pt)
@xy = pt
@track.last << @xy if @pen_is_down
end
# Turn to face the given point.
def toward(pt)
raise ArgumentError unless is_point?(pt)
@heading = atan2(pt.first - @xy.first, pt.last - @xy.last) /
DEG % 360
end
# Return the distance between the turtle and the given point.
def distance(pt)
raise ArgumentError unless is_point?(pt)
return sqrt( (pt.first - @xy.first) ** 2 +
(pt.last - @xy.last) ** 2 )
end
# ...
```

go() is just forward() without needing to calculate the new point. (In

fact,

forward() could have called go() with the new point for even more

aggregation

goodness.) toward() uses an arc tangent calculation to change headings

and

distance() uses the Pythagorean theorem to tell you how many steps the

given

point is from where you are.

Here’s the final bit of code:

```
# ...
# 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 is_point?(pt)
pt.is_a?(Array) and pt.length == 2 and
pt.first.is_a?(Numeric) and pt.last.is_a?(Numeric)
end
end
```

Those aliases were provided with the quiz and is_point?() is the helper

method

used to check the passed arguments to xy=(), go(), toward(), and

distance().

If you slot that file into the provided quiz project and start running

samples,

you should see pretty pictures and I’m a real sucker for pretty

pictures.

Thanks again Morton. Great quiz idea!

Tomorrow we will tackle a fun algorithmic problem for us tournament

players…