Classic Computer Magazine Archive A.N.A.L.O.G. ISSUE 72 / MAY 1989 / PAGE 16

Game Design Workshop

by Craig Patchett


Fighting Back
    We're now at a point in the game where we could do a number of things, and different people will recommend doing different things. For example, we've ignored the next-level and end-of-game sections of the program so far, and some game designers will argue that these sections should be completed next. We also haven't done anything about the score yet, and we haven't given the invaders the ability to fire at the player. So what's our choice? Well, the point I'm trying to make is that it could be any of these. I personally have chosen to give the invaders some fighting spirit, so that's what we'll do. You should realize, however, that at this point in the game the order of doing things is not as important as it was before.

Firing Back at the Players
    Before we start the programming again, let's stop and take a look at the idea behind what we're going to do. To state it simply, we're going to make the invaders fire missiles at the player. No big deal, right? Not in this game, but giving the computer the ability and intelligence to fight back against the player can quickly turn into an extremely complicated task in other games. As a matter of fact, as I mentioned in the column on logic, a good part of a game's logic is usually involved with making the computer seem intelligent.
    Unfortunately, this kind of thing takes a lot of programming and, therefore, a lot of time, which is why most BASIC games are either not very intelligent or very slow. BASIC invaders falls into the "not very intelligent" category and is still somewhat on the slow side. So, if you feel the urge to devote a lot of time to designing a game that shows incredible intelligence, I would suggest either doing something where speed isn't important (such as a strategy game of some sort) or learning machine language.
    Okay, back to our game. What we'd like to do is have the invaders in the bottom row fire missiles at the player's base. We'll make it easy on the player by only allowing the invaders to fire one missile at a time and having them fire randomly. (You may like to try changing the program later so that the invader nearest to the player's base is the one that fires.) So let's jump in and make some program changes:


400 X=USR(ADR(MISCLR$),PA+7
68,255,243):POKE 1721,0:RET
URN
1100 IF PEEK(1721)<>0 THEN
GOSUB 400
1120 IF STRIG(0)=1 OR PEEK(
1700 <>0 OR PEEK(1720)<>0 O
R EF>1 THEN 1170
1170 IF PEEK(1701)<>0 OR PE
EK(1721)<>0 OR LINE+BOTROW*
2=19 THEN 1250
1180 X=USR(ADR(MISCLR$),PA+
768,255,243):X=PEEK(20)
1190 IF PEEK(20)=X THEN 1190
1200 POKE 53278,0:FC=INT(RND
(0)*8)*2:FR=BOTROW*48
1210 IF INV$(FR+FC+27,FR+FC+
27)="" OR INV$(FR+FC+27,FR+
FC+27)="
" THEN FC=FC-2+16*(
FC=0):GOTO 1210
1220 POKE 1693,FC*8+55+SCROL
L+COARSE*8:POKE 53253,PEEK(1
693):FV=BOTROW*16+LINE*8+46
1230 POKE 1697,FV:FV=FV+PA+7
68:POKE FV,PEEK(FV)+4:POKE F
V+1,PEEK(FV+1)+4
1240 POKE 1713,4:POKE 1717,1
:POKE 1781,1
5340 POKE 1700,0:POKE 1701,0
:POKE 1720,0:POKE 1721,0
5350 POKE 1704,0:POKE 1705,0
:POKE 1708,129:POKE 1709,1

    And now yet another incrdibly in-depth explanation:

400 This is our collision routine for the invader missile. For now it simply erases the missile and clears the collision flag.

1100 Here we check to see if the invader missile has collided with anything important and go off to the collision routine if it has.

1120 A simple change to this line so that it now goes to line 1170 if we aren't going to fire a player missile.

1170 Now we decide whether or not we're ready to fire an invader missile. If there's already one in the air, if an old collision hasn't been taken care of yet or if the invaders are on the last line before the base (at which point the player would have no time to react to the missile), then we skip ahead.

1180 If we are ready to fire an invader missile (i.e., if the program got past the previous line), then we can clear out Missile I and clear the system clock.

1190 We wait here until a jiffy has passed (to make sure that the missile is completely off the screen).

1200 Now we clear the collision registers and randomly choose an invader on the bottom row.

