|
BFOIT
Introduction Multiple Turtles and Animation |
|
In this lesson, you will learn
We will write a program that consists of a cannon, a few (reusable) projectiles, and a flying saucer - each implemented with its own turtle. As you will see, the graphics is easy, getting them to work together in our simulated environment is the hard part.
| SpaceWar Applet |
Use the left- and right-arrow keys to move the cannon (the equilateral triangle) and the up-arrow key to fire.
If you are having trouble, review the animation part of the lesson on iteration.
Responding to Keyboard Events - the keyPressed Procedure
Similar to the way mouseClicked
events are available to you, the TurtleGraphics applet and application
receive events when a key on the keyboard is pressed. If you
enter a procedure with the name keyPressed that has one input (a number),
it will be performed when a key is pressed.
Use the TurtleGraphics application (TG) or go back up to the applet and type in the following definition of keyPressed.
to keyPressed :num
if equal? :num 129 [ println "left-arrow stop ]
if equal? :num 130 [ println "right-arrow stop ]
println :num
end
Once you have this entered, remember to click the mouse in the graphics
window, and then try out the left- and right-arrow keys. What happens
when you press the up- and down-arrow keys?
Now, write a keyPressed procedure that moves the triangle left 5 or 10 turtlesteps when the left-arrow is pressed. Once you have this working, enhance keyPressed to move the triangle to the right each time the right-arrow key is pressed.
Finally, add an invocation of the tracer procedure you wrote earlier in this lesson. Change keyPressed so that when the up-arrow key is pressed, a projectile appears to come from the tip of your triangle and travels off the top of the graphics window.
repeat quotient :distance 10 [ fd 10 wait 200 setc 7 bk 10 fd 10 setc 4 ]
Not until
What we need is multiple turtles that each do things independently.
The syntax of the newturtle and talkto commands is:
| newturtle | <name> | <List-of-Instructions> |
| talkto | <name> | <List-of-Instructions> |
And, the syntax of a <List-of-Instructions> is the same as for the repeat and if commands. To refresh your memory:
| [ | <Instructions> | ] |
Use the TurtleGraphics application (TG) or go back up to the applet and type in the following example which demonstrates the use of the newturtle command.
to c1
repeat 72 [ fd 10 rt 10 wait 1800 ]
end
to c2
repeat 108 [ fd 10 lt 10 wait 900 ]
end
to c3
repeat 216 [ fd 10 rt 10 wait 600 ]
end
newturtle "t2
setc 1
newturtle "t3
setc 4 seth 90
talkto "t1 [c1]
talkto "t2 [c2]
c3
What the above does is
So, you end up with three turtles going round in circles. When they all stop, you can tell them to draw the circles again by typing
clean
talkto "t1 [ c1 ]
talkto "t2 [ c2 ]
c3
If you tried:
talkto "p1 [ tracer xcor ycor ]
it didn't work! No matter where the cannon (the triangle) was moved
to, the first projectile would start at 0,0. The reason is that the
xcor and ycor operations return the current location of the
current turtle - the projectile turtle ("p1") - NOT the turtle that's the
cannon ("t1").
What you need to do is use global variables to pass information from one turtle to another. Here is what I did in my fireCannon procedure (my keyevent procedure invokes fireCannon when it gets an up-arrow event, see Figure 13.1).
to fireCannon
make "p1x xcor
make "p1y sum ycor 20
talkto "p1 [ tracer :p1x :p1y ]
end
What this does is to put the coordinates of the tip of the triangle into
a couple of global variables, named p1x and p1y. The
turtle performing the fireCannon is the same one that receives the
key events and animates the triangle (the cannon). It then tells the
projectile-1 turtle ("p1") to invoke tracer with references
to the contents of these global variables.
Just for your information, the first thing I always do in the source code for my programs is to declare all global variables. By doing this, it is obvious to anyone reading my programs what global variables the programs contain. Following the global variables, I declare/create the additional turtles I am going to use. Here's the new code:
make "p1x 0 ;projectile 1's starting x postion make "p1y 0 ; and its starting y position newturtle "fs [hideturtle] ;flying saucer turtle to black output 0 end to white output 7 end to drawSaucer setpensize 10 penup setpencolor black repeat 12 [forward 10 pendown forward 10 penup back 20 right 30] setpensize 14 pendown forward 50 back 100 end to advanceSaucer setpensize 42 setpencolor white back 2 forward 124 back 42 drawSaucer end to saucerJourney penup setxy -300 100 pendown setheading 90 drawSaucer repeat 60 [ advanceSaucer wait 800 ] endWith this code, all you need to do is invoke saucerJourney and the saucer will traverse the graphics window. Try it out...
Got all that? If so, go and write the code. If not, keep reading, I'll go into the solution in a bit more detail.
Here's the structure of my program.
|
| Figure 13.1 |
Although it looks pretty simple, there is a lot of information in this diagram. You have everything here you need to write the program. If you are having trouble reading any of the labels in the diagram, click on it to get a full-sized version of it. Let me walk you through some of the parts/pieces.
First of all, I've put three kinds of things in the diagram. The global variables for the program are shown as boxes; there are five in the diagram labeled (left to right) p1y, p1x, hit, saucerX, and saucerY. The procedures that make up the program are drawn as labeled bubbles/clouds. I've colored the bubbles so that you can see which procedures are performed by the different turtles. Specifically, the green bubble procedures are performed by the initial turtle (t1). The blue bubble procedures are for the flying saucer turtle (fs) And, the red bubble procedures are for the projectile turtle (p1).
And then there are all of the arrows. The simple black line arrows show flow of control, i.e., procedure invocation. As with all of my programs that are GUI-based, I have a procedure named init which initializes everything. In this program, it just happens to use one of each type of arrow, so I'll use it to explain the arrows.
Checkout init; it has a solid black line arrow pointing at the bubble labeled drawCannon. This denotes init invoking drawCannon. Above this is a big yellow arrow pointing to the hit box. Flow of data into the global variables and accessing the contents of the data that is in them is shown with these big yellow arrows. In the arrow is the word false. So, in this case, init is storing false into the global variable named hit. Above the yellow line is a dashed black line pointing at the saucerJourney bubble. Dashed lines in the drawing indicate that a talkto command was used to tell the destination to do something. In this case, init told the fs turtle to perform the saucerJourney procedure.
When init completes, the program just waits for some event. In this program, it waits for a key event, which will cause the keyPressed procedure to be performed.
There is one other arrow that I want to make sure you understand. Find traceHelper, a red bubble at the bottom. The arrow pointing to it (coming from tracer) has the word "count" in it. Count is an input expected by traceHelper. The arrow I want you to understand is the one that originates at the right hand side and loops around to point back at itself. This signifies that traceHelper is a recursive procedure - it invokes itself. Here is part of the its code, enough for you to see the flow of control.
to traceHelper :count
if equal? :count 0 [ stop ]
...
traceHelper difference :count 1
end
When tracer invokes traceHelper, it specifies a number of
times it wants traceHelper to do what it does - move the projectile.
So, think of traceHelper as the instructions-list in a
repeat command.
traceHelper is the procedure that animates a projectile. After the projectile is moved forward, traceHelper has the following if command.
if and xInSaucer? xcor yInSaucer? ycor [make "hit "true stop]
This if command combines the results of two procedures which return
boolean results. If you need to review the and operator,
I first covered it in the Built-in
Operators For Combining/Manipulating Boolean Values section in the
Predicates lesson.
Figure 13.1 shows this interaction. If you look at the traceHelper bubble, you'll see it's interaction with both xInSaucer? and yInSaucer?. The invocation of xInSaucer? consists of passing it the current X coordinate, which is what the xcor primitive returns, and getting back either true or false. The invocation of yInSaucer? is similar.
So, if the coordinates of the projectile are in the saucer, the projectile has collided with the flying saucer. So, what does the if command above do in this case? It sets the global variable hit to true. Figure 13.1 shows this as a big yellow arrow with true in it leading out of traceHelper and pointing to the hit box.
The next time the flying saucer turtle looks at hit (as it performs saucerJourney), it will see that it has been hit!
Not quite... Let's take another look at fireCannon. Here's a new version of it with new code that controls a second projectile, a copy of what we did with projectile 1.
to fireCannon
make "p1x xcor
make "p1y sum ycor 20
talkto "p1 [ tracer :p1x :p1y ]
make "p2x xcor
make "p2y sum ycor 20
talkto "p2 [ tracer :p2x :p2y ]
end
What's wrong with this new version of fireCannon?
Well, everytime that it's invoked, it tells both projectile turtles to perform tracer. We need to know if a projectile turtle is already doing something. If it is, it can't be given anything else to do until it's done. And, we only want one projectile turtle to perform tracer. If we tell p1 do it, that's it, we're done.
Time for a couple more global variables, i named mine p1Avl and p2Avl. They are used to determine whether or not a projectile turtle is available.
Here's the idea:
to tracer1 :xcor :ycor ... make "p1Avl "true end
Here's the start of the new fireCannon
to fireCannon
if :p1Avl [make "p1Avl "false make "p1x xcor make "p1y sum ycor 20 talkto "p1 [tracer1 :p1x :p1y] stop]
That should do it. Add a few more projectiles to your program. Don't get too carried away, there is a limit to the number of turtles that the TurtleGraphics applet/application lets you create. I've chosen to give you 12 turtles - if you need more you'll need to get me to up this number...
NOTE: for performance reasons, you should include a cg or clean command as part of wrap-up of each flying saucer journey. This is an easy way to clean up the mess that splat makes. But, even if the flying saucer did not get hit, cg and clean free up a lot stuff the TurtleGraphics environment saves in case you re-size the graphics area.
An alternative is to turn off collection of graphics stuff with the norefresh command.
| Racquetball Applet |
Summary
Animation is easy! You were able to animate a triangle very easily.
Hopefully, interacting with keyboard events was easy for you too.
Writing a keyPressed procedure is pretty much like writing
a mouseClicked procedure.
You also learned how to create multiple turtles with the newturtle command and give them commands with the talkto command. These commands by themselves are not hard.
The difficult part of the program we wrote was figuring out how the turtles should communicate. How does one turtle know where another turtle is, or in our case where an object being drawn by the turtle is? How do you make sure a turtle available/ready to do something you want it to do?
| New jLogo Procedures Used In This Lesson | |||||||||||||||||
| Name | Input | Description | Example | ||||||||||||||
| KEYPRESSED | keyNumber |
When a keyboard key is pressed, TG receives an event.
If the key is one that TG is interested in, a user-
defined procedure with the name KEYPRESSED (expecting
one input) is invoked if it has been defined.
|
TO keyPressed :num PRINTLN :num END |
||||||||||||||
| NEWTURTLE |
name instructionList |
Creates a new turtle with the specified name and
directs future input to it.
If an optional instructionList is supplied, it is given to the new turtle to do and future input remains directed to the current turtle. |
NEWTURTLE "t2 [ HT ] | ||||||||||||||
| TALKTO |
name instructionList |
Directs future input to the specified turtle or, if the optional instructionList id provided, it is given to the specified turtle and future input remains directed to the current turtle. | TALKTO "t2 [ FD 10 ] | ||||||||||||||