Cannon Balls

Developer(s): Jerry Cohler
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Game

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:

LinesSection
1–30Header and high-score initialization, then jump to UDG setup
100–190Main game loop
200–230Row 17 collision: catch or lose catcher
300–340Row 18 bounce (floor)
400–430Row 14 bounce (ceiling/cannon level)
500–510Ball exits right edge; deduct cannon ball
900–930Opening screen and instructions
1000–1600Stage setup: floor, cannon graphics, HUD
2000–2999Variable initialization and ball launch
3000–3600Catcher destroyed sequence
4000–4500Game over closing animation
5000–5600New game / high score handling
6000–7200UDG 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=255 used 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 ATTR and SCREEN$ checks. If ATTR matches the catcher’s destroyed state (46) and the impact flag i=1, the catcher is lost. If SCREEN$ returns "y" (the catcher character) and i=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 i set 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 AT placements 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 0 to return to column 0 after each row segment within a single PRINT statement — a compact multi-row sprite technique.
  • The FOR a=1 TO 20 sound 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

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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.

Scroll to Top