Cannon Balls is a single-player arcade-style game in which the player moves a catcher left and right along the bottom of the screen to intercept bouncing cannon balls fired from a cannon. The game tracks score, remaining cannon balls (starting at 50), and catchers (starting at 7), ending when either resource reaches zero. Four custom UDG characters (A–D) are defined at startup using POKEd pixel data to render the cannon, cannon ball, and explosion graphics. Ball movement uses independent horizontal and vertical delta variables, with ATTR checks used to detect collisions with the catcher and walls rather than coordinate comparison alone.
Program Structure
The program is organized into clearly labeled sections separated by REM statements, using high line numbers for subroutines and setup routines:
| Lines | Section |
|---|---|
| 1–30 | Header and high-score initialization, then jump to UDG setup |
| 100–190 | Main game loop |
| 200–230 | Row 17 collision: catch or lose catcher |
| 300–340 | Row 18 bounce (floor) |
| 400–430 | Row 14 bounce (ceiling/cannon level) |
| 500–510 | Ball exits right edge; deduct cannon ball |
| 900–930 | Opening screen and instructions |
| 1000–1600 | Stage setup: floor, cannon graphics, HUD |
| 2000–2999 | Variable initialization and ball launch |
| 3000–3600 | Catcher destroyed sequence |
| 4000–4500 | Game over closing animation |
| 5000–5600 | New game / high score handling |
| 6000–7200 | UDG definition via DATA and POKE, then jump to opening |
UDG Graphics
Four UDG characters are defined at lines 6200–7100 using a FOR/READ/POKE USR loop. The character letter is itself read from a DATA string, making the loop compact:
"A"— lower cannon base (two pixels, bottom rows only)"B"— upper cannon base (two pixels, top rows only)"C"— cannon ball (solid rounded shape,z=255used for full rows)"D"— explosion/hit graphic (circular burst pattern)
The variable z is set to 255 at line 6100 as a shorthand to avoid typing the literal three times in the DATA at line 7000, reducing keystrokes and potential entry errors.
Ball Movement and Collision Detection
The cannon ball position is tracked by column a and row d, with directional deltas aa (horizontal) and dd (vertical). Movement is applied at line 150. Collision logic is row-based:
- Row 17 (lines 200–230): Uses both
ATTRandSCREEN$checks. IfATTRmatches the catcher’s destroyed state (46) and the impact flagi=1, the catcher is lost. IfSCREEN$returns"y"(the catcher character) andi=1, a successful catch is scored (+5 points). - Row 18 (lines 300–330): Ball bounces vertically; horizontal direction may randomly reset to zero using
INT(RND*2+1). - Row 14 (lines 400–420): Ball bounces back down; horizontal delta is forced to 1 and impact flag
iset to 1 (ball is “live” for catching). - Column >31 (line 100/500): Ball exits the right side, costing one cannon ball from
j.
The flag i distinguishes a ball traveling downward from cannon (catchable) versus one still ascending or bouncing along the floor (not catchable), preventing false catch detections.
Catcher Movement
Line 120 handles catcher movement in a single expression using Boolean arithmetic — a common Sinclair BASIC idiom:
LET f=f+(INKEY$="8" AND f<=29)-(INKEY$="5" AND f>=11)
Keys 8 (right) and 5 (left) are read, with boundary clamping built into the Boolean conditions. Movement is only read after each ball-position update, so the catcher responds once per game loop iteration rather than in a separate polling loop.
HUD and Score Display
The heads-up display printed at lines 1400 and updated throughout tracks: current score (s), high score (h), remaining cannon balls (j), and remaining catchers (g). The high score h is initialized once at line 20 (LET h=0) and persists across games within a session; it is only updated at line 5400 when the session ends.
Game Over and Restart
The closing sequence (lines 4000–4500) runs a border color cycling loop three times, counting down from 7 to 0 with an accompanying BEEP tone for each step. After the animation, lines 5100–5300 wait for the Enter key (CHR$ 13) using a tight IF INKEY$ polling loop before resetting state and jumping back to line 2000 to reinitialize variables without recreating the stage graphics.
Notable Techniques and Anomalies
- Line 300 contains
IF ATTR(d,a)=30 THEN LET a=a-1: LET aa=0— this detects a wall attribute at row 18 and nudges the ball left, preventing it from embedding in a wall tile. - The cannon firing animation at line 2950 uses a sequence of
PRINT ATplacements with UDGs A and D, followed by erasure, to simulate a muzzle flash. - At line 1200, the cannon is assembled from multiple UDG characters using
TAB 0to return to column 0 after each row segment within a singlePRINTstatement — a compact multi-row sprite technique. - The
FOR a=1 TO 20sound loops at lines 930 and 1600 play ascending then descending BEEP sequences (and vice versa) as stage transition fanfares without requiring separate subroutines. - Line 110 erases the previous catcher position before redrawing, but the erase is conditional on
ATTR(17,f)<>40— this avoids accidentally blanking the catcher if the attribute already matches a non-catcher tile.
Content
Source Code
1 REM *********************** *Underlined characters* *are entered in * *GRAPHICS mode. * ***********************
10 REM *BOUNCE DOWN* BY J. COHLER
20 LET h=0
30 GO TO 6000
90 REM *MAIN ROUTINE*(NB"GRAPHICS"+D,C)
100 IF a>31 THEN GO TO 500
105 PRINT AT d,a;"\c"
110 PRINT AT 17,f+1;" ":PRINT AT 18,f;" ": IF ATTR (17,f) <>40 THEN PRINT AT 17,f;" "
120 LET f=f+(INKEY$="8" AND f <=29)-(INKEY$="5" AND f >=11)
130 PRINT ; INK 5; AT 17,f;"y"; TAB f+1; INK 6;"\c"; AT 18,f; INK 6; PAPER 3;"\::\::"
140 IF ATTR (d,a) <>40 THEN PRINT AT d,a;" "
150 LET d=d+dd: LET a=a+aa
160 IF d=17 THEN GO TO 200
170 IF d=18 THEN GO TO 300
180 IF d=14 THEN GO TO 400
190 GO TO 100
200 REM *DECIDES CATCH/LOST CATCHER*(NB"GRAPHICS+D)
210 IF ATTR (d,a) =46 AND i=1 THEN GO TO 3000
220 IF SCREEN$ (d,a)="y" AND i=1 THEN PRINT AT d,a;"\d": BEEP 1,0: PRINT AT d,a;" ": LET s=s+5:PRINT ; INK 7; PAPER 0; AT 2,9;s; AT 1,28;j-1;" ": LET j=j-1: GO TO 2400
230 GO TO 100
300 IF ATTR (d,a)=30 THEN LET a=a-1 : LET aa=0
310 LET dd=-dd
320 IF INT (RND*2+1)=1 THEN LET aa=0
330 LET i=0
340 GO TO 100
400 LET dd=-dd
410 LET aa=1
420 LET i=1
430 GO TO 100
500 LET j=j-1:PRINT ; INK 7; PAPER 0; AT 1,28;j;" ": IF j=0 THEN GO TO 4000
510 GO TO 2400
900 REM *OPENING*
910 BORDER 0:PAPER 5:CLS
920 PRINT ; INK 1; AT 5,7;"IF BALL HITS TOP-"; AT 7,7;"CATCHER DESTROYED"; AT 12,6;"TO MOVE CATCHER USE"; AT 14,11;"<- OR ->"; AT 16,6;"(AFTER CANNON FIRES)"
930 FOR a=1 TO 20:BEEP .1,a:BEEP .1,20-a: NEXT a: CLS
1000 REM *SETS STAGE*(NB "GRAPHICS"+A,B,C)
1100 FOR a=19 TO 21:FOR b=0 TO 31:PRINT ; INK 4; AT a,b;"\::";:NEXT b:NEXT a
1200 PRINT AT 14,1;"\a"; TAB 0;"\::\::"; TAB 0;"\:.\b"; TAB 0;"\::"; TAB 0;"0"
1300 FOR a=0 TO 2: FOR b=0 TO 31:PRINT ; INK 0; AT a,b;"\::";: NEXT b:NEXT a
1400 PRINT ; INK 7; PAPER 0; AT 1,1;"HIGHEST :0"; TAB 15;"CANNON BALLS:50"; TAB 1;"SCORE:0"; TAB 15;"CATCHERS :7"
1500 PRINT ; INK 6; AT 17,15;"\c"; AT 18,14;"\::\::"
1600 FOR a=1 TO 20: BEEP .1,20-a:BEEP .1,a:NEXT a
2000 REM *INITIALISES VARIABLES ETC*(NB "GRAPHICS"+D)
2100 LET s=0
2200 LET f=14
2300 LET g=7
2350 LET i=0
2375 LET j=50
2400 LET c= INT (RND*2+1)
2500 LET e= INT (RND*2+1)
2600 LET d=14+c
2700 LET dd=1
2800 LET a=1+e
2900 LET aa=1
2950 PRINT ; INK 7; AT 14,3;"\d"; AT 15,2;"\::\d":BEEP .3,-20:PRINT AT 14,3;" "; AT 15,2;" "; AT 16,3;" "
2999 GO TO 100
3000 REM *LOST CATCHER* (NB "GRAPHICS"+D)
3100 PRINT AT d,a;"\d"
3200 FOR a=1 TO 3:BEEP .2,5:BEEP .2,-5:NEXT a
3300 PRINT AT 17,f;" "; AT 18,f;" "
3400 LET g=g-1:PRINT ; INK 7; PAPER 0; AT 2,28;g; AT 1,28;j-1;" ":LET j=j-1
3500 IF g=0 OR j=0 THEN GO TO 4000
3600 GO TO 2400
4000 REM *CLOSING*
4050 FOR b=1 TO 3
4100 FOR a=7 TO 0 STEP -1
4200 BORDER a
4300 BEEP .1,a
4400 NEXT a
4500 NEXT b
5000 REM *NEW GAME*
5100 PRINT ; INK 1; AT 5,9;"FOR NEW GAME-"; AT 7,9;"PRESS ""ENTER""";
5200 IF INKEY$= CHR$ 13 THEN GO TO 5400
5300 GO TO 5200
5400 IF s>h THEN LET h=s
5450 PRINT ; INK 7; PAPER 0; AT 2,9;0;" "; AT 1,28;50; AT 2,28;7; AT 1,9;h
5500 PRINT AT 17,f;" "; AT 18,f;" "; AT 5,9;" "; AT 7,9;" "
5600 GO TO 2000
6000 REM *CREATES GRAPHICS*
6100 LET z=255
6200 FOR a=1 TO 4
6300 READ a$
6400 FOR b=0 TO 7
6500 READ c:POKE USR a$+b,c
6600 NEXT b
6700 NEXT a
6800 DATA "A",0,0,0,0,0,0,3,3
6900 DATA "B",3,3,0,0,0,0,0,0
7000 DATA "C",252,254,z,z,z,z,z,z
7100 DATA "D",60,126,239,223,223,z,126,60
7200 GO TO 900
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
