--- title: "Zombies" id: 57514 type: "computer_media" slug: "zombies-2" url: "http://localhost/computer_media/zombies-2/" markdown_url: "http://localhost/computer_media/zombies-2.md" published_at: "2024-10-05T21:19:06+00:00" modified_at: "2026-04-03T07:58:25+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/zombie.jpeg" caption: "Decaying zombie horde lurching across a neon-lit arena, overhead perspective, shambling undead figures closing in on a lone survivor, glowing grid floor, dramatic top-down viewpoint, bold black border framing the scene, rotting hands reaching forward, pallid green flesh, bloodshot eyes, stark shadows, airbrush illustration, saturated lime green and sickly yellow against deep black, vintage ZX Spectrum cassette inlay art, hand-painted commercial art style, British home computing aesthetic, garish horror typography, circa 1983 --ar 3:4 --raw --v 6 Job ID: f77856b5-5cfc-41fe-bc5e-c6943a6082c6" excerpt: "A zombie-chase game where up to 21 enemies hunt you across a bordered arena, with collision detection done by reading characters directly from the display file." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 56737 title: "Timex Sinclair Public Domain Library Tape 1006" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1006/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/zombie.jpeg" caption: "Decaying zombie horde lurching across a neon-lit arena, overhead perspective, shambling undead figures closing in on a lone survivor, glowing grid floor, dramatic top-down viewpoint, bold black border framing the scene, rotting hands reaching forward, pallid green flesh, bloodshot eyes, stark shadows, airbrush illustration, saturated lime green and sickly yellow against deep black, vintage ZX Spectrum cassette inlay art, hand-painted commercial art style, British home computing aesthetic, garish horror typography, circa 1983 --ar 3:4 --raw --v 6 Job ID: f77856b5-5cfc-41fe-bc5e-c6943a6082c6" - url: "http://localhost/wp-content/uploads/2024/10/269_Zomb.png" media_type_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 codes | Action | | --- | --- | | 33, 112, 114 | Move left (U−1) | | 34, 113, 114 | Move down (V+1) | | 35, 112, 115 | Move up (V−1) | | 36, 113, 115 | Move 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). ## 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 ```