Programming a Game
In this chapter I'll show you how to use the techniques you've learned to write an arcade-style game. It doesn't have the speed of a machine language program, but thanks to Atari's PMG it contains a lot of action-packed features: animated players, sound effects, missiles, lasers, collision detection, and flashing colors. And best of all, since you've come this far, you'll be able to modify it to your liking. Before you know it, you'll be creating your own original games!
Let's call the game "Mazeduel" since two players will be fighting it out as they chase each other around a maze. I'll go over the rules of the game first and then discuss the programming techniques.
Winning a round. The game will have rounds (sort of like a boxing match). In the upper right corner of the maze you'll see a magic crystal. If a player touches the crystal, an alarm will sound as the screen flashes different colors. The player who touches the crystal wins the round and is awarded five points. Both players will then be moved back to the starting box.
A player can also win a round by hitting the crystal with a missile. In this case, however, that player gets only one point.
Firing missiles. Players can fire missiles at each other. Missiles contain a strange substance that causes players to instantly expand to double size if they are hit. If a player expands to double size, he stays that way until the other player gets zapped.
If you are hit by a missile you will be zapped back to the starting box and enlarged to double size. You get one point for hitting another player with a missile.
Maze walls. You must exercise extreme caution when moving around the maze. If you touch a maze wall, you are also zapped back to the starting box and enlarged to double size. In addition, the other player will gain a point.
Beware of the crystal. The crystal is harmless--unless you try to win a round by touching it. When the crystal's defense system is activated, it may send out a storm of laser-type missiles. If you are quick enough though, you may be able to avoid the lasers. But if you don't, you will again be expanded to double size and zapped back to the starting box. Another penalty is that the other player will gain one point. The lasers destroy any part of the maze that they hit, but only stun players. As the maze starts to break up more and more, you will find it easier to sneak up on the crystal.
It's possible for a player to escape from the maze and sneak up on the crystal while "off screen." (I won't tell you how.) Be alert when hiding behind maze walls. Missiles fired by players sometimes penetrate walls! To fire a missile, move the player in the direction you want the missile to go and press the fire button. The first player to get ten or more points wins. You can start a new game by pressing your fire button.
Selecting colors. At the beginning of each game, you can select the color of the playfield by pressing the SELECT key. Just keep pressing it (or hold it down) until you get a color you like. When you're ready to start the game, press the START key.
To select colors for players, press the OPTION key.
As usual, the complete listing appears at the end of this chapter. You may wish to load the program from the previous chapter. MISSCOL.SAV, and then modify it to produce MAZEDUEL.SAV. I've made a lot of modifications, however, and it may be just as easy to type it in from the start.
For maximum execution speed, don't type in any of the REM statements. I included them to help you understand the program (and to help myself keep track of what I was doing).
Again, the subroutine beginning at line 2000 takes care of calling the various setup subroutines. Here are the setup subroutines along with their beginning line numbers:
10000 Initialize Constants
12000 Draw Playfield
11000 Set up PMG
13000 Specify player colors and initial position (off screen)
5000 Allow users to select colors
Note that line 10000 is no longer a REM statement. If it were, the GOSUB 10000 statement at line 2000 would produce an error if you were to delete the REM statements.
In the 13000 subroutine, at line 1345, I poke PLAYER1$ with the data in LEG1$. Remember, PLAYER1$ is the PM memory area for our second player. PLAYER0$ is the memory area for our first player. To simplify the programming, I made both players have exactly the same image. (They are easy to tell apart, because each has its own color.)
Notice that in the subroutine starting at line 5000. I move the players onto the screen and display a message in the text window. Location 53279 tells me which of the console keys have been pressed. When 53279 contains a 6, I know the user has pressed the START key and it's time to return from this subroutine.
Control then returns to the "executive" setup subroutine at line 2000. I call the subroutine at line 2000 an "executive" because it controls other subroutines rather than carrying out any direct action of its own. After the last subroutine within the executive setup subroutine, control returns to line 1. From there we jump to the main loop at line 200.
CHECKING FOR COLLISIONS
Lines 200 and 201 now check for the various collisions. Notice how easy this is. We simply call various subroutines. The subroutine called depends on the contents of the appropriate collision register. For example, look at the first statement in line 200:
- If the player 0 to playfield collision register (P0PF) contains a zero, meaning no collision, which subroutine will be executed?
So at line 100 we put a RETURN statement. Now, the magic crystal was drawn using COLOR 2. So the magic crystal is considered a "COLOR 2 playfield."
- Suppose player 0 touches the crystal. Which subroutine will be executed?
The other collision detection subroutine calls work the same way. Slick, huh? And much faster than IF-statements!*
*I'd like to thank my son. Dan Seyer, for suggesting this collision detection method.
At line 204 we check to see if joystick 0 has been moved. J0 is now a variable initialized to 632, the memory location that contains the value for joystick 0. If J0 is not equal to 15 (upright joystick), then I add 1 to Z. Z will now be equal to 1. At this point, I GOSUB line 35 (34+Z will equal 35). Line 35 moves LEG1$ data into PLAYER0$. The next time through the main loop, line 204 will call the subroutine at line 36 (since Z will now be equal to 2).
This is similar to the earlier leg movement routine. I modified the earlier routine to make it easier to handle the movement of two separate players.
If the joystick is not moved, then control will pass to line 205. At line 205 I simply move the "standing still" image of player 0 into PLAYER0$. I do a similar thing for player 1 at lines 206 and 207.
CRYSTAL DEFENSE SYSTEM
Control now passes to the crystal defense system at line 210. Notice that I am using the random number generator at location 53770. (RANDOM is initialized to 53770.) If RANDOM contains a number greater than 220 then we check to see if either of the players has entered the crystal's attack zone. We know a player is in the crystal's attack zone if its X coordinate is greater than 142 and its Y coordinate is less than 34. During game development, you can easily discover the coordinates for a specific location by positioning a player where you want him, hitting the break key, and then printing the coordinates.
Why the check of the random number? Well, this way the crystal's defense system isn't perfect. Sometimes a player will be able to sneak past it and reach the crystal before it starts wildly firing lasers in all directions. By changing 220 to some other number, you can increase or decrease the probability that a player will be able to sneak in and touch the crystal. The higher the number (up to 255) the better the player's chances will be. (By the way, location 53770 generates a random number between 0 and 255.)
If all conditions are satisfied, then the laser firing routine is activated starting at line 450. This is a fairly simple routine, but the results are quite dramatic. In this routine I use DRAWTO statements to create the lasers. A nice effect is produced by drawing the various random vertical coordinates. Again I used location 53770 to set a variable called VT to various values. VT is then used in the DRAWTO statement as the vertical coordinate. Notice that I use COLOR 1 when drawing the laser. The collision detection routine is already set up to detect a collision with anything drawn with COLOR 1. So if the laser hits the player, zapo-whamo! After drawing a laser, I erase it by redrawing it using COLOR 0.
After the crystal defense check at line 210, control passes on to the missile move routine at 300.
MISSILE MOVE ROUTINE
This routine is quite similar to the one in the previous chapter, except that now we have to deal with two missiles. This can present a problem when two missiles are both fired at the same time. The binary image data for missile 0 is:
00000011 (or 3 in decimal)
For missile 1, the binary data is:
00001100 (or 12 in decimal)
Suppose both missiles happen to occupy the same byte in missile memory. (This will happen whenever MY0=MY1.)
- What if we move 00000011 into byte 22 of MISSILE$ and then immediately move 00001100 into that same byte of MISSILE$? What will be the effect on missile 0?
Look again at the binary data:
Missile 0: 00000011
Missile 1: 00001100
This can be solved with a machine language subroutine that addresses specific bits. That is, if we wanted to turn on missile 0, we would turn on only the two bits on the far right. But we would not move zeros into the other bits.
In BASIC we cannot address specific bits. That is, we cannot move data only into certain bits, but not others. In BASIC we must deal in bytes.
My solution to the problem was to "turn on" both missiles whenever both missiles happened to occupy the same byte in missile memory. I did this by initializing a variable called BMISS$ to 15. Then at line 335 I check to see if MX0=MX1. If MX0=MX1, I jump to line 400 where I move 15 into MISSILE$.
- Why does putting a decimal 15 into MISSILES$ turn on both missiles$?
Now if MY0 does not equal MY1, the missiles are not destined to occupy the same byte in missile memory. Consequently, I move the missile data for each missile with separate statements in line 340.
Yes, the IF-statements do slow things down. You're right if you're thinking that this is a good place for a machine language subroutine. (Look for it in my next book on PMG)
After the missile data is moved into the proper byte of MISSILE$, control returns to line 200. the first line of the main routine.
TYPING IN THE PROGRAM
The program listing appears at the end of this chapter. It may look long, but remember, a lot of it is a duplication of the code from the previous chapter.
As I mentioned earlier, it's probably a good idea to omit all REM statements when typing in the program. It will require less memory this way and run faster. The program will fit into a 16K cassette system or a disk drive system with 24K.
Since this program was designed for instructional purposes, more line numbers are used than actually needed. You can speed up the program somewhat by combining some lines. For example, line 330 could be combined with 335. But be careful in doing this. For example, don't try to combine two IF-statements. If you do, then the second IF-statement won't execute unless the first condition is true. For best results, I suggest you type in the program exactly as is. Then after you've saved a working copy, you can do your thing! Have fun. Here are some revision suggestions:
- Make the crystal more interesting by creating him with PLAYER2$. You can put PLAYER2$ right on top of (or underneath) a playfield.
- You could create a three-colored crystal by combining PLAYER2$ with PLAYER3$ (see the next chapter for details).
- If you make the crystal with PMG, then you can make it glow by poking different values into the appropriate color registers. You could also easily animate it or change its size, with just a few statements. If you do this at line 450 (the laser firing routine), you won't slow down the main loop. That's because line 450 executes only when a laser is fired by the crystal.
- Using the other players, you might want to have various alien creatures pop up in the maze and block the movement of the main two players.
- You may wish to experiment with changing the players to different sizes at different times. Here are the size registers:
To set a player's size, poke the appropriate register with 0,1,2, or 3 as follows:
Value to Poke
0 or 2
So for normal player size, poke the appropriate register with zero or 2. For double player size, poke it with 1. For quadruple player size, poke it with 3.
- For faster action, you might want to try your hand at converting part of the program to machine language. MAZEDUEL is written entirely in BASIC. Again, look for my next book on PMG for hints on how to do this! In the meantime, I suggest you keep reading your favorite magazine for ideas. My favorites (in alphabetical order) are: Analog, Antic, Byte, Compute, Creative Computing, and Micro.
Congratulations. You've just about finished this book. By now, you've mastered most of the fundamentals of PMG--one of the most powerful and least understood features of the Atari computer.
In the next chapter, I'll wrap things up (at least for now), by discussing a few additional PMG odds and ends.
Return to Table of Contents | Previous Chapter | Next Chapter