Programming Turtle Graphic
It somewhat annoys me when people are teaching turtle graphic as the basis of learning computer programming. It's not that turtle graphic is bad, as I do believe that the thought process does indeed teach programming. It is that the turtle is presented as a module. A black box, if you will.
I think it's more appropriate, especially for computer programming students to learn how simple and easy it can be to write their own turtle module. Therefore, I propose that instead of turtle graphic module, it should be a turtle graphic routines. Instead of a mysterious black box, it should be a transparent wrapper.
Done as a function call, especially interested students can take a peek into the function and see exactly how easy it can be to write a useful program such as turtle graphic.
In fact, I believe that it can be done in one hour. Turtle Graphic algorithm as an "Hour of Code" challenge? Bring it on!
First, what is involved in a turtle graphic program? Well, there's turtle and there's graphic. What are the commands necessary? Forward, Left, Right, and Pen. Each of those commands are followed by a number. We'll call that Number variable. Well, well. There are only 4 commands, are they? Would you believe that they are doable in about one hour? Why not?
If you are familiar with Object Oriented methodology, those are called "methods". What about variables? There's the position of the turtle, X0 and Y0. There's the destination of the turtle, X1 and Y1. There's Angle and Distance. And there's Pen Color. Is that 8 variables total? Are you saying coding these 8 variables into 4 commands is a great challenge? I don't think so!
Of course, we're skipping a lot of things. Namely, the LOGO language. LOGO is a programming language and if you are learning LOGO, you are learning computer programming language. The first turtle graphic and LOGO make effective combination. Most people incorrectly assume that turtle graphic must involve LOGO somewhat. Yet, Turtle graphic is just the graphical component of it. As for the language, we'll use Petit Computer Smile Basic.
That's all there is to it. So, let's set the timer and begin. 1 hour is all that takes. For simplicity, I will assume that commands are given in Strings and consist of 3 characters: A letter command, followed by 2 digit number. If the string has more than 3 characters, then the program will extract the first three characters only. If the string is less than 3 characters, it will be ignored. Also, Color 0 means Pen Up, which means moving the turtle without drawing the line. And perhaps, we should expand the language so we can do GPAINT command for colorful final image. With that in mind, let's begin!
In 15 minutes, I got the initial framework already.
- ACLS:CLEAR
- @INIT
- C$="":'COMMAND STRING
- N=0:'COMMAND PARAMETER
- X0=128:Y0=96:'TURTLE POSITION
- X1=0:Y1=0:'TURTLE DESTINATION
- A=0:'ANGLE
- D=0:'DISTANCE
- C=0:'PEN COLOR
- SPSET 1,139,0,0,0,0:SPOFS 1,X0,Y0
- @MAIN
- 'CLASSIC RECTANGLE
- C$="F35R90F35R90F35R90F35R90"
- GOSUB @TURTLE
- WAIT 300
- @END
- BEEP 6
- END
- @TURTLE
- IF LEN(C$)<3 THEN RETURN
- TC$=LEFT$(C$,3)
- N=VAL(RIGHT$(TC$,2))
- TC$=LEFT$(C$,1)
- C$=RIGHT$(C$,LEN(C$)-3)
- ?TC$;" ",N;" ":WAIT 60
- GOTO @TURTLE
If you look at the code, there is nothing much there. It's simply the structure to pass on the command for the turtle. We can use IF-THEN ladder structure, or we can use ON-GOTO command structure. Depending on TC$, which contains the actual command. We can actually skip TC$, and specify LEFT$(C$,1) instead, but using that extra variable clarifies things for now.
I decided to do the easy thing first and implement Paint and Color command. Change C$ to "C01P00C02P00C03P00C04P00" for now. Also, we'll use ON-GOTO. This requires a numeric variable, so we'll do this
- TN=INSTR("FLRCP",TC$)
- ?TC$,TN,N:WAIT 60
- ON TN GOTO @TTF,@TTL,@TTR,@TTC,@TTP
- GOTO @TURTLE
- @TTF
- GOTO @TURTLE
- @TTL
- GOTO @TURTLE
- @TTR
- GOTO @TURTLE
- @TTC
- C=N:GCOLOR C
- GOTO @TURTLE
- @TTP
- IF N==0 THEN GPAINT X0,Y0,C ELSE GPAINT X0,Y0,N
- GOTO @TURTLE
Wow, that was easy! Of course, the hard part is coming up. Specifically, we're going to talk a bit about polar coordinates. We need it to draw the turtle. But first, we're going to just set the angle. That's not too difficult. All we have to do is adjust the angle variable just like we did with color. About the only thing that we have to do is to convert the angle to radians.
A bit of caution here. Mostly, we can just to "L180" to do a turn-about movement. However, since we decided to limit the numeric portion to just 2 digits, the most we can do is quarter turn. If we want to have turn-about the turtle, then we have to do it twice, with "L90L90" command.
Another thing. We want to rotate the turtle to the appropriate angle. So far, all we did was display the sprite. Now we want to set the angle and have the turtle facing in the right direction. So, there are two steps. First, we want to convert degrees to radians, and second, we want to orient the sprite accordingly.
There is a complication here. That is, computers have 0,0 coordinate on the top-left corner of the screen, unlike math with their 0,0 coordinate on the bottom-left of the screen. We'll have to fudge the math a bit, and the best way to do it is to just experiment. So, we're going to set up a new subroutine @ROTATE which will rotate the sprites according to angles. That is, given the value of A in degree, set the direction accordingly. All we have to do then, is decide which values goes which direction. I think 0 degree should point up. With that in mind, do this real quick:
- @TEST
- FOR A=0 to 360 STEP 360/12
- GOSUB @ROTATE:WAIT 60
- NEXT
- @ROTATE
- A=A%360
- SPANGLE 1,A,6
- RETURN
We discovered a weakness in our program in that the rotation is not centered on the sprite. We fix it by adding this line on the @INIT subroutine.
SPHOME 1,8,8
Then we simply add these lines to the LEFT and RIGHT portion of the code:
- @TTL
- A=A-N:'TURN LEFT
- GOSUB @ROTATE
- @TTR
- A=A+N:'TURN RIGHT
- GOSUB @ROTATE
And there you go! Just one more function and that's all there is to it! Who says turtle graphic programming is hard? Haha, joking aside, we have been delaying the inevitable. Not only we have to convert degree to radians, but we also have to make sure that the turtle is facing the right way. Fortunately, there are functions RAD(),SIN() and COS() that makes it simple. There is something else that we have to worry about, but we'll defer that until later. For now, let's draw!
- @TEST
- FOR A=0 to 360 STEP 360/12
- SPOFS 1,128,96:GOSUB @ROTATE
- X1=50*SIN(RAD(A)):'RSINTHETA
- Y1=50*COS(RAD(A)):'RCOSTHETA
- SPOFS 1,128+X1,96+Y1,50:'50 IS THE DISTANCE
- WAIT 60
- NEXT
If you run this code, you will see that the whole thing breaks up. You see that the sprite and the movement do not match. We want to see the sprites to move in a clock-wise pattern. The problem now is how to fix it? Remember that the coordinate is centered at top-left? That's the problem! To the math savvy among you, it may interest you to come up with the solution yourself. Otherwise, here's the fix!
Y1=-50*COS(RAD(A)):'RCOSTHETA
Yes, it's that easy! Simply turn R into negative R, and that's all there is to it. Really. Now, all we have to do is do the @TTF subroutine and we'll be done!
- @TTF
- D=N
- X1=D*SIN(RAD(A))
- Y1=(-1*D)*COS(RAD(A))
- SPOFS 1,X0+X1,Y0+Y1,D
- @TTF1
- SPREAD(1),TTFX,TTFY:'GOSUB @WRAP
- IF C!=0 THEN GPSET TTFX,TTFY,C
- IF SPCHK(1)!=0 GOTO @TTF1
- X0=X0+X1:Y0=Y0+Y1
And that's it! You're probably wondering why we're doing interval D on SPOFS. The reason for it is so that we can retrieve sprite coordinates TTFX,TTFY along the way, and plot the points with color C. That's a pretty neat trick. Otherwise, we'd have to mess around with line drawing algorithm. Not that coming up with line drawing algorithm is bad, just that it's an unnecessary complexity if all that we want is a simple subroutine.
Oh, one more thing: Suppose we want to do a wrap around? What should we do? Well, I don't like to mess with the current code, but basically, what we want to do is adjust the sprite location and update the target accordingly. So, we need to check whether TTFX or TTFY is below zero and if so, shift everything by the screenful. Trivial, had we did our own line drawing algorithm. Not so much if we're dependent upon internal routines, just like we did.
What exactly do we need to implement coordinate wrapping, anyway? Well, we need the start and stop coordinates, which are X0,y0 and X1,Y1. We also need a coordinate that specifies the current location going there. And we want to be able to reset the coordinates from the sprites.
So, we can do a bunch of calculation, but basically, we want to handle these 2 lines:
SPOFS 1,NEWX,NEWY
SPOFS 1,ADJX,ADJY,TIMELEFT
So, we need 5 variables to be set. To the math savvy among you, I suggest that you work it out yourself.
Time left is easy. We have already have D. For the new D, all we have to do is calculate the distance of the current position with the target coordinate. TIMELEFT=SQR((X1-TTFX)^2+(Y1-TTFY)^2) The old distance formula standby. NEWX and NEWY is simply OLDX=OLDX+256 and OLDY=OLDy+192. Basically, screen width and height. Same thing with ADJX and ADJY.
There's nothing to it, is there? We need to add WRAP=1 and replace the commented 'GOSUB @WRAP with IF WRAP THEN GOSUB @WRAP.
Before we do a bunch of complicated math, though, I'll point out to you something very interesting. The fact that there is only one GPSET makes it easy. If we don't care about drawing the point where the turtle is, and just worry about wrap, we can easily modify the line to this:
- IF C!=0 THEN GPSET ((TTFX+256)%256),((TTFY+192)%192),C
- IF SPCHK(1)!=0 GOTO @TTF1
- X0=((X0+X1+256)%256):Y0=((Y0+Y1+192)%192)
- SPOFS 1,XO,YO
That's something to think about. Do we want to write some complicated routines or do we just want to get the job done? With that in mind, instead of doing a bunch of mathematics, we can simply create a secondary sprite and put it into the current point set whenever the original turtle is out of bound! Simple, yet effective!
- @WRAP
- SPOFS 2,((TTFX+256)%256),((TTFY+192)%192)
- SPANGLE 2,A,0
- RETURN
And that's all there is to it! We can easily implement this within one hour. With wrap function, we're pushing it into the hour, depending on how fast you can type with the stylus. Regardless, we're only typing less than 100 lines program, with testing subroutine. What's the test? Well, remember that we're limiting the data to only 2 digits? That helps when we're doing wrapping test cases. But how about when we want to do large drawings? The test subroutine is used throughout the development process in order to try out subroutines outside the main program.
I commented out the test subroutine in the final program, but it's a good example on how you can use it in your program, so I'm leaving it there. The total time that it takes to do this program is:
15 min Initial Framework
10 min C and P command
10 min Rotation
15 min TTF drawing
10 min Wrap option
Total Development time? 1 hour, including screen wrap function. Now that's what I call "Hour of Code"!
- REM TURTLE GRAPHIC
- REM BY HARRY HARDJONO
- REM DEC 2013
- ACLS:CLEAR
- @INIT
- WRAP=1
- C$="":'COMMAND STRING
- N=0:'COMMAND PARAMETER
- X0=128:Y0=96:'TURTLE POSITION
- X1=0:Y1=0:'TURTLE DESTINATION
- A=0:'ANGLE
- D=0:'DISTANCE
- C=0:'PEN COLOR
- SPSET 1,139,0,0,0,0:SPOFS 1,X0,Y0
- SPHOME 1,8,8
- SPSET 2,139,0,0,0,0:SPOFS 1,-255,-255
- SPHOME 2,8,8
- @MAIN
- 'GOSUB @TEST
- 'CLASSIC RECTANGLE
- FOR I=1 TO 8
- C$="F35R90F35R90F35R90F35R90"
- GOSUB @TURTLE
- NEXT
- @END
- BEEP 6:WAIT 600
- END
- @TURTLE
- IF LEN(C$)<3 THEN RETURN
- TC$=LEFT$(C$,3)
- N=VAL(RIGHT$(TC$,2))
- TC$=LEFT$(C$,1)
- C$=RIGHT$(C$,LEN(C$)-3)
- TN=INSTR("FLRCP",TC$)
- ON TN GOTO @TTF,@TTL,@TTR,@TTC,@TTP
- GOTO @TURTLE
- @TTF
- D=N
- X1=D*SIN(RAD(A))
- Y1=(-1*D)*COS(RAD(A))
- SPOFS 1,X0+X1,Y0+Y1,D
- @TTF1
- SPREAD(1),TTFX,TTFY:IF WRAP THEN GOSUB @WRAP
- IF C!=0 AND WRAP==1 THEN GPSET ((TTFX+256)%256),((TTFY+192)%102),C
- IF C!=0 AND WRAP==0 THEN GPSET TTFX,TTFY,C
- IF SPCHK(1)!=0 GOTO @TTF1
- IF WRAP==1 THEN X0=((X0+X1+256)%256:Y0=((Y0+Y1+192)%192):SPOFS1,X0,Y0
- IF WRAP==0 THEN X0=X0+X1:Y0=Y0+Y1
- GOTO @TURTLE
- @TTL
- A=A-N:'TURN LEFT
- GOSUB @ROTATE
- GOTO @TURTLE
- @TTR
- A=A+N:'TURN RIGHT
- GOSUB @ROTATE
- GOTO @TURTLE
- @TTC
- C=N:GCOLOR C
- GOTO @TURTLE
- @TTP
- IF N==0 THEN GPAINT X0,Y0,C ELSE GPAINT X0,Y0,N
- GOTO @TURTLE
- @TEST
- WRAP=1:'0 OR 1
- FOR SP=0 TO 150 STEP 3
- V=SP:LOCATE 0,0:?SP
- @LINER
- IF V>99 THEN C$="F99":GOSUB @TURTLE:V=V-99:GOTO @LINER
- L1$=RIGHT$(("000"+STR$(V)),2)
- C$="F"+L1$+"R70C"+L1$:GOSUB @TURTLE
- NEXT
- RETURN
- @ROTATE
- IF A<0 THEN A=A+360:GOTO @ROTATE
- A=A%360:SPANGLE 1,A,6
- RETURN
- @WRAP
- SPOFS 2,((TTFX+256)%256),((TTFY+192)%192)
- SPANGLE 2,A,0
- RETURN
No comments:
Post a Comment