Programming by Example

 

A BB4W Compendium

freeman69@gmx.com

IDIC BBC_Owl2 M&P

Asteroid Belt

This game combines several of the methods and techniques applied to previous programs including: arrays of structures, drawing, rotational transformations and collision detection using circles.

 

         Z= Rotate anticlockwise

         X= Rotate clockwise

         M= Thrust

         K= Fire

 

    This games includes:

 

         The player's rotating ship

         Bullets (and debris)

         Rotating rocks of various sizes

         Flying saucer

 

  10 MODE 9:OFF

  20 *REFRESH OFF

  30 ORIGIN 640,512

  40 VDU 23,23,2;0;0;0;:REM line thickness=2

  50 *FONT Arial,12

  60

  70 MAXOBJS=99

  80 DIM shapes(45,1),bubbles(13,2),td(3,4)

  90 DIM obj{(MAXOBJS) status,type,x,y,xvel,yvel,scale,angle,spin,col,life,msg,firedby}

 100 DIM stack(MAXOBJS)

 110

 120 XMIN=-739:XMAX=740:XW=1480

 130 YMIN=-611:YMAX=612:YH=1224

 140 SHOWBUB=TRUE

 150

 160 PROCloaddata

 170 PROCclearobjects

 180

 190 funeral=0:wplayer=-1:trocks=0

 200 wave=0:lives=3:score=0:hiscore=0:nowplaying=FALSE

 210 frame=0:quantum=0:saucer=10:ufotype=4:walien=-1

 220 PROCbelt(4)

 230

 240 REPEAT

 250   TIME=0:CLS

 260   VDU 5:GCOL 0,3

 270   MOVE -640,512:PRINT"Lives: ";lives

 280   MOVE -300,512:PRINT"Score: ";score

 290   MOVE 40,512:PRINT"Wave: ";wave

 300   MOVE 380,512:PRINT"Hi-Score: ";hiscore

 310   IF NOT nowplaying MOVE -160,0:PRINT"Press SPACE to play"

 320   VDU 4

 330  

 340   IF nowplaying THEN

 350     IF wplayer=-1 AND funeral>0 AND funeral<25 THEN

 360       VDU 5:MOVE -60,0:PRINT"Ready?":VDU 4

 370     ENDIF

 380     IF trocks=0 THEN

 390       PROCclearobjects

 400       wave+=1:PROCbelt(4+(wave-1) MOD 4):funeral=50:saucer=10:ufotype=4

 410     ENDIF

 420     IF wplayer=-1 AND funeral=0 AND FNclearspace THEN

 430       wplayer=FNmakeobject(2,0,0,0,0,8,0,0,3,-1,-1)

 440       thrust=0:reloaded=0:frame=0

 450     ENDIF

 460   ELSE

 470     IF INKEY(-99) THEN

 480       PROCclearobjects:SHOWBUB=FALSE

 490       nowplaying=TRUE:lives=3:score=0:wave=0:quantum=0:saucer=10:ufotype=4

 500     ENDIF

 510   ENDIF

 520   IF funeral>0 funeral-=1

 530  

 540   PROCthink

 550  

 560   IF wplayer>-1 AND walien=-1 THEN

 570     frame+=1

 580     IF frame MOD 25=0 THEN

 590       IF quantum=0 saucer-=1

 600       IF saucer=0 THEN

 610         IF ufotype>1 ufotype-=1

 620         PROCnewsaucer:saucer=ufotype

 630       ENDIF

 640       quantum=0

 650     ENDIF

 660   ENDIF

 670  

 680   *REFRESH

 690   *FX21

 700   REM not a Windows-friendly pause, but more accurate than WAIT

 710   WHILE TIME<4:ENDWHILE

 720 UNTIL FALSE

 730 END

 740

 750 DEF PROCloaddata

 760 FOR d=0 TO 45:READ shapes(d,0),shapes(d,1):NEXT

 770 FOR d=0 TO 13:READ bubbles(d,0),bubbles(d,1),bubbles(d,2):NEXT

 780 FOR d=0 TO 3:READ td(d,0),td(d,1),td(d,2),td(d,3),td(d,4):NEXT

 790 ENDPROC

 800 REM Rock (type=0)

 810 DATA -2,5,0,5,1,4,4,4,5,3,5,1,4,0

 820 DATA 5,-1,5,-3,3,-5,1,-5,0,-4,-1,-5,-3,-5

 830 DATA -4,-4,-4,-2,-5,-1,-5,1,-4,2,-4,3,-2,5

 840 REM Saucer (type=1)

 850 DATA -2,1,-2,2,-1,3,1,3,2,2,2,1,5,0,5,-1

 860 DATA 3,-3,-3,-3,-5,-1,-5,0,-2,1,-1,0,1,0,2,1

 870 REM Player's ship (type=2)

 880 DATA 0,5,3,-4,0,-2,-3,-4,0,5

 890 REM Bullet (type=3)

 900 DATA 0,6,5,-3,-5,-3,0,6

 910 REM Multiple collision circles for each object type: x,y,radius

 920 DATA -1,2,3,3,2,2,2,-2,3,-2,-3,2,-3,0,2

 930 DATA 0,-1,2,-3,-1,2,0,1,2,3,-1,2

 940 DATA 0,0,2,0,5,1,3,-4,1,-3,-4,1

 950 DATA 0,0,6

 960 REM pointer (shapes), no., pointer (bubbles), no., max radius

 970 DATA 0,21,0,5,6

 980 DATA 21,16,5,4,5

 990 DATA 37,5,9,4,5

