CANNONADE

Developer(s): Drew Nisbet
Date: 1981
Type: Program
Platform(s): TS 2068
Tags: Game

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:

  1. Initialization (lines 10–130): Arrays L(20) (the display line buffer), M(6) (row assignment per enemy), and P(6) (column position per enemy) are set up. Enemies 1–3 start on row 1, enemies 4–6 start on row 2.
  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 using CHR$.
  3. 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.
  4. 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)=20 inside a loop over I=1 TO 6, but always tests enemy 1’s position regardless of I. 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 be P(I)=20.
  • Line 600 references variable K in P(1)<K, but at this point K holds 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 maximum P(I), which would require initializing K=0 before the loop.
  • The input prompt says "MAN:" and expects the player to type a character corresponding to an enemy, but no validation is performed on A$ — any character is accepted and used to compute Q, which could produce an out-of-range array index.
  • Line 540 uses LET I=M(Q) then LET M(Q)=I+K, but K is only set in lines 500–510 when J=2 or J=3; when J=1, the GO TO 550 skips the row-shift block entirely, so K is 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.