Overview of the turtle
A turtle is a creature that moves over the screen, making marks, in response to commands. The turtle you will implement will respond to the following elementary commands:
c ("crawl"): move distance D in the current direction, making a line as you go
r ("right turn"): without moving, turn right by A radians
l ("left turn"): without moving, turn left by A radians
Implementation, Phase 1: Elementary turtle commands.
Define constants PI, D, and A. Values of 10 for D and PI/2 for A are reasonable for now.
Write functions with these prototypes and behaviors:
void crawl(float& x, float&y, float angle); // Makes turtle crawl, starting at coordinates x and y, // and updates x and y to coordinates at end of crawl. // Turtle should wrap around if it tries to crawl off the edge of the screen. // Bonus points: It is a hard problem to get the turtle to correctly // wrap around when a single crawl crosses more than one edge. You will get // bonus points if you solve that problem correctly. float right(float angle); // returns a new angle A radians to the right of angle. // Notice that the answer and parameter are both radians. float left(float angle); // returns a new angle A radians to the left of angle. // Notice that the answer and parameter are both radians. void do_elem_command( char command, float &x, float &y, float &angle ); // The command parameter is the character for a command; the other // parameters are the current state of turtle (position and angle); // the function executes the command and updates the state. // It should ignore any invalid command chars.
In your main() function, write a loop in which you read a command character and execute it, stopping when the user types "q" (for quit.) A do-while is a good choice here... why? The following code fragment shows how you can read a character without disrupting the graphics:
char c; ... c = getch( );
Your main() will also need variables in which to keep track of the position of the turtle; start these out with values that put the turtle in the middle of the screen facing to the right.
Try out your turtle. See if you can get it to draw a square by controlling it from the keyboard. Also make sure the wraparound in crawl works properly.
Notice how nice it is to have reference arguments, so that a function like crawl() can update both x and y. Remember that the ant needed two functions for this kind of updating, one for each coordinate, because using value arguments a function could only produce one new value.
Phase 2: Two more commands.
Besides the elementary commands, it useful for turtles to be able to execute two other commands:
m ("mark"): save the turtle's state (current position and angle) for future reference
g (" go back"): restore position and angle to last marked state
Add to your program functions with these prototypes:
void mark( float x, float y, float angle, float &saved_x, float &saved_y, float &saved_angle ); // Updates the saved coordinates and angle to contain the current // values as supplied in the first three parameters. void go_back( float &x, float &y, float &angle, float saved_x, float saved_y, float saved_angle ); // Sets the current coordinates and angle to the saved values.
void do_command( char c, float &x, float &y, float &angle, float &saved_x, float &saved_y, float &saved_angle ); // Is given character for a command, and current state of turtle, including saved values
// The function executes the command and updates all components of state as appropriate
Note that you can use do_elem_command() when implementing do_command(); no point in throwing away that earlier work.
Your main() function should be much the same as in Phase 1, but it should use do_command() instead of do_elem_command(), so that it can handle any of the 5 commands. It will need variables to contain the saved state components, as well as the current position and heading of the turtle. Start the saved values out with coordinates in the middle of the screen, heading to the right (by doing this you make something sensible happen if the user calls for a g command before executing an m.)
Try out your turtle. Be sure to test m and g.
Phase 3: A programmed shape.
Driving a turtle from the keyboard is tedious. Soon you'll learn how to process collections of values, such as collections of command characters, in an organized way, but for now you'll use another technique to get your turtle to execute a sequence of commands without typing them in each time. We'll just write a program that calls for the commands we want in order.
Write a function with this prototype:
void draw_shape(float& x, float& y, float& angle);
This function should contain these calls, in order:
do_command('c', x, y, angle, saved_x, saved_y, saved_angle); do_command('m', x, y, angle, saved_x, saved_y, saved_angle); do_command('l', x, y, angle, saved_x, saved_y, saved_angle); do_command('c', x, y, angle, saved_x, saved_y, saved_angle); do_command('g', x, y, angle, saved_x, saved_y, saved_angle); do_command('r', x, y, angle, saved_x, saved_y, saved_angle); do_command('c', x, y, angle, saved_x, saved_y, saved_angle); do_command('g', x, y, angle, saved_x, saved_y, saved_angle); do_command('c', x, y, angle, saved_x, saved_y, saved_angle);
Important: notice that the quotes in these calls are all single quotes. If you use double quotes the compiler will give you character strings rather than single characters, which are not what you want here.
Your draw_shape() function will also need local variables saved_x, saved_y, and saved_angle to pass as arguments to do_command(), so that m and g commands will work.
Modify your main() so that it does not prompt the user for commands, and has no loop, but just calls draw_shape() after any necessary setup of initial values. Notice that main() no longer needs variables saved_x, etc, since draw_shape() takes care of these. You'll want to change A to PI/4 for this pattern.
As in your ant program you'll want to include a call to getch() at the end to keep the graphics from disappearing before you've seen them. When you run this program you should see a little fork pattern.
Notice that it's a nuisance to have to pass all these arguments around in order to get a bunch of things updated. You might even be tempted to use global variables (but DON'T DO IT). As we'll see soon, the right way to deal with this kind of issue is to bundle multiple values into a single data structure, which we could call something like turtle_state. But for now we have to use lots of arguments.
Phase 4. A recursive turtle.
To draw really interesting patterns, the turtle needs to be able to execute sequences of commands that contain copies of themselves.Such sequences are called "recursive".
If you think about this you'll see that there's a potential problem: a sequence of commands that contains itself will take forever to draw. To avoid this problem, we get our turtle to keep track of how many copies of a command sequence it has drawn, and stop working when it has drawn some predetermined number. Actually, instead of keeping track of the number of copies, we'll keep track of levels of detail:: for high levels of detail, we'll ask the turtle to draw the parts of a sequence at a bit lower level of detail. When the level of the detail gets low enough, we'll have the turtle just sketch in the pattern crudely with a crawl command.
To do this, write a function with this prototype and behavior. You'll probably find it a good idea to work by modifying draw_shape(), which is similar to what you need, rather than starting from scratch.
void draw_shape_recursive(int detail, float &x, float &y, float &angle);
This function is given a level of detail. Before it does anything else it checks the level of detail: if detail is 1, it executes a crawl:
do_command('c', x, y, angle, saved_x, saved_y, saved_angle);
and returns. That's how the turtle handles any detail level 1 request, regardless of what the shape is.
If detail is greater than one, draw_shape_recursive() executes these calls:
draw_shape_recursive(detail-1, x, y, angle); do_command('m', x, y, angle,saved_x, saved_y, saved_angle); do_command('l', x, y, angle, saved_x, saved_y, saved_angle); draw_shape_recursive(detail-1, x, y, angle); do_command('g', x, y, angle, saved_x, saved_y, saved_angle); do_command('r', x, y, angle, saved_x, saved_y, saved_angle); draw_shape_recursive(detail-1, x, y, angle); do_command('g', x, y, angle, saved_x, saved_y, saved_angle); draw_shape_recursive(detail-1, x, y, angle);
If you compare draw_shape_recursive() with draw_shape(), you'll see that each crawl in draw_shape() has been replaced by a call to draw_shape_recursive() with a lower level of detail. That is, the shape that draw_shape_recursive() draws will be like the one drawn by draw_shape(), except that all the crawls are replaced by less detailed copies of the whole shape.
Like draw_shape(), draw_shape_recursive() needs to declare variables saved_x, saved_y, and saved_angle to pass to do_command().
Your main() program should now be just like that for Phase 3, except that it should call draw_shape_recursive() rather than draw_shape(). In that call it should ask for detail 6. This program should draw a nice symmetrical branching pattern. (You can try higher levels of detail, but the pattern will not all fit on your screen and will wrap around... interesting to watch. Note that levels of 10 or so take a while and fill in most of the screen.)
Phase 5. Interesting patterns.
You can now draw all kinds of interesting shapes. All you need to do is change the turtle command calls in draw_shape_recursive(), remembering not to mess with the check on detail, that keeps patterns from going on forever, and remembering that any calls to draw_shape_recursive() that you put into draw_shape_recursive() need to ask for detail-1.
Try this pattern, which we'll call "flags":
Set your constant A to PI/2 and constant D to 1.6 for this one.
crawl, mark, left, crawl, crawl, right, crawl, right, crawl, right, crawl, go back, crawl, crawl
In putting this into draw_shape_recursive(), replace each crawl with a call to draw_shape_recursive(detail-1,...), so that each "line" in the shape will be replaced by a less detailed version of the whole shape.
In the call to draw_shape_recursive() in your main() function, ask for detail 6 and initialize x and y to start the pattern near the lower left corner of the screen, because the pattern grows up and to the right.
Optional: Come up with your own recursive patterns. See if you can produce a version of the Koch snowflake (see e.g. http://math.rice.edu/~lanius/frac/koch.html.) You'll find it easier to produce just one side of the snowflake than the whole form, the way our program is put together, but if you are smart about what you put in main() you can get the whole thing. For Koch patterns you'll want to set A to PI/3. Let us know about any interesting pictures you create for this or other patterns, and we'll find a way to share them.
What to submit to DORA.
Give DORA your Phase 5 program for the flags pattern. DORA cannot look at the graphics your program produces, but she can check to make sure your do_command() function updates its arguments correctly, so make sure you have set your constants to the right values for the flags pattern.