Death Race

Developer(s): Peter Shaw
Date: 1983
Type: Program
Platform(s): TS 2068
Tags: Game

Death Race is a two-entity chase game in which the player maneuvers a directional car sprite around a bordered arena while a randomly moving enemy car pursues them, with a 60-unit countdown timer adding urgency. The player uses keys 5–8 for movement, and the car’s heading is represented by one of four custom UDG characters (a–d) that change depending on the last directional key pressed. Six UDGs are defined via POKE and DATA statements: four directional player cars, one collision explosion graphic, and one enemy car. Collision detection relies on SCREEN$ to test whether the destination cell contains a blank space, a border underscore, or another sprite. The score increments on each successful collision with the enemy, and a high-score variable persists across rounds within the same session.


Program Analysis

Program Structure

The program is organized into a main game loop and several subroutines with well-defined responsibilities:

  • Lines 10–200: Main game loop — handles player input, movement, collision detection, enemy movement, and display.
  • Lines 1000–1060: Collision/scoring handler — awards a point, plays a sound, resets positions, and restarts the round.
  • Lines 2000–2050: Game-over sequence — displays score, compares to high score, prompts for replay.
  • Lines 7000–7060: Arena drawing subroutine — draws a bordered play field using inverse underscores.
  • Lines 8000–8990: Variable initialization subroutine — sets player/enemy positions, score, timer, and initial sprite direction.
  • Lines 9000–9080: UDG loader — POKEs six custom characters (a–f) from DATA statements.
  • Lines 9500–9510: REM comments labeling the six UDGs for reference.

UDG Sprite Definitions

Six UDGs are loaded into memory by subroutine 9000, which iterates from USR "a" to USR "f"+7, POKEing 8 bytes each from six DATA lines. Their roles are:

UDGEscapeRole
a\aPlayer car facing up
b\bPlayer car facing right
c\cPlayer car facing down
d\dPlayer car facing left
e\eCollision/explosion graphic
f\fEnemy car sprite

Player Movement and Direction Sprite

Movement uses keys 5–8 in the standard Sinclair layout. Lines 60–70 update vertical (v) and horizontal (h) coordinates using Boolean arithmetic: LET v=v+(a$="6")-(a$="7") neatly adds or subtracts 1 depending on which key is held. Line 51 only updates a$ when a valid movement key is detected, preserving the last direction when no key is pressed.

Line 110 updates the current car sprite c$ based on a$, using string concatenation with Boolean masking — e.g., ("\a" AND a$="7") returns the UDG character if the condition is true, or an empty string otherwise. Line 120 falls back to the previous sprite d$ if no directional key is active, so the car always displays a valid heading.

Collision Detection via SCREEN$

All collision detection is performed by reading back screen content with SCREEN$, avoiding the need for separate coordinate arrays. Three distinct outcomes are handled:

  • SCREEN$(v,h)="" (line 80) — destination is empty, movement is allowed; but this also triggers the scoring event at line 1000 if it coincides with the enemy position overwrite. Actually, a blank cell at the player’s new position goes to line 1000, implying a hit on the enemy (whose cell was just cleared at line 140).
  • SCREEN$(v,h)="_" (line 90) — wall collision; reverts position and clears direction.
  • Any other content (line 100 proceeds) — cell is occupied by the enemy sprite, causing a score event at line 1000 via the blank-cell test after the enemy moves.

The enemy uses the same technique at lines 170–175: if its randomly chosen new cell contains an underscore it retries, and if it contains a blank it triggers line 1000.

Enemy Movement

The enemy moves pseudo-randomly each frame. Line 160 computes j=j+INT(RND*3)-1 and similarly for k, giving a ±1 or 0 delta in each axis each tick. If the target cell is a wall ("_"), the move is retried (lines 170–150 loop back), though there is no limit on retries, which could theoretically stall in a tight corner. The enemy is drawn in INK 5 (cyan) to distinguish it from the player.

Timer and Scoring

The timer ti is initialized to 60 and decremented by 0.2 each main loop iteration at line 125, giving approximately 300 loops before time expires. Score sc increments by 1 per collision (three BEEP tones are played in a FOR loop at lines 1010–1050). A session high score hi is maintained across replays but is reset only at line 20 on first run.

Notable Bugs and Anomalies

  • Line 1060 duplicate assignment: LET k=20: LET v=10: LET k=16k is assigned twice; the final value is 16, and the intermediate assignment of 20 is dead code. The intended value of h (the player’s horizontal position) is likely meant to be reset here as well, but h is never reassigned in line 1060, so the player’s column position is not reset between scoring events.
  • Line 80 logic: The condition SCREEN$(v,h)="" is used to detect a hit, relying on the enemy having just been erased at line 140. This is fragile and depends on execution order; if the enemy and player happen to be on the same cell without the erase having occurred, the check may not fire correctly.
  • Line 100 uses uppercase V,H instead of lowercase v,h. In Sinclair BASIC, variable names are case-sensitive only for string variables; numeric variable names are case-insensitive, so V and v refer to the same variable. This is not a bug but is inconsistent style.

Display and Sound

The arena border is drawn using inverse INK 6 (yellow) underscores filling the top and bottom rows and the leftmost and rightmost columns. Sound feedback is continuous: line 130 emits a short BEEP each loop iteration at pitch sc, so the tone rises as the score increases. Game-over and scoring events use longer BEEPs with fixed or score-relative pitches.

Content

Appears On

Capital Area Timex Sinclair User Group’s Library Tape.
Library tape of the Indiana Sinclair Timex User’s Group.

Related Products

Related Articles

Related Content

Image Gallery

Death Race

Source Code

   10 REM DEATH RACE from Games for Your Timex-Sinclair 2000, p.63
   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$=("\a" AND a$="7")+("\b" AND a$="8")+("\c" AND a$="6")+("\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;"\f"
  200 GO TO 50
 1000 PRINT AT j,k;"\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,26; 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$="\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 238,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
 9500 REM a b c d e f
 9510 REM \a \b \c \d \e \f
 9998 SAVE "Race" LINE 1: BEEP .2,15
 9999 VERIFY ""

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

Scroll to Top