Racer 2 is a ZX81/TS1000 scrolling racing game in which the player steers a car left and right along a road while the road itself weaves randomly up the screen. The road is drawn each frame as a row of block-graphic characters at a horizontally drifting position, and the player’s car is printed one row above as an inverse-H character. Collision detection is performed via a PEEK of the display file: the program reads the two-byte D_FILE pointer from system variables at addresses 16398–16399 and checks whether the character under the car’s position equals 128 (a block graphic), halting the game on impact. Road drift is controlled by a pair of conditional nudges using RND, keeping variable D (column) within roughly 7–17 to constrain the road to a playable corridor.
Program Analysis
Program Structure
The program is a short main loop with initialisation at lines 20–50, a rendering and input phase at lines 60–130, a collision check at line 140, and a loop-back at line 150. Lines 160–170 are utility lines (save and auto-run) that are never reached during normal play.
- Initialisation (lines 20–50): Sets
A=10(car row),B=A(car column, starts at 10),C=A+A=20(road row, near the bottom),D=9(road column). - Road drawing (line 60): Prints a road segment of block-graphic characters at row
C(20), columnD. - Car drawing (line 70): Prints a block-graphic at the car’s current position row
A, columnB. - Scroll and input (lines 80–100): Scrolls the screen up one line, then reads
INKEY$twice to move the car right (M) or left (Z). - Car sprite (line 110): Redraws the car as an inverse-H character (
%H) at the updated position. - Road steering (lines 115–130): Nudges
Dupward if below 17, downward if above 7, using2*RNDfractional increments. Line 120 positions the print cursor without printing anything, possibly as a cursor-placement side-effect. - Collision detection (line 140): Peeks the display file to detect a crash.
Display and Graphics
The road is rendered using ZX81 block-graphic characters (\## escapes, solid block character 128) surrounded by spaces, giving a short road segment roughly eight characters wide. The player’s car is a single inverse-H character (%H, i.e. character code for H in inverse video), visually distinct from the road graphics. The SCROLL command at line 80 moves all rows up, creating the illusion of forward movement.
Collision Detection Technique
Line 140 implements a display-file collision check, a classic ZX81 technique. PEEK 16398 and PEEK 16399 read the low and high bytes of the D_FILE system variable (the pointer to the display file in RAM). The combined 16-bit address PEEK 16398 + PEEK 16399 * 256 gives the start of the display file, and PEEK of that address checks the first character in the file. A value of 128 (the solid block graphic) indicates the car has hit the road and STOP ends the game.
There is a subtle anomaly here: the PEEK always reads the very first byte of the display file (the top-left cell), not the byte at the car’s current position. This means the collision check only triggers when a road block graphic has scrolled all the way to the top-left corner of the screen, rather than detecting a true overlap between car and road. As a result the game ends somewhat unpredictably rather than precisely on contact.
Road Drift Algorithm
The road column D is kept within a soft corridor of 7–17 by two opposing nudges per frame:
- Line 115: if
D < 17, add2*RND(a random value in [0,2)). - Line 130: if
D > 7, subtract2*RND.
Because both conditions can be true simultaneously (when 7 < D < 17), both nudges apply each frame, producing a random walk bounded loosely between the two thresholds. The road therefore drifts smoothly but erratically across the screen.
Key BASIC Idioms
| Line | Idiom | Purpose |
|---|---|---|
| 80 | SCROLL | Shifts display up one row to simulate movement |
| 90–100 | Back-to-back INKEY$ tests | Single-frame key polling without PAUSE |
| 110 | %H inverse character | Visually distinct player sprite using inverse video |
| 140 | PEEK(PEEK lo + PEEK hi * 256) | Indirect address read via system variable pointer |
| 115, 130 | 2*RND fractional drift | Smooth stochastic road steering |
Bugs and Anomalies
- The collision check at line 140 reads the first byte of the display file rather than the byte at the car’s screen position. A correct check would need to offset the pointer by the car’s row and column within the display file (accounting for the newline bytes that separate rows on the ZX81).
- Line 120 (
PRINT AT 11,B;) positions the print cursor at row 11 without outputting any character. This may be an artefact of an earlier version of the program or intended to pre-position the cursor, but it has no visible effect in the current flow. - The car column
Bis never range-checked, so pressing M or Z repeatedly can drive the car off the edges of the 32-column display, causing a potentially confusing wrap or error.
Content
Source Code
10 REM "RACER 2"
20 LET A=10
30 LET B=A
40 LET C=A+A
50 LET D=9
60 PRINT AT C,D;"% \##\##\##\##% "
70 PRINT AT A,B;"\##"
80 SCROLL
90 IF INKEY$="M" THEN LET B=B+1
100 IF INKEY$="Z" THEN LET B=B-1
110 PRINT AT A,B;"%H"
115 IF D<17 THEN LET D=D+2*RND
120 PRINT AT 11,B;
130 IF D>7 THEN LET D=D-2*RND
140 IF PEEK (PEEK 16398+PEEK 16399*256)=128 THEN STOP
150 GOTO 60
160 SAVE "1022%3"
170 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
