This program implements a maze-chase game where the player navigates a character through a randomly generated room while being hunted by four skeleton enemies. The display address is calculated at runtime using PEEK 16396/16397 (the ZX81 D_FILE system variable), allowing the player sprite and enemy sprites to be placed directly into the display file by POKEing character codes. Movement is handled by reading INKEY$ with offsets of 32, 33, and 34 to move diagonally and orthogonally within the display file’s linear memory layout. Enemy AI at lines 11–28 uses SGN and INT arithmetic on the difference between enemy and player positions to implement a simple tracking algorithm, with a random component (RND*9>4) to occasionally allow enemies to move through certain cells.
Program Analysis
Program Structure
The program is organised into four logical blocks:
- Initialisation (lines 9900–9940): Calculates display file address, dimensions the enemy array, draws the play-field, and jumps to the main loop.
- Player movement subroutine (lines 2–10): Called via
GOSUB 2from inside the enemy loop; reads INKEY$ and updates the player’s display-file address. - Enemy AI loop (lines 11–30): Iterates over four enemies, calls the player subroutine, then moves each skeleton one step toward the player.
- End-game / restart (lines 8000–9008, 9950–9970): Handles win/loss messages, play-again prompt, and the SAVE/auto-run block.
Display File Addressing
The ZX81 display file starts at the address stored in system variables at addresses 16396–16397. Line 9900 computes the player’s starting position as 133 + PEEK 16396 + 256*PEEK 16397, placing it four rows down and a few columns in. Line 9937 derives the base address D as PEEK 16396 + 256*PEEK 16397 + 1 (skipping the leading NEWLINE of the first row). All sprite positions are stored as absolute display-file addresses, so movement is achieved by simple addition or subtraction rather than maintaining separate X/Y coordinates.
Coordinate Arithmetic
Because the ZX81 display file stores each row as 33 bytes (32 characters plus a NEWLINE at code 118), horizontal movement adds or subtracts 1, vertical movement adds or subtracts 33, and diagonal movement combines both. Line 3 encodes all eight directions (keys 1–8 on a numeric layout) in a single expression:
"8"/"5"— up/down (±1)"6"/"7"— right/left (±33)"3"/"1"— diagonals (±34)"2"/"4"— diagonals (±32)
Line 4 adds 1 to P if the byte at the new position is 118 (a NEWLINE), effectively skipping over row-terminator bytes so the player cannot get stuck on them.
Sprite Rendering
Sprites are rendered by POKEing single character codes directly into the display file:
| Code | Meaning |
|---|---|
0 | Space — erase sprite |
52 | Player character (“0” in ZX81 character set) |
61 | Enemy skeleton sprite (“=”) |
8 | Wall tile |
5 | Goal/exit tile |
151 | Inverse character — player win sprite |
180 | Inverse character — player death sprite |
Maze Generation
The play-field is drawn in lines 9916–9926 using an 8×20 grid. The string A$=" \@@" (line 9919) contains a space and a block graphic, and the border rows (I=1 or I=8) always print the solid character. Interior cells pick randomly between space and block: A$(VAL "INT(RND*8)+1>5" + SGN PI) evaluates to either index 1 (space) or index 2 (block) with roughly 62%/38% probability. PRINT "\: " at line 9924 appends the row-terminating NEWLINE graphic character.
Enemy AI
The enemy tracking in lines 14–24 converts both the enemy address A(I) and the player address P into relative offsets from D, then extracts row and column components using integer division by 33. The movement delta J is adjusted by SGN of the row and column differences, giving a Manhattan-geometry step toward the player. The collision check on line 24 permits the enemy to move if the destination cell is empty (=0), contains the player (=52), or is a wall only when a random condition (RND*9+1>4) is also true, giving enemies a chance to pass through walls.
Key BASIC Idioms
VAL "expression"is used throughout initialisation (lines 9900, 9902, 9905, 9912, 9937, 9940) to save memory, as ZX81 BASIC stores numeric literals with a 5-byte floating-point value appended;VALof a string avoids this overhead.SGN PIevaluates to 1 and is used as the loop start value instead of the literal1, again saving bytes.- The saved variable
J=Pat line 2 records the old player position so it can be erased withPOKE J,0at line 6 only when the position actually changed (IF NOT P=J). - Line 7 uses
PEEK P=8 OR PEEK P=61to detect wall or enemy collision and revert the move by restoringP=J.
Bugs and Anomalies
- Line 9937 contains a typo:
PEEK 16396+256*PEEK 6397— a space intrudes into the second address, making itPEEK 6397instead ofPEEK 16397. This will likely read the wrong memory location and produce an incorrect value forD, causing sprites to appear in wrong positions or crash the program. - Line 8001 contains the grammatical error “proved your self” and line 9001 says “You haved proved”, but these are cosmetic only.
- The player subroutine at lines 2–10 is called from inside the enemy loop (line 12), meaning player input is polled once per enemy per frame rather than once per frame, making movement feel faster with more enemies alive.
- Lines 9950–9970 (
CLEAR,SAVE,RUN) are never reached by normal execution flow and appear to be a development remnant for saving the program with auto-run.
Content
Source Code
1 GOTO 9900
2 LET J=P
3 LET P=P+(INKEY$="8")-(INKEY$="5")+((INKEY$="6")-(INKEY$="7"))*33+((INKEY$="3")-(INKEY$="1"))*34+((INKEY$="2")-(INKEY$="4"))*32
4 LET P=P+(PEEK P=118)
6 IF NOT P=J THEN POKE J,0
7 IF PEEK P=8 OR PEEK P=61 THEN LET P=J
8 IF PEEK P=5 THEN GOTO 8000
9 POKE P,52
10 RETURN
11 FOR I=1 TO 4
12 GOSUB 2
14 LET J=A(I)-D
16 LET K=P-D
17 LET L=INT (K/33)
19 LET K=K-L*33
20 LET J=J-SGN ((INT (J/33)-L))*33-SGN (((J-INT (J/33)*33))-K)
22 POKE A(I),0
24 IF (PEEK (D+J)<>118 AND (((PEEK (D+J)=8) AND INT (RND*9)+1>4))) OR PEEK (D+J)=0 OR PEEK (D+J)=52 THEN LET A(I)=D+J
25 IF PEEK A(I)=52 THEN GOTO 9000
26 POKE A(I),61
28 NEXT I
30 GOTO 11
\n8000 POKE P,151
\n8001 PRINT "YOU HAVE PROVED YOUR SELF TO BE SUPERIOR TO THE SKELETONS YOU WERE PITTED AGAINST."
\n8002 GOTO 9002
\n9000 POKE A(I),180
\n9001 PRINT "YOU HAVED PROVED TO BE INFERIOR TO THE SKELETONS YOU WERE PITTED AGAINST. YOUR RACE WILL BE ELIMINATED FROM SOCIETY."
\n9002 PRINT "PLAY AGAIN?(Y/N)"
\n9004 INPUT A$
\n9006 IF A$="N" THEN STOP
\n9008 CLS
\n9900 LET P=VAL "133+PEEK 16396+256*PEEK 16397"
\n9901 FAST
\n9902 DIM A(VAL "4")
\n9905 FOR I=SGN PI TO VAL "4"
\n9912 LET A(I)=VAL "49+PEEK 16396+256*PEEK 16397"+INT (RND*6)+INT (RND*5+1)*33
\n9914 NEXT I
\n9916 FOR I=SGN PI TO VAL "8"
\n9918 FOR J=SGN PI TO VAL "20"
\n9919 LET A$=" \@@"
\n9920 IF VAL "I=1 OR I=8" THEN LET A$="\@@\@@"
\n9921 PRINT A$(VAL "INT (RND*8)+1>5"+SGN PI);
\n9922 NEXT J
\n9924 PRINT "\: "
\n9926 NEXT I
\n9928 SLOW
\n9937 LET D=VAL "PEEK 16396+256*PEEK 6397+1"
\n9938 POKE P,CODE "0"
\n9940 GOTO VAL "11"
\n9950 CLEAR
\n9960 SAVE "1029%0"
\n9970 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
