Chapter 7 - Arcade Games
7.1 Alien Zapping
Until the advent of reasonably cheap processors and VDUs this type of game couldn't exist. Unlike all other types they have no direct parallel in the real world being, in their simplest form, a very fast reaction test. No real world shoot-'em-up can possibly compare with the speed and volume of targets these games present.
7.1.1 Movement Tables
The most notable feature of this class of game is the smooth movement of the attacking aliens as they loop, dip and dive. At first sight programming this looks a daunting prospect, but is in fact quite easy. On close examination you find that in most games only a few fixed movement patterns are in fact used. These are most easily programmed by using look-up tables. These tables consist of lists of X,Y coordinates to be used within the main game loop interleaved with any other movement or animation. Bear in mind that these movement tables can be used in both directions, and can also be used as an offset rather than absolute plotting positions. This latter arrangement gives far more flexibility as it allows complex manoeuvres to be initiated from any point of the display.
The only problem remaining is that of producing the movement table itself. There are several ways this can be done. The obvious one is to draw the movement onto a sheet of graph paper set out as one division per graphic unit, then pick off evenly spaced coordinate pairs. These can be put in lines of data for reading into the array when the game program runs. This can be extremely tedious but is potentially very accurate, and you can immediately see exactly what you will get.
Figure 7.1 shows how this would look. For clarity I've not drawn in the background grid. As the objects following the path will probably be moving quite quickly, you can afford to space the plotting points out quite a lot. Changing the spacing of the plotting points can be used to good effect. In the example, movement will seem slower going upwards.
Another way of handing the problem is to write a small program that uses the mouse to draw the movement shape on-screen while mouse clicks store the coordinates. This can be a bit hit-and-miss and, in reality, is only a little faster than hand drawing. Apart from other considerations you will still need to turn the coordinates into lines of data.
For the most elegant solution you usually find that you can break the movement down into groups of circles, arcs and straight lines. Coordinates from these taken during game initialising can then be dropped straight into the movement tables, rather than being used for screen plotting. As well as being a neat, accurate solution this method doesn't require any lines of data, so will be more efficient.
You can see the practical implementation of this in listing 7.1. Character printing has been used so that you can easily see which object is moving where. As it stands only 9 objects can be moved this way without screen jitter developing. You would, of course, be able to have more if you use sprite plotting. However, if you were to use system sprites you actually can't have as many objects without jitter, which just underlines the need to stick to directly addressed user sprites.
REM > Movement
:
ON ERROR PROCerror:END
PROCinitialise
IF INKEY 100
REPEAT
WAIT
SYS byte%,113,sc%
sc%=sc% EOR 3
SYS byte%,112,sc%
CLS
FOR I%=0 TO numchar%
n%=char%(I%)
IF n%>=0 PROCmove
NEXT
UNTIL FALSE
END
:
DEFPROCerror
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 12
MODE 9
COLOUR 0,128,0,0
PRINT TAB(8,10) "Movement Table Example"
PRINT TAB(9,13) "Press Escape to stop"
VDU 5
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
sc%=1
maxpoints%=200
DIM x%(maxpoints%)
DIM y%(maxpoints%)
numchar%=8
DIM char%(numchar%)
char%()=-1
char%(0)=0
A%=ASC"A"
x%=0
y%=512
mark%=-1
PROCline(40)
PROCcircle(128,1)
PROCline(10)
PROCcircle(128,-1)
PROCline(55)
ENDPROC
:
DEFPROCline(n%)
FOR I%=0 TO n%
mark%+=1
x%+=12
x%(mark%)=x%
y%(mark%)=y%
NEXT
ENDPROC
:
DEFPROCcircle(rad%,dir%)
start=-PI/2
step=PI/20
end=start+PI*2+step
FOR I=start TO end STEP step
mark%+=1
x%(mark%)=x%+COS(I*dir%)*rad%
y%(mark%)=y%+SIN(I*dir%)*rad%+rad%*dir%
NEXT
ENDPROC
:
DEFPROCmove
MOVE x%(n%),y%(n%)
VDU A%+I%
IF n%<mark% char%(I%)+=1 ELSE char%(I%)=0
IF n%=4:IF I%<numchar% char%(I%+1)=0
ENDPROC
A point that needs a little explanation with this example is that there are in fact two tables. The first consists of the parallel arrays x%() and y%(). These contain the actual plotting coordinates, and is the true movement table. The other table is char%(). This is a list of pointers into the movement table, so that each individual character can progress along the movement table independently. In a more sophisticated version objects could then traverse the table at different speeds and even opposite directions.
Finally, whichever method you use to generate the tables, you can always create them in a separate program and save them as data files. These can then be quickly and efficiently loaded into the final game.
7.1.2 Formations
Usually, along with one or two independent attackers, there is a large group of aliens in a holding pattern with very little movement. This presents a very real problem, as unless you use your own ARM code sprite system you'll have great difficulty moving 40 to 50 sprites round the screen. The simplest solution is to use a special pre-defined sprite that consists of a whole block of aliens. If you detect a collision with one of these aliens you rub it out of the sprite, and initiate an explosion film animation sequence with a free moving sprite, plotted on top of that alien's position in the main sprite. The rubbing out process can be performed by clearing individual bits from the sprite definition. As it will be masked by the explosion sequence, it can take several screen refreshes. This will prevent the erasure routine slowing things down significantly. Another way to erase the alien is to temporarily redirect output to the sprite then use the rectangle fill with the graphic colour set to the background to overprint the offending creature.
Listing 7.2 shows all these points together. The main sprite uses a film animation of eight sprites. Erasure is by rectangle filling, and the erase procedure is masked by an explosion sprite. For best effects this latter should also be film animated.
REM > Formation
:
ON ERROR PROCerror:END
PROCinitialise
PROCbuild
PROCstart
REPEAT
FOR J%=0 TO mark%
WAIT
SYS byte%,113,sc%
sc%=sc% EOR 3
SYS byte%,112,sc%
CLS
SYS sprite%,put%,area%,frame%(L%),x%(J%),y%(J%)
IF INKEY-74 AND (J% AND 7)=0 PROCshoot
IF INKEY-98 PROCgun(-4)
IF INKEY-67 PROCgun(4)
SYS sprite%,put%,area%,gun%,gx%,gy%
IF wipef%(L%) PROCwipe(wipex%(L%),wipey%(L%),wipef%(L%))
FOR I%=0 TO pins%
IF wipef%(I%) SYS sprite%,put%,area%,blot%,wipex%(I%)+x%(J%),wipey%(I%)+y%(J%)
IF piny%(I%) PROCpin(pinx%(I%),piny%(I%),wipex%(I%),wipey%(I%),wipef%(I%))
NEXT
IF L%=0 OR L%=pins% D%=-D%
L%+=D%
NEXT
UNTIL FALSE
END
:
DEFPROCerror
VOICE 1,"WaveSynth-Beep"
MODE 12
*FX 9 25
*FX 10 25
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 12
MODE 9
mode%=MODE
OFF
COLOUR 0,128,128,128
*FX 9 2
*FX 10 2
PRINT TAB(12,6) "Alien formation"
PRINT TAB(2,10) "Z - left X - right Return - fire"
PRINT TAB(14,14) "Please wait"
PRINT TAB(10,18) "Press Escape to exit"
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
sc%=1
SYS "OS_SWINumberFromString",,"OS_SpriteOp" TO sprite%
DIM block% 19
block%!0=4
block%!4=5
block%!8=-1
SYS "OS_ReadVduVariables",block%,block%+12
xeig%=block%!12
yeig%=block%!16
size%=&1F000
DIM area% size%
area%!0=size%
area%!4=0
area%!8=16
init%=256+9
def%=256+15
select%=256+24
put%=512+34
swap%=512+60
SYS sprite%,init%,area%
DIM frame%(7)
:
xmin%=8
xmax%=1244
ymin%=0
ymax%=800
asize%=24
ah%=12
av%=4
ax2%=asize%*2
ax3%=asize%*3
xsize%=ah%*ax3%-asize% DIV 2
ysize%=av%*ax3%-asize%
xpos%=(1280-xsize%)>>1
ypos%=(1024-ysize%)>>1
:
maxpoints%=200
DIM x%(maxpoints%)
DIM y%(maxpoints%)
mark%=-1
PROCcircle(64,1)
PROCcircle(64,-1)
:
gwide%=24
ghigh%=32
gx%=(xmax%-xmin%-gwide%)>>1
gy%=ymin%+16
pins%=7
DIM pinx%(pins%)
DIM piny%(pins%)
DIM wipex%(pins%)
DIM wipey%(pins%)
DIM wipef%(pins%)
ENDPROC
:
DEFPROCcircle(rad%,dir%)
step=PI/50
start=step*2
end=start+PI*2-step*2
FOR I=start TO end STEP step
mark%+=1
x%(mark%)=xpos%+(COS(I)*rad%-rad%)*dir%
y%(mark%)=ypos%+SIN(I)*rad%/3
NEXT
ENDPROC
:
DEFPROCbuild
a=PI/2
p=PI*2
FOR L%=0 TO pins%
SYS sprite%,def%,area%,"S"+STR$L%,0,xsize%>>xeig%,ysize%>>yeig%,mode%
SYS sprite%,select%,area%,"S"+STR$L% TO ,,frame%(L%)
SYS sprite%,swap%,area%,frame%(L%)
PROCdraw(asize%+4,asize%+4,L%+6)
NEXT
:
SYS sprite%,def%,area%,"SG",0,(gwide%+4)>>xeig%, (ghigh%+4)>>yeig%,mode%
SYS sprite%,select%,area%,"SG" TO ,,gun%
SYS sprite%,swap%,area%,gun%
GCOL 7
MOVE 0,0
MOVE gwide%,0
PLOT 85,gwide% DIV 2,ghigh%
:
SYS sprite%,def%,area%,"SB",0,(ax2%+12)>>xeig%, (ax2%+12)>>yeig%,mode%
SYS sprite%,select%,area%,"SB" TO ,,blot%
SYS sprite%,swap%,area%,blot%
GCOL 8
CIRCLE FILL asize%+4,asize%+4,asize%+4
ENDPROC
:
DEFPROCdraw(x%,y%,l%)
FOR K%=0 TO av%-1
FOR J%=0 TO ah%-1
FOR I%=3 TO 1 STEP-1
dx%=x%+J%*ax3%
dy%=y%+K%*ax3%+I%
GCOL I%
MOVE dx%,dy%
MOVE dx%+COS(l%/p+I%/a)*asize%,dy%-SIN(l%/p+I%/a)*asize%
PLOT &B5,dx%-COS(l%/p+I%/a)*asize%,dy%-SIN(l%/p+I%/a)*asize%
NEXT
NEXT
NEXT
ENDPROC
:
DEFPROCstart
SYS sprite%,swap%,area%
OFF
next%=0
D%=1
L%=1
GCOL 7
PRINT TAB(10,14) "Press a key to start"
IF GET
VOICE 1,"Percussion-Medium"
ENDPROC
:
DEFPROCshoot
IF piny%(next%) ENDPROC
pinx%(next%)=gx%+gwide% DIV 2
piny%(next%)=gy%+ghigh%+4
POINT pinx%(next%),piny%(next%)
next%+=1
IF next%=pins% next%=0
ENDPROC
:
DEFPROCgun(x%)
IF gx%+x%<xmin% OR gx%+x%>xmax% ENDPROC
gx%+=x%
ENDPROC
:
DEFPROCpin(x%,RETURN y%,RETURN wx%,RETURN wy%,RETURN wf%)
POINT x%,y%
y%+=4
IF y%>ymax% y%=0:ENDPROC
IF POINT(x%,y%) AND 7 PROCcollide
ENDPROC
:
DEFPROCcollide
wx%=x%-x%(J%)
wx%-=(wx% MOD ax3%)
wy%=y%-y%(J%)
wy%-=(wy% MOD ax3%)
wf%=1
y%=0
SYS sprite%,put%,area%,blot%,wx%+x%(J%),wy%+y%(J%)
VDU 7
ENDPROC
:
DEFPROCwipe(x%,y%,RETURN f%)
SYS sprite%,swap%,area%,frame%(f%-1)
GCOL 0
RECTANGLE FILL x%,y%,ax2%+8,ax2%
SYS sprite%,swap%,area%
OFF
f%+=1
IF f%>8 f%=0
ENDPROC
By now you should be familiar with the techniques used for building sprites and animating them. The only complication is interleaving the main sprite movement with it's animation and the rest of the game. I could have cheated and made the movement count an exact multiple of the frames of animation but I decided to retain the flexibility of keeping a separate counter, L%, for the animation, with D% as a switch for running up and down through the sprite list, effectively doubling the number of frames.
Movement of the gun is quite straightforward and could have been animated. In the same way. You could animate the actual firing sequence,giving, for example, a little puff of smoke and a shower of sparks as it fires.
Arranging the firing of the bullets seems a bit complicated at first. Arrays are maintained for up to eight bullets. When the 'fire' key is pressed the Y coordinate of the currently selected bullet is examined to see if it is in motion and so can't be fired again. If the bullet isn't active then its X coordinate is set to the centre of the gun and the Y coordinate to the tip. It is then plotted for the first time and the counter for the bullets incremented for the next free one.
Once started each bullet will move up the screen for every pass of the main loop, until it has either reached top of the screen or it collides with an alien. In either case its Y coordinate is set to zero, marking it as no longer active.
In PROCcollide the alien hit is identified by calculating its coordinates in the overall sprite and the absolute screen position of the sprite in its movement track. The coordinates are marked and a flag set for the wiping routine.
Actual erasing of the alien has to be spread through the animation sequence as every frame must have the alien at that position erased. You will see that the alien wiping routine is interleaved with the bullet movement to ensure that two bullets don't claim the same alien. Each time PROCwipe is entered screen writing is re-directed to the main sprite, the rectangle filled and screen writing restored. The counter is then updated. When all aliens have been erased, the counter is zeroed to prevent further entries in PROCwipe for that alien.
This complexity is necessary as you can't be sure on which frame a collision takes place, so you need to count through all of them. In fact there is a bug in this routine as I failed to allow for the fact that if you are half way through the animation sequence you will still fail to erase some sprites as the routine just counts sprites to erase. It doesn't allow for the fact that the animation is counting up then down again. A sledgehammer cure would be to simply double the count, and hence the explosion time. A more elegant solution involves a flag table. I'll leave it to you to work this out if you wish.
There is much room for improvement in other ways, but the essential ideas are there. It is a little jerky when everything is happening at once, but bear in mind that this program is running in interpreted Basic yet is still superior to many eight bit machines running machine code. These often use a 25 frames per second film speed whereas in this example we manage 50 fps, the screen refresh rate, most of the time. With the type of movement of the aliens the game could easily be run at 25 fps, doubling the movement distances, with little loss of smoothness.
7.1.3 Flocking
Some games have developed the idea of groups of enemies homing in on the player's object. This 'flocking' is easy to implement. The objects you want to flock have their positions and movement vectors stored in arrays. The position elements for each flock object are compared with that of the victim and the movement elements altered accordingly so the flock is always moving towards the victim.
This would probably make the flocking too savage for most games, giving the player little chance of escape. The usual remedy is to add flocking deviation in the form of a random fluctuation in the flock object's movement vectors. Many games then adopt an approach of reducing the deviation with each attack wave or game level, making it progressively harder to escape.
A more subtle approach is to modify the deviation by an amount dependent on each flock object's distance from the victim, so as it 'sees' the victim it becomes steadily more determined to reach it. Alternatively you could make the player's object repel the flocking objects, as with sheep and a sheepdog.
In listing 7.3 this is done with stars as the flock and the mouse pointer as victim.
REM > Flocks
:
ON ERROR PROCerror:END
PROCinitialise
:
REPEAT
WAIT
SYS byte%,113,sc%
sc%=sc% EOR 3
SYS byte%,112,sc%
CLS
MOUSE x%,y%,b%
IF b%=4 REPEAT UNTIL NOT INKEY-10:repel%=1-repel%
vx()=x%-x()
vy()=y%-y()
distx()=vx()*vx()
disty()=vy()*vy()
distx()=distx()+disty()
FOR I%=0 TO num%
IF x(I%)<xmin% OR x(I%)>xmax% dx(I%)=-dx(I%):x(I%)+=dx(I%)
IF y(I%)<ymin% OR y(I%)>ymax% dy(I%)=-dy(I%):y(I%)+=dy(I%)
GCOL I%+1
MOVE x(I%),y(I%)
PRINT"*";
IF distx(I%)<scatter% distx(I%)=scatter%
vx(I%)=force%*SGN vx(I%)
vy(I%)=force%*SGN vy(I%)
NEXT
vx()=vx()/distx()
vy()=vy()/distx()
dx()=dx()-vx()
dy()=dy()-vy()
dx()=dx()*damp
dy()=dy()*damp
IF repel% x()=x()+dx():y()=y()+dy() ELSE x()=x()-dx():y()=y()-dy()
UNTIL b%=2
*FX 112 1
*FX 113 1
END
:
DEFPROCerror
MODE 12
PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 13
MODE 9
*Pointer 1
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
sc%=1
num%=6
DIM x(num%),y(num%)
DIM dx(num%),dy(num%)
DIM vx(num%),vy(num%)
DIM distx(num%),disty(num%)
xmin%=-630
xmax%=610
ymin%=-490
ymax%=510
xs%=xmax%-xmin%
ys%=ymax%-ymin%
force%=6400
scatter%=900
damp=0.9
repel%=0
FOR I%=0 TO num%
x(I%)=(RND(xs%)-xs% DIV 2)DIV 2
y(I%)=(RND(ys%)-ys% DIV 2)DIV 2
NEXT
ORIGIN 640,512
PRINT TAB(10,7) "Flocks and Herds" TAB(5,12) "Move mouse to track" TAB(5,14)
"Select - toggle attract/repel" TAB(5,16) "Menu - quit"
TAB(5,20) "Any button to start";
REPEAT
MOUSE x%,y%,b%
UNTIL b%=0
REPEAT
MOUSE x%,y%,b%
UNTIL b%
REPEAT
MOUSE x%,y%,b%
UNTIL b%=0
VDU 5
ENDPROC
I've made heavy use of the whole array calculations in this example. Although making it rather long-winded and harder to follow the program runs about 30% faster than it would if all the calculations were done inside the main FOR-NEXT loop. Some operations can't be performed on whole arrays so these are inside the loop along with the printing of the stars.
Despite its apparent complexity there are no new programming ideas. For each object an attractive force is calculated, based on its distance from the pointer, and put in vx(), vy(). This is used to modify the movement vectors dx(),dy(), which in turn modify the X,Y coordinates of the object. Finally, a damping factor is used on dx(),dy() to prevent oscillations building up. The repel% flag determines whether the movement vector is subtracted or added, thus giving attraction or repulsion.
In the FOR-NEXT loop a check is made against scatter% to see if an object is too close to the pointer. It stops acceleration becoming too fierce and guards against zero division. You will see that I've cheated by using the square of the distance to speed things up.
Screen limit testing is only necessary to stop repelled objects going off screen.
In a game using this technique a degree of randomness can still still be retained and you can use the various game levels and attack waves to set the number of flock objects and the distance at which they seem to become aware of the victim.
A further enhancement to flocking can be provided by making all the flock objects repel each other. This will result in a realistic jostling action. This is most easily done with pixel collision detection in the main collision detection routine.
7.2 Rebounds
Probably the best known rebound game is Breakout, in it's many guises. One of the attractions of the earlier forms, both for players and programmers, is the limited freedom of movement of the ball. This is usually restricted to one of eight possible directions, as shown in Figure 7.2. With the usual rectangular play area and rectangular objects you will see that there are very few bounce conditions that have to be met. Usually there is no in-flight change of speed or direction, so straight line interpolations are adequate.
In the first two examples shown you only need reverse the sign of the Y vector for bounce. If it is a side wall that is met then it is the X vector that needs to be reversed. The third example requires the sign of both X and Y vectors to be reversed. Object collisions are similar. The only complicated one is collision with the corner of an object. Three possible bounces are simply chosen at random. This gives variety and makes the game more interesting.
It is unlikely that you would get away with such a simple structure so you have to consider more realistic bounce conditions. First more realistic rebounds. This means, in the extreme, taking a tangent at the point of impact and calculating X and Y vector quantities. However, we can get very presentable results by simply increasing the number of bounce surfaces and permissible directions.
If you use pixel collisions with different colours for all object and border types then on any collision you can read the type of bounce that must take place with a simple CASE statement. You may, for example, have a soft mat at the bottom of the play area that gives a reduced vertical speed.
7.2.1 Bounce Patterns
A common problem with this type of game is that of the unreachable brick. This is caused by having a tightly controlled bounce pattern on an effective grid that is wider spaced than than the length of the smallest brick. One solution would be to draw out all the possible bounce patterns and look for holes where a brick might be sitting. A far more practical solution is to add a very small random element to the X and Y coordinates on certain bounces that you know will regularly occur. The obvious first choice for this, in bat and ball games, is the bat itself. Other likely points are side and top edges. However, you only need to add this random element to a few surfaces that are guaranteed to be met, overdoing it will just slow the game down. This technique will ensure that every point of the playing area is reachable, although getting the last brick may still take quite a time.
7.2.2 Baby Breakout
By the time you get this far you've actually got rid of the fixed bounce patterns completely. You can see this more clearly in listing 7.4, a cut-down rebound game.
REM > Rebound
:
ON ERROR PROCerror:END
PROCinitialise
PROCscreen
REPEAT
PROCstart
REPEAT
WAIT
CIRCLE FILL x%,y%,8
x%+=dx%
y%+=dy%
bounce%=POINT(x%,y%)
IF bounce% PROCbounce ELSE IF glue% x%=rx%+rw% DIV 2
RECTANGLE FILL rx%,ry%,rw%,rh%
IF INKEY-98 PROCleft ELSE IF INKEY-67 PROCright
RECTANGLE FILL rx%,ry%,rw%,rh%
CIRCLE FILL x%,y%,8
IF INKEY-74:IF glue% glue%=FALSE:dy%=RND(6)+3
UNTIL end%
CIRCLE FILL x%,y%,8
RECTANGLE FILL rx%,ry%,rw%,rh%
*FX 21
t%=TIME
REPEAT UNTIL TIME-t%>100
UNTIL bnum%=0
END
:
DEFPROCerror
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 13
MODE 9
OFF
COLOUR 8,192,192,192
COLOUR 9,96,96,96
PRINT TAB(11,7) "Basic Rebound Game" TAB(5,11) "Z - Left"
TAB(5,13) "X - Right" TAB(5,15) "Return - Release ball"
TAB(12,19) "Escape to exit" TAB(11,21) "Any key to start"
IF GET
CLS
min%=30 :REM no objects to be smaller
rs%=12 :REM bat speed
rw%=196 :REM bat width
rh%=32 :REM bat height
ry%=min%+4 :REM bat Y axis
bw%=64 :REM brick width
bh%=32 :REM brick height
hb%=bw% DIV 2 :REM half brick
wy%=bh%*16 :REM wall position
bn%=15 :REM bricks per course
wh%=8 :REM wall height
z%=rh%+ry%+12 :REM ball on bat Y axis
s%=min%-8 :REM ball speed
ENDPROC
:
DEFPROCscreen
GCOL 1
RECTANGLE FILL 0,0,min%,1023
RECTANGLE FILL 1280-min%,0,min%,1023
GCOL 2
RECTANGLE FILL 0,0,1279,min%
RECTANGLE FILL 0,1024-min%,1279,min%
off%=hb%
FOR J%=0 TO wh%
off%=off% EOR hb%
IF off% PROCbrick(bw%*2,wy%+bh%*J%,bw% DIV 2,bh%)
FOR I%=0 TO bn%+(off%>0)
PROCbrick(bw%*2+off%+bw%*I%,wy%+bh%*J%,bw%,bh%)
NEXT
IF off% PROCbrick(bw%*2+off%+bw%*bn%,wy%+bh%*J%,bw% DIV 2,bh%)
NEXT
GCOL 4
FOR I%=0 TO 8
GCOL 8+(I% MOD 2)
RECTANGLE FILL 96+I%*128,0,64,min%
NEXT
GCOL 5
MOVE 0,1023
MOVE 0,895
PLOT &55,128,1023
GCOL 6
MOVE 1279,1023
MOVE 1279-128,1023
PLOT &55,1279,895
bnum%=(bn%+1)*(wh%+1)+(wh%+1)DIV 2
ENDPROC
:
DEFPROCbrick(x%,y%,w%,h%)
GCOL 3
RECTANGLE FILL x%,y%,w%-4,h%-4
GCOL 4
RECTANGLE x%,y%,w%-4,h%-4
ENDPROC
:
DEFPROCstart
end%=FALSE
glue%=TRUE
rx%=480
x%=rx%+rw% DIV 2
y%=z%
dx%=0
dy%=0
GCOL 3,7
CIRCLE FILL x%,y%,8
RECTANGLE FILL rx%,ry%,rw%,rh%
ENDPROC
:
DEFPROCbounce
a%=ABS dx%
b%=ABS dy%
c%=SGN dx%
d%=SGN dy%
CASE bounce% OF
WHEN 1:x%-=dx%:dx%=-dx%
WHEN 2:y%-=dy%:dy%=-dy%:c%+=RND(3)-2
WHEN 3,4:PROChit
WHEN 5:x%-=dx%:y%-=dy%:SWAP dx%,dy%
WHEN 6:x%-=dx%:y%-=dy%:SWAP dx%,dy%:dy%=-dy%:dx%=-dx%
WHEN 7:PROCbat
WHEN 8:y%-=dy%:dy%=-dy% DIV 2
WHEN 9:end%=TRUE
ENDCASE
IF a%<4 dx%=4*c% ELSE IF a%>s% dx%=s%*c%
IF b%<4 dy%=4*d% ELSE IF b%>s% dy%=s%*d%
VDU 7
ENDPROC
:
DEFPROCleft
IF rx%>min%+rs% rx%-=rs%
ENDPROC
:
DEFPROCright
IF rx%+rw%<1280-min%-rs% rx%+=rs%
ENDPROC
:
DEFPROChit
by%=y% DIV bh%*bh%
y%-=dy%
IF by% DIV bh% MOD 2 bx%=(x%+hb%)DIV bw%*bw%:bx%-=hb% ELSE bx%=x% DIV bw%*bw%
x%-=dx%
GCOL 0
RECTANGLE FILL bx%,by%,bw%,bh%
GCOL 3,7
dy%=-dy%
IF a%<3 c%=RND(5)-3
bnum%-=1
IF bnum%=0 end%=TRUE
ENDPROC
:
DEFPROCbat
y%=z%
dy%=b%+4
IF RND(9)>1 dx%+=RND(3)-2 ELSE glue%=TRUE:dx%=0:dy%=0
ENDPROC
In this example, I've deliberately kept the logical colours used looking visibly different so that you can see how the surfaces are built up. In practice you would make all the edges look the same colour. If you are using more attractive sprites in a 256 colour mode, then you'd probably be better off using a combination of tints and coordinates for collisions.
By limiting the ball speed to less than the thickness of any object, I've avoided the need for a complex look-ahead system. Also, by using Exclusive OR plotting any slight overlaps will go unnoticed.
Working out which brick has to be removed is done in PROChit by turning the ball coordinates into exact brick multiples, with a half brick horizontal offset being added for the even numbered courses. Simple rectangle filling then erases the brick.
I've fiddled the bounce of the ball on the bat so that the ball always sits on top of it. This saves having to do anything special about edge hits.
A point of interest is the way the ball follows the bat when you start. This is controlled by the flag glue%. If it is set then the ball's X coordinate is continually adjusted to that of the centre of the bat. You could easily make this flag a countdown timer, so that if the player doesn't release the ball after a certain number of game loops then it releases itself.
7.2.3 Spin
It is worth considering some form of allowance for forward and reverse spin in bat and ball type rebounds. This is simple to implement, but only really practical if you allow all direction movement instead of the eight direction forms. All you need do is perform a rotation of the X,Y vectors in the same way as was used for 3D rotation. The amount of spin determines the rotation angle. The direction, of course, relates directly. The program fragment below assumes dx% and dy% are the vectors, and ang% is the amount of spin that you want to emulate.
tempx%=dx%
dx%=dx%*COS(ang%)-dy%*SIN(ang%)
dy%=dy%*COS(ang%)+tempx%*SIN(ang%)
Normally this spin only takes effect when objects come into contact with a solid surface. However, you can simulate drag effects of spinning objects through air or water. To do this, continually add a tiny fraction of the spin angle every pass of the game loop. The won't be terribly accurate, but will look convincing enough.
7.2.4 Gravity
Another useful extension is for objects to attract or repel each other in flight. To be realistic you have to consider how gravitation and magnetic forces work although you don't have to be too precise. I'll only look at the two body situation. If you want to go further then you'll have to delve into some physics books.
At first the total attractive force between two objects is inversely proportional to the square of the distance between them. It will also be proportional to the product of their masses. In our game world it is generally easiest to equate mass with size as we already need this figure for collision calculations. The actual deflection of each object will be inversely proportional to the mass ratio. Armed with that information we can produce the example of listing 7.5
REM > Attract
:
ON ERROR PROCerror:END
PROCinitialise
PRINT TAB(30,12) "Press Escape to stop"
IF INKEY 100
FOR I%=0 TO loops%
flag%=FALSE
IF INKEY 50
CLG
READ mass1%,mass2%
READ x1,y1,x2,y2
READ dx1,dy1,dx2,dy2
ratio=mass1%/mass2%
PROCplot
REPEAT
WAIT
SYS byte%,113,sc%
sc%=sc% EOR 3
SYS byte%,112,sc%
CLS
dist=(x2-x1)^2+(y2-y1)^2
IF dist<1 dist=1:REM prevents division by zero
attract=(mass1%*mass2%)/dist
xs%=SGN(x2-x1)
ys%=SGN(y2-y1)
dx1+=(attract/ratio*xs%)
dx2-=(attract*ratio*xs%)
dy1+=(attract/ratio*ys%)
dy2-=(attract*ratio*ys%)
x1+=dx1
x2+=dx2
y1+=dy1
y2+=dy2
PROCplot
IF x1<xmin% OR x1>xmax% flag%=TRUE
IF x2<xmin% OR x2>xmax% flag%=TRUE
IF y2<ymin% OR y2>ymax% flag%=TRUE
IF y1<ymin% OR y1>ymax% flag%=TRUE
IF dist<(mass1%+mass2%)^2 flag%=TRUE
UNTIL flag%
NEXT
PRINT'"End of program"
END
:
DEFPROCerror
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
MODE 15
MODE 12
OFF
COLOUR 0,0,0,128
SYS "OS_SWINumberFromString",,"OS_Byte" TO byte%
sc%=1
xmin%=0
xmax%=1279
ymin%=0
ymax%=1023
RESTORE+3
READ loops%
ENDPROC
DATA 3
DATA 20,40
DATA 320,1000,800,1000
DATA 0,-1,0,-3
DATA 17,34
DATA 768,704,384,448
DATA -1,0,.4,0
DATA 32,32
DATA 256,512,1023,512
DATA .4,-.75,-.4,.75
DATA 8,128
DATA 256,512,640,512
DATA 0,8.5,0,-.03
:
DEFPROCplot
GCOL 3
CIRCLE FILL x1,y1,mass1%
GCOL 1
CIRCLE FILL x2,y2,mass2%
ENDPROC
The simulation isn't perfect because of inaccuracies in the mathematics and the limits of precision of Basic V. However the results are quite good enough for all but the most exacting cases.
You will see that the distance calculation is used not only for the deflection calculations but also for collision detection. It's always rather nice when you can make one piece of arithmetic do two jobs.
To repel objects just reverse the signs of the dx and dy additions and subtractions.
7.3 Platforms
The basis for most platform games is the old board game snakes and ladders. However, instead of relying on chance dice throws you have direct control of the character. Jumps and lifts take the place of ladders while holes, monsters and 'soft' platforms play the part of snakes.
One of the features most platform games have in common with the older rebounds is that there is very restricted movement.
By the nature of the game platform games assume the player's character is always on a platform of some sort. This presents a small problem when looking at collisions. Not only do you have to look ahead in the direction you are moving but also down to check that the platform is still there and what type of platform it is.
You may chose to write a game where the player character can stick to a wall or ceiling, in which case up, left or right may be another direction that must be checked. To further complicate matters if you are handling a jump you may need to modify the second direction part until the completion of the jump action.
As usual there's more than one solution to the problem. You could keep a list of all the coordinates where a change in direction is required, but this would be rather tedious and inflexible. A better idea is to use cell collisions, and instead of marking the row of cells below the players object, mark the actual row that the player is on with, say, a 1 value. Now, whichever way the object moves it will see the track of ones. Any other value would be invalid for normal movement. Zero would be acceptable for jumps, and other values would represent collisions.
The zero value would be particularly important as in the event of no other movement information from the player or other movement adjustment in the game you would assume that gravity takes over and the object moves gracefully down until it is sitting over some other value. If you keep count of the number of cells dropped in this way you can decide whether it was a survivable drop or not and take the appropriate action. This is shown in listing 7.6.
REM > Platform
:
ON ERROR PROCerror:END
PROCinitialise
PROCassemble
REPEAT
PROCbuild
PROCdraw
PROCstart
REPEAT
FOR mark%=0 TO 7
CALL code%
monx%()=monx%()+mondx%()
mony%()=mony%()+mondy%()
FOR I%=0 TO mons%
IF mark%=0 PROCmonmove
CIRCLE FILL monx%(I%)+half%,mony%(I%)+half%,10
NEXT
CASE objects%(nx%>>5,ny%>>5) OF
WHEN 0,15:PROCdrop(0,-32)
WHEN 7:mark%=7:end%=TRUE
WHEN 9,11,13:IF count%=0 PROCsink
ENDCASE
IF count% PROCanimate ELSE PROCkey
CIRCLE FILL X%+half%,Y%+half%,circ%
FOR I%=0 TO mons%
IF mark%=0 objects%(monx%(I%)>>5,mony%(I%)>>5)=oldmon%(I%)
NEXT
NEXT
UNTIL end%
VDU 7
UNTIL FALSE
END
:
DEFPROCerror
*FX 4
*FX 21
MODE 12
IF ERR<>17 PRINT REPORT$ " @ ";ERL
ENDPROC
:
DEFPROCinitialise
*FX 4 1
MODE 15
MODE 9
PRINT TAB(12,6) "Platform Demo" TAB(5,9) "Z - Left" SPC 9 "X - Right"
TAB(5,11) "' - Up" SPC 11 "/ - Down"
TAB(5,13) "Return - Jump" SPC 4 "Escape - Stop"
COLOUR 8,128,128,128
COLOUR 9,128,128,128
COLOUR 10,128,128,128
COLOUR 11,128,128,128
VDU 5
DIM objects%(39,31)
DIM plot%(39,31)
circ%=12
vert%=32-4
half%=16
maxmonsters%=9
DIM monx%(maxmonsters%)
DIM mondx%(maxmonsters%)
DIM mony%(maxmonsters%)
DIM mondy%(maxmonsters%)
DIM oldmon%(maxmonsters%)
VDU23,129,&81,&81,&FF,&81,&81,&81,&FF,&81
VDU23,130,&FF,&00,&FF,&00,&FF,&00,&FF,&00
VDU23,131,&AA,&55,&AA,&55,&AA,&55,&AA,&55
VDU23,132,&FF,&FF,&C3,&C3,&C3,&C3,&FF,&FF
VDU23,136,&AA,&55,&AA,&55,&AA,&55,&AA,&55
VDU23,137,&00,&00,&00,&00,&AA,&55,&AA,&55
VDU23,138,&00,&00,&AA,&55,&00,&00,&00,&00
VDU23,139,&AA,&55,&00,&00,&00,&00,&00,&00
X%=-1
ENDPROC
:
DEFPROCassemble
DIM block% &100
block%!0=148
block%!4=7
block%!8=-1
SYS "OS_ReadVduVariables",block%,block%+12
!block%=1 : REM bank number
block%!4=block%!16 : REM screen size
block%!8=block%!12 : REM screen start
block%!12+=(block%!16)*2 : REM stored screen start
block%!16+=block%!12 : REM stored screen end
lowreg=0
highreg=7
bank=7
size=8
screen=9
memory=10
end=11
store=12
link=14
code%=block%+20
FOR I%=0 TO 2 STEP 2
P%=code%
[ OPT I%
ADR store,block%
LDMIA store,
MOV R0,#19
SWI "OS_Byte"
MOV R0,#113
MOV R1,bank
SWI "OS_Byte"
EOR bank,bank,#3
MOV R0,#112
MOV R1,bank
SWI "OS_Byte"
CMP bank,#2
ADDEQ screen,screen,size
STR bank,[store]
.copy
LDMIA (memory)!,
STMIA (screen)!,
CMP memory,end
BLT copy
MOV PC,link
]
NEXT
ENDPROC
:
DEFPROCbuild
RESTORE+21
READ num%
FOR J%=0 TO num%
READ x%,y%,horiz%,count%,value%
FOR I%=0 TO count%
IF horiz% THEN
objects%(x%+I%,y%)=value%
ELSE
objects%(x%,y%+I%)=value%
ENDIF
NEXT
NEXT
READ single%
FOR I%=0 TO single%
READ x%,y%,value%
objects%(x%,y%)=value%
NEXT
:
READ num%
FOR J%=0 TO num%
READ x%,y%,horiz%,count%,char%
FOR I%=0 TO count%
IF horiz% THEN
plot%(x%+I%,y%)=char%
ELSE
plot%(x%,y%+I%)=char%
ENDIF
NEXT
NEXT
READ single%
FOR I%=0 TO single%
READ x%,y%,char%
plot%(x%,y%)=char%
NEXT
REM monsters must be defined after all other objects
READ mons%
FOR I%=0 TO mons%
READ monx%(I%),mony%(I%),mondx%(I%),mondy%(I%)
monx%(I%)=monx%(I%)<<5
mony%(I%)=mony%(I%)<<5
NEXT
ENDPROC
DATA 11: REM objects
DATA 20,3,1,15,1
DATA 1,15,1,37,1
DATA 1,24,1,28,1
DATA 32,26,1,6,1
DATA 19,3,0,12,3
DATA 2,16,0,8,3
DATA 29,16,0,8,3
DATA 0,0,1,39,7
DATA 0,1,0,29,7
DATA 39,1,0,29,7
DATA 0,31,1,39,7
DATA 30,24,1,5,9
DATA 7
DATA 12,24,15
DATA 19,2,15
DATA 1,15,17
DATA 20,15,17
DATA 1,3,17
DATA 32,3,17
DATA 34,3,17
DATA 34,25,17
:
DATA 11: REM visible bits
DATA 20,2,1,15,130
DATA 1,14,1,37,130
DATA 1,23,1,28,130
DATA 32,25,1,6,130
DATA 19,2,0,12,129
DATA 2,15,0,8,129
DATA 29,15,0,8,129
DATA 0,0,1,39,132
DATA 0,1,0,29,132
DATA 39,1,0,29,132
DATA 0,31,1,39,132
DATA 30,23,1,5,136
DATA 0
DATA 12,23,131
:
DATA 2: REM monsters
DATA 2,15,4,0
DATA 28,3,-4,0
DATA 34,4,0,4
:
DEFPROCdraw
*FX 112 3
CLS
FOR J%=0 TO 39
FOR I%=0 TO 31
GCOL plot%(J%,I%) AND 127
IF plot%(J%,I%) MOVE J%<<5,(I%<<5)+vert%:VDU plot%(J%,I%)
NEXT
NEXT
E%=1
*FX 112 1
GCOL 7
ENDPROC
:
DEFPROCstart
IF X%<0 THEN
PRINT TAB(5,17) "Any key to start"
IF GET
ENDIF
X%=28<<5
Y%=15<<5
nx%=X%
ny%=Y%
count%=0
end%=FALSE
ENDPROC
:
DEFPROCmonmove
oldmon%(I%)=objects%(monx%(I%)>>5,mony%(I%)>>5)
IF objects%(monx%(I%)>>5,mony%(I%)>>5)=17 PROCmonrev
ELSE objects%(monx%(I%)>>5,mony%(I%)>>5)=7
ENDPROC
:
DEFPROCmonrev
mondx%(I%)=-mondx%(I%)
mondy%(I%)=-mondy%(I%)
ENDPROC
:
DEFPROCsink
objects%(X%>>5,Y%>>5)+=2
y%=Y%-32
*FX 112 3
plot%(X%>>5,y%>>5)+=1
GCOL 3,8
MOVE X%,y%+vert%
VDU plot%(X%>>5,y%>>5)
*FX112 1
GCOL 7
ENDPROC
:
DEFPROCanimate
X%+=ix%
Y%+=iy%
count%-=1
ENDPROC
:
DEFPROCkey
CASE TRUE OF
WHEN INKEY-98:PROCmove(-32,0)
WHEN INKEY-67:PROCmove(32,0)
WHEN INKEY-80:PROCmove(0,32)
WHEN INKEY-105:PROCmove(0,-32)
WHEN INKEY-74:PROCmove(0,0)
ENDCASE
ENDPROC
:
DEFPROCmove(h%,v%)
IF INKEY-74 v%=64
IF v%=64 OR objects%((X%+h%)>>5,(Y%+v%)>>5)AND 1 THEN
nx%=X%+h%
ny%=Y%+v%
ix%=h%>>3
iy%=v%>>3
X%+=ix%
Y%+=iy%
count%=7
ENDIF
ENDPROC
:
DEFPROCdrop(h%,v%)
IF count%=0 THEN
nx%=X%+h%
ny%=Y%+v%
ix%=h%>>3
iy%=v%>>3
count%=8
ENDIF
ENDPROC
Instead of restricting the player's character to movement over cells marked with the value 1, to allow for a range of additional action the game recognises any odd number value as being valid for the player to move onto. In the example this allows ladders, platforms, and soft platforms all to be uniquely recognised.
You will see that cell collisions allow for automatic movement of the monsters as well as the player. All that is needed is a marker value in the object array at the end of each monster's track. Value 17 in used in this example. This lets the player move over it unimpeded while causing a reversal of monster movement direction.
While using cells for the collision system, the actual coordinates have all been kept in graphic units. This makes plotting and animation easier and faster. Where array accessing has to be done, a simple barrel shift is all that's necessary to find the array indices. In this example the animation simply consists of a straight line plot. In fact you would normally use a film animation, and for jumps you would plot out a curved trajectory taken from a suitable movement table.
To give further flexibility I've separated the actual platforms from their visible representations by using two different arrays. If you go on to use a table of sprites for the creating the visible platforms then you can add various different types of edging and platform without having to disturb the main sensing array. This particularly relevant for things like brittle stalactites, or soft and collapsing platforms. These are an essential feature in the best games, and the crude character swapping I've done in the example just doesn't do the idea justice.
You will see that I've modified the screen Store and Copy routine to use a spare screen bank. This allows the screen to be drawn invisibly at a leisurely pace with ordinary drawing commands and, by bank switching, allows the stored screen to be modified. In this example we only use the feature for soft platforms, but it would be applicable to picking up treasures etc.
In the example all the monsters have the same effect - they kill you. However, if you keep another parallel array of monster type then you can have different actions by embedding a different number in the main object array. This can include collectable items.
There is a bug in the game. If you move towards one of the monsters while it is moving towards you then under some circumstances you will miss each other. This happens because if the two counters count% and mark% become synchronised your object and the monster will simply swap places in the same game loop.
7.4 Map Compression
The two dimensional grid used by the previous example can be considered as a basic map, or more correctly a pair of maps. As shown these are wasteful of memory, bearing in mind that you will need a similar pair for every game level.
The first thing to realise is that you don't need to use full integers for the information in the arrays. It is unlikely that you would want more than 255 different cell types or sprite blocks so you can immediately think about splitting up integers into four byte numbers consisting of consecutive cells. This works particularly well if you intend to put some of the program into ARM code.
Unfortunately the integer splitting and, where necessary, re-combining is rather time consuming, so a better idea is to use a byte array. The program fragment below shows how a two dimensional array can be synthesised from this in Basic.
wide%=39 : REM number of cells across - 1
high%=31 : REM number of cells down - 1
size%=(high%+1)*(wide%+1)
DIM objects% size%
REM some code
IF FNread(X%,Y%)>23 REM do something
REM more code
PROCwrite(X%,Y%,monstertype%)
DEFFNread(xpos%,ypos%)
=objects%?(xpos%+ypos%*wide%)
DEFPROCwrite(xpos%,ypos%,byte%)
objects%?(xpos%+ypos%*wide%)=byte%
ENDPROC
If you can limit your object values and sprite list to just sixteen each, then you can combine the two arrays as below.
DEFFNreadobject(xpos%,ypos%)
=(objects%?(xpos%+ypos%*wide%))AND 15
DEFFNreadsprite(xpos%,ypos%)
=(objects%?(xpos%+ypos%*wide%))AND 240
Object numbers are now from 0 to 15 and sprites are 16 to 240 in steps of sixteen. Barrel shifting the sprite numbers to get 0 to 15, while possible, serves no useful purpose and wastes processor time.
For the size of screen we've been discussing map data is now only 1280 bytes per level. These levels are best constructed in an editor program of some sort then saved as binary files. These can then be loaded directly into the game. If your game turns out to be really successful, you can offer the editor program as an extra. This has been done with many commercially produced games.
By using a byte array you can instantly switch to another level. Simply store all the levels as one continuous line of bytes, then instead of using the array base itself, use a pointer that can be stepped up and down in whole level blocks, as below.
levels%=30
step%=1280
DIM objects% step%*levels%
point%=objects%
top%=point%+step%*(levels%-1)
REM some code
IF donelev% AND point%<top% point%+=step%:PROCdrawit
REM yet more
DEFFNreadsprite(xpos%,ypos%)
=(point%?(xpos%+ypos%*wide%))AND 240
7.5 Score Tables
It is tempting to forget about score tables until a game is completed and then hurriedly tack it on, and as a result it is pretty awful. You should make score tables an integral part of the game. Include bonus points achieved, completion times and the number and type of aliens dispatched.
Players may not bother to enter a name, leaving a blank entry by their score. Some programmers try to prevent this by making the scoring routine insist on three or more letters. This tends to irritate players who just want to get on with the game. A better approach is to set up an array of silly names. If the player just hits the return key without an entry, pick one of these at random.
You should provide the player with the option of saving a score table, re-loading it, and especially deleting the stored copy and starting from scratch, otherwise continual playing of the game will eventually produce a situation where it is impossible for anyone to get their name on the table, which will discourage further play. It is also worth considering saving only part of the table.
If you have a table of eight entries, which is fairly typical, only save the top four. When you next reload the score table you add four synthesised fairly mediocre scores at the bottom. This means that any player has a chance of getting onto the table. Persistent average players will always have their names saved, and the really good players can compete with each other for the top few places.
Time spent livening up the your score table routine adds polish, as mentioned in the section on layouts. It is still common to see games using the system font for score tables, either because the programmer can't handle Acorn's fancy fonts or through lack of disk space.
One possible solution to this is to develop your own set of drawn characters. To do this you will have to plan out each character on graph paper, then translate the lines into a combination of MOVE, DRAW, CIRCLE, and other plot commands. These can then be read off from lines of data in the game. The result is very compact, albeit tedious to develop.
An example of a score table that covers most of the points above is given in listing 7.7. This was originally developed on the BBC Model B, and the ARM code section added for extra speed with a few other improvements. This is provided 'as is', but you can experiment with it to see how it works and what the possibilities are.
REM > Scores
:
PROCinitialise
PROCassemble
PROCloadscores
:
PROCtable(8020)
PROCtable(6741)
PROCtable(4127)
PROCtable(391)
END
:
DEFPROCinitialise
DIM score$(8),score%(8)
DIM font$(94)
DIM code% &100
DIM D% 100,T% 80,U% 20 : REM don't use elswhere!!
file$="ScoreTable"
RESTORE+4
FOR I%=13 TO 90
READ font$(I%)
NEXT
ENDPROC
DATA DZ0NH1
DATA IH0@I1
DATA
DATA FH0FH1DJ1BL1@`1>L1<J1:H1<F1>D1@01BD1DF1
DATA FH0FH1=H0@l1?F1>G1
DATA @f0BL1DJ1FH1DF1BD1@B1>D12:1>D1@F1RH1
DATA @f0BL1DJ1FH1DF1BD1@B1>D1<F1DF1BD1@B1>D1<F1:H1<J1>L1
DATA LH0@l1401RH1
DATA Rl0.H1@61BL1DJ1FH1DF1BD1@<1>D1<F1:H1<J1>L1
DATA Rf0>L1<J1:H1<F1>D1@01BD1DF1FH1DJ1BL1@T1>L1<J1:H1<F1>D1
DATA @l0RH1.$1
DATA FH0FH1DJ1BL1@N1>L1<J1:H1<J1>L1@N1BL1DJ1FH1DF1BD1@B1>D1<F1:H0<F1>D1@B1BD1DF1
DATA @N0BD1DF1FH1DJ1BL1@`1>L1<J1:H1<F1>D1@<1BD1DF1FH1DJ1BL1
DATA,,,,,,
DATA @f1BL1DJ1FH1DF1BD1@*1@Z0.H1
DATA LH1DJ1BL1@N1>L1<J14H1JH0DJ1BL1@N1>L1<J16H1@$1
DATA RN0>D1<F1:H1<J1>L1@`1BL1DJ1FH1DF1BD1
DATA LH1DJ1BL1@`1>L1<J14H1@$1
DATA RH1.Z0LH1FZ0.H1@$1
DATA @Z0LH1FZ0.H1@$1
DATA RH0@Z1:H1F<0>D1<F1:H1<J1>L1@`1BL1DJ1FH1DF1BD1
DATA @l1@60RH1@Z0@$1
DATA FH0FH1=H0@l1CH0:H1
DATA @N0BD1DF1FH1DJ1BL1@f1
DATA @l1@00R`1481L41
DATA RH1.H0@l1
DATA @l1I$1Il1@$1
DATA @l1R$1@l1
DATA @N0BD1DF1FH1DJ1BL1@`1>L1<J1:H1<F1>D1@01
DATA @l1LH1DF1BD1@B1>D1<F14H1
DATA @N0BD1DF1FH1DJ1BL1@`1>L1<J1:H1<F1>D1@01LH0FB1
DATA @l1LH1DF1BD1@B1>D1<F14H1IH0I61
DATA @N0BD1DF1FH1DJ1BL1@N1>L1<J1:H1<J1>L1@N1BL1DJ1FH1DF1BD1
DATA @l0RH17H0@$1
DATA @l0@*1BD1DF1FH1DJ1BL1@f1
DATA @l0I$1Il1
DATA @l0E$1Dl1E$1El1
DATA Rl1.H0R$1
DATA IH0@Z1IZ1.H0I61
DATA @l0RH1.$1RH1
DATA,,,,,
DATA @Z0BL1DJ1DH1DF1BD1@81BF1>N0>D1<F1<H1<J1>L1@L1BL1DJ1DH1DF1BD1
DATA @l0@*1BD1DF1FH1DJ1BL1@T1>L1<J1:H1<F1>D1
DATA RZ0>L1<J1:H1<F1>D1@<1BD1DF1FH1DJ1BL1
DATA Rl0@$1@N0>D1<F1:H1<J1>L1@T1BL1DJ1FH1DF1BD1
DATA @R0LH1DJ1BL1@J1>L1<J1:H1<F1>D1@<1BD1DF1FH1DJ1BL1
DATA @H0@f1BL1DJ1FH1DF1BD1.<0LH1
DATA FL0<F1>D1@D1BD1DF1FH1DJ1BL1@L1>L1<J1:H1>J1@J1BJ1<J1>
DATA L1@J1BL1DJ1DH1DF1BD1@F1>D1<F1<H1JR0BJ1
DATA @l1@60BL1DJ1FH1DF1BD1@61
DATA GH0DH1>H0@Z1>H1BM0@I1
DATA F<0DJ1BL1@`1@M0@I1
DATA @l1@*0RZ17?0I91
DATA LH0<J1>L1@f1
DATA @]1AJ1BI1CF1BE1@D1@L0BK1CJ1BG1AF1@31
DATA @`1@B0BL1DJ1FH1DF1BD1@61
DATA @N0BD1DF1FH1DJ1BL1@T1>L1<J1:H1<F1>D1@<1
DATA @<0@l1@B0BL1DJ1FH1DF1BD1@<1>D1<F1:H1<J1>L1
DATA T<0>H1@l1@B0>L1<J1:H1<F1>D1@<1BD1DF1FH1DJ1BL1
DATA @`1@B0BL1DJ1FH1DF1BD1
DATA @N0BD1DF1FH1DJ1BL1>L1<J1:H1<J1>L1BL1DJ1FH1DF1BD1
DATA Fl0@*1BD1DF1DJ1BL11Z0OH1
DATA @`0@61BD1DF1FH1DJ1BL1@Z0@01
DATA @`0I01I`1
DATA @`0@31AE1CG1CJ1BK1@L1@D0BE1CF1CI1AK1@]1
DATA R`1.H0R01
DATA @`0@61BD1DF1FH1DJ1BL1@Z0@*1>D1<F1:H1<J1>L1
DATA @`0RH1.01RH1
:
DEFPROCassemble
link=14
sp=13
FOR pass=0 TO 2 STEP 2
P%=code%
[ OPT pass
STMFD (sp)!,
MOV R10,R3
SWI &112
SWI &100
MOV R0,R5
SWI "OS_WriteC"
MOV R7,R1
MOV R8,R2
SUB R2,R2,#4
BL char
SUB R1,R7,#4
MOV R2,R8
MOV R3,R10
BL char
MOV R1,R7
ADD R2,R8,#4
MOV R3,R10
BL char
ADD R1,R7,#4
MOV R2,R8
MOV R3,R10
BL char
SWI &112
SWI &100
MOV R0,R6
SWI "OS_WriteC"
MOV R1,R7
MOV R2,R8
MOV R3,R10
BL frontChar
LDMFD (sp)!,
:
.frontChar
STMFD (sp)!,
MOV R5,R1
BL char
MOV R0,#24
MLA R0,R4,R0,R5
LDMFD (sp)!,
:
.char
MOV R0,#4
SWI "OS_Plot"
:
.char_loop
LDRB R1,[R3],#1
CMP R1,#32
MOVLT PC,link
SUB R1,R1,#64
MUL R1,R4,R1
LDRB R2,[R3],#1
SUB R2,R2,#72
MUL R2,R4,R2
LDRB R0,[R3],#1
SUB R0,R0,#48
SWI "OS_Plot"
B char_loop
]
NEXT
ENDPROC
:
DEFPROCloadscores
FOR i%=0 TO 7
score$(i%)="Little Me"
score%(i%)=8000-i%*1000
NEXT
f%=OPENIN file$
IF f% THEN
FOR i%=0 TO 3
INPUT# f%,score$(i%),score%(i%)
NEXT
CLOSE# f%
ENDIF
ENDPROC
:
DEFPROCtable(newscore%)
LOCAL I%,i%,col%
MODE 12
OFF
IF newscore%>=score%(7) PROCinsert
PROCoutline(256,944,"DEVILISH DEMOS",2,3,6)
FOR i%=0 TO 7
IF score%(i%)=newscore% THEN
col%=5
newscore%+=1
ELSE
col%=3
ENDIF
PROCoutline(64,832-i%*96,score$(i%)+STRING$(23-LENscore$(i%)
-LEN STR$ score%(i%),".")+STR$ score%(i%),2,1,col%)
NEXT
IF newscore%>=score%(3) THEN
f%=OPENOUT file$
FOR i%=0 TO 3
PRINT# f%,score$(i%),score%(i%)
NEXT
CLOSE# f%
ENDIF
PROCoutline(128,32,"Hit spacebar to play",2,6,3)
REPEAT
*FX 21
UNTIL NOT INKEY-99
REPEAT
UNTIL GET=32
ENDPROC
:
DEFPROCinsert
LOCAL char%,i%,mark%
PROCoutline(96,800,"CONGRATULATIONS",3,3,2)
IF newscore%>=score%(0) THEN
PROCoutline(224,560,"WOW",4,6,7)
PROCoutline(608,560,"Top Score",2,1,3)
ELSE
PROCoutline(0,656,"You have gained a place in",2,3,1)
PROCoutline(0,528,"the DEMO hall of fame.",2,3,1)
ENDIF
mark%=8
REPEAT
mark%-=1
score$(mark%+1)=score$(mark%)
score%(mark%+1)=score%(mark%)
UNTIL score%(mark%)>newscore% OR mark%=0
IF score%(mark%)>newscore% mark%+=1
score%(mark%)=newscore%
i%=0
char%=32
U%?i%=13
PROCoutline(0,336,"Please enter your name.",2,3,1)
PROCoutline(0,120,"-------------",2,4,6)
WHILE i%<14 AND char%<>13
REPEAT
char%=GET
IF char%=127 THEN
char%=0
IF i%>0 THEN
GCOL 0,0
RECTANGLE FILL i%*48-52,164,56,104
i%=i%-1
ENDIF
ENDIF
IF char%>31 AND i%>12 char%=0
IF char%>=ASC "a" char%-=32
IF char%=13 AND i%=0 PROCsilly ELSE IF char%<ASC"!" AND i%=0 char%=0
UNTIL char%=13 OR char%=32 OR (char%>=ASC"A" AND char%<=ASC "Z")
IF char%>=ASC"A" THEN
IF i%>0 AND U%?(i%-1)>32 char%=char% OR 32
PROCoutline(i%*48,192,CHR$ char%,2,4,6)
ENDIF
U%?i%=char%
i%=i%+1
ENDWHILE
score$(mark%)=$U%
CLS
ENDPROC
:
DEFPROCsilly
LOCAL j%
RESTORE+5
FOR j%=1 TO RND(6)
READ $U%
NEXT
i%=LEN $U%
ENDPROC
DATA Willie Wibble,Mickey Mouse,Fred Flatfoot,Bossy Bess,Moaning Megan,Fuzzy Bear
:
DEFPROCoutline(B%,C%,$T%,E%,F%,G%)
FOR I%=0 TO LEN $T%-1
$D%=font$(T%?I%-32)
B%=USR code%
NEXT
ENDPROC
The data lines are in a highly compacted form, and each line represents all the draw commands for a character. Only the numbers, upper and lower case letters, full stop, and minus are provided, the other characters would just waste space.
The routine first fills in a dummy score table, then looks for a top half to load from disk. There are then four demo calls to the score table covering all the possibilities. These are, top score, savable score, non savable score, below table score. You will notice that the routine only actually saves the score table if a new entry appears in the top four places.
Suitable messages are given for two scoring situations. A score too low is quietly ignored to save embarrassment and no note is made of whether a score will be saved or not. You could easily change this if you want to.
Entering a player's name traps unwanted characters, and intelligently sets upper and lower case letters in the name. If no name is entered then one of six silly names is chosen at random.
|