Monster

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Game

Monster is a platform-style maze game in which the player navigates a character around a multi-level screen, collecting jewels, eliminating monsters, and reaching a door before a countdown timer expires. The screen layout is built procedurally each level using UDGs (user-defined graphics) loaded via a DATA/POKE USR loop that defines eight custom characters (codes 144–151). Movement is handled with keys 5, 6, 7, and 8, and the ATTR function is used extensively to detect collisions with colored screen objects, distinguishing platforms, monsters, jewels, and the exit door by their ink and paper attribute bytes. Killing a monster triggers an “invisible gas” mechanic that doubles the rate of timer decay, tracked with a flag variable. A high-score variable persists across games within the same session.


Program Structure

The program is organized into clearly delineated functional blocks:

  1. Lines 1–6: Initialization, high-score reset, and instructions prompt.
  2. Line 7: Level selection and validation.
  3. Lines 8–120: Level setup — drawing platforms, monster positions, jewels, and the exit door, plus an intro beep sweep.
  4. Lines 195–300: Main game loop — player movement, collision detection, scoring, and timer management.
  5. Lines 1000–1010: Death-by-falling routine.
  6. Lines 2000–2090: UDG definition subroutine via DATA and POKE USR.
  7. Lines 5000–5040: End-of-game screen with high-score comparison and replay prompt.
  8. Lines 6000–6030: Instructions display (note: called as GO SUB 5500 in line 5, but defined at line 6000 — see Bugs section).

UDG Definitions

Eight UDGs are defined at startup by the subroutine at line 2000, using a nested FOR loop that reads eight bytes per character from DATA lines 2010–2080 and POKEs them into memory with POKE USR CHR$ z+x, a for z running from 144 to 151. The characters and their roles are:

UDGCodeRole
[UDG-A]144Player character
[UDG-B]145Player death sprite
[UDG-C]146Jewel / collectible
[UDG-D]147Monster (placed on platforms)
[UDG-E]148Platform block
[UDG-F]149Killable monster (row 1)
[UDG-G]150Door left half
[UDG-H]151Door right half

Level Generation

Platforms are drawn in a loop (lines 10–30) that prints INK 5 platform blocks across each row, stepping up three rows at a time from row 21 down to row 3. Monster sprites are scattered at random column positions using RND *29+1 (line 45), printed in INK 6 pairs two cells tall. Jewels (line 100) are placed at six positions spaced three rows apart, printed BRIGHT 1 INK 7. The exit door (line 110) is fixed at row 20, column 29 in INK 2.

ATTR-Based Collision Detection

The game makes heavy use of ATTR to identify objects by their color attributes rather than tracking sprite positions in variables. Key checks in the main loop include:

  • ATTR (a-1,b)=6 — detects a monster above the player (INK 6 = yellow), used for jumping onto platforms with monsters (line 275) and for the downward move (line 281).
  • ATTR (a-1,b)=4 — detects a killable monster at row 1 in INK 4 (green), awards 100 points (line 280).
  • ATTR (a,b)=71 — detects a jewel (attribute byte 71 = BRIGHT 1 + INK 7 + PAPER 0), awards 50 points (line 285).
  • ATTR (a,b)=2 — detects the exit door in INK 2 (red), triggering level advance (line 291).
  • SCREEN$ (a-1,b)=" " — checks for empty space above before a simple upward step (line 270).

Movement and Physics

The player position is stored in row a and column b. Movement keys are 5 (left), 8 (right), 7 (up), and 6 (down). Upward movement on a plain space (line 270) is a short one-cell hop that immediately returns the player to their original row — effectively a cosmetic jump with no sustained elevation. The jump onto a monster platform (line 275) moves the player up three rows at once, matching the three-row platform spacing. The downward move through a monster (line 281) similarly shifts three rows and is gated by k >= 16, meaning the player must have collected at least 16 kills. Boundary detection (line 290) triggers the fall subroutine when b=0 or b=31.

Timer and Gas Mechanic

The timer t starts at 1000 and is decremented by l (the current level) each iteration of the main loop (line 282). When a monster is killed, flag n is set to 1 (line 280), and line 287 subtracts an additional 2*l per loop while n=1, effectively tripling the timer drain. The flag is cleared when the player descends through a monster (line 281, LET n=0). Timer display is updated each loop at line 288, and exhaustion at line 289 routes to the end-game subroutine.

Falling Death Routine