1000 DATA 42,4,13,1,6

1010

1020 DEF PROCdrawobject(type,x,y,scale,angle,col)

1030 LOCAL first,a,cx,cy

1040 IF SHOWBUB THEN

1050   GCOL 0,4

1060   FOR a=td(type,2) TO td(type,2)+td(type,3)-1

1070     cx=FNrotx(bubbles(a,0),bubbles(a,1),angle)

1080     cy=FNroty(bubbles(a,0),bubbles(a,1),angle)

1090     CIRCLE x+cx*scale,y+cy*scale,bubbles(a,2)*scale

1100   NEXT

1110   CIRCLE x,y,td(type,4)*scale

1120 ENDIF

1130 first=TRUE:GCOL 0,col

1140 FOR a=td(type,0) TO td(type,0)+td(type,1)-1

1150   cx=FNrotx(shapes(a,0),shapes(a,1),angle)

1160   cy=FNroty(shapes(a,0),shapes(a,1),angle)

1170   IF first THEN

1180     MOVE x+cx*scale,y+cy*scale:first=FALSE

1190   ELSE

1200     DRAW x+cx*scale,y+cy*scale

1210   ENDIF

1220 NEXT

1230 ENDPROC

1240

1250 DEF FNrotx(x,y,angle)

1260 =COS(RAD(angle))*x-SIN(RAD(angle))*y

1270

1280 DEF FNroty(x,y,angle)

1290 =SIN(RAD(angle))*x+COS(RAD(angle))*y

1300

1310 DEF PROCclearobjects

1320 FOR a=0 TO MAXOBJS

1330   obj{(a)}.status=FALSE:stack(a)=a

1340 NEXT

1350 stackptr=0:trocks=0

1360 wplayer=-1:walien=-1

1370 ENDPROC

1380

1390 DEF FNmakeobject(type,x,y,xvel,yvel,scale,angle,spin,col,life,firedby)

1400 LOCAL a

1410 a=stack(stackptr):stackptr+=1

1420 obj{(a)}.status=TRUE

1430 obj{(a)}.type=type

1440 obj{(a)}.x=x

1450 obj{(a)}.y=y

1460 obj{(a)}.xvel=xvel

1470 obj{(a)}.yvel=yvel

1480 obj{(a)}.scale=scale

1490 obj{(a)}.angle=angle

1500 obj{(a)}.spin=spin

1510 obj{(a)}.col=col

1520 obj{(a)}.life=life

