Gunner is a shooting gallery game where the player moves a cannon left and right along the bottom of the screen, firing upward at two scrolling rows of random alphabetic characters. The scrolling text is stored in a single one-dimensional string array DIM A$(1,6000), with a window into it advancing by two characters per frame to create the horizontal scroll effect. Characters are drawn from CHR$(166) to CHR$(191), corresponding to the ZX81’s block-graphic and letter characters. The player scores points based on the ASCII value of the character hit (R-165), making letters deeper in the alphabet worth more. A special “@@ ” target occasionally appears in the scroll; if it reaches the leftmost visible position without being shot, the round ends.
Program Analysis
Program Structure
The program begins with a title/instructions block (lines 500–600) before jumping back to initialization. The main game loop runs from line 60 onward, with the bullet flight handled inline rather than in a subroutine.
- Lines 1–2: REM header and
GOTO 500to skip to the title screen. - Lines 5–15: Initialize high score, cannon height, and the large scroll buffer array.
- Lines 20–40: Pre-fill odd-indexed positions 1–65 of
A$with random characters (CHR$ 166–191). - Lines 45–135: Main game loop — display scores, draw the two scrolling rows and the cannon, handle keyboard input.
- Lines 140–210: Bullet movement upward; uses PEEK of the display file to detect hits.
- Lines 250–310: Hit processing — calculate score, erase the hit character from the scroll buffer, reset bullet.
- Lines 350–470: End-of-round sequence; checks high score, prompts replay.
- Lines 500–600: Title screen and instructions.
- Lines 700–710: SAVE and RUN trailer.
Scroll Buffer Design
All scrolling data lives in DIM A$(1,6000), a single-row string array 6000 characters wide. The pointer N starts at 2 and increments by 2 each frame. The display routine prints A$(1,N TO N+31) on row 3 and A$(1,N+33 TO N+65) on row 5, giving two 32-character wide lanes. New characters are appended at A$(1,N+65) each frame, so the buffer grows rightward as the game progresses. Because N only increments, the program will eventually exhaust the 6000-character buffer after approximately 2967 frames without a round end.
Hit Detection via PEEK
Rather than tracking bullet-vs-character coordinates algebraically, the game uses a direct display-file inspection technique. Lines 155–156 place the print position at the bullet’s location, PEEK the display file address from system variables at addresses 16398–16399, read the character code already at that screen cell, then overprint with ".". If the PEEKed value R is non-zero (i.e., something other than a space was already there), a hit is registered.
PEEK 16398 + 256 * PEEK 16399gives the address of the current print position in the display file.- A score contribution of
R - 165is calculated, so CHR$(166)=’A’ scores 1, up to CHR$(191) scoring 26.
Special Target (“@@”)
Approximately once every 40 frames (INT(RND*40)=10), the character appended to the buffer is the two-character sequence \@@ (rendered as a block-graphic pair). Line 95 checks whether A$(1,N+1) equals this token; if so, the round ends with a “got away” message. This means the special target must scroll entirely off the left edge of the visible window without being shot to trigger the loss condition.
Cannon Rendering
The cannon graphic occupies two screen rows: row 21 shows " \.: " (the barrel, using ZX81 block graphics) and row 20 shows " . " (the base). The horizontal position is controlled by variable Z, clamped between 1 and 28 by lines 110–120. Keys 5 and 8 move left and right; key 7 fires.
Bullet Flight
The bullet is a single "." character that travels from row 19 upward to row 3 in steps of 2 (H decrements by 2 per frame). Flag variable X tracks whether a bullet is in flight: X=1 means in-flight, X=0 means idle. When X<>0, keyboard input is skipped (line 105), so the player cannot move or fire again until the bullet resolves.
Score Erasure on Hit
When a hit occurs, the program attempts to blank the character in the scroll buffer at the appropriate offset. Line 280 handles a hit on row 3 (H=3) using A$(1,N+(Z-1))=" ", and line 290 handles row 5 (H=5) using A$(1,N+(32+Z))=" ". The index arithmetic is approximate and may be off by one depending on exact scroll position, which can cause occasional visual artifacts.
Notable Bugs and Anomalies
- The odd-index pre-fill loop (lines 20–40,
STEP 2) only populates positions 1, 3, 5, …, 65, leaving even positions as null bytes (CHR$ 0). This means the initial scroll contains interleaved garbage/null characters until the per-frame append (line 80) fills the buffer further right. - The bullet height variable
His initialized to 19 at line 8 but is reset to 19 only at line 300 (after a hit). A missed shot that reaches row 3 resetsX=0at line 200 but does not resetH, so the next shot starts from whereverHwas last left (row 1 or 3), not from row 19. This is a definite bug. - The INPUT at line 430 accepts full words “YES”/”NO” as well as single letters, but the GOTO loop is only re-entered if none of the four options match, meaning any other input is also silently accepted and falls through — the condition logic at line 460 then treats an unrecognized answer as “no”.
- The
DIM A$(1,6000)on line 10 declares a two-index string array but is accessed throughout with single-index slice notationA$(1,…). This is valid ZX81 syntax for a 1-row string array.
Key Variables
| Variable | Role |
|---|---|
A$ | 6000-character scroll buffer |
N | Current read offset into scroll buffer |
Z | Cannon horizontal position (column) |
H | Bullet vertical position (row) |
X | Bullet in-flight flag (0=idle, 1=in-flight) |
S | Current score |
HS | High score (persists across rounds) |
R | PEEKed character code at bullet position |
Content
Source Code
1 REM ***GUNNER***
2 GOTO 500
5 LET HS=0
8 LET H=19
10 DIM A$(1,6000)
15 LET X=0
20 FOR N=1 TO 65 STEP 2
30 LET A$(1,N)=CHR$ (INT (RND*26)+166)
40 NEXT N
45 LET N=2
50 LET Z=0
55 LET S=0
60 PRINT AT 0,0;"SCORE","HIGH-SCORE";AT 1,0;S,HS
70 PRINT AT 3,0;A$(1,N TO N+31);AT 5,0;A$(1,N+33 TO N+65);AT 21,Z;" \.: ";AT 20,Z;" . "
80 LET A$(1,N+65)=CHR$ (INT (RND*26)+166)
90 IF INT (RND*40)=10 THEN LET A$(1,N+65)="\@@"
95 IF A$(1,N+1)="\@@" THEN GOTO 350
100 LET N=N+2
105 IF X<>0 THEN GOTO 140
110 IF INKEY$="8" AND Z<28 THEN LET Z=Z+1
120 IF INKEY$="5" AND Z>1 THEN LET Z=Z-1
130 IF INKEY$="7" THEN GOTO 140
135 GOTO 70
140 LET H=H-2
150 PRINT AT H,Z+1;
155 LET R=PEEK (PEEK 16398+256*PEEK 16399)
156 PRINT "."
160 PRINT AT H+2,Z+1;" "
170 LET X=1
180 IF R<>0 THEN GOTO 250
190 IF H<3 THEN PRINT AT H,Z+1;" "
200 IF H<3 THEN LET X=0
210 GOTO 70
250 IF R-165<0 THEN LET R=165
255 LET S=S+(R-165)
260 LET X=0
270 PRINT AT 1,0;S
280 IF H=3 THEN LET A$(1,N+(Z-1))=" "
290 IF H=5 THEN LET A$(1,N+(32+Z))=" "
300 LET H=19
310 GOTO 70
350 CLS
360 PRINT "SCORE","HIGH-SCORE"
370 IF S>HS THEN LET HS=S
380 PRINT S,HS
390 PRINT
400 PRINT "THE ""\@@"" GOT AWAY."
410 PRINT
420 PRINT "DO YOU WANT TO TRY AGAIN(Y/N)?"
430 INPUT Z$
440 IF Z$<>"YES" AND Z$<>"Y" AND Z$<>"NO" AND Z$<>"N" THEN GOTO 430
450 CLS
460 IF Z$="YES" OR Z$="Y" THEN GOTO 8
470 STOP
500 PRINT TAB 12;"GUNNER"
510 PRINT TAB 12;"------"
520 PRINT
530 PRINT "THE POINT OF THIS GAME IS TO GET AS MANY POINTS AS POSSIBLE BY SHOOTING THE LETTERS; BUT YOU MUST NOT LET THE ""\@@"" GET AWAY."
540 PRINT "THE FURTHER THE LETTER IS IN THE ALPHABET THE MORE ITS WORTH. FOR EXAMPLE ""%Z"" IS WORTH MORE THAN ""%A"". TO MOVE THE GUN YOU USE THE"
550 PRINT "CURSOR KEYS AND THE UPWARD POINTING CURSOR KEY TO FIRE.",,,"GOOD LUCK"
560 PRINT
570 PRINT "HIT ANY KEY TO CONTINUE."
580 IF INKEY$="" THEN GOTO 580
590 CLS
600 GOTO 5
700 SAVE "1006%7"
710 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