Lines 1000–1010 implement a falling animation: the player sprite is printed one row lower on each iteration with an accompanying descending BEEP, and the previous position is erased. When row 21 is reached, the death sprite (UDG-B) is displayed with FLASH 1 in INK 2, a long low beep plays, and the routine falls through to the end-game screen at line 5000.

Notable Techniques

  • The OVER 1 mode in lines 250 and 260 XORs the player sprite onto the screen when erasing the previous position, a common sprite-movement technique to avoid explicit background redrawing.
  • The intro music (line 120) is a simple FOR sweep from -10 to 20 semitones, producing a rising glide.
  • The high-score hs is initialized once at line 1 and persists for the session, compared at line 5000.
  • Platform row spacing of 3 is hardcoded throughout and must be consistent between generation (lines 10–30) and movement (lines 275, 281) for gameplay to work correctly.

Bugs and Anomalies

  • Missing subroutine target: Line 5 calls GO SUB 5500, but the instructions routine begins at line 6000. There is no line 5500, so requesting instructions would cause a BASIC error. The instructions are never reachable in normal play.
  • Level validation error: Line 7 checks IF l<1 OR 1>9 — the second operand should be l>9 not 1>9. Since 1>9 is always false, any value of l that is not less than 1 (i.e., any positive number) bypasses the FOOL message, allowing levels above 9.
  • Variable reuse of b: The variable b is used both as a platform-drawing row counter (lines 8–95) and as the player’s column position (lines 195 onward). This is resolved by the explicit reset at line 195 (LET b=1), but the dual use is a potential source of confusion.
  • Line 270 jump behavior: The upward hop immediately restores a to its original value with LET a=a+1, so the jump has no lasting effect on position — it is purely cosmetic.
  • Line 95 loop variable conflict: The loop at line 95 uses b as both its FOR variable and increments it manually with LET b=b+1 inside the loop body, which interferes with the NEXT b counter and will cause non-standard stepping behavior.

Image Gallery