1530 obj{(a)}.msg=0

1540 obj{(a)}.firedby=firedby

1550 =a

1560

1570 DEF PROCkillobject(a)

1580 obj{(a)}.status=FALSE:stackptr-=1:stack(stackptr)=a

1590 ENDPROC

1600

1610 DEF FNcollide(a,b)

1620 LOCAL d,r,hit,g,h,cx,cy,gx,gy,hx,hy

1630 d=SQR((obj{(a)}.x-obj{(b)}.x)^2+(obj{(a)}.y-obj{(b)}.y)^2)

1640 r=td(obj{(a)}.type,4)*obj{(a)}.scale+td(obj{(b)}.type,4)*obj{(b)}.scale

1650 IF d>r THEN

1660   =FALSE

1670 ELSE

1680   hit=FALSE

1690   g=td(obj{(a)}.type,2)

1700   REPEAT

1710     cx=bubbles(g,0):cy=bubbles(g,1)

1720     gx=obj{(a)}.x+FNrotx(cx,cy,obj{(a)}.angle)*obj{(a)}.scale

1730     gy=obj{(a)}.y+FNroty(cx,cy,obj{(a)}.angle)*obj{(a)}.scale

1740     h=td(obj{(b)}.type,2)

1750     REPEAT

1760       cx=bubbles(h,0):cy=bubbles(h,1)

1770       hx=obj{(b)}.x+FNrotx(cx,cy,obj{(b)}.angle)*obj{(b)}.scale

1780       hy=obj{(b)}.y+FNroty(cx,cy,obj{(b)}.angle)*obj{(b)}.scale

1790       d=SQR((gx-hx)^2+(gy-hy)^2)

1800       r=bubbles(g,2)*obj{(a)}.scale+bubbles(h,2)*obj{(b)}.scale

1810       IF d<=r hit=TRUE

1820       h+=1

1830     UNTIL hit OR h=td(obj{(b)}.type,2)+td(obj{(b)}.type,3)

1840     g+=1

1850   UNTIL hit OR g=td(obj{(a)}.type,2)+td(obj{(a)}.type,3)

1860   =hit

1870 ENDIF

1880

1890 DEF FNgroupcollide(a)

1900 LOCAL c,hit

1910 c=0:hit=FALSE

1920 REPEAT

1930   IF obj{(c)}.status AND c<>a AND c<>obj{(a)}.firedby THEN

1940     IF obj{(a)}.type=2 AND obj{(c)}.type=0 hit=FNcollide(a,c)

1950     IF obj{(a)}.type=3 THEN

1960       IF obj{(c)}.type<>3 hit=FNcollide(a,c)

1970       IF hit THEN

1980         obj{(c)}.msg+=1

1990         IF wplayer>-1 AND obj{(a)}.firedby=wplayer THEN

2000           quantum+=1

2010           IF obj{(c)}.type=0 score+=10 ELSE IF obj{(c)}.type=1 score+=100

2020         ENDIF

2030       ENDIF

2040     ENDIF

2050   ENDIF

2060   c+=1

2070 UNTIL hit OR c>MAXOBJS

2080 =hit

2090

2100 DEF PROCthink

2110 LOCAL a,type,scale,col,dead

2120 FOR a=0 TO MAXOBJS

2130   IF obj{(a)}.status THEN

2140     dead=FALSE

2150     type=obj{(a)}.type

2160     CASE type OF

2170       WHEN 0 : PROCrocks(a)

2180       WHEN 1 : PROCalien(a)

2190       WHEN 2 : PROCflyship(a)

2200       WHEN 3 : IF FNgroupcollide(a) dead=TRUE

2210     ENDCASE

2220     IF obj{(a)}.life>0 THEN

2230       obj{(a)}.life-=1

2240       IF obj{(a)}.life=0 dead=TRUE:IF a=walien walien=-1

2250     ENDIF

2260     IF NOT dead THEN

2270       obj{(a)}.x+=obj{(a)}.xvel

