Skip to main content

Turtle Graphics

Summary: We explore MediaScript’s implementation of Turtle Graphics, a model of graphics based on turtles that draw.

Introduction

As you might expect, there are a wide variety of models for describing images algorithmically. One of the more interesting models is typically called turtle graphics, and is based on a fairly simple and intuitive model of sketching: All sketching is done by a robotic turtle that has two basic actions: moving forward and turning. The turtle also has a collection of pens that it use to draw with. (Alternately, it has a magic pen which can change colors and tips.)

The turtle graphics model is popular for a number of reasons. One important reason is that it is simple: At the core, turtle graphics only requires four operations: moving the turtle forward, turning the turtle, lifting the pen up, and putting the pen down. (Often, turtle graphics implementations provide additional features, but these four suffice.) Given these few operations, plus a few additional control structures, it is possible to draw a wide variety of interesting images. Many programmers and designers also appreciate the challenge of seeing what they can do with these simple operations.

Another reason that turtle graphics is popular is that it is relatively easy to implement in the physical world. Particularly with the advent of easy-to-construct robotics kits, such as Lego Mindstorms™, “anyone” can build a robot that draws. (In most cases, the unpredictability of the physical world means that sketches with physical turtles have an interesting imperfection as compared to those from their digital counterparts.)

Turtle graphics is also popular because it was a core component of Logo, one of the most important languages created for novice programmers. Logo, designed by Seymour Papert at MIT, was intended as an environment in which children could learn to think about algorithms and programming. It was surprisingly successful, enough so that Logo is still used in some contexts, a few decades after it was first created. Logo and turtle graphics also promote a mode of teaching and learning called constructionism in which students learn by playing and experimenting.

MediaScripts’s Basic Turtle Graphics Procedures

MediaScript provides a somewhat larger group of turtle graphics procedures than the basic four. In part, this is because MediaScript permits you to have more than one turtle. (Multiple turtles can be useful when you want to draw overlapping figures.)

There are two ways to create a turtle. The most straightforward is (turtle-new image-id), which creates a new turtle associated with a particular image. You will, of course, need to name that turtle. For instance, if you’ve already created an image called canvas, you could create a turtle on that image.

> (define tommy (turtle-new canvas))

One can also clone an existing turtle with (turtle-clone turtle). The clone is associated with the same image and has the same position and orientation as the original turtle. Once again, when you create a new turtle with turtle-clone, you should name that clone.

> (define tommy2 (turtle-clone tommy))

One can, of course, tell turtles to do the four core turtle operations.

  • (turtle-forward! turtle amt) advances the turtle the specified amount, sketching if the pen is down.
  • (turtle-turn! turtle degrees) turns the turtle clockwise the specified number of degrees.
  • (turtle-up! turtle) lifts the turtle’s pen.
  • (turtle-down! turtle) drops the turtle’s pen.

With these procedures, we can draw squares, equilateral triangles, and even circles.

Orienting the Turtle

In an ideal world, either (a) the programmer knows the position and direction of the turtle or (b) the programmer can write programs that are independent of the initial position and direction. In fact, common turtle coding practice suggests that you should often design sequences of operations that return a turtle to its original position and orientation.

However, when experimenting with turtles, a programmer benefits from the ability to move the turtle to a particular position and orient it in a particular direction. MediaScript provide two “hacks” that let you place and orient the turtle precisely.

  • (turtle-teleport! turtle column row) moves the turtle to a particular column and row.
  • (turtle-face! turtle degrees) makes the turtle face a direction the specified number of degrees clockwise from right.

Before we explore more about turtles, let’s start by writing a procedure that places the turtle at (50,50), facing right.

;;; Procedure:
;;;   turtle-reset!
;;; Parameters:
;;;   turtle, a turtle
;;; Purpose:
;;;   Resets the turtle to a standard location and orientation for
;;;   convenience of testing.
;;; Produces:
;;;   turtle, the same turtle, now at a standard location and orientation.
(define turtle-reset!
  (lambda (turtle)
    (turtle-teleport! turtle 50 50)
    (turtle-face! turtle 0)))

You may notice that this looks a bit different than other procedures we’ve written. First, the name turtle-reset! ends with an exclamation point. You may recall that we also used exclamation points in many of the GIMP tools procedures. It is tradition in Scheme that when procedures change one of their parameters, or the context in which future procedures are evaluated, we end the names with an exclamation point.

