A racy tutorial
In the August issue of ANTIC, the game "Maze Maniac" made use of one of the Atari's many special features, "page flipping." Page flipping is a powerful technique. It flashes a complete picture on the screen in an instant; the viewer does not have to wait while the picture is drawn.
In Maze Maniac, the player negotiates a maze shown on the screen, but each time he moves off the edge of the screen another maze appears instantly. While the accompanying article touched briefly on how this was done, the purpose of this article is to explain through example the mechanics of page flipping.
Why bother? The answer is speed. In the maze game, you change screens every few seconds. If you had to wait for each new screen to be drawn, you would soon tire of the game. With page flipping, a complete new screen is displayed in a split second. Full-screen animation is possible by making each picture just a little different from the one before. Flash them on the screen fast enough and the changes look continuous, ,like a movie. "Attack on the Death Star" (ANTIC, June 1982) works this way, and so does "Road Race," at the end of this article. If you plan carefully, your last frame can feed into the first, producing a continuous loop of animation. Full-screen animation is the most powerful use of page flipping.
Before we get into the nitty-gritty of page flipping, there are several important memory locations you should know about.
Memory locations 560 and 561 contain the address of the display lists in lo-byte/hi-byte form. This tells the Atari how to display a picture on the screen. To find the actual display list, use the equation:
DLIST = PEEK(560)+ 256*PEEK(561)
Once you have found the display list, its fifth and sixth bytes (DLIST + 4 and DLIST + 5) contain the address of the chunk of memory to put on the screen. We will talk about modifying these memory locations later.
Memory locations 88 and 89 contain the address at which the Atari "thinks" the screen begins. This address is used in executing text and graphics commands to print and draw on the screen. While the contents of DLIST + 4 and DLIST+5 are generally the same as the contents of 88 and 89 respectively, they do not have to be. You can change the values in 88 and 89 (write memory) from BASIC.
The concept of page flipping is really quite simple. First, you draw all your screen images during the initialization stage at various places in memory. To draw a picture other than on the screen, you merely adjust the values in locations 88 and 89 to point to the section of memory where you want the picture. Then execute your PLOTs and DRAWTOs. You won't actually see the picture being drawn, but it will be there in memory. When all the pictures are done and you are ready to use them, you must put them on the screen. This is done by adjusting the values in DLIST + 4 and DLIST + 5 to be the address where the picture is drawn (generally the same values as those you used in 88,89 when drawing the picture).
Let's take a short example. First, let's find a place to put our picture. We'll use the old standby, stepping back RAMTOP (location 106) to fool the computer into thinking we have less memory:
10 RT = PEEK(106)-12:POKE 106,RT:CRAPHICS 0:DIM A$(1)
One "page" of memory equals 256 bytes. Since we have stepped back 12 pages, or 3K, and each GR.0 screen takes up about 1K, we now have room to draw three GR.O pictures, We'll fill Screen 1 with asterisks:
20 POKE 88,0:POKE 89,RT:A$+"*":GOSUB 150
Note how we pointed 88 (lo-byte, which happens to be zero here) and 89 to the address of our first screen. Note also that the screen we are looking at didn't change while this was being executed. Now we'll fill Screens 2 and 3 with other characters:
30 POKE 88,0:POKE 89,RT + 4:A$ = "A":GOSUB 150
40 POKE 88,0:POKE 89,RT + 8:A$ ="M":GOSUB 150
To find the display list:
50 DLIST = PEEK(560) + 256:tPEEK(561)
To print a message on the screen, we'll need to bring 88 and 89 back to point at the current screen you're looking at:
60 POKE 88,PEEK(DLIST + 4):POKE 89,PEEK (DLIST + 5):POSITION 0,0:? "PRESS FIRE"
70 IF STRIG(O)=1 THEN 70
80 POKE DLIST + 4,0:POKE DLIST + S,RT
90 FOR REPEATS = 100 TO 1 STEP-1
100 POKE DLIST + S,RT:FOR TIME = REPEATS TO 0 STEP -1:NEXT TIME
110 POKE DLIST + 5,RT + 4:FOR TIME = REPEATS TO 0 STEP-1:NEXT TIME
120 POKE DLIST + 5,RT + 8:FOR TIME = REPEATS TO 0 STEP-1:NEXT TIME
130 NEXT REPEATS
140 GRAPHICS 0:STOP
The little routine from 80 to 130 flashes each picture on the screen successively, faster and faster, to show you how fast page flipping can be. Note that we only adjust the LMS hi-byte (DLIST + 5) because the lo-byte has been set to zero (how convenient) for all of the screens. Finally, we have the small routine to put the characters on the screen:
150 FOR X=0 TO 38
160 FOR Y=O TO 23
170 POSITION X,Y:PRINT A$;
180 NEXT Y:NEXT X:RETURN
As you can see, page flipping can move images around fast. There are some limits to page flipping, and we'll take those up now. First, the more pictures you store, the more memory must be reserved for the pictures. GR.0 and GR.5 screens each use about 1K of memory per picture. The redefined character sets use relatively little memory, yet give high resolution. Of course, you'll have to construct your pictures from redefined characters, and that can be tedious. GR.7 (3.5K per picture) and GR.8 (8K) require to much memory to be practical for page flipping.
Also, since you are looking at successive pages of memory, you really can't modify your animation much during a program. However, page flipping is ideal for making a moving background, relegating variable motion to Player/Missile graphics, which doesn't affect what is "underneath" it.
Finally, when page flipping is executed from BASIC, the illusion of speed can be lost because the speed isn't constant - the frequency of flipping (and thus its apparent speed) depends on how many other things are happening. For example, as the number of statements executed in each loop varies due to IF/THEN or FOR/NEXT statements, the frequency of page flipping also varies. This can be fixed by using interrupts, which free BASIC from the page flipping business altogether.
You've probably heard about vertical blank interrupts, and how you can put your own program into the VBI so that it will execute every sixtieth of a second, without affecting BASIC. The system timers work similarly, but are somewhat more flexible than VBIs. We are going to develop a page-flipping routine on the system-timer interrupt which will run fast and smooth and which you can adjust from a running program.
To install your own machine-language routine in System Timer 2, which is available for your use, you need only place the address of your routine in the System Timer 2 vectar: $0228 (lo-byte, 552 decimal) and $0229 (hi-byte, 553 decimal). Next, you place a value (in jiffes, or sixtieths of a second) into the countdown register, $021A (decimal 538). Every sixtieth of a second, the countdown register is decremented. When it reaches zero, the Atari vectors to your roufine and executes it. Your routine should finish by placing a new value in the countdown register. A zero will prevent your routine from being executed.
I have included the assembly-language routine for your study; you do not need to type it in to play Roadrace, as these instructions are already included in the BASIC listing DATA statements (lines 5060-5080). The advantage in using the countdown timer is that you can decide how often you want your routine to execute, rather than executed with every VBI, which is often too fast for page flipping. If you don't believe me, change SPD in line 5340 to:
SPD = 100
Pretty fast, huh! The routine is executing with every VBI, and is a flickering blur. In the program, the name of the memory location that contains the value to be put in the countdown timer (1571) is SPEED. By adjusting the value of SPEED, you can make the roadway move at various constant speeds that are independent of BASIC.
To use this routine in your own programs, make the following adjustments:
1) Replace the 4 in line 5070 with the number of pages that each picture occupies. Note that this routine only adjusts the hi-byte, so each picture automatically starts on a page boundary.
2) Replace the 12 in line 5070 with the number of pictures multiplied by the number in (1) above.
3) Find the address of your first screen, as in line 5040, and insert it (hi-byte only) into the system-timer routine, as in line 5410.
The game I use to demonstrate page flipping Is called Roadrace. To play, type it in (or take advantage of the copy offer) and run TYPO to eliminate the errors. Plug your joystick into Port 1 and RUN the program.
You control the race car at the bottom of the screen. Moving your stick left and right moves the car; pushing the stick forward increases your speed, while pulling it back slows you down. The object is to pass as many cars as possible. The score you get for passing each car depends on how fast you are going when you pass. Passing cars is difficult, because they tend to weave back and forth across the two-lane highway. Your score, current speed and the time elapsed are shown at the bottom of the screen.
Roadrace is an endurance race. The time elapsed is shown in hours, and at the end of each 24-hour period you must have achieved a certain score or the game is over. As in real life, the game is also over if you crash into another car. Getting more than the necessary score allows you to race for another day. Extra touches, such as the sky darkening and the moon rising at night, lend added realism.
By the way, those of you who do not wish to type this program in may send me a disk (no cassettes, please) along with a stamped, self-addressed return mailer (or address label and postage, and I'll reuse your mailer) and $3.00 for a copy. Or, if you prefer, simply send $7.00 and I'll use my own disk and mailer. Send to:
488 Walnut Ave.
Walnut Creek, CA 94598
No phone calls, please!
10 Initialize variables, set up screens.
100 Read joystick and set flip speed and sound.
110 Print the speed, and decide how far each enemy car will move down the screen based on your speed. Reset the difficulty level at 1000 points.
120 Test to see if Car 1 is already on the road (CAR1<>0).
130 Car 1 is not on the road, so decide whether to put Car 1 on the road. Note that once your score exceeds 1000 points (DIF=1), you'll always put Car 1 on the road.
140 Choose a dark random color for Car 1, and decide which side of the road(lane) to start the car.
150 Set the X and Y coordinates (C1X and C1Y) based on line 140.
160 Place car 1 on the screen and skip over the Car 1 move sequence.
170 Adjust the Y coordinates of Car 1 and test to see if it has gone off the bottom of the screen. If it has, erase it, bump up the score and turn off the "weave" flag (FLAG1).
175 Decide whether to weave Car 1 accross the road. Reverse Car1 to have the car change lanes, and turn on the weave flag.
180 We are not going to weave, so adjust the X coordinates based on Y coordinates, so the car follows the edges of the road.
185 The car weaves. Adjust the X coordinate (MX1) to change lanes and test to see if you hit the opposite edge while moving right. If you did, stop the weave and adjust the base X coordinate (C1X) to be on the other side of the road.
186 Test to see if you hit the edge of the road while moving left. If you did, stop the weave and adjust the base X coordinate to the left side of the road.
190 Draw Car 1 on the screen. The third argument in the USR call decides what image to draw: the cars get larger and more detailed as they get closer (C1Y gets larger).
200-280 Essentially the same as 120-190 for Car 2.
285 Test to see if another hour of racing has elapsed.
290 Print the score and test for a collision.
300-390 Collision routine.
400 Test to see what time it is. If it's evening or dawn, turn the sky dark blue.
410 If it is late evening or early morning, turn the sky very dark blue.
420 Night. Turn the sky black and bring out the moon.
425 End of one day of racing. Make sky original color.
430 Print the time.
440 Test to see if another day has gone by.
450 Reset the timer to zero each day. If a sufficient score has been achieved, play for another day.
460 Erase all cars, stop page flipping and jump to "End of Game" routine.
5000 Step back RAMTOP to reserve memory for pictures. Set up area for cars (players).
5010 Clear memory by setting memory locations 88 and 89 to the beginning of the memory and doing a "clear screen" (CHR$(125)).
5020 Set RAMTOP and dimension D$ for picture drawing and JUMP$ to hold a machine0language subroutine.
5030 Find the display list, and the addresses of hi- and lo-bytes.
5040 Break the LMS hi-byte address into hi- and lo-bytes.
5050 Read in the machine code for the page flip routine.
5090 Set up the memory locations needed.
5108 The machine-language routine to instantly draw each screen from the data in lines 2000 to 20208. From ARTWORX's "DRAWPIC."
5120-5140 Redifine D$, set the area of memory to draw in (DAT) and execute the machine-language program from line 5108.
5150 Store the routine to move your car on the immeadiate VBI. The three-color car is two players overlaid for three colors.
5210 Store machine-language routine in JUMP$ to move the enemy cars on, around, and off the screen.
5240 Store the player shapes in the empty space at the beginning of P/M memory.
5300 INstall immediate VBI player move routine.
5310-5320 Set colors, priorities, and widths.
5330 Put your car on the screen, set priority to get three colors (POKE 623,33).
5340 Modify the display list to customize the text window.
5350 Print the necessary quantities in the text window.
5360 Racing car sound.
5400 Initialize the INDEX and PTR memory locations.
5410 Stuff the lo- and hi-bytes of the LMS hi-byte address into the pageflipping routine.
5420 Point to the first picture and start the system timer.