GRAVE-YARD is a two-character chase game in which the player maneuvers a ghost sprite around a bordered arena while a second ghost wanders randomly, and both characters must avoid collision with the arena’s underscore walls. The player moves using keys 5–8 (the standard Spectrum directional keys), with the player sprite changing shape based on the direction of travel using one of four UDGs (A–D). A 60-unit countdown timer drives the game’s pace, displayed at the bottom of the screen alongside a score that increments by three each time the wandering ghost (UDG F) catches the player’s ghost, triggering a respawn at fixed coordinates. Six UDGs are defined at startup via a DATA-driven POKE loop targeting USR “A” through USR “F”, covering directional player sprites, a collision sprite (UDG E), and the enemy sprite (UDG F). The high score is retained across rounds within the same session and displayed on the game-over screen alongside a prompt to restart.
Program Analysis
Program Structure
The program is organized into a main game loop with clearly separated initialization and setup subroutines:
9000–9080: UDG definition — reads pixel data and POKEs six UDGs (A–F) via a loop overUSR "A"toUSR "F"+7.8000–8990: Game variable initialization — sets border/paper/ink, positions, score, timer, and initial sprite character.7000–7060: Screen setup — draws a bordered arena using INVERSE INK 6 underscores and prints the score line.50–200: Main game loop — handles player input, player movement, enemy movement, collision detection, and timer management.1000–1060: Score/respawn handler — increments score, plays a beep sequence, and respawns both characters.2000–2050: Game-over sequence — displays score, updates high score, and prompts replay.
UDG Initialization
Subroutine 9000 defines six UDGs (A through F) by reading 8 bytes each from DATA statements at lines 9030–9080 and POKEing them into the UDG area. The loop iterates from USR "A" to USR "F"+7, covering all 48 bytes. UDGs A–D are directional player sprites (left, right, down, up), UDG E is a collision/capture sprite, and UDG F is the enemy ghost sprite.
Player Input and Movement
Lines 51–70 implement a latching input scheme. INKEY$ is only accepted if it falls in the range "5"–"8"; otherwise the last direction stored in A$ is retained. Movement is computed using Boolean arithmetic:
V(row) changes by(A$="6")-(A$="7")— key 6 moves down, key 7 moves up.H(column) changes by(A$="8")-(A$="5")— key 8 moves right, key 5 moves left.
The previous position is saved in V1, H1 before each move. If the new cell contains an underscore (wall), the move is cancelled by restoring V1, H1 and clearing A$ at line 90.
Directional Sprite Switching
Line 110 updates the player’s display character C$ based on the current direction key using concatenated Boolean string expressions — a common Spectrum BASIC idiom where a string multiplied by a true condition yields the string and by a false condition yields an empty string. The four cases map keys 5–8 to UDGs D, A, C, and B respectively. If no direction key is active (C$=""), the previous sprite D$ is retained (line 120).
Enemy Ghost Movement
The enemy ghost at coordinates (J,K) moves pseudo-randomly each frame (lines 150–180). Its row and column each change by INT(RND*3)-1, giving a displacement of −1, 0, or +1. Like the player, it saves its prior position and cancels moves into wall cells. Collision with an empty cell (SCREEN$="") at line 175 triggers the catch event at line 1000.
Collision Detection via SCREEN$
Both player and enemy use SCREEN$ to detect the contents of the target cell before moving into it. An empty string result indicates the player and enemy have overlapped, triggering the score event at line 1000. An underscore character indicates a wall boundary, blocking movement.
Timer Mechanism
The timer variable TI is initialized to 60 and decremented by 0.2 each main loop iteration at line 125. This is a software timer driven by loop speed rather than the system clock. When TI drops below zero, the game-over routine at line 2000 is triggered. The display uses INT TI to show whole seconds.
Bugs and Anomalies
- Line
1060setsK=20and then immediately overwrites it withK=16— the intent was almost certainlyH=16to reset the player’s column, sinceHis the player’s horizontal coordinate. As written,His never reset here, so the player respawns at whatever column they were at when the catch occurred. - Line
80checksSCREEN$(V,H)=""after the player has already moved, meaning the check fires when the player moves into a cell occupied by the enemy — this is the intended catch condition, but the logic is inverted from what a reader might expect (empty screen = collision rather than a solid obstacle). - The
BEEP .008,SCat line130pitches the movement sound to the current score, so the tone rises throughout the game as the score increases.
Key Variable Summary
| Variable | Purpose |
|---|---|
V, H | Player row and column |
V1, H1 | Previous player position (for wall revert) |
J, K | Enemy ghost row and column |
J1, K1 | Previous enemy position (for wall revert) |
A$ | Current/latched direction key |
C$ | Current player UDG sprite character |
D$ | Previous player UDG sprite (fallback) |
SC | Current score |
HI | Session high score |
TI | Countdown timer (software-driven) |
Source Code
10 REM "GRAVE-YARD"
20 GO SUB 9000:LET HI=0
30 GO SUB 8000
40 GO SUB 7000
50 PRINT AT V,H;" "
51 IF INKEY$<"5" OR INKEY$>"8" THEN GO TO 55
53 LET A$= INKEY$
55 LET V1=V:LET H1=H
60 LET v=v+(a$="6")-(a$="7")
70 LET H=H+(A$="8")-(A$="5")
80 IF SCREEN$ (V,H)="" THEN GO TO 1000
90 IF SCREEN$ (V,H)="_" THEN LET V=V1:LET H=H1:LET A$="":GO TO 55
100 PRINT AT V,H; INK 3;C$
105 LET D$=C$
110 LET C$=("[UDG-A]" AND A$="7")+("[UDG-B]" AND A$="8")+("[UDG-C]" AND A$="6")+("[UDG-D]" AND A$="5")
120 IF C$="" THEN LET C$=D$
125 PRINT AT 21,16;"TIME "; INT TI;" " :LET TI=TI-.2:IF TI<0 THEN GO TO 2000
130 BEEP .008,SC
140 PRINT AT J,K;" "
150 LET J1=J:LET K1=K
160 LET J=J+ INT (RND*3)-1:LET K=K+ INT (RND*3)-1
170 IF SCREEN$ (J,K)="_" THEN LET j=j1:LET K=K1:GO TO 150
175 IF SCREEN$ (J,K)="" THEN GO TO 1000
180 PRINT AT J,K; INK 5;"[UDG-F]"
200 GO TO 50
1000 PRINT AT J,K;"[UDG-E]"
1010 FOR A=1 TO 3
1020 LET SC=SC+1
1030 PRINT AT 21,0;"SCORE ";SC
1040 BEEP .7,SC
1050 NEXT A
1060 LET J=4:LET K=20:LET V=10:LET K=16:GO TO 40
2000 PRINT AT 21,16; PAPER 1;"TIME 0":BEEP 2,-10:CLS
2010 PRINT AT 2,12; PAPER 2;"GAME OVER"
2020 PRINT AT 5,10; PAPER 2;"YOU SCORED ";SC
2030 IF SC>HI THEN LET HI=SC
2040 PRINT AT 18,6; PAPER 6;"HIGHEST SCORE TODAY";HI
2050 INPUT PAPER 1;"PRESS "; PAPER 2;"ENTER"; PAPER 1;" TO PLAY AGAIN "; LINE A$:GO TO 30
7000 CLS :PRINT INVERSE 1; INK 6;"________________________________"
7010 FOR A=1 TO 19
7020 PRINT INVERSE 1; INK 6;"_"; AT A,31;"_"
7030 NEXT A
7040 PRINT INVERSE 1; INK 6;"________________________________"
7050 PRINT AT 21,0;"SCORE ";SC
7060 RETURN
8000 BORDER 0:PAPER 0:INK 9:CLS
8010 LET V=10:LET H=16
8020 LET J=4:LET K=20
8030 LET SC=0:LET TI=60
8040 LET C$="[UDG-A]"
8050 LET A$=""
8990 RETURN
9000 FOR A= USR "A" TO USR "F"+7
9010 READ USER:POKE A,USER
9020 NEXT A:RETURN
9030 DATA 60,153,255,153,24,189,255,189
9040 DATA 236,68,229,255,255,229,68,238
9050 DATA 189,255,189,24,153,255,153,60
9060 DATA 119,34,167,255,255,167,34,119
9070 DATA 56,124,238,198,238,238,254,254
9080 DATA 56,56,16,124,16,16,40,68
9998 REM A B C D E F
9999 REM [UDG-A] [UDG-B] [UDG-C] [UDG-D] [UDG-E] [UDG-F] Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