2280       IF obj{(a)}.x<XMIN obj{(a)}.x+=XW

2290       IF obj{(a)}.x>=XMAX obj{(a)}.x-=XW

2300       obj{(a)}.y+=obj{(a)}.yvel

2310       IF obj{(a)}.y<YMIN obj{(a)}.y+=YH

2320       IF obj{(a)}.y>=YMAX obj{(a)}.y-=YH

2330       obj{(a)}.angle+=obj{(a)}.spin

2340       scale=obj{(a)}.scale:col=obj{(a)}.col

2350       PROCdrawobject(type,obj{(a)}.x,obj{(a)}.y,scale,obj{(a)}.angle,col)

2360     ELSE

2370       PROCkillobject(a)

2380     ENDIF

2390   ENDIF

2400 NEXT

2410 ENDPROC

2420

2430 DEF PROCrocks(a)

2440 LOCAL r,angle,x,y,xvel,yvel,scale,rot,spin,d

2450 IF obj{(a)}.msg>0 THEN

2460   IF obj{(a)}.scale>4 THEN

2470     angle=RND(360)

2480     FOR r=1 TO 2

2490       x=obj{(a)}.x+SIN(RAD(angle))*obj{(a)}.scale*2

2500       y=obj{(a)}.y+COS(RAD(angle))*obj{(a)}.scale*2

2510       xvel=FNvelocity*16/obj{(a)}.scale

2520       yvel=FNvelocity*16/obj{(a)}.scale

2530       scale=obj{(a)}.scale DIV 2

2540       rot=RND(360):spin=(RND(1)-0.5)*6

2550       d=FNmakeobject(0,x,y,xvel,yvel,scale,rot,spin,6,-1,-1)

2560       angle+=180

2570     NEXT

2580     trocks+=2

2590   ENDIF

2600   dead=TRUE:trocks-=1

2610 ENDIF

2620 ENDPROC

2630

2640 DEF FNvelocity

2650 LOCAL a,v

2660 a=RND(70)+10

2670 v=(SIN(RAD(a))+0.25)*4*(1+((wave-1) MOD 4)/4)

2680 IF RND(2)=1 v*=-1

2690 =v

2700

2710 DEF PROCbelt(n)

2720 LOCAL r,x,y,xvel,yvel,rot,spin,d

2730 FOR r=1 TO n

2740   x=RND(1080)+100-640:y=RND(824)+100-512

2750   xvel=FNvelocity:yvel=FNvelocity

2760   rot=RND(360):spin=(RND(1)-0.5)*8

2770   d=FNmakeobject(0,x,y,xvel,yvel,16,rot,spin,6,-1,-1)

2780 NEXT

2790 trocks+=n

2800 ENDPROC

2810

2820 DEF PROCflyship(a)

2830 LOCAL x,y,xvel,yvel,d

2840 IF INKEY(-98) obj{(a)}.angle+=12

2850 IF INKEY(-67) obj{(a)}.angle-=12

2860 IF INKEY(-102) AND thrust<2 thrust+=0.5 ELSE thrust*=0.8

2870 IF reloaded>0 reloaded-=1

2880 IF INKEY(-71) AND reloaded=0 THEN

2890   x=obj{(a)}.x-SIN(RAD(obj{(a)}.angle))*obj{(a)}.scale*5

2900   y=obj{(a)}.y+COS(RAD(obj{(a)}.angle))*obj{(a)}.scale*5

2910   xvel=-SIN(RAD(obj{(a)}.angle))*36

2920   yvel=COS(RAD(obj{(a)}.angle))*36

2930   d=FNmakeobject(3,x,y,xvel,yvel,2,obj{(a)}.angle,0,1,20,a)

2940   reloaded=5

2950 ENDIF

2960 obj{(a)}.xvel+=-SIN(RAD(obj{(a)}.angle))*thrust:obj{(a)}.xvel*=0.98

2970 obj{(a)}.yvel+=COS(RAD(obj{(a)}.angle))*thrust:obj{(a)}.yvel*=0.98

