# Turtle Graphics (#104)

**URL:** <https://www.ruby-forum.com/t/turtle-graphics-104/78653>\
**Category:** Ruby\
**Created:** [December 7, 2006, 4:33pm UTC](https://www.ruby-forum.com/t/turtle-graphics-104/78653 "2006-12-07T16:33:17Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [December 7, 2006, 4:33pm UTC](https://www.ruby-forum.com/t/turtle-graphics-104/78653/1 "2006-12-07T16:33:17Z")

</div>

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…

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [December 7, 2006, 6:15pm UTC](https://www.ruby-forum.com/t/turtle-graphics-104/78653/2 "2006-12-07T18:15:48Z")

</div>

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

I think that you should mention a subtlety here. The calculation of a  
new point is usually

[x,y] = [x\_old + dist \* cos(theta), y\_old + dist \* sin(theta)]

Because the turtle geometry is 90 degrees out of phase and rotating in  
the other direction, Pete has flipped the x and y so the new point is

[x,y] = [x\_old + dist \* sin(theta), y\_old + dist \* cos(theta)]

Another subtlety is that Pete used atan2, which handles division by zero  
and the ambiguity of which quadrant you are in.

(Well, it was subtle to me!!)

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [December 7, 2006, 9:28pm UTC](https://www.ruby-forum.com/t/turtle-graphics-104/78653/3 "2006-12-07T21:28:55Z")

</div>

On Dec 7, 2006, at 11:15 AM, Edwin F. wrote:

> (Well, it was subtle to me!!)

Both terrific points. Thanks for bringing them up!

James Edward G. II
