CANNONADE is a turn-based cannon battle game in which the player attempts to advance enemy gun crews (labeled by letter) across a 20-position battlefield before they reach position 20 and capture the cannon. Each of the six enemies occupies one of seven rows (M array tracks row, P array tracks column position), and the player inputs a letter code each turn to fire at a chosen enemy, nudging their position forward by a random 3–8 spaces. Enemy rows shift randomly each turn, and one enemy at a time is designated as the “danger” target (D), displayed using inverse video characters to highlight the threat. The game ends either when an enemy reaches position 20 or the player surrenders by pressing Enter, printing the number of moves taken.
Program Structure
The program is organized into four logical phases:
- Initialization (lines 10–130): Arrays
L(20)(the display line buffer),M(6)(row assignment per enemy), andP(6)(column position per enemy) are set up. Enemies 1–3 start on row 1, enemies 4–6 start on row 2. - Display loop (lines 140–370): The 7-row battlefield is drawn each refresh. Each row is assembled into
L()and printed character by character usingCHR$. - Win/input check (lines 380–650): After drawing, the program checks if any enemy has reached position 20, then prompts the player for a letter command, applies movement, and possibly designates a new danger target.
- End conditions (lines 660–680): Either the gun is captured (enemy at position 20) or the player surrenders (empty input), both reporting the move count.
Array and Display Technique
The array L(20) serves as a one-dimensional frame buffer for each printed row. It is cleared to 0 at the start of each row iteration (lines 170–190), then enemy characters are written into it at their column position P(J). The value stored is J+37, which when passed to CHR$ produces the letters %, &, ', (, ), * for enemies 1–6 — these are the characters the player types to target an enemy. The danger enemy is stored as J+165 instead, which yields an inverse-video character, visually highlighting it on screen. Position 20 is permanently set to 2 (line 320) which renders as a " character acting as the cannon marker.
Danger Target Logic
One enemy at a time is tracked as the “danger” target in variable D. Lines 590–630 scan all six enemies to find the one with the greatest column position (P(I)), making the most-advanced enemy the new danger. Its row M(I) is saved in T so the cannon graphic (a block graphic printed with "\\''#\\..") is drawn on that row. When the danger target changes, the previous one’s position is reset to 1 (line 560), which is a significant gameplay mechanic: retreating an enemy when focus shifts away.
Movement and Randomness
When the player inputs a letter, its ASCII code minus 37 (line 450) recovers the enemy index Q. The targeted enemy advances by RND*5+3 positions (a value from 3 to 7 inclusive on the ZX81). The enemy’s row is then randomly shifted: RND*3 is compared to 1, 2, or 3 — if 1, no row change; if 2, shift up (K=-1); if 3, shift down (K=1). Boundary checks at line 530 prevent rows going below 1 or above 7. A further RND*3=2 check (line 550) decides whether to redraw immediately or first recalculate the danger target.
Notable Bugs and Anomalies
- Line 390 checks
P(1)=20inside a loop overI=1 TO 6, but always tests enemy 1’s position regardless ofI. Only enemy 1 reaching position 20 triggers the end condition; the other five enemies are never checked for capture. This appears to be a bug — the condition should likely beP(I)=20. - Line 600 references variable
KinP(1)<K, but at this pointKholds whatever value it last had from the row-shift calculation (lines 500–510), not a meaningful comparison baseline. The intended logic is likely to find the maximumP(I), which would require initializingK=0before the loop. - The input prompt says
"MAN:"and expects the player to type a character corresponding to an enemy, but no validation is performed onA$— any character is accepted and used to computeQ, which could produce an out-of-range array index. - Line 540 uses
LET I=M(Q)thenLET M(Q)=I+K, butKis only set in lines 500–510 whenJ=2orJ=3; whenJ=1, theGO TO 550skips the row-shift block entirely, soKis not used — this path is correct.
Key BASIC Idioms
- The display is rebuilt from scratch each turn by clearing
L()and reprinting, rather than using any cursor positioning — a simple but effective full-refresh strategy.
Source Code
10 REM *CANNONADE SYNC 1-5 PAGE 32
20 LET T=4
30 LET D=0
40 LET X=0
50 DIM L(20)
60 DIM M(6)
70 DIM P(6)
80 LET L(20)=2
90 FOR I=1 TO 6
100 LET P(I)=1
110 LET M(I)=1
120 IF I>3 THEN LET M(I)=M(I)+1
130 NEXT I
140 RANDOMIZE
150 CLS
160 FOR I=1 TO 7
170 FOR K=1 TO 19
180 LET L(K)=0
190 NEXT K
200 PRINT I;" ";
210 FOR J=1 TO 6
220 IF NOT M(J)=I THEN GO TO 280
230 IF L(P(J))=0 OR L(P(J))=2 THEN GO TO 260
240 LET P(J)=P(J)+1
250 GO TO 230
260 LET L(P(J))=J+37
270 IF D=J THEN LET L(P(J))=J+165
280 NEXT J
290 FOR K=1 TO 20
300 PRINT CHR$ (L(K));
310 NEXT K
320 LET L(20)=2
330 IF NOT T=I THEN GO TO 350
340 PRINT "\''#\.. ";I;
350 PRINT
360 NEXT I
370 PRINT
380 FOR I=1 TO 6
390 IF P(1)=20 THEN GO TO 660
400 NEXT I
410 PRINT "MAN:"
420 INPUT A$
430 IF A$="" THEN GO TO 670
440 LET X=X+1
450 LET Q= CODE (A$)-37
460 LET P(Q)=P(Q)+ RND*5+3
470 IF P(Q)>20 THEN LET P(Q)=20
480 LET J= RND*3
490 IF J=1 THEN GO TO 550
500 IF J=2 THEN LET K=-1
510 IF J=3 THEN LET K=1
520 LET I=M(Q)
530 IF I+K=0 OR I+K=8 THEN GO TO 480
540 LET M(Q)=I+K
550 IF RND*3=2 THEN GO TO 140
560 LET P(D)=1
570 LET D=0
580 LET J= RND*3
590 FOR I=1 TO 6
600 IF P(1)<K OR D=I THEN GO TO 640
610 LET K=P(I)
620 LET D=I
630 LET T=M(I)
640 NEXT I
650 GO TO 140
660 PRINT "GUN CAPTURED IN ";
670 PRINT X;" MOVES."
680 STOP
690 SAVE "CANNONADE" LINE 10
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.