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
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