Zombies

This file is part of and Timex Sinclair Public Domain Library Tape 1006. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 1000
Tags: Game

This is a maze-and-chase game in which the player navigates a 20×20 bordered arena while up to 21 zombies hunt them down. The arena is drawn with inverse-video border characters and the player character (%A, inverse A) moves via keyboard codes checked against INKEY$. Zombies are stored in parallel arrays X() and Y() and each frame they step one cell closer to the player by computing the sign of the difference on each axis. Collision detection is done by PEEKing the display file address held at system variables 16398–16399 to read the character currently under each zombie after it moves, branching on whether it is a wall (inverse H) or the player character.


Program Analysis

Program Structure

The program divides into four logical phases:

  1. Arena drawing (lines 10–50): Prints a 22-column wide bordered rectangle using inverse-H characters as walls and inverse-space as floor.
  2. Sprite placement (lines 70–140): A single loop of 42 iterations places 21 player-side markers (first pass, INT(Z/21)=0) and 21 zombies (second pass, INT(Z/21)=1), saving zombie coordinates into arrays X() and Y(). The subroutine at 1000 is reused for both passes with A controlling which character to print (A=136 for first pass, A=151 for second).
  3. Main game loop (lines 130–360): Reads a keypress, moves the player, then iterates over all 21 zombie slots updating each zombie’s position and checking for collisions.
  4. Death sequence (lines 370–390): An infinite loop flashing * and inverse-* at the player’s last position.

Placement Subroutine (line 1000)

The subroutine generates coordinates near the centre (10,10) of the grid. The expression Z/3 AND Z<>42 exploits ZX81 Boolean arithmetic: Z<>42 returns 1 or 0, so the spread radius is Z/3 for all placements except the very last (Z=42), which forces a radius of 0 — placing the final zombie exactly at (10,10). SGN(RND-0.5) gives a random sign (±1 or 0). A retry loop at lines 1010 and 1030 rejects out-of-bounds results.

Player Movement

Movement uses raw key codes read via CODE INKEY$. Code 56 (key ‘8’ in ZX81 layout) stops the program. The movement conditions are checked against multiple key-code combinations, allowing diagonal movement when two directional codes are combined, though only one axis is updated per condition line:

Key codesAction
33, 112, 114Move left (U−1)
34, 113, 114Move down (V+1)
35, 112, 115Move up (V−1)
36, 113, 115Move right (U+1)

Note that codes 112–115 appear in multiple conditions simultaneously, meaning pressing those keys can trigger two movement conditions in a single frame (diagonal movement). The old player position is erased with inverse-space before the new position is printed.

Zombie AI

Each zombie moves one cell per frame directly toward the player. Lines 290 and 310 compute the sign of the X and Y distance respectively, using the idiom IF X(Z)-U THEN LET S=(X(Z)-U)/ABS(X(Z)-U) — a clean way to obtain −1, 0, or +1 without SGN, exploiting the fact that the IF skips the assignment when the difference is zero.

Display-File Collision Detection

After moving a zombie, lines 331–334 position the PRINT AT cursor at the zombie’s new location and then PEEK the display file to read back what character is already there. System variables at addresses 16398 and 16399 hold the low and high bytes of the display-file pointer (D_FILE), and PEEK(PEEK 16398 + 256*PEEK 16399) reads the first byte of the display file, not the character at the cursor — this is a likely bug. The intended behaviour was presumably to read the character at the current cursor position to detect wall (inverse H, code 200) or player (inverse A, code 193) occupancy, but the PEEK expression always reads the same fixed address rather than the cursor-relative display cell.

A zombie whose X(Z) becomes 0 is considered dead/removed (line 260 skips it). Line 340 uses IF X(Z) THEN PRINT "%*" — printing the zombie character only when X(Z) is non-zero (i.e. still alive).

