Functional Problem Solving (CSC 151 2014F) : Readings
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Setup] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Davis (2013F)] [Rebelsky (2014S)] [Weinman (2014F)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
Summary: We explore MediaScript's implementation of Turtle Graphics, a model of graphics based on turtles that draw.
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.
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 (
, 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
turtle-new
image-id
)canvas
, you could create a turtle on that image.
>
(define tommy (turtle-new canvas))
One can also clone an existing
turtle with (
. 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
turtle
)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.
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.
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) (action 01! tommy)
|
![]() |
(turtle-reset! tommy) (action02! tommy) (action 02! tommy)
|
![]() |
(turtle-reset! tommy) (action01! tommy) (action 01! tommy) (action 01! tommy)
|
You might ask yourself what happens if we continue to repeat
action01!
.
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.
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.
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 (
procedure,
which repeatedly applies repeat
n
proc!
value
)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)
|
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.
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.
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.
b. What is the difference between the procedures
turtle-turn!
and
turtle-face!
?
c. Why might it be advantageous for a turtle to return to its original position and orientation after running a command?
d. 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?
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.