1210 The next step is to make sure that the chosen invader still exists, so we check its place in INV$. If we find a blank or an explosion, then we go one invader to the left (wrapping around to the right side if we're on the leftmost invader already) and try again. We don't leave line 1210 until a live invader has been found. Incidentally, the two funny-looking characters in this line are produced with CTRL-COMMA and inverse CTRL-M.

1220 We now have the position of our attacking invader, so we position the missile horizontally and figure out the vertical position.

1230 Here we position the missile vertically and turn it on within the PMG area. You should notice that instead of turning on the missile by POKEing two 4s into the PMG missile area, we are adding 4 to the values that are already there. Why? Remember that the player missile is in the same group of bytes as the invader missile, and there is a chance that the player missile may be in the same two bytes that we're going to be putting the invader missile into. To make sure that we don't erase the player missile, we add 4 instead of POKEing 4. What about lines 1140 and 1150, though, where we turn on the player missile by simply POKEing? Isn't it possible that we'll accidentally erase the invader missile? It is, but by the time the invader missile gets that low on the screen, we don't need to worry about it anymore. If it bothers you, however, there's no reason why you can't update lines 1140 and 1150 so that the invader missile is left intact.

1240 We tell PMOVE to watch for the invader missile colliding with Playfield 2 (the barriers) or Player 0 (the base), and then we tell PMOVE to start the missile moving.

5340-5350
These lines are part of the PMOVE initialization section, and we've changed them so that they initialize Missile I so that all the flags are clear and it's set to move upwards when we turn it on.

    Well, that wasn't too difficult, and as a result we now have the invaders firing happily away at us. Of course if you shoot away all but one invader on the bottom row, you'll notice that things tend to slow down quite a bit. Unfortunately, we can't do anything about this unless we were to introduce some more machine language. It's something you might like to try if you know machine language, but I won't do it here. This column has been designed to teach you how to program a game from BASIC, and machine language is only used when it's in a form that can easily be adapted to your own games. Such is not the case now.

Invader-Missile Collisions
    The next step is to take care of the invader-missile collision routine at line 400. Let's take care of a small problem first that you may or may not have noticed. We're using Missile 1 for the invader missile, and Missile 1 has the same color as Player 1. Well, we're using Player 1 for the alien saucer, which means that the invader missile has the same color as the alien saucer. As you can see if you run the program with the above changes, this seems to be perfectly acceptable. But try shooting down the alien saucer and see what happens. When the saucer fades out, so does the invader missile, and it doesn't return to normal until another saucer appears (which is when we restore Player 1's color). The solution to this is to restore Player 1's color after the saucer has been destroyed. To do this, just make the following change to line 220:

220 POKE 53249,0:POKE 53250
,5:POKE 705,40

    Now we're ready to take care of collisions, so go ahead and make the following changes:

400 X=USR(ADR(MISCLR$),PA+7
68,255,243):POKE 1721,0:IF
PEEK(1717)<>255 THEN 470
410 POKE 1664,255
420 X=USR(ADR(MISCLR$),PA+7
68,255,252):POKE 1700,0
430 FOR X=117 TO 250 STEP 4
:POKE 704,112+INT((250-X)/9
):NEXT X
450 POKE 53248,128:POKE 168
4,128:POKE 704,15
460 POKE 1664,0:RETURN
470 X=PEEK(1693)-47+2*(RND(
0)>0.5)-1:Y=PEEK(1697)-159:
GOTO 260

400 Instead of RETURNing at the end of this line, we now check to see whether a collision with the base has occurred. If it hasn't, then the missile must have collided with one of the barriers, so we skip ahead to line 470.

410 The missile has collided with the base, so we temporarily disconnect the base from the joystick (so that the player can't move the base while we're destroying it).

420 We also want to get rid of the player missile if it's in the air, so we clear it and turn it off.

430 Now we fade out the base in the same way that we faded out the saucer.

450-460 
We want to give the player a new base, so we position it in the middle of the screen, restore its color, reconnect it to the joystick, and then return to the main part of the program.

470 This line takes care of the missile colliding with a barrier. Since the code to do the actual explosion is already in place starting at line 260, we may as well make use of it, so all we do here is figure out where the explosion is to take place and then skip over to line 260.





Keeping Track of Player Bases

    We now have all our explosions in place, but when the player's base gets destroyed, he automatically gets a new one, regardless of how many he's already lost. As you're probably well aware, video games do not generally give you an infinite number of bases, or lives, unless the game is timed. In BASIC invaders the game ends when all your bases are destroyed or when the invaders reach the level of your base, not when your time runs out. This means that we're going to have to somehow keep track of the number of bases that we give out. These changes will do the trick:

440 BASES=BASES-1:IF BASES=
0 THEN POKE 53248,0:GOTO 20
20
4020 BASES=3

440 The variable BASES now keeps track of how many bases the player has left. Each time a base is des troyed, we subtract 1 from BASES and check to see if BASES is equal to 0, which would mean that the player has no bases remaining. If BASES is equal to 0, we move Player 0 off the screen and go off to the end-of-game routine.

4020 
The player is given three bases to begin with, although you can change this line if you'd like to start with more (or less).

How Many Bases Are Left?
    So far so good, but we need to have some way of showing the player how many bases are left. We could just print the values of BASES next to the score somewhere, but that can be a little confusing. You may recall that we included a base character in our redefined character set, and it was for this exact reason that we did so. If you make the following changes to the game, the extra bases will appear on the screen right next to the score:

450 POKE 87,1:POKE 88,SCRL:
POKE 89,SCRH:POSITION 19-BA
SES,0:? #6;" ";:POKE 53248,
128:POKE 1684,128:POKE 704,
15
5190 POKE 87,1:POKE 88,SCRL
:POKE 89,SCRH:POSITION 0,0:
? #6;" SCORE:     0    "


450 Each time we give the player a new base, we will now erase one of the extra bases from the screen. This line sets up the computer so that we can print on the score line, and then replace one of the extra base characters with a space.

5190   In case you were wondering where the extra base characters came from, this line has been modified to print two extra bases alongside the score. Just in case you've forgotten, the base character is typed in as CTRL-0.

    Now we're all set.

Keeping Score
    Now that we're keeping accurate track of extra bases and the like, we may as well keep track of the score as well. The score is an integral part of a video game, since it is what most people use to gauge how well they have played. Some people play to better their own score, some to get the best score on the machine, and there are even those who spend hours and hours playing one game, in an attempt to hold the dubious honor of being the best in the world at that game. In any case, because of all this emphasis on score, it is important to design the game so that the score is representative of how well the player has done. This is not as easy as it sounds, however, and before we add scoring to BASIC invaders, let's take a look at just what's involved.

What Changes the Score?
    The first step in designing the scoring for a game is to decide which events on the screen are going to produce a change in the score. For example, some games credit the player for shooting anything that moves, some give points for each time unit that the player remains alive, and others reward the player for avoiding objects on the screen.
    Now it's true that how things are done depends a lot on the type of game that's involved, and there's no reason why we couldn't apply all the above mentioned scoring techniques to our simple BASIC invaders. (We won't though.) How? Well, we could give points for shooting the invaders and the saucer, for destroying all the invaders in as short a time as possible, and for avoiding the invader missiles by as much room as possible.
    Trying to avoid the missiles by as much room as possible is pretty stupid, and wouldn't be something that we'd want to give points for. Destroying the invaders as quickly as possible isn't a bad idea, although we won't be using it here (you may want to try adding it to the program yourself). We will be giving points for shooting the invaders and saucer, so let's look at the different ways we can approach this seemingly simple task.

How Many Points for What?
    Once we've decided what we're going to give points for, the next question is "how many points?", and there are a few guidelines we can follow here. First of all, today's video game players are used to high scores in the 1,000s. No matter how good a game is, a player isn't likely to be too enthusiastic if he does really well and gets a high score of 357. A high score of 357,000, on the other hand, is something that he or she will feel good about, even if the only difference is three extra 0s. As a matter of fact, you'll find that most games tend to pad out their scores with extra 0s for this exact reason. As a result, you'll see high scores like 54,200, but never like 54,237. It's a silly world. Anyway, we're going to pad out our score with one 0, as you'll see later.
    For now, we still have to figure out how many points to put behind the 0s. Usually, or ideally, the points given for shooting something are representative of how difficult the object was to shoot. We have three different types of invaders on the screen, as well as an alien saucer. The invaders are the easiest to hit, so we'll give more points for hitting the saucer. The invaders vary in size from large to small, with the smaller ones being slightly more difficult to hit, so we'll give more points for hitting the smaller invaders.
    Picking some numbers now, we'll give 10 points for the large invaders, 20 for the medium, 30 for the small, and 300 points for an alien saucer. Where do these numbers come from? Wherever you want. We could have given 50, 60, 70, and 500 if we wanted, or any combination that looked good. You may not agree with the numbers I'm using, and are more than welcome to change them. The only thing that matters in scoring is that harder tasks are awarded with more points. The actual numbers themselves will only affect how high the scores will be.
    Well, with all that raving out of the way, we're now ready to make the necessary changes to our program. And here they are:

220 POKE 53249,0:POKE 53250
,5:SCORE=SCORE+30:GOTO 350
340 SCORE=SCORE+3-INT(R/96)
350 SCORE$=STR$(SCORE):POKE
 88,SCRL:POKE 89,SCRH:POKE
87,1:POSITION 12-LEN(SCORE$
),0:? :#6;SCORE$;
390 RETURN
3030 DIM MLANGS(90),INV$(57
8),DAT$(16),ROW(5),DL$(30),
SCORE$(6)
4020 8ASES=3:SCORE=0

220 When the saucer is shot down, we now add 30 to SCORE (which keeps track of the score, of course) and skip ahead to a routine that prints the updated score on the screen. In case you're wondering why we're adding 30 instead of 300, don't forget about the extra 0 that we're using to pad out the score. That 0 is permanently on the screen and is not included in SCORE. It's actually just as easy to keep the whole thing in SCORE. When you're designing a game in machine language you look at the score as a series of digits instead of a whole number. Since I do a lot of machine-language programming, I'm used to doing things this way, and so that's my excuse for doing things in a funny way here. Besides, it's a sneaky way of emphasizing the padded score.

340 You'll recall that R is equal to the row that the shot invader is in, times 48. The rows are numbered from 0 to 5, so if we divide R by 96 and take the integer part of the result, we'll get 0, 1 or 2, depending on which row the invader was in. Well, this also tells us what type of invader was shot, and we can use it to adjust SCORE. Which is exactly what this line does.

350-390  
This is a simple routine that puts the updated score on the screen. Our first step is to convert SCORE into a string. We do this so that we can tell how many digits are in the score, which in turn allows us to right-justify it on the screen (try changing this line so that it prints SCORE, not SCORE$, and you'll see why we're doing this). Next we set up the computer so that it's ready to print in the score section of screen memory, position the cursor appropriately, print the string, and then return back to the main part of the program.

3030 We need to reserve some space for SCORE$.

4020 And we also have to initialize SCORE to 0.


    That's about all there is to scoring, with one exception. As a further incentive to getting a good score, most games give bonus lives (or time) to the player when he or she reaches a certain score. Some games give only one extra life, while others give many. Some limit the number of extra lives you can keep in reserve, some let you continue to accumulate lives as long as you can keep earning them. It's entirely up to the game designer. For our game, we're going to give one bonus base when the player reaches 10,000 points. The following lines take care of it for us:

360 IF SCORE<1000 OR BB=1 T
HEN 390
370 BB=1:BASES=BASES+1:POSI
TION 20-BASES,0:? #6;"";
4020 BASES=3:SCORE=0:BB=0

360 We don't want to award a base if the score is less than 10,000 (remember the extra 0) or if the base has already been awarded. BB is a flag that gets set to 1 when the bonus base is awarded. I originally forgot to include such a flag, and as a result the game awarded me an extra base every time I shot something, once I'd gotten 10,000 points. Be careful of little details like this when you add a new feature to your game.

370 If we are going to award a base, then we set the bonus base flag, add 1 to our base count, and print the new base next to the other extra bases.

4020   Here we just make sure that BB is initialized to 0.

    Of course right now the game won't let you get 10,000 points, since it ends when all the invaders are shot down. We'll take care of this in the next section, but you can change line 360 so that the extra base is awarded at a lower score if you want to see for yourself that the above lines do indeed work.

A New Level
    We now have all the basic game play elements in place, but we're still limited to one screen of invaders, or one level of the game. Our next logical step, therefore, is to extend the game to more than one level, and that's exactly what we're going to do in this section.
    In all of the early video games, a new level meant a more difficult version of the level before it. But that soon changed as games like Donkey Kong, Tempest and Miner 2049er hit the market. In these games a new level introduced a different challenge, which meant that the game took longer. Each level was progressively more difficult, and this is the basic guideline that all good games should follow. If a player is not consistently challenged by a game, then he or she will quickly tire of it, and this is something the game designer would prefer to avoid.

Increasing the Challenge
    How do we go about increasing the challenge? As I mentioned already in the section on game logic, the most common ways are to speed things up, add more opponents, and give the opponents more strength. In other words, you want to give the computer more of an advantage over the player.
    At the same time, you want to make sure that the player still has a chance. It's very important to maintain the impression that hey, 1 made a dumb mistake but I'll do better next time, rather than there's no way I could have gotten out of that. After all, why should anyone play a game they know they have no chance at?
    It's very hard to predict how difficult the higher levels will get in some games, so you have to be careful when you're programming the difficulty changes. Another thing to watch out for is the fact that you will tend to be very good at your own game. As a result, a beginner may tend to find the game very difficult. It's a good idea to have a friend try the game out and give you their opinion. Make sure that they're not afraid to be honest, however!
    It's extremely difficult to talk specifically about things like this, since each game is, and should be, different. You might want to spend some time at your local arcade, playing a variety of games and noticing what they do to make each level more difficult.
    As far as our own game is concerned, we don't have many choices. Because of the speed problems with BASIC, we can't really do anything to speed things up. We do have a little room to add more opponents, but not much. We need something that we can use level after level.
    As far as making the invaders stronger is concerned, we could add an extra missile, but again, we can't do this at every level. So what is left? What if we begin each level with the invaders a little closer to the bottom of the screen? That way the player has less time in which to destroy them all (since the game is over when they reach the bottom). We'll place a limit on how far down they can start, of course, since we have to make sure the player has a chance. This is a simple but effective solution that is also easy to do, something which we need to consider when using BASIC. Let's go ahead and do it:

2000 X=USR(ADR(VBLOFF$))
2010 X=USR(ADR(MOVMEM$),ADR
("
"),MEM1+LINE*24+TMP*48+24,2
3):GOTO 5000
4020 BASES=3:SCORE=0:BB=0:L
EVEL=0
5000 POKE 559,0:POKE 54276,
0:X=USR(ADR(MISCLR$),PA+768
,255,240):LEVEL=LEVEL+1
5005 X=USR(ADR(VBLOFF$))
5010 DLIST=PEEK(560)+PEEK(5
61)*256:IF SCRH<>0 THEN POK
E DLIST+4,SCRL:POKE DLIST+5
,SCRH
5190 IF LEVEL=1 THEN POKE 0
7,1:POKE 88,SCRL:POKE 89,SC
RH:POSITION 0,0:? #6;" SCOR
E:     0    "
5280 LINE=LEVEL-2:IF LINE>8
 THEN LINE=8


2000   
When we finish a level, the first thing we want to do is turn off the VBLANK routines, mainly so that the player can't move the base around while we're getting things ready. This line takes care of that for us.

2010 We also have to take care of the fact that the last invader explosion is still on the screen. The solution to this is to simply move a string of blanks (remember that the internal code for a blank is the ATASCII code for a heart) into the line of the screen where the last explosion occurred. After we do this we're ready for the new level, so we skip ahead to the code that initializes a level. I'll go into a little more detail on why things are arranged this way after the rest of the explanation.

4020 We now have to keep track of which level we're on, which we'll do with the variable LEVEL. Here we just initialize LEVEL to 0.

5000 This is the beginning of the code that initializes a level. First we make sure that the screen is turned off. Then we initialize the fine scroll register to 0, clear the player and invader missiles, and increase the level number.

5010 If IF SCRH    < > 0 is true, this means that we've already completed at least one level, in which case we want to restore the first LMS in the display list (to get rid of the coarse scrolling we've done).

5190 We only want to set up the score and extra bases if this is the first level. If it isn't, then the score and bases are already on the screen and we don't want to change them.

5280 As our last step, we want LINE to reflect the level that we're on. (Remember that LINE specifies how far down the screen the invaders are). At the same time, we also want to make sure that the invaders don't start too far down, so we let LINE start no higher than 8. If LINE is equal to 8, then the bottom row of invaders will start two lines above the base. This gives the player just enough time to destroy the bottom row before it reaches the base.


    You've no doubt noticed that lines 5000 through 5280 fall right in the middle of the initialization part of BASIC invaders that we'd already written. As a result, we only have a few lines to add rather than a whole bunch. Let's take a few minutes to look at how the initialization section of the program has been organized, since I've been somewhat sneaky about it.

Initialization Routines
    In a game such as BASIC invaders, the initialization section can be divided into three sections. We'll call these sections "FIRST RUN," "NEW GAME," and "NEW LEVEL." Their purposes match their names.
    FIRST RUN takes care of things that only need to be done when the program is first run. The FIRST RUN section of BASIC invaders takes up lines 3000 through 3310 and handles things like setting up the machinelanguage routines, the PMG area, and the character set.
    NEW GAMES takes care of things that need to be done each time a new game is begun. It takes up lines 4000 through 4020 in BASIC invaders and handles the title page, initializing the screen, and initializing a few variables such as LEVEL and SCORE.
    NEW LEVEL is the section of the code that we're dealing with now, and takes up lines 5000 through 5490. Apart from the tasks we just added, it also sets up the display list, draws the barriers, sets up INV$, initializes some more variables, and takes care of a few other sundry items.
    As you design your game, you should try and group your initialization code into these three categories. I have to admit that I didn't when I first wrote BASIC invaders, and ended up having to go through and sort things out later. Try not to find yourself in the same predicament (easy to say).

Another Change
    And now, back to the program. I conveniently left something very important out of the above additions, but you won't notice what until you reach Level 7. Can you guess what it is? Well, at Level 7 the invaders reach the barriers, and at the moment the program is only set up to handle this if it occurs during a level, not at the beginning of one. So, we have to make another change to the NEW LEVEL section:

5130 IF LEVEL<7 THEN BARLIM
=0:GOTO 5180
5140 TMP=LEVEL-7:IF LEVEL>9
 THEN TMP=2
5150 BARLIM=TMP+1:F0R X=1 T
O BARLIM:POKE DLIST+20+X,22
:NEXT x
5160 X=USR(ADR(M0VMEM$),DLI
ST+31+10*TMP,ADR(DL$),29-10
*TMP)
5170 X=USR(ADR(M0VMEM$),ADR
(DL$),DLIST+22+TMP,29-10*TM
P)

5220 IF BARLIM=3 THEN 5270


5130 If we aren't at Level 7 yet, then we haven't struck the barriers, so we set BARLIM to 0 and skip over the next part.

5140 TMP is actually equal to BARLIM-1, as you'll see in the next line. The only reason that we bother with it is because we'll need to use BARLIM-1 a lot in the next few lines, and using TMP instead is a lot simpler.

5150 Here we set BARLIM, and then we POKE the correct number of CHR 6 HSCs into the display list. You may want to read the "Onwards and Downwards" section again if you're not sure why we're doing this.

5160-5170   Now we want to "scrunch" the display list to get rid of the extra CHR 14 lines, so we use the same technique that we did in lines 1440-1450. You can also see now why we needed TMP.

5220 If BARLIM is equal to 3, which means that the invaders are starting far enough down so that the barriers are completely gone, then there's no point in drawing them.

    Now everything works as it should. One final note before we move on. In the arcade version of Space Invaders, the difficulty of each level is further increased by the invaders speeding up as their numbers decrease. By the time you get down to the last invader, it is zooming back and forth across the screen and is much more difficult to hit before it reaches the bottom. Why don't we add this to our game? In case the answer isn't obvious, the problem is speed. If, however, you can get your hands on a BASIC compiler, which speeds BASIC programs up considerably, you could add a time delay between invader movements, and then have this delay decrease with the number of invaders remaining. Since you should seriously consider investing in a compiler if you're doing a lot of BASIC game programming, I thought I would just mention this now, since it pertains somewhat to this section.

Sound
    Here are the changes to BASIC invaders that will get some simple sound effects up and running:

200 FOR X=4 TO 0 STEP -1:SO
UND 2,16,4,X:POKE 785,112+X
*3:POKE 706,112+X*3:FOR L=1
 TO 10:NEXT L:NEXT X
300 FOR X=55 TO 50 STEP -1:
SOUND 1,X,8,55-X:NEXT X:SOU
ND 1,0,0,0:RETURN
335 FOR X=250 TO 50 STEP -2
5:SOUND 1,X,10,8:NEXT X:SOU
ND 1,0,0,0
380 FOR X=1 TO 5:FOR Y=8 TO
 0 STEP -1:SOUND 1,10,10,Y:
NEXT Y:NEXT X:FOR PAUSE=1 T
O 10 :NEXT PAUSE
430 FOR X=117 TO 250 STEP 4
:SOUND 1,X,8,INT((250-X1/9)
:POKE 704,112+INT((250-X)/9
):NEXT X
1010 FOR X=100+PI TO 116+PI
 STEP 2:SOUND 0,X,2,116+PI-
X:NEXT X:PI=PI+20:PI=PI*(PI
<70)
1160 FOR X=16 TO 8 STEP -2:
SOUND 1,20,8,X:NEXT X
1300 POKE 675,15:POKE 1685,
235:POKE 1686,240:POKE 705,
40:POKE 706,40:SAUCER=I:SOU
ND 2,10,4,4
1370 IF SAUCER=1 AND PEEK(1
686)<40 THEN SAUCER=0:SOUND
 2,0,0,0:POKE 705,15:FOR PA
USE=1 TO 10:NEXT PAUSE
2005 SOUND 2,0,0,0
2025 FOR X=0 TO 3:SOUND X,0
,0,0:NEXT X

    And here's the explanation:

200   
When the alien saucer has been hit, we now fade out its sound as well as its color.

220 This just turns off the sound of the missile, since it has collided with the saucer and therefore doesn't exist anymore.

300 This is at the end of the barrier explosion section, and makes a small explosion sound every time a bar rier is hit. Notice that we're changing both pitch and volume.

335 An invader has been hit, so we make an explosion sound by varying the pitch on one of the "noise" distortion modes.

380 Here we make a "ting-ting-tingting-ting" sound when the bonus base is awarded. Notice that we're using a high-pitched pure note and just changing the volume.

430 The player base has been hit, so make an explosion sound as we fade it out (this line is similar to line 200 above, with the exception that here we're varying the pitch instead of the volume).

1010 This line is interesting, because it adds a lot to the effect of the game, but at the same time it also slows it down. You may or may not want to remove it. What it does is make a "tromping" or footstep sound as the invaders move across the screen. Because of its repetition it tends to draw the player deeper into the game, in a way that's almost hypnotic. Try it and see what I mean. In any case, the sound is created by varying the volume and pitch of one of the "noise" distortion modes. We also change the pitch between tromps, so that there are four differently pitched tromps arranged in a "one-two-three-four, one-two-three-four" pattern.

1160 Here we've just fired a player's missile, so we make a simple firing sound by varying the volume on a "noise" distortion mode.

1300 When we turn the saucer on, we also want to turn its sound on as well. The distortion mode that we use in this line is a good one to use for engine-type sounds, since at the right pitch it has a throbbing quality. Thus we can just set it once to get the effect we want, without having to worry about varying pitch or volume.

1370 We want to turn the saucer sound off when it goes off the edge of the screen, and that's exactly what we do here.

2005 There is a chance that the player will finish a level with the saucer in mid-flight, so we want to make sure that the saucer sound is turned off at the end of each level.

2025 Finally, at the end of a game we want to turn all sounds off.


The End
    Don't be misled by the title of this section; it isn't the end of our programming. Instead we're going to be discussing the end of the game, that inevitable moment when the computer finally overcomes the player. Since there isn't really that much that happens at the end (other than things ending), this is going to be a pleasantly short section, so let's get right into it.
    For the most part, a video game has two responsibilities at its termination. First, it should tell the player that the game is, indeed, over. As seemingly silly as this may sound, there are quite a few games out there, mostly in the home market, in which the player suddenly finds himself back in the title page with absolutely no idea of how they got there. At least give the player a chance to realize that they've lost.
    The second responsibility is, of course, to go to the title page so that the player can take another shot at it (so to speak). And that's all there is to it. Of course some game designers feel the need to come up with some elaborate way of presenting the "you blew it" message, such as blowing up the screen or having the aliens stand there and laugh. This tends to annoy the player to the point where they play the game again just to get revenge. I've even seen some games with end screens that are so darn cute that I play the game again just so I can lose. You may wish to adopt one of these methods if you have the extra time and memory. Just keep in mind that an exploding screen won't work too well in a "Saturday morning cartoons" type of game, and a cute ending is totally out of place in a "destroy the galaxy" type.

Letting the Player Know What's Going On
    Enough talk; let's get down to action. We're not going to add anything fancy to BASIC Invaders, just a simple message to let the player know what's going on. We'll also take care of a few other details that I forgot to mention above, such as cleaning up the screen a little. Here are our new additions:

2020 X=USR(ADR(MEMCLR$),MEM
1,528)
2030 POKE 54276,0:POKE 559,
0,POKE DLIST+5,INT(MEM1/256
):POKE DLIST+4,MEM1-PEEK(DL
IST+5)*256
2040 X=USR(ADR(MOVMEM$),ADR
("gameover"
),MEMI+192,23)
2050 FOR X=53248 TO 53255:P
OKE X,0:NEXT X:POKE 559,62
2060 IF SCORE*10>HISCORE TH
EN HISCORE=SCORE*10
2070 POKE 20,1
2080 IF PEEK(20)<>8 THEN 20
00
2090 GOTO 4000


2020 
Our first step is to clear the screen, since there are still invaders on it. Our MEMCLR routine does this quickly for us.

2030 We're going to be printing a message on the screen and we want to make sure it's centered, so we set the fine scroll register back to 0. We also want to reset the coarse scrolling, so we turn off the screen and give the first LMS in the display list its original value back.

2040 Now we put up our message (avoiding the temptation to be nasty about it).

2050 Next we move all the players and missiles off-screen and turn the screen back on.

2060 This is as good a time as any to check for a new high score, so we do.

2070-2080 
We need to give the player time to realize what's going on (i.e. time to read the message), so we set the system clock to 1 and then wait for it to reach 0 again (which takes 4.25 seconds).

2090 We're all done, so it's off to the NEW GAME section of our initialization code.

    See, I told you this was going to be an easy section.

The Finishing Touches
    Here we are with an actual working game. Of course I'll be the first one to admit that it's not quite as fast as we'd like, but that's the problem with BASIC. A good BASIC compiler will take care of this problem for us, but before we worry about speeding things up, we still have a few embellishments to add to the program.
    The first thing we'd like to do is give the program a beginning. As it stands now, the game starts as soon as the program is RUN, and a new game begins as soon as the old one ends. It is preferable to have some sort of screen that comes up before the game begins, thereby giving the player an opportunity to start when he or she is ready.
    This screen is called the title screen and you'll find it, in one form or another, in every video game on the market. Some of these screens are as simple as a few words announcing the name of the game (as ours will be), while others are almost an entire program within themselves.
    Why should so much work be put into such a seemingly meaningless part of the program? In the old days, when video games were restricted to the arcades, it was the title screen that attracted players to the game. When video games came home, especially on the home computers, game designers essentially imitated the whole style of the arcade games, and thus the fancy title screens made their way into the home. Of course the homes games were bought on the basis of media advertising and not on the basis of the title screen, but programmers didn't seem to care. Not that a fancy screen isn't a nice touch, but it tends to take up a lot of precious memory.

The Title Screen
    Since we're trying to keep BASIC invaders as short as possible, our title screen is going to be short but sweet. It will announce the name of the game, the authors and copyright notice. It will also show the high score to date, and will sit patiently on the screen until the player presses the Start button. We'll do a little bit of adjusting to the display list to get a nice-looking screen layout, but otherwise the rest of it is quite straightforward. See for yourself:


4000 GOSUB 6000:GRAPHICS 24
:POKE 559,0:POKE 756,CB+2
6000 GRAPHICS 0:POKE 559,0
6010 D2=PEEK(560)+PEEK(561)
*256
6020 SETCOLOR 4,7,0:SETCOLO
R 2,7,0:SETCOLOR 1,9,15:SET
COLOR 3,4,8:SETCOLOR 0,12,1
0
6030 POKE D2+7,7:POKE D2+16
,6:POKE D2+25,6
6040 POKE 752,1:POKE 82,0:P
OKE 83,39:POSITION 3,2:? "
"
6050 POSITION 28,2:? "By Cr
aig Patchett & You"
6060 POSITION 22,10:? "HI S
CORE:":POSITION 37-LEN(STR$
(HISCORE)),18:? HISCORE
6070 POSITION 0,19:? "PRESS
 start TO BEGIN"
6080 POSITION 22,20:? "(C)
1984 Educational Software,
Inc."
6090 POKE 559,34
6100 IF PEEK(53279)<>6 THEN
 6100
6110 RETURN

    This shouldn't be necessary, but here's the explanation:

4000 Before we start the game, we now go off to our title-screen routine.

6000 We get the computer to set up a graphics mode 0 display list, and then turn off the screen.

6010 Then we set the screen colors to something that will be a little more appealing than the regular colors.

6020-6030   Next we figure out where the display list is and add a graphics mode 2 line and two graphics mode 1 lines to it.

6040-6080 We don't want to see the cursor on the screen so we turn it off. We also set the margins so that we can print across the whole screen, and then we print the text.

6090 Our title screen is all set up now, so we go ahead and turn it on.

6100 All that's left now is to wait until the Start button is pressed.

6110 And then return back to line 4000.


Disabling the Break Key
    As you can see, the result is simple but serves a purpose. If you're feeling adventurous, you may like to spruce it up a little. A simple but effective change would be to add some of the invader characters and have them walk in place or even across the screen. Use your imagination.
    We have one more final change to the game that will make it complete. It's a small change, and one that isn't really necessary other than as a precaution. We're going to disable the Break key so that the player can't accidentally stop the program. All it takes is a simple two-line routine that has to be executed each time the GRAPHICS command is used (since this command sets the Break key back to normal). And now (drum roll), here are the last additions to BASIC invaders.

90 GOTO 3000
3000 GOSUS 7000
4000 GOSUB 6000:GRAPHICS 24
:GOSUB 7000:POKE 559,0:POKE
 756,CB+2
6000 GRAPHICS 0:GOSUB 7000:
POKE 559,0
7000 IF PEEK(16)-128)=0 THE
N POKE 16,PEEK(16)-128:POKE
 53774,PEEK(16)
7010 RETURN

    And, of course, the last explanation:

90 A simple change to this line due to the addition of the next line.

3000 Here we've added the first call to our routine, right after the GRAPHICS command.

6000 Again, a call to the routine after the GRAPHICS command.

7000-7010 
This is the routine. You don't need to understand what it's doing, just make sure that you use it exactly as it appears here.

 
    Congratulations! Our game is now complete!

The Final Listing
    Now that you have hopefully typed everything in and have a working version of the game it may help to see all of the code together. The following listing is here for that purpose.
    To create your complete copy of BASIC Invaders, first type in Listing 1 (checking your work with BASIC Editor II, found elsewhere in this issue) and save it to disk. (Don't bother trying to type lines 29000 through 32510; Listing 2 will do that for you.) Now type in Listing 2, save it and run it. When the program has finished creating the lines containing the ML strings, type LIST "D: LINES. LST",29000,32510 to save the newly created lines to disk. Now load the program you typed from Listing 1 into your computer's memory and type ENTER "D:LINES.LST" to merge the lines created by Listing 2 with the main program. Finally, save the completed program to disk.

LISTING 1: BASIC

IU 90 GOTO 3000
UU 180 X=USR(ADR(MISCLR$),PA+768,255,252)
   :POKE 1720,0:IF PEEK(1716)<>255 THEN 2
   30
QI 190 POKE 675,15
AK 200 FOR X=4 TO 0 STEP -1:SOUND 2,16,4,
   X:POKE 705,112+X*3:POKE 706,112+X*3:FO
   R L=1 TO 10:NEXT L:NEXT X
GS 210 SOUND 2,0,0,0:POKE 1685,0:POKE 168
   6,5:POKE 705,15
MH 220 POKE 53249,0:POKE 53250,0:SCORE=SC
   ORE+30:GOTO 350
JN 230 TMP=PEEK(53248):IF TMP<4 OR TMP=8
   THEN 310
WF 240 SOUND 1,0,0,0
FK 250 X=PEEK(1692)-47+2*(RND(0)>0.5)-1:Y
   =PEEK(1696)-158-8*BARLIM
BL 260 POKE 89,INT((MEM7+320*BARLIM)/256)
   :POKE 88,MEM7+320*BARLIM-256*PEEK(89):
   POKE 87,7:COLOR 0
OL 270 YT=Y-3:YT=YT*(YT>=0)
AC 280 PLOT X-2,YT:DRAWTO X+2,YT+6:PLOT X
   ,YT:DRAWTO X,YT+6:PLOT X+2,YT:DRAWTO X
   -2,YT+6
EO 290 PLOT X-2,Y*(Y>=0):DRAWTO X+2,Y*(Y>
   =0)
DI 300 FOR X=55 TO 50 STEP -1:SOUND 1,X,8
   ,55-X:NEXT X:SOUND 1,0,0,0:RETURN
AS 310 IF TMP=0 THEN 250
PX 320 R=48*INT((PEEK(1696)-38-8*(LINE))/
   16):C=2*INT((PEEK(1692)-SCROLL-COARSE*
   8-55)/16):R=R*(R>=0)
TN 330 INV$(R+C+28,R+C+29)="":INV$(R+C+
   315,R+C+316)="":EF=2
UN 340 SCORE=SCORE+3-INT(R/96)
MH 350 SCORE$=STR$(SCORE):POKE 88,SCRL:PO
   KE 89,SCRH:POKE 87,1:POSITION 12-LEN(S
   CORE$),0:? #6;SCORE$;
GH 360 IF SCORE<1000 OR BB=1 THEN 390
UG 370 BB=1:BASES=BASES+1:POSITION 20-BAS
   ES,0:? #6;"";
LL 380 FOR X=1 TO 5:FOR Y=8 TO 0 STEP -1:
   SOUND 1,10,10,Y:NEXT Y:NEXT X:FOR PAUS
   E=1 TO 10:NEXT PAUSE
FT 390 FOR X=250 TO 50 STEP -25:SOUND 1,X
   ,10,8:NEXT X:SOUND 1,0,0,0:RETURN
QH 400 X=USR(ADR(MISCLR$),PA+768,255,243)
   :POKE 1721,0:IF PEEK(1717)<>255 THEN 4
   70
CU 410 POKE 1664,255
CI 420 X=USR(ADR(MISCLR$),PA+768,255,252)
   :POKE 1700,0
AD 430 FOR X=117 TO 258 STEP 4:SOUND 1,X,
   8,INT((250-X)/9):POKE 704,112+INT((250
   -X)/9):NEXT X
XF 440 BASES=BASES-1:IF BASES=0 THEN POKE
   53248,0:GOTO 2020
BK 450 POKE 87,1:POKE 88,SCRL:POKE 89,SCR
   H:POSITION 19-BASES,0:? #6;" ";:POKE 5
   3248,128:POKE 1684,128:POKE 704,15
SV 460 POKE 1664,0:RETURN
CO 470 X=PEEK(1693)-47+2*(RND(0)>0.5)-1:Y
   =PEEK(1697)-159-8*BARLIM:GOTO 260
UJ 1000 X=USR(ADR(MOVMEM$),ADR(INV$)+SB,M
   EM1+LINE*24,287-48*(5-BOTROW))
AE 1020 IF EF=0 THEN 1080
AD 1030 INV$(R+C+28,R+C+29)="":INV$(R+C
   +315,R+C+316)=""
PB 1040 TMP=R/48:ROW(TMP)=ROW(TMP)-1:IF R
   OW(TMP)<>0 OR TMP<>BOTROW THEN 1080
GK 1050 FOR LP=BOTROW TO 0 STEP -1:IF ROW
   (LP)=0 THEN NEXT LP:GOTO 2000
DS 1060 POP :BOTROW=LP
PO 1070 X=USR(ADR(MOVMEM$),ADR("
   "),MEM1+LINE*24+TMP*48+
   24,237
FM 1080 SB=SB+287*(SB=0)-287*(SB=287)
PD 1090 IF PEEK(1720)<>0 THEN GOSUB 180
HZ 1100 IF PEEK(1721<>0 THEN GOSUB 400
DF 1110 POKE 53278,0
KY 1120 IF STRIG(0)=1 OR PEEK(1700)<>0 OR
    PEEK(1720)<>0 OR EF<>0 THEN 1170
HT 1130 SOUND 1,0,0,0
SI 1140 X=USR(ADR(MISCLR$),PA+768,255,252
   ):POKE 1692,PEEK(1684)+2:POKE 53252,PE
   EK(1692):POKE 1696,201:POKE PA+969,1
US 1150 POKE PA+970,1:POKE 53278,0:POKE 1
   712,15:POKE 1716,6:POKE 1720,0:POKE 17
   00,1
FJ 1160 FOR X=16 TO 0 STEP -2:SOUND 1,20,
   8,X:NEXT X
QG 1170 IF PEEK(1701)<>0 OR PEEK(1721)<>0
    OR LINE+BOTROW*2=19 THEN 1250
BX 1180 X=USR(ADR(MISCLR$),PA+768,255,243
   ):X=PEEK(20)+1:IF X=256 THEN X=0
PL 1190 IF PEEK(20)<X THEN 1190
IN 1200 POKE 53278,0:FC=INT(RND(0)*8)*2:F
   R=BOTROW*48
KB 1210 IF INV$(FR+FC+28,FR+FC+28)="" OR
    INV$(FR+FC+28,FR+FC+28)="" THEN FC=F
   C-2+16*(FC=0):GOT0 1210
NO 1220 POKE 1693,FC*8+62+SCROLL+COARSE*8
   :POKE 53253,PEEK(1693):FV=BOTROW*16+LI
   NE*8+46
HW 1230 POKE 1713,4:POKE 1717,1
GK 1240 POKE 1697,FV:FV=FV+PA+768:POKE FV
   ,PEEK(FV)+4:POKE FV+1,PEEK(FV+1)+4:POK
   E 1701,1
HZ 1250 EF=EF-(EF<>0)
LQ 1260 IF PEEK(20)<30 AND PEEK(19)=0 THE
   N 1330
MI 1270 IF PEEK(53251)=0 THEN 1330
RR 1280 CHANGE=-CHANGE:POKE 1791,129-PEEK
   (1791):POKE 19,0:POKE 28,0:POKE 53255,
   127+77*CHANGE:LINE=LINE+1
GW 1290 IF CHANGE=1 OR LINE+BOTROW*2=20 T
   HEN 1400
HF 1300 POKE 675,15:POKE 1685,235:POKE 16
   86,240:POKE 705,40:POKE 706,40:SAUCER=
   1:SOUND 2,10,4,4
UW 1310 POKE 675,11
OW 1320 GOTO 1400
TJ 1330 SCROLL=SCROLL+CHANGE
CK 1340 IF SCROLL>15 THEN SCROLL=SCROLL-1
   6:COARSE=COARSE+2:POKE 1790,2:GOTO 136
   0
JC 1350 IF SCROLL<0 THEN SCROLL=SCROLL+16
   :COARSE=COARSE-2:POKE 1790,2
MZ 1360 POKE 1789,SCROLL:POKE 1788,1
ST 1370 IF SAUCER=1 AND PEEK(1686)<40 THE
   N SAUCER=0:SOUND 2,0,0,0:POKE 705,15:F
   OR PAUSE=1 TO 10:NEXT PAUSE
DP 1380 IF PEEK(1790)<>0 THEN 1380
NV 1390 GOTO 1000
HO 1400 POKE 53278,0:IF LINE+BOTROW*2<><20
   THEN 1430
UU 1410 X=USR(ADR(MOVMEM$),ADR(INV$)+SB,M
   EM1+LINE*24,287-48*(5-BOTROW))
ON 1420 GOTO 2020
LE 1430 IF LINE+BOTROW*2<>15+BARLIM OR BA
   RLIM=3 THEN 1000
CI 1440 POKE 559,0:POKE DLIST+21+BARLIM,2
   2:X=USR(ADR(MOVMEM$),DLIST+31+BARLIM,A
   DR(DL$),29-10*BARLIM)
UM 1450 X=USR(ADR(MOVMEM$),ADR(DL$),DLIST
   +22+BARLIM,29-10*BARLIM):POKE 20,0
OX 1460 IF PEEK(20)<2 THEN 1460
VT 1470 POKE 559,62:BARLIM=BARLIM+1:GOTO
   1000
DT 2000 X=USR(ADR(VBLOFF$)):X=USR(ADR(MOV
   MEM$),MEM1-44,ADR(DL$),19)
DT 2010 X=USR(ADR(MOVMEM$),ADR("
   "),MEM1+LINE*24+TMP*48
   +24,23):GOTO 5000
SD 2020 FOR R=0 TO 21:X=USR(ADR(MOVMEM$),
   ADR(""),MEM1+2
   4*R,23):NEXT R
PY 2025 FOR X=0 TO 3:SOUND X,0,0,0:NEXT X
HZ 2030 POKE 54276,0:POKE 559,0:POKE DLIS
   T+S,INT(MEM1/256):POKE DLIST+4,MEM1-PE
   EK(DLIST+5)*256
LR 2040 X=USR(ADR(MOVMEM$),ADR("ga
   meover"),MEM1+192,23)
RC 2050 POKE 53277,0:FOR X=53261 TO 53265
   :POKE X,0:NEXT X:POKE 559,62
XP 2060 IF SCORE*10>HISCORE THEN HISCORE=
   SCORE*10
IT 2070 POKE 20,2
ON 2880 IF PEEK(20)>1 THEN 2080
OX 2090 GOTO 4000
ZF 3000 GOSUB 7000
QQ 3010 POKE 559,0
SP 3020 FOR X=53248 TO 53255:POKE X,0:NEX
   T X
SZ 3030 DIM MLANG$(90),DL$(30),INV$(578),
   DAT$(16),SCORE$(6),ROW(5)
PG 3040 DIM VBLOFF$(20):GOSUB 29000:VBLOF
   F$=MLANG$
GE 3050 DIM MOVMEM$(41):GOSUB 29500:MOVME
   M$=MLANG$
IV 3060 DIM MISCLR$(26):GOSUB 36000:MISCL
   R$=MLANG$
XG 3070 DIM MEMCLR$(36):GOSUB 30500:MEMCL
   R$=MLANG$
VT 3100 FOR BYTE=0 TO 10:READ DAT:POKE 15
   36+BYTE,DAT:NEXT BYTE
KM 3110 DATA 72,169,212,141,10,212,141,26
   ,208,104,64
DI 3120 FOR BYTE=1 TO 40:READ DAT:POKE 17
   37+BYTE,DAT:NEXT BYTE
LF 3130 DATA 252,243,207,63,0,128,0,128,1
   28,2,2,3,3,1,0,0,0,0,0,4,5,6,7,3,76,12
   8
LA 3140 DATA 64,76,80,64,76,177,64,76,5,6
   5,76,88,65,0
NE 3150 PB=PEEK(740)-8:CB=PB-4:POKE 106,C
   B-4:CA=CB*256:PA=PB*256
CE 3160 GOSUB 31000
QK 3170 X=USR(ADR(MOVMEM$),ADR(MLANG$),CA
   -256,78)
JE 3180 MEM=PA
SQ 3190 FOR SEC=0 TO 7:GOSUB 32000+10*SEC
   :X=USR(ADR(MOVMEM$),ADR(MLANG$),MEM,LE
   N(MLANG$)-1)
YD 3200 MEM=MEM+LEN(MLANG$):NEXT SEC
RQ 3210 X=USR(ADR(MOVMEM$),57344,CA,1023)
VL 3220 MEM=CA+512:FOR SEC=0 TO 1:GOSUB 3
   2500+10*SEC:X=USR(ADR(MOVMEM$),ADR(MLA
   NG$),MEM,LEN(MLANG$))
YM 3230 MEM=MEM+LEN(MLANG$):NEXT SEC
OR 3240 X=USR(ADR(MOVMEM$),CA+128,CA+640,
   336)
RB 3250 X=USR(ADR(MEMCLR$),PA+768,1280):P
   OKE 54279,PB:POKE 623,4
CD 3270 FOR BYTE=201 TO 208:READ DAT:POKE
    PA+1824+BYTE,DAT:NEXT BYTE
YN 3280 DATA 16,16,55,56,124,124,198,198
AB 3290 FOR BYTE=30 TO 38:READ DAT:POKE P
   A+1280+BYTE,DAT:READ DAT:POKE PA+1536+
   BYTE,DAT:NEXT BYTE
SS 3300 DATA 15,16,31,24,63,28,106,22,106
   ,22,255,31,255,31,56,28,16,8
CF 3310 FOR BYTE=39 TO 205:POKE PA+768+BY
   TE,192:NEXT BYTE
ZA 4000 GOSUB 6000:GRAPHICS 24:GOSUB 7000
   :POKE 559,0:POKE 756,CB+2
MJ 4010 SETCOLOR 0,4,14:SETCOLOR 1,4,6:SE
   TCOLOR 2,15,14:SETCOLOR 3,4,10:SETCOLO
   R 4,7,0
FB 4020 LEVEL=0:BASES=3:BB=0:SCORE=0
ZK 5000 POKE 559,0:X=USR(ADR(MISCLR$),PA+
   768,255,240):LEVEL=LEVEL+1
QH 5005 X=USR(ADR(VBLOFF$))
CU 5010 DLIST=PEEK(560)+PEEK(561)*256:IF
   SCRH<>0 THEN POKE DLIST+4,SCRL:POKE DL
   IST+5,SCRH
WH 5620 POKE DLIST+3,86
MJ 5030 L=PEEK(DLIST+4)+44:POKE DLIST+5,P
   EEK(DLIST+5)+(L)255):POKE DLIST+4,L-25
   6*(L>255)
TB 5040 FOR X=6 TO 20:POKE DLIST+X,22:NEX
   T X:FOR X=24 TO 50:POKE DLIST+X,14:NEX
   T X
EF 5050 MEM7=PEEK(88)+PEEK(89)*256+600
UJ 5060 POKE DLIST+21,78:POKE DLIST+23,IN
   T(MEM7/256):POKE DLIST+22,MEM7-INT(MEM
   7/256)*256
CR 5670 POKE DLIST+31,78:POKE DLIST+33,IN
   T((MEM7+320)/256):POKE DLIST+32,MEM7+3
   20-PEEK(DLIST+33)*256
IR 5080 POKE DLIST+41,78:POKE DLIST+43,IN
   T((MEM7+648)/256):POKE DLIST+42,MEM7+6
   40-PEEK(DLIST+43)*256
QL 5090 POKE DLIST+51,22:POKE DLIST+52,22
RN 5100 POKE DLIST+53,150:POKE 512,0:POKE
   513,6:POKE 54286,192
KY 5110 POKE DLIST+54,112:POKE DLIST+55,7
   0:POKE DLIST+56,PEEK(88):POKE DLIST+57
   ,PEEK(89)
FV 5120 POKE DLIST+58,65:POKE DLIST+59,PE
   EK(560):POKE DLZST+60,PEEK(561)
PS 5130 IF LEVEL<7 THEN BARLIM=0:GOTO 518
   0
EW 5140 TMP=LEVEL-7:IF LEVEL>9 THEN TMP=2
UI 5150 BARLIM=TMP+1:FOR X=1 TO BARLIM:PO
   KE DLIST+20+X,22:NEXT X
JR 5160 X=USR(ADR(MOVMEM$),DLIST+31+10*TM
   P,ADR(DL$),29-10*TMP)
LY 5170 X=USR(ADR(MOVMEM$),ADR(DL$),DLIST
   +22+TMP,29-10*TMP)
MH 5180 MEMI=PEEK(DLIST+4)+PEEK(DLIST+5)*
   256:SCRL=PEEK(DLIST+56):SCRH=PEEK(DLIS
   T+57)
YC 5190 IF LEVEL=1 THEN POKE 87,1:POKE,88
   ,SCRL:POKE 89,SCRH:POSITION 0,0:? #6;"
    SCORE:     0    "
YZ 5200 POKE 559,62
ML 5210 POKE 87,7:POKE 89,INT(MEM7/2567:P
   OKE 88,MEM7-PEEK(89)*256:COLOR 3
VR 5220 IF BARLIM=3 THEN 5270
OJ 5230 RESTORE 5260
KH 5240 FOR X=1 TO 10:READ N,XS,Y,XE:FOR
   T=0 TO N-1:FOR Z=0 TO 3:PLOT Z*48+9+XS
   ,Y+T:DRAWTO Z*40+9+XE,Y+T:NEXT Z
KU 5245 NEXT T
LV 5250 NEXT X
ON 5260 DATA 2,4,0,17,2,3,2,18,2,2,4,19,1
   0,1,6,20,2,1,16,7,2,14,16,20,2,1,18,6,
   2,15,18,20,4,1,20,5,4,16,20,20
UT 5270 SCROLL=0:CHANGE=1:SB=0:EF=0:BOTRO
   W=5:COARSE=0:SAUCER=0
AN 5280 LINE=LEVEL-2:IF LINE>8 THEN LINE=
   8
DR 5290 POKE 54276,0
LB 5300 POKE 1664,0:POKE 1665,43:POKE 166
   6,43:POKE 1667,255
US 5310 POKE 1668,50:POKE 1669,0:POKE 167
   0,5:POKE 1672,200:POKE 1673,235:POKE 1
   674,240
VD 5320 POKE 1676,201:POKE 1677,30:POKE 1
   678,30:POKE 1680,201:POKE 1681,30:POKE
    1682,30
BR 5330 POKE 1684,128:POKE 1685,0:POKE 16
   86,5:POKE 1688,201:POKE 1689,30:POKE 1
   690,30
AZ 5340 POKE 1700,0:POKE 1701,0:POKE 1720
   ,0:POKE 1721,0
VQ 5350 POKE 1704,0:POKE 1705,0:POKE 1708
   ,129:POKE 1789,1
YU 5360 POKE 1789,0:POKE 1790,0:POKE 1791
   ,128
YV 5365 POKE 675,15
FE 5370 INV$="":INV$(578)="":INV$(2)=IN
   V$
NC 5380 RESTORE 5410
QD 5390 FOR LP=0 TO 4 STEP 2:READ DAT$:IN
   V$(LP*48+28,LP*48+43)=DAT$:INV$(LP*48+
   363,LP*48+378)=DAT$
UI 5400 READ DAT$:INV$(LP*48+76,LP*48+91)
   =DAT$:INV$(LP*48+315,LP*48+330)=DAT$:N
   EMT LP
CE
SC 5420 FOR R=0 TO 5:ROW(R)=8:NEXT R
PI 5430 POKE 19,0:POKE 20,0:POKE 53278,0
LP 5440 POKE 704,15:POKE 705,15:POKE 706,
   70:POKE 787,112
HF 5450 POKE 53248,128:POKE 53249,0:POKE
   53250,5:POKE 53255,205
HH 5460 GOSUB 31500:X=USR(ADR(MLANG$),CA-
   256)
PV 5470 X=USR(PA,PB,PB)
FU 5480 POKE 53277,3
OB 5490 GOTO 1000
QN 6000 GRAPHICS 0:GOSUB 7000:POKE 559,0
BV 6010 D2=PEEK(560)+PEEK(561)*256
ES 6020 SETCOLOR 4,7,0:SETCOLOR 2,7,0:SET
   COLOR 1,9,15:SETCOLOR 3,4,8:SETCOLOR 0
   ,12,10
RQ 6030 POKE D2+7,7:POKE D2+16,6:POKE D2+
   25,6
YU 6040 POKE 752,1:POKE 82,0:POKE 83,39:P
   OSITION 3,2:? ""
HE 6850 POSITION 28,2:? "By Craig Patchet
   t & You"
UP 6060 POSITION 22,19:? "HI SCORE:":POST
   TION 37-LEN(STR$(HISCORE)),10:? HISCOR
   E
TZ 6070 POSITION 0,19:? "PRESS start TO B
   EGIN"
SR 6080 POSITION 22,28:? "(C) 1983 Educat
   ional Software, Inc."
ZK 6090 POKE 559,34
TE 6100 IF PEEK(53279)<>6 THEN 6100
AJ 6110 RETURN
GR 7000 IF PEEK(16)-128>=0 THEN POKE 16,P
   EEK(16)-128:POKE 53774,PEEK(16)
AI 7010 RETURN




LISTING 2: BASIC

VW 100 GRAPHICS 0:? "Make sure you have s
   aved a copy of":? "this program before
   RUNning it":FOR X=1 TO 1050:NEXT X
SQ 110 ? :?
RO 120 DIM LN(8):FOR X=1 TO 8:READ DAT:LN
   (X)=DAT:NEXT X
PE 130 DATA 20,41,26,36,112,11,657,128
OJ 140 FOR X=1 TO 8:TOT=0:N=0:GOSUB 1000
NH 150 FOR N=1 TO LN(X):READ DAT:TOT=TOT+
   DAT
HP 160 IF N/25<>INT(N/25) THEN 190
QP 170 T=TOT:TOT=0:READ DAT:IF DAT<>T THE
   N ? "...ERROR":STOP
QY 180 GOSUB 1000
JW 190 NEXT N:READ DAT:IF DAT<>TOT THEN ?
   " ..ERROR":STOP
LM 200 NEXT X
AJ 210 RESTORE 20800
OV 220 FOR X=1 TO 8:L=28580+580*X:GOSUB 1
   618
BP 230 FOR N=1 TO LNCX):READ DAT:? CHRSC2
   7);CHR$CDAT);
TJ 240 IF N/25=INTCN/25) THEN READ DAT
NF 250 IF N/90=INTCN/90) THEN GOSUB 1020:
   L=L+LO:GOSUB 1818
RQ 260 NEXT N:READ DAT:GOSUB 1020
MA 270 NEXT X
OH 280 END
LW 1000 ? :? "CHECKING LINE ";19000+1000*
   X+10*INT(N/25);:RETURN
PJ 1010 GRAPHICS 0:POSITION 2,4:? L;" MLA
   NG$=";CHR$(34);:RETURN
CU 1020 ? CHR$(34);":RETURN":? "CONT":POS
   ITION 0,0:POKE 842,13:STOP
UF 1030 POKE 842,12:RETURN
UG 20000 DATA 104,162,228,160,95,169,6,32
   92,228,162,228,168,98,169,7,32,92,228
   ,96,2548
QY 21000 DATA 104,104,133,207,104,133,206
   ,104,133,209,104,133,208,104,170,168,2
   55,138,208,2,104,168,177,206,145,3719
EW 21010 DATA 208,136,192,255,208,247,230
   ,207,230,209,202,224,255,208,233,96,33
   40
TH 22000 DATA 104,104,133,207,104,133,206
   ,104,104,168,104,104,133,208,177,206,3
   7,208,145,206,136,192,255,208,245,3931
YN 22010 DATA 96,96
JJ 23000 DATA 104,104,133,204,104,133,203
   ,104,170,169,0,160,255,224,0,208,4,104
   ,168,169,0,145,203,136,192,3396
PM 23010 DATA 255,208,249,230,204,202,224
   ,255,208,234,96,2365
FT 24000 DATA 173,251,6,240,184,173,252,6
   ,141,4,212,173,253,6,141,5,212,173,254
   ,6,248,79,173,48,2,3327
JY 24010 DATA 133,204,173,49,2,133,205,16
   0,3,177,204,201,65,240,61,201,1,240,52
   ,41,112,201,64,144,48,3114
IX 24020 DATA 201,80,144,42,200,173,255,6
   ,48,18,177,204,24,216,109,254,6,145,20
   4,200,177,204,105,0,145,3337
SL 24030 DATA 204,144,20,177,204,56,216,2
   37,254,6,145,204,200,177,204,233,0,145
   ,204,144,2,200,200,200,208,3984
NY 24040 DATA 189,169,0,141,254,6,141,251
   ,6,76,95,228,1556
IE 25000 DATA 104,104,170,104,168,169,6,3
   2,92,228,96,1273
HM 26000 DATA 104,104,104,141,188,6,104,1
   04,141,228,6,141,231,6,141,234,6,141,2
   37,6,238,237,6,141,240,3235
WO 26010 DATA 6,238,240,6,169,127,141,199
   ,6,162,9,160,4,173,47,2,41,16,240,9,16
   9,255,141,199,6,2765
FA 26020 DATA 162,19,160,8,140,200,6,160,
   9,189,206,6,153,189,6,282,136,16,246,1
   69,7,174,240,6,160,2969
OH 26030 DATA 108,32,92,228,96,32,238,6,1
   89,152,6,24,109,200,6,168,205,199,6,14
   4,3,172,199,6,189,2809
BK 26040 DATA 152,6,56,237,200,6,141,201,
   6,136,177,204,200,145,204,136,240,5,20
   4,201,6,176,242,169,0,3450
BE 26050 DATA 145,204,96,32,238,6,189,152
   ,6,56,237,200,6,168,176,2,160,0,189,15
   2,6,24,109,200,6,2759
MY 26060 DATA 141,201,6,200,177,204,136,1
   45,204,200,204,199,6,240,7,204,201,6,1
   44,239,240,237,169,0,145,3855
TM 26070 DATA 204,96,138,72,162,4,32,238,
   6,104,170,189,160,6,56,237,200,6,168,1
   76,2,160,0,189,160,2935
HO 26080 DATA 6,24,109,200,6,141,201,6,13
   6,177,204,61,202,6,145,204,200,200,189
   ,202,6,73,255,49,204,3206
OF 26090 DATA 136,136,17,204,145,204,200,
   200,204,199,6,176,7,204,201,6,144,221,
   240,219,189,202,6,49,204,3719
UU 26100 DATA 145,204,136,189,202,6,49,20
   4,145,204,96,138,72,162,4,32,238,6,104
   ,170,189,168,6,24,109,2994
IH 26110 DATA 200,6,168,205,199,6,144,3,1
   72,199,6,189,160,6,56,237,200,6,141,20
   1,6,200,177,204,61,3152
SK 26120 DATA 202,6,145,204,136,136,189,2
   02,6,73,255,49,204,200,200,17,204,145,
   204,136,136,240,5,204,201,3699
CO 26130 DATA 6,176,224,189,202,6,49,204,
   145,204,200,189,202,6,49,204,145,204,9
   6,189,189,6,133,204,24,3445
GY 26140 DATA 216,173,188,6,125,194,6,133
   ,205,169,0,133,77,96,162,0,188,128,6,4
   8,186,185,120,2,41,2707
MS 26150 DATA 8,208,23,189,148,6,221,136,
   6,240,43,169,0,133,77,254,148,6,189,14
   8,6,157,0,208,208,2931
WH 26160 DATA 28,185,120,2,41,4,208,21,16
   9,0,133,77,189,148,6,221,132,6,240,9,2
   22,148,6,189,148,2652
WY 26170 DATA 6,157,0,208,188,128,6,185,1
   20,2,41,2,208,17,189,152,6,221,144,6,2
   40,30,254,152,6,2668
UM 26180 DATA 32,229,6,138,16,21,185,120,
   2,41,1,208,14,189,152,6,221,140,6,240,
   6,222,152,6,32,2385
EX 26190 DATA 226,6,232,224,4,208,140,162
   ,0,189,164,6,240,83,189,168,6,240,50,1
   6,23,222,156,6,222,3182
NF 26200 DATA 156,6,189,156,6,157,4,208,2
   01,47,176,32,169,0,157,164,6,240,53,25
   4,156,6,254,156,6,2959
EL 26210 DATA 189,156,6,157,4,208,201,208
   ,144,9,169,0,157,164,6,240,106,208,196
   ,189,172,6,240,57,16,3208
XO 26220 DATA 23,222,160,6,222,160,6,32,2
   32,6,189,160,6,201,16,176,39,169,0,157
   ,164,6,240,74,254,2920
01 26230 DATA 160,6,254,160,6,32,235,6,18
   9,160,6,24,216,105,16,205,199,6,176,4,
   41,240,208,7,169,2830
AX 26240 DATA 0,157,164,6,240,42,189,176,
   6,61,0,208,240,13,169,255,157,176,6,15
   7,184,6,169,0,157,2938
MY 26250 DATA 164,6,189,180,6,61,8,208,24
   0,13,169,255,157,180,6,157,184,6,169,0
   ,157,164,6,232,224,3141
KA 26260 DATA 4,208,145,76,98,228,0,759
NF 27000 DATA 0,0,0,0,0,0,0,0,1,3,7,13,15
   ,2,5,10,128,192,224,176,240,64,160,80,
   1,1321
NO 27010 DATA 3,7,13,15,5,8,4,128,192,224
   ,176,240,160,16,32,8,4,15,29,31,23,20,
   2,16,32,1403
PT 27020 DATA 240,184,248,232,40,64,2,20,
   23,29,31,15,4,8,64,40,232,184,248,240,
   32,16,3,15,31,2245
CW 27030 DATA 25,31,6,9,48,192,240,248,15
   2,248,96,144,12,3,15,31,25,31,13,24,12
   ,192,240,248,152,2437
XA 27040 DATA 248,176,24,48,0,9,5,0,12,0,
   5,9,0,32,64,0,96,0,64,32,16,16,56,56,1
   24,1092
UU 27050 DATA 124,198,198,520