Notable Techniques and Idioms

  • RAND at line 2 re-seeds the random number generator for varied games each run.
  • The parallel arrays X(21) and Y(21) (lines 5–6) store zombie positions; a zero value in X() flags a slot as inactive.
  • The column offset X+4 in all PRINT AT statements accounts for the 4-space left margin printed in the border lines, keeping coordinate arithmetic in the range 1–20.
  • The SAVE "1026%9" at line 1070 (inverse 9) is an auto-run save command.
  • The death loop (lines 370–390) is intentionally infinite with no exit, requiring a manual break.

Bugs and Anomalies

  • The display-file PEEK at lines 331–332 reads PEEK(PEEK 16398 + 256*PEEK 16399), which always reads the very first byte of the display file regardless of cursor position. Correct collision detection would require computing the address of the specific screen cell.
  • Line 160 busy-loops on INKEY$="" waiting for a key, then line 170 reads INKEY$ again — if the key is released between the two reads, B will be 0 (code of empty string) and no movement occurs that frame.
  • The movement conditions on lines 200–230 do not prevent the player from walking into the wall border (coordinates 0 or 21 are blocked, but the border is drawn at column/row 0 and 21 of the arena’s internal space — the guards U>1 and U<20 are correct for a 1–20 playfield).

Content

Appears On

Assembled by Tim Ward from many sources. Contains programs 10252 – 10293.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

   1 REM *ZOMBIES*
   2 RAND 
   5 DIM X(21)
   6 DIM Y(21)
  10 PRINT "    %H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H"
  20 FOR Z=1 TO 20
  30 PRINT "    %H% % % % % % % % % % % % % % % % % % % % %H"
  40 NEXT Z
  50 PRINT "    %H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H%H"
  70 FOR Z=1 TO 42
  80 LET A=15*INT (Z/21)+136
  90 GOSUB 1000
 100 IF INT (Z/21)=1 THEN LET X(Z-20)=X
 110 IF INT (Z/21)=1 THEN LET Y(Z-20)=Y
 120 NEXT Z
 130 LET U=X
 140 LET V=Y
 160 IF INKEY$="" THEN GOTO 160
 170 LET B=CODE INKEY$
 180 IF B=56 THEN STOP 
 200 IF (B=33 OR B=112 OR B=114) AND U>1 THEN LET U=U-1
 210 IF (B=34 OR B=113 OR B=114) AND V<20 THEN LET V=V+1
 220 IF (B=35 OR B=112 OR B=115) AND V>1 THEN LET V=V-1
 230 IF (B=36 OR B=113 OR B=115) AND U<20 THEN LET U=U+1
 235 PRINT AT Y,X+4;"% "
 236 LET X=U
 237 LET Y=V
 240 PRINT AT Y,X+4;"%A"
 250 FOR Z=1 TO 21
 260 IF X(Z)=0 THEN GOTO 350
 270 PRINT AT Y(Z),X(Z)+4;"% "
 280 LET S=0
 290 IF X(Z)-U THEN LET S=(X(Z)-U)/ABS (X(Z)-U)
 300 LET T=0
 310 IF Y(Z)-V THEN LET T=(Y(Z)-V)/ABS (Y(Z)-V)
 320 LET X(Z)=X(Z)-S
 330 LET Y(Z)=Y(Z)-T
 331 PRINT AT Y(Z),X(Z)+4;
 332 LET A$=CHR$ PEEK (PEEK 16398+256*PEEK 16399)
 333 IF A$="%H" THEN LET X(Z)=0
 334 IF A$="%A" THEN GOTO 370
 340 IF X(Z) THEN PRINT "%*"
 350 NEXT Z
 360 GOTO 130
 370 PRINT AT Y,X+4;"*"
 380 PRINT AT Y,X+4;"%*"
 390 GOTO 370
 1000 LET X=10+SGN (RND-.5)*INT (RND*(Z/3 AND Z<>42)+1)
 1010 IF X<1 OR X>20 THEN GOTO 1000
 1020 LET Y=10+SGN (RND-.5)*INT (RND*(Z/3 AND Z<>42)+1)
 1030 IF Y<1 OR Y>20 THEN GOTO 1020
 1040 PRINT AT Y,X+4;CHR$ A
 1050 RETURN 
 1060 CLEAR 
 1070 SAVE "1026%9"
 1080 RUN 

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top