2980 d=SQR(obj{(a)}.xvel^2+obj{(a)}.yvel^2)

2990 IF d>32 obj{(a)}.xvel*=32/d:obj{(a)}.yvel*=32/d

3000 IF obj{(a)}.msg>0 OR FNgroupcollide(a) THEN

3010   PROCexplode(a)

3020   dead=TRUE:funeral=50:wplayer=-1:lives-=1

3030   IF lives=0 THEN

3040     nowplaying=FALSE:SHOWBUB=TRUE

3050     IF score>hiscore hiscore=score

3060   ENDIF

3070 ENDIF

3080 ENDPROC

3090

3100 DEF PROCexplode(a)

3110 LOCAL r,x,y,xvel,yvel,rot,spin,life,d

3120 FOR r=1 TO 8

3130   xvel=FNvelocity*2:yvel=FNvelocity*2

3140   x=obj{(a)}.x+xvel*2:y=obj{(a)}.y+yvel*2

3150   rot=RND(360):spin=(RND(13)-7)*4:life=RND(40)+10

3160   d=FNmakeobject(3,x,y,xvel,yvel,3,rot,spin,3,life,-1)

3170 NEXT

3180 ENDPROC

3190

3200 DEF FNclearspace

3210 LOCAL inopen,a

3220 inopen=TRUE:a=0

3230 REPEAT

3240   IF obj{(a)}.status AND SQR(obj{(a)}.x^2+obj{(a)}.y^2)<250 inopen=FALSE

3250   a+=1

3260 UNTIL NOT inopen OR a>MAXOBJS

3270 =inopen

3280

3290 DEF PROCnewsaucer

3300 LOCAL x,y,xvel,scale,col,life

3310 IF RND(2)=1 x=XMIN:xvel=8 ELSE x=XMAX:xvel=-8

3320 y=RND(600)-300:life=INT(XW/ABS(xvel))

3330 IF (ufotype AND 2)=2 scale=10:col=1 ELSE scale=6:col=5

3340 walien=FNmakeobject(1,x,y,xvel,0,scale,0,0,col,life,0)

3350 ENDPROC

3360

3370 DEF PROCalien(a)

3380 LOCAL x,y,xvel,yvel

3390 IF obj{(a)}.msg>0 THEN

3400   dead=TRUE:walien=-1

3410 ELSE

3420   obj{(a)}.firedby+=1

3430   obj{(a)}.yvel+=COS(RAD(obj{(a)}.firedby*10))

3440   IF RND(30)=1 THEN

3450     x=obj{(a)}.x:y=obj{(a)}.y

3460     IF wplayer>-1 AND (ufotype=1 OR RND(16)=1) THEN

3470       xvel=(obj{(wplayer)}.x-obj{(a)}.x)/20+obj{(wplayer)}.xvel

3480       yvel=(obj{(wplayer)}.y-obj{(a)}.y)/20+obj{(wplayer)}.yvel

3490     ELSE

3500       xvel=FNvelocity*4:yvel=FNvelocity*4

3510     ENDIF

3520     d=FNmakeobject(3,x,y,xvel,yvel,2,0,12,5,35,a)

3530   ENDIF

3540 ENDIF

3550 ENDPROC

Asteroid Belt: Code explained...

 

  50 *FONT Arial,12

 

Line 50 specifically selects the Arial font, size 12, otherwise BB4W uses its own font by default.

 

  90 DIM obj{(MAXOBJS) status,type,x,y,xvel,yvel,scale,angle,spin,col,life,msg,firedby}

 

The 'obj' array holds the details of every unique object that exists in the current game. Rocks, saucers, the player's ship and bullets, all share common traits that can be stored efficiently in a single array.

This generic approach to storing data supports a similarly generic approach to object processing. All objects have a position, velocity, scale and colour. These commonalities allow us to create a single routine to draw all 4 types of objects, as well as a single routine to handle basic movement.

 

 120 XMIN=-739:XMAX=740:XW=1480

 130 YMIN=-611:YMAX=612:YH=1224

 

