This program implements a single-player dodgem-style game in which the player steers a character (“$”) across a screen filled with randomly placed inverse-video obstacles, trying to avoid walls and obstacles while accumulating a score. The screen is set up in FAST mode: columns of inverse spaces are printed to form barriers, then one cell per column is blanked to create gaps, and a solid border of block-graphic characters forms the perimeter. Movement is controlled via Q (up), Z (down), and L (right), and the program reads the display file directly using PEEK on the system variable at address 16396/16397 to detect collisions with inverse-video cells (value 128). The score starts at 20,000 and decrements by 50 each move and by 673 on each obstacle hit; hitting a wall divides the score by 3. A high-score tracker persists across rounds in variable U, and the SAVE command at line 530 uses an inverse character in the filename as an auto-run flag.
Program Analysis
Program Structure
The program is divided into clearly separated phases, each handled by a distinct block of lines:
- Initialisation and screen setup (lines 10–140): Sets the high-score accumulator
Uto zero, entersFASTmode, draws random column obstacles, and paints a solid border. - Round initialisation subroutine (lines 440–510): Called via
GOSUB 440at line 150; sets starting position (A=10,B=1), initial scoreZ=20000, and last-directionA$="Z"(downward), then switches toSLOWmode. - Main game loop (lines 160–340): Reads
INKEY$, applies movement, erases the old sprite, performs collision detection, redraws the sprite, and loops back. - End-of-round handling (lines 350–430): Displays score, updates high score, flashes the player sprite, clears the screen, and restarts from line 20.
- Obstacle-hit subroutine (lines 170–200): Replaces the hit cell with an inverse “$”, penalises the score by 673, updates the score display, and returns.
- Utility lines (520–540):
CLEAR,SAVEwith auto-run filename, andRUN— executed only when preparing the tape image.
Screen Setup
Lines 30–80 build the obstacle field. The outer FOR B loop steps through even columns 2 to 28. For each column, every row 0–19 is filled with an inverse space ("% ", i.e. an inverse-video space character). Then line 70 blanks one random cell in that column (RND*14+3, giving rows 3–17 approximately) to create a navigable gap. This produces a series of near-solid vertical barriers with a single passable hole each.
Lines 90–140 draw the border: line 100 prints the block-graphic character \@@ (a solid block, character 128) along rows 0 and 19, and lines 120–140 print it down columns 0 and 30, forming a rectangular enclosure.
Collision Detection via Display File PEEK
The most technically notable feature is the collision routine at line 290:
IF PEEK (PEEK 16396+256*PEEK 16397+33*A+B+1)=128 THEN GOSUB 170
System variables at addresses 16396 and 16397 hold the low and high bytes of the display file start address (D_FILE). Adding 33*A+B+1 computes the offset into the display file for row A, column B (each row is 33 bytes wide: 32 character cells plus a NEWLINE). A PEEK value of 128 corresponds to an inverse space — the obstacle character. This technique reads the screen contents directly rather than maintaining a separate map array, saving RAM and eliminating synchronisation issues.
Movement and Inertia
Line 220 implements a simple inertia system: if no key is pressed (INKEY$=""), the last valid keypress stored in A$ is reused. This means the player continues moving in the last chosen direction without holding a key. A$ is initialised to "Z" (move down) in the setup subroutine, so the player immediately starts moving downward.
Movement deltas are computed on lines 260–270:
A=A+(Z$="Z")-(Z$="Q")— Z moves down (increases row), Q moves up.B=B+(Z$="L")— L moves right; there is no leftward control.
The absence of a left-movement key is intentional design: the player must progress rightward across the screen, navigating gaps in each vertical barrier.
Scoring
| Event | Score change |
|---|---|
| Each move (every iteration of main loop) | −50 |
| Hitting an obstacle cell | −673 (plus the −50 for the move) |
| Hitting a wall (out of bounds) | Score divided by 3 (integer) |
The score starts at 20,000. Hitting a wall also terminates the round (line 320 branches to 350 if out of bounds). Line 360 updates the persistent high score U if the current score exceeds it.
End-of-Round Animation
Lines 370–410 loop 6 times, flashing the player’s position between normal and inverse “$” while alternating the “BEST SO FAR” display between normal and inverse text ("%B%E%S%T" is “BEST” in inverse video). This provides simple visual feedback without any additional machinery.
Key BASIC Idioms
- Boolean arithmetic for movement (
Z$="Z"evaluates to 1 or 0) avoidsIF/THENbranching for direction calculation. FAST/SLOWswitching: the screen is built inFASTmode for speed, thenSLOWis restored for gameplay so the display remains visible.- The coordinate variables
Y/Xstore the previous position so the old sprite can be erased before the new position is drawn (lines 240–250, 280).
Bugs and Anomalies
- Line 310 applies the wall penalty (
Z=INT(Z/3)) and line 320 then checks the same boundary condition again to branch to the end-of-round section. This duplication means the penalty is correctly applied before the branch, but the condition is evaluated twice unnecessarily. - There is no upward boundary column guard for
Bin the wall-penalty check at line 310 (B<1is checked butB>29is not), while line 320 does checkB>29. A player reaching column 30 or beyond gets the end-of-round without the score-division penalty from line 310 — this may be intentional (reaching the right edge is a success state). - The display-file offset formula uses
+1to skip the NEWLINE byte at the start of each row, which is correct for the ZX81 display file layout.
Content
Source Code
10 LET U=0
20 FAST
30 FOR B=2 TO 28 STEP 2
40 FOR A=0 TO 19
50 PRINT AT A,B;"% "
60 NEXT A
70 PRINT AT RND*14+3,B;" "
80 NEXT B
90 FOR A=0 TO 30
100 PRINT AT 0,A;"\@@";AT 19,A;"\@@"
110 NEXT A
120 FOR B=1 TO 18
130 PRINT AT B,0;"\@@";AT B,30;"\@@"
140 NEXT B
150 GOSUB 440
160 GOTO 210
170 PRINT AT A,B;"%$"
180 LET Z=Z-673
190 PRINT AT 20,0;"SCORE: ";Z;" "
200 RETURN
210 LET Z$=INKEY$
220 IF Z$="" THEN LET Z$=A$
230 LET Z=Z-50
240 LET Y=A
250 LET X=B
260 LET A=A+(Z$="Z")-(Z$="Q")
270 LET B=B+(Z$="L")
280 PRINT AT Y,X;" "
290 IF PEEK (PEEK 16396+256*PEEK 16397+33*A+B+1)=128 THEN GOSUB 170
300 PRINT AT A,B;"$"
310 IF A>18 OR A<2 OR B<1 THEN LET Z=INT (Z/3)
320 IF A>18 OR A<2 OR B<1 OR B>29 THEN GOTO 350
330 LET A$=Z$
340 GOTO 160
350 PRINT AT 20,0;"END OF ROUND SCORE: ";Z
360 IF Z>U THEN LET U=Z
370 FOR G=1 TO 6
380 PRINT AT 21,3;"BEST SO FAR: ";U
390 PRINT AT A,B;"%$";AT A,B;"$";AT A,B;"%$"
400 PRINT AT 21,3;"%B%E%S%T"
410 NEXT G
420 CLS
430 GOTO 20
440 LET A=10
450 LET B=1
460 LET Z=20000
470 LET Y=A
480 LET X=B
490 LET A$="Z"
500 SLOW
510 RETURN
520 CLEAR
530 SAVE "1028%8"
540 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
