Skeletons

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 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:

  1. Initialisation (lines 9900–9940): Calculates display file address, dimensions the enemy array, draws the play-field, and jumps to the main loop.
  2. Player movement subroutine (lines 2–10): Called via GOSUB 2 from inside the enemy loop; reads INKEY$ and updates the player’s display-file address.
  3. Enemy AI loop (lines 11–30): Iterates over four enemies, calls the player subroutine, then moves each skeleton one step toward the player.
  4. 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:

CodeMeaning
0Space — erase sprite
52Player character (“0” in ZX81 character set)
61Enemy skeleton sprite (“=”)
8Wall tile
5Goal/exit tile
151Inverse character — player win sprite
180Inverse 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; VAL of a string avoids this overhead.
  • SGN PI evaluates to 1 and is used as the loop start value instead of the literal 1, again saving bytes.
  • The saved variable J=P at line 2 records the old player position so it can be erased with POKE J,0 at line 6 only when the position actually changed (IF NOT P=J).
  • Line 7 uses PEEK P=8 OR PEEK P=61 to detect wall or enemy collision and revert the move by restoring P=J.

Bugs and Anomalies

  • Line 9937 contains a typo: PEEK 16396+256*PEEK 6397 — a space intrudes into the second address, making it PEEK 6397 instead of PEEK 16397. This will likely read the wrong memory location and produce an incorrect value for D, 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

Appears On

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

Related Products

Related Articles

Related Content

Image Gallery

Skeletons

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.

People

No people associated with this content.

Scroll to Top