The Asteroid Belt universe wraps around. Objects that move off one side of the window appear on the other side. The variables (or constants) above, indicate the true limits.

 

 140 SHOWBUB=TRUE

 

In order to demonstrate the collision detection method, all collision circles (bubbles) are drawn, but only during the time between games.

 

 190 funeral=0:wplayer=-1:trocks=0

 200 wave=0:lives=3:score=0:hiscore=0:nowplaying=FALSE

 210 frame=0:quantum=0:saucer=10:ufotype=4:walien=-1

 220 PROCbelt(4)

 

'funeral' is a countdown that acts as a delay at the end of a game; allowing for an end-of-game animation. We also use it to delay the start of a game to display the word 'Ready?'.

'wplayer' holds the 'obj' array row assigned to the player's ship (or -1).

'walien' holds the 'obj' array row assigned to a saucer (or -1).

'trocks' holds total number of rocks currently active. When this reaches zero then a new wave of asteroids needs to be generated.

'nowplaying' indicates that the user has requested a new game.

'frame', 'quantum', 'saucer' and 'ufotype' all control the timing, appearance and type of saucer to appear when the user's hit rate drops below a minimum level.

PROCbelt creates a number of asteroids. At this point the asteroids are for a background display only, until the user requests a new game.

 

Previous games have placed the player at the centre of the code: one inner-loop cycles for each life available and a middle-loop cycles for each game played. A final outer-loop cycles endlessly until the window is closed. This game is subtly different. There is only one main loop: the endless cycle. This is because the player (the player's ship), is handled in the same way as any other object. One can imagine that the game never really ends, and the player 'respawns' into the Asteroid Belt universe on request. Similarly, asteroids automatically respawn when the last one is destroyed.

 

 430       wplayer=FNmakeobject(2,0,0,0,0,8,0,0,3,-1,-1)

 440       thrust=0:reloaded=0:frame=0

 

Line 430 respawns the player through a request to create an 'obj' array object. Not all of the parameters are applicable to the player's ship.

The ship is object type 2, drawn at a scale of 8 and yellow in colour (3), centred at x=0, y=0 with zero velocity.

 

 540   PROCthink

 

PROCthink is the umbrella routine that controls all active objects.

 

 750 DEF PROCloaddata

 

The first set of data consists of pairs of co-ordinates for drawing each object type. One set of data runs into the next in a single, continuous list.

The second set of data defines the circles associated with each object type. These circles are used for collision detection. For instance, the player's ship contains 4 circles, while rocks contain 5. When we want to determine if the player has collided with an asteroid then we must test whether any of the 4 ship circles overlap any of the 5 rock circles. Obviously, this process must be repeated for each rock present.

The final set of data contains pointers and counts for the first two sets of data. For instance, the co-ordinates defining a template rock start at row zero and there are 21 co-ordinates. Rock collision circles (bubbles) also start at row zero and there are 5 of them.

The final value defines the maximum radius of each object type. This allows us to speed up collision testing. For instance, if the circle enclosing the player's ship doesn't overlap the circle enclosing a particular rock, then we don't have to check any further. If these circles do overlap, then we go on to check the ship's inner circles against the rock's inner circles i.e. potentially 20 additional tests.

 

1020 DEF PROCdrawobject(type,x,y,scale,angle,col)

 

As mentioned previously, PROCdrawobject handles the drawing of all 4 object types (because of the manner in which object data are stored). Collision detection circles are drawn for demonstration purposes only.

 

1310 DEF PROCclearobjects

 

When a new game begins, all existing objects must be destroyed.

 

1390 DEF FNmakeobject(type,x,y,xvel,yvel,scale,angle,spin,col,life,firedby)

 

Unlike the Bugs program, we don't check for row availability. The 'obj' array is large enough to accommodate all of the objects that could exist at any given moment.

FNmakeobject merely takes the next free row in the 'obj' array and populates it with the parameters provided; retuning the row number.

'life' applies to bullets (debris) and saucers. This variable is decremented each animation frame. When it reaches zero the object is destroyed.

When 'msg' (message) contains a value other than zero, it indicates that the object has been hit by a bullet (or debris). An object with a non-zero value in this variable must destroy itself.

'firedby' applies to bullets only. It prevents the player's ship, and saucers, from being destroyed by their own weaponry.

 

1570 DEF PROCkillobject(a)

 

Called whenever an object needs to be destroyed. A pointer to the relevant row in the 'obj' array is returned to the object 'stack'.

 

1610 DEF FNcollide(a,b)

 

FNcollide tests for a collision between 2 specific objects and returns a value of TRUE if a collision is detected. As mentioned above, the first test uses the maximum radius of each object type. If this test fails then no further tests are required. Otherwise, the inner circles of object 'a' must be tested for overlapping the inner circles of object 'b'. Note that when a collision is detected then the test sequence can be exited immediately.

 

1890 DEF FNgroupcollide(a)

 

FNgroupcollide(a) tests all of the relevant objects that could collide with object 'a' by making relevant calls to FNcollide.

The player can collide with rocks.

Bullets (or debris) can collide with rocks, the player's ship or a saucer.

The player's bullets score points for hitting rocks and saucers.

 

2100 DEF PROCthink

 

This routine is the generic control routine for all object types.

PROCthink defines a local (boolean) variable named 'dead'. This variable is updated by routines called by PROCthink. (Local variables only cease to exist when the defining routine ends. This is the only instance of a local variable being used outside of the defining procedure.)

Because each object type has its own rules, PROCthink calls specific routines for each object type. It is these routines that may update the variable 'dead'.

The 'obj' array variable 'life' is handled generically. If it holds a value greater than zero then it decrements and tests for becoming zero, which triggers the destruction of the object.

If an object isn't flagged as 'dead' by this stage, then it's position is updated and the object is drawn. Otherwise, the object is destroyed.

 

2430 DEF PROCrocks(a)

 

Rocks only need to check 'msg' for being hit by bullets. On being hit, larger rocks create two smaller rocks with random velocities and spin. The parent rock is destroyed.

 

2640 DEF FNvelocity

 

This function returns a random velocity component within certain limits. Because the game includes off-window margins, objects should never be allowed to move exactly vertically or horizontally. The range of angles used by the routine guarantee a minimum degree of diagonal movement. The velocity component returned is also a function of the current 'wave' value.

 

2710 DEF PROCbelt(n)

 

Creates a requested number of large asteroids, randomly placed, with random velocities and spin.

 

2820 DEF PROCflyship(a)

 

The player's ship can be rotated and accelerated forward, as well as fire. The velocity of the ship diminishes slowly. The maximum velocity of the ship is limited.

'reloaded' limits the firing rate of the ship and the maximum number of bullets active at any given moment. The payer's bullets are oriented with the ship.

The ship checks 'msg' for being shot (by a saucer). The routine also checks for collisions with rocks.

 

3100 DEF PROCexplode(a)

 

When the player's ship is destroyed, 8 bullets (debris) are scattered, potentially colliding with rocks and saucers.

 

3200 DEF FNclearspace

 

Due to the unpredictably hazardous motion of the asteroids, we need to make sure that the player's ship is not respawned directly in front of, or over, a rock. To prevent this, respawning is inhibited while rocks are present within a certain radius of the centre of the window.

 

3290 DEF PROCnewsaucer

 

Saucers are spawned in the off-window margins to the left and right. The player is confronted by a saucer whenever the hit rate falls below a certain level. If the hit rate fails to recover by the second saucer, subsequent enemies present much smaller and deadlier targets.

The 'firedby' variable of the 'obj' array is put to an alternative use by saucers: Saucer motion defines a sine wave, which is derived from an incrementing count stored in 'firedby'.

 

3370 DEF PROCalien(a)

 

Large saucers fire bullets at random intervals and in random directions, but occasionally target the player. Small saucers always target the player.

Saucers only have to check 'msg' for being hit.

Asteroid2 Arrow black large Arrow black large