Second, instead of nesting operations, as we did when creating drawings, we are sequencing operations. First, we teleport the turtle to (50,50). Then we turn the turtle to the right.

You’ll find both characteristics in the procedures we write that deal with turtles. That is: We will often change the turtles (e.g., moving them to new places or making them face other directions) and we will usually create a sequence of operations for the turtle to follow.

Turtle Graphics: Initial Explorations

Now that we know the basics of turtles, let’s explore the basic operations visually. First, we’ll reset the position of the turtle using the procedure we just created.

   
(turtle-reset! tommy)

Not very interesting, is it? It is a tradition that you only see the ink left by the turtle’s pen, and not the turtle itself. However, it’s not very helpful to have invisible turtles if we’re trying to understand the turtle operations. Hence, for this discussion of turtles, we will use a small figure to show the position and orientation of the turtle.

   
(turtle-reset! tommy)

So, what happens when we move the turtle forward 25 spaces? Probably what you’d expect: We get a horizontal line of length 25, and the turtle is at the end of the line.

   
(turtle-reset! tommy) (turtle-forward! tommy 25)

Okay, suppose instead of moving the turtle forward, we started by turning it 135 degrees counter-clockwise or 90 degrees clockwise. We can envision that something like the following.

   
(turtle-reset! tommy) (turtle-turn! tommy -135)
   
(turtle-reset! tommy) (turtle-turn! tommy 90)

Not very exciting yet, is it? But we can start to get some interesting things when we combine the basic operations into more complex actions and sequence those actions.

(define action01!
  (lambda (turtle)
    (turtle-forward! turtle 25)
    (turtle-turn! turtle -135)))

(define action02!
  (lambda (turtle)
    (turtle-turn! turtle -135)
    (turtle-forward! turtle 25)))

Let’s see what we get out of these new actions.

   
(turtle-reset! tommy) (action01! tommy)
   
(turtle-reset! tommy) (action02! tommy)

Hmmmm… the same two commands, but in different orders, produced somewhat different “states” of the turtle and different images. To some, the result of action01! are a bit strange, since the turtle is not pointing the same way as the line. However, the turtle turns after sketching the line. Hence, we would expect it to be pointing in a different direction.

You’ll note that neither of these actions meets the basic criterion that, after making a sketch, the programmer is supposed to ensure that the turtle ends up in its initial position and orientation. That’s okay for now, since we’re creating building blocks for making sketches. However, we should eventually think about how to return the turtle to its original position (other than by calling turtle-reset!).

Let’s try some sequences of operations now.

   
(turtle-reset! tommy) (action01! tommy) (action01! tommy)
   
(turtle-reset! tommy) (action02! tommy) (action 02! tommy)
   
(turtle-reset! tommy) (action01! tommy) (action01! tommy) (action01! tommy)

You might ask yourself what happens if we continue to repeat action01!.

Some Puzzles

See if you can figure out what kind of figure each of these procedures makes.

(define figure01!
  (lambda (turtle)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 90)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 90)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 90)
    (turtle-forward! turtle 50)))

(define figure02!
  (lambda (turtle)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 120)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 120)
    (turtle-forward! turtle 50)))
(define figure03!
  (lambda (turtle)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 144)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 144)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 144)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 144)
    (turtle-forward! turtle 50)))

You should also ask yourself whether any of these procedures meet the criterion that it returns the turtle to the original position and orientation and, if not, how you might return it to that position and orientation.

Getting Fancy: Brushes and Colors

It is possible to draw a wide range of fascinating images without every changing the color or brush shape the turtle uses. However, we can have even more fun if we are able to change those values. The following procedures allow us to update the pen. Both procedures take similar parameters to context-set-fgcolor! and context-set-brush!, respectively.

  • (turtle-set-color! turtle color) sets the color of the brush the turtle uses.
  • (turtle-set-brush! turtle brush) sets the brush the turtle uses.
  • (turtle-set-brush! turtle brush size) sets both the brush and the size of the brush.

Repeating Commands

As you may have noted in our initial, English-language, discussions of turtle graphics, turtle-graphics algorithms benefit from the ability to repeat commands. For example, we might draw a square by repeating the following action four times.

