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
8000 POKE P,151
8001 PRINT "YOU HAVE PROVED YOUR SELF TO BE SUPERIOR TO THE SKELETONS YOU WERE PITTED AGAINST."
8002 GOTO 9002
9000 POKE A(I),180
9001 PRINT "YOU HAVED PROVED TO BE INFERIOR TO THE SKELETONS YOU WERE PITTED AGAINST. YOUR RACE WILL BE ELIMINATED FROM SOCIETY."
9002 PRINT "PLAY AGAIN?(Y/N)"
9004 INPUT A$
9006 IF A$="N" THEN STOP
9008 CLS
9900 LET P=VAL "133+PEEK 16396+256*PEEK 16397"
9901 FAST
9902 DIM A(VAL "4")
9905 FOR I=SGN PI TO VAL "4"
9912 LET A(I)=VAL "49+PEEK 16396+256*PEEK 16397"+INT (RND*6)+INT (RND*5+1)*33
9914 NEXT I
9916 FOR I=SGN PI TO VAL "8"
9918 FOR J=SGN PI TO VAL "20"
9919 LET A$=" @@"
9920 IF VAL "I=1 OR I=8" THEN LET A$="@@@@"
9921 PRINT A$(VAL "INT (RND*8)+1>5"+SGN PI);
9922 NEXT J
9924 PRINT ": "
9926 NEXT I
9928 SLOW
9937 LET D=VAL "PEEK 16396+256*PEEK 6397+1"
9938 POKE P,CODE "0"
9940 GOTO VAL "11"
9950 CLEAR
9960 SAVE "1029%0"
9970 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