Source Code

   1 LET hs=0
    2 INK 3:BORDER 0:PAPER 0:CLS :GO SUB 2000
    3 CLS :PRINT  AT 10,0; INK 7;"Do you need instructions ? (y/n)"
    4 IF  INKEY$ ="n"  THEN  GO TO 7
    5 IF  INKEY$ ="y" THEN  GO SUB 5500
    6 GO TO 4
    7 CLS :PRINT  INK 7;"START LEVEL ? (1-9)":INPUT l:LET sc=0:LET k=0:IF l<1 OR 1>9 THEN  CLS :PRINT  AT 10,13; FLASH 1; INK 7;"FOOL":PAUSE 100:GO TO 3                                    
    8 LET b=21:LET t=1000
    9 CLS :PRINT  AT 0,0; INK 7;"SCORE=";sc; AT 0,13;"TIME=";t; AT 0,25;"LEVEL=";l
   10 FOR a=1 TO 30
   11 PRINT  AT b,a; INK 5;"[UDG-E]"
   20 NEXT a
   30 IF b>3 THEN  LET b=b-3:GO TO 10
   40 FOR m=1 TO 2
   45 LET r= RND *29+1
   50 FOR s=1 TO 2:PRINT  AT b,r; INK 6;"[UDG-D]":LET b=b+1:NEXT s                                       
   70 IF b<19 THEN  LET b=b+1: GO TO 45
   80 LET b=3:NEXT m
   90 LET z=5
   95 FOR b=1 TO 14:PRINT  AT 1,b; INK 4;"[UDG-F]":LET b=b+1:NEXT b:PRINT  AT 1,15; INK 4;"[UDG-F][UDG-F]":FOR b=18 TO 30:PRINT  AT 1,b; INK 4;"[UDG-F]":LET b=b+1:NEXT b
  100 FOR n=0 TO 5:PRINT  AT z, RND *29+1; INK 7; BRIGHT 1;"[UDG-C]":LET z=z+3:NEXT n
  110 PRINT  AT 20,29; INK 2;"[UDG-G][UDG-H]"                                 
  120 FOR m=-10 TO 20:BEEP .05,m:NEXT m
  195 LET a=20:LET b=1
  200 PRINT  AT a,b;"[UDG-A]"
  201 BEEP .002,0:BEEP .002,10                                   
  205 PRINT  AT 0,6; INK 7;sc
  250 IF  INKEY$ ="5" AND b>0   THEN LET b=b-1:PRINT  AT a,b+1; OVER 1;"[UDG-A]"
  260 IF  INKEY$ ="8" AND b<31 THEN  LET b=b+1:PRINT  AT a,b-1; OVER 1; "[UDG-A]"
  270 IF  SCREEN$ (a-1,b)=" " AND  INKEY$ ="7" THEN PRINT  AT a,b;;" ":LET a=a-1:PRINT  AT a,b;"[UDG-A]":BEEP .1,2:LET a=a+1:PRINT  AT a-1,b;" "
  275 IF  ATTR (a-1,b)=6 AND  INKEY$ ="7" AND a>3 THEN  PRINT  AT a,b;" ":LET a=a-3:BEEP .01,10:BEEP .01,5
  280 IF  ATTR (a-1,b) =4 AND INKEY$ ="7" AND a<3 THEN  PRINT  AT a,b;" ":PRINT  AT a-1,b;"[UDG-A]":BEEP .1,-20:PRINT  AT a-1,b;" ":LET sc=sc+100:LET n=1:LET k=k+1
  281 IF  ATTR (a+1,b) =6 AND INKEY$ ="6" AND k >= 16 THEN  LET a=a+3:PRINT  AT a-3,b;" ":BEEP .01,10:BEEP .01,0:LET n=0
  282 LET t=t-l
  285 IF  ATTR (a,b)=71 THEN  BEEP .1,40:LET sc=sc+50
  287 IF n=1 THEN  LET t=t-(2*l)                                     
  288 PRINT  AT 0,18;"    "; AT 0,18; INK 7;t
  289 IF t<0 THEN  LET t=0:PRINT  AT 0,18;"   "; AT 0,18; INK 7;"0":IF t=0 THEN  GO SUB 5000
  290 IF b=0 OR b=31 THEN  GO SUB 1000
  291 IF  ATTR (a,b)=2 THEN  LET l=l+1:GO TO 8
  300 GO TO 200
 1000 LET a=a+1:PRINT  AT a,b;"[UDG-A]":BEEP .1,-a:PRINT  AT a,b;" ":IF a <> 21 THEN  GO TO 1000
 1010 PRINT  AT a,b; FLASH 1; INK 2;"[UDG-B]":BEEP 2,-40:GO TO 5000                                   
 2000 FOR z=144 TO 151:FOR x=0 TO 7:READ a:POKE  USR  CHR$ z+x,a:NEXT x:NEXT z
 2010 DATA 24,36,24,60,90,24,36,66
 2020 DATA 60,92,191,251,190,255,86,24
 2030 DATA 0,16,56,84,254,84,56,16
 2040 DATA 195,255,255,195,195,255,255,195
 2050 DATA 255,255,129,66,36,24,255,255
 2060 DATA 102,153,60,90,60,102,255,126
 2070 DATA 63,33,33,33,33,33,33,63
 2080 DATA 248,248,248,248,232,248,248,248
 2090 RETURN 
 5000 CLS :IF sc>hs THEN  PRINT  AT 5,8; INK 7; FLASH 1;"CONGRATULATIONS"; AT 7,3;"YOU HAVE BEATEN THE HI-SCORE"; AT 9,10;"of ";hs; AT 11,8;"with a score of ";sc:LET hs=sc:GO TO 5010
 5005 PRINT  AT 10,7; INK 7;"Your score was ";sc
 5010 PRINT  AT 15,1; INK 7;"Do you want another go ? (y/n)"
 5020 IF  INKEY$ ="n" THEN  CLS :PRINT  AT 10,11; INK 7; FLASH 1;"GOODBYE":STOP 
 5030 IF  INKEY$ ="y" THEN  GO TO 3
 5040 GO TO 5020
 6000 CLS :PRINT  INK 7;"Your task is to steer yourself  ([UDG-A]) around the screen without   falling to your death or runningout of time.You must attempt to collect the jewels ([UDG-C]) and kill all the monsters ([UDG-F]) before     moving on to the next level     through the door ([UDG-G][UDG-H])."
 6005 PRINT  INK 7''"Press any key to continue.":PAUSE 0
 6010 CLS :PRINT  INK 7'"Once you have killed a monster  an invisible gas is released    and your time decreases more    rapidly."''"You score 50 points for each    jewel and 100 for each monster."
 6020 PRINT  AT 10,10; INK 7;"INSTRUCTIONS"; AT 12,5;"5...............left"; AT 14,5;"6...............down"; AT 16,5;"7...............up"; AT 18,5;"8...............right"
 6025 PRINT  INK 7''"Press any key to continue.":PAUSE 0
 6030 GO TO 7

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