(define action03!
  (lambda (turtle)
    (turtle-forward! turtle 40)
    (turtle-turn! turtle 90)))

How do we repeat the action four times? We can type four instructions.

(action03! tommy)
(action03! tommy)
(action03! tommy)
(action03! tommy)

We can write a procedure that repeats the action four times.

(define action04!
  (lambda (turtle)
    (action03! turtle)
    (action03! turtle)
    (action03! turtle)
    (action03! turtle)))

Or we can use the (repeat n proc! value) procedure, which repeatedly applies proc! to value. (It does the application n times.)

   
(action03! tommy)
   
(repeat 2 action03! tommy)
   
(repeat 3 action03! tommy)
   
(repeat 4 action03! tommy)

As some of you have noted when trying to design circles earlier in the semester, we can have turtles draw circles by having them repeatedly move a small amount forward and turn a small amount.

(define action05!
  (lambda (turtle)
    (turtle-forward! turtle 5)
    (turtle-turn! turtle 10)))
   
(repeat 20 action05! tommy)
   
(repeat 36 action05! tommy)

Turtle Values

In the past, you’ve seen that brushes are represented by strings and images are represented by integers. How are turtles represented? In the current version of MediaScript, turtles are procedures.

> (turtle-new canvas)
#<procedure:...heme/gimplib.scm:5480:6>

What does that mean? It means the designers of MediaScript wanted to make the implementation of turtles a bit obscure. The main outcome is that you cannot type turtles in directly. Rather, you must create them with turtle-new or turtle-clone and you must name them in order to use them.

The technique of hiding the underlying details of a data type is part of a strategy called encapsulation. Provided that the designer of the data type includes sufficiently many procedures for working with the data (e.g., the various procedures we’ve provided for turtles), it should not matter to the client programmer how the data type is implemented. Among other things, encapsulation allows the designer to change underlying representation. It turns out that encapsulation gives benefits to client programmers, too. In particular, they need not worry about particular details, just about the procedures provided.

In fact, even when you know the underlying implementation (which you will for some data types we’ll develop over the next few weeks, and even for turtles toward the end of the course), it is still appropriate to use the procedures that are designed for manipulating the object, for both of those reasons. That is, by using those procedures, you allow yourself or someone else to change the underlying representation, and, just as importantly, you focus more on how you’re using the data than on how it’s represented.

Getting Information About Turtles

As we noted above, in an ideal world, programmers carefully analyze their programs and always know the position and orientation of the turtle. However, in practice, many programmers, particularly novice programmers, are unsure of where there turtle is or which direction it faces. To support such programmers, the MediaScript turtle library includes a few simple procedures:

  • turtle-show! updates the turtle so that it shows its position and orientation after each time it moves or turns.
  • turtle-hide! restores the turtle to its normal invisible state.
  • turtle-col and turtle-row give the column and row of the turtle’s current position.
  • turtle-angle gives the angle that the turtle is facing.

Self Checks

Check 1: Turtle Basics

a. Are turtles primarily functional, like drawings, or imperative, like the GIMP Tools?

b. List as many states or variables associated with a turtle that you can think of.

c. What is the difference between the procedures turtle-turn! and turtle-face!?

d. Why might it be advantageous for a turtle to return to its original position and orientation after running a command?

e. Suppose you have a turtle whose associated image has been displayed in the GIMP (i.e., with image-show) but then closed. What do you think will happen if you try to draw with that turtle after its image has been closed?

Check 2: repeat

The following three procedures all accomplish the same thing.

(define figure02!
  (lambda (turtle)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 120)
    (turtle-forward! turtle 50)
    (turtle-turn! turtle 120)
    (turtle-forward! turtle 50)))

(define figure02!
  (lambda (turtle)
    (let ([forward-turn! 
           (lambda ()
             (turtle-forward! turtle 50)
             (turtle-turn! turtle 120))])
      (repeat 2 forward-turn!)
      (turtle-forward! turtle 50))))

(define figure02!
  (let ([forward-turn! 
         (lambda (turtle)
           (turtle-forward! turtle 50)
           (turtle-turn! turtle 120))])
    (lambda (turtle)
      (repeat 2 forward-turn! turtle)
      (turtle-forward! turtle 50))))

Which do you prefer? Why? What if the forward-and-turn action were repeated (in the original) several more times.