Moon Rescue

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

Moon Rescue is a Spectrum arcade game in which the player pilots a rescue ship to retrieve stranded astronauts from a lunar surface and ferry them back to a mothership. The game uses 14 user-defined graphics (UDGs “a” through “n”), loaded from binary DATA at lines 9900–9913 via a POKE loop targeting USR addresses, to draw the mothership, rescuer craft, astronauts, and terrain. Scrolling alien terrain rows are implemented using string slicing on 5×32 character arrays, with a rotating pointer variable to achieve a sideways-scroll effect entirely in BASIC. Difficulty scales with a running man counter: the RND threshold for obstacle placement at line 1020 uses the expression RND*(11-man), making each successive stage harder. The game tracks a persistent high score across playthroughs within the same session.

Program Structure

The program is organized into clearly separated functional blocks, with line numbers used as named subroutine addresses stored in variables (e.g., stage1=1000, stage2=2000, shiploss=6000, home=5500). This pattern, established in lines 9030–9048, allows GO TO stage1 and GO SUB surface style calls throughout, making the flow more readable and the line numbers refactorable without cascading edits.

  • Lines 1–40: Boot and game loop dispatcher
  • Lines 1000–1999: Stage 1 — descent phase (mothership → surface)
  • Lines 2000–2999: Stage 2 — ascent phase (surface → mothership)
  • Lines 3000–3050: Wait/starfield intro screen (wait subroutine)
  • Lines 4000–4010: Surface drawing subroutine (surface)
  • Lines 5000–5020: HUD/top-of-screen print (topprint)
  • Lines 5500–5510: Successful docking with mothership (home)
  • Lines 6000–6010: Ship loss handler (shiploss)
  • Lines 8000–8020: Game over screen (endgame)
  • Lines 9000–9080: Variable and array initialization
  • Lines 9100–9101: UDG loading and high score reset
  • Lines 9900–9913: Binary UDG data (14 characters, “a”–”n”)

User-Defined Graphics

Fourteen UDGs are defined by POKEing binary values from DATA statements into memory starting at USR "a", covering characters “a” through “n” (line 9100). The loop runs FOR c = USR "a" TO USR "n"+7, writing 8 bytes per character. These UDGs represent the mothership body parts (\\a, \\b, \\c, \\d, \\f), the rescue craft (\\e), terrain/obstacle blocks (\\g, \\h), landing pad elements (\\i, \\j, \\k, \\l, \\m, \\n), and astronaut figures.

Sideways Scrolling Technique

The illusion of horizontally scrolling terrain rows is achieved without machine code, using Spectrum BASIC’s string-slicing syntax on 5×32 character arrays (a$ for descent, i$ for ascent). A variable pointer is incremented by pinc (set to 2) each frame, and each row is printed as two concatenated slices:

PRINT AT 4+c*2,0; a$(c,pointer TO ); a$(c, TO pointer-1)

This wraps the row at the pointer position, creating continuous lateral motion. When pointer exceeds 32 it wraps back to 1 (lines 1210, 2110).

Difficulty Scaling

Obstacle density in terrain rows is controlled probabilistically at line 1020:

IF RND*(11-man)<.4 THEN LET a$(c,d)="\\g"

As man increments with each rescue attempt, the multiplier (11-man) shrinks, increasing the chance of any given cell containing an obstacle. This provides a smooth, automatic difficulty curve without explicit level definitions.

Mothership Movement and Docking

The mothership oscillates horizontally between columns 1 and 26 (descent) or 1 and 28 (ascent) by flipping an increment variable inc between +1 and −1 at boundary checks (lines 1201–1202, 2101–2102). The mothership is rendered as a multi-character sprite across two rows using UDGs for its body. Docking is detected at line 2160 by checking both vertical position (y=3) and horizontal alignment (x=mothx+2), requiring the player to match the mothership’s moving position.

Collision Detection

Two collision methods are used in combination:

  • ATTR (y,x)=3 — checks for INK 3 (cyan) attribute at the rescuer’s position, used at line 1235 to detect landing on an astronaut location during descent (triggering stage 2).
  • SCREEN$ (y,x)="" — detects a non-space character at the rescuer’s cell, used at lines 1240 and 2190 to detect collision with terrain obstacles, triggering shiploss.

Note: at line 1240 the condition SCREEN$(y,x)="" will be true for a space character (empty cell), but the rescuer has already been erased at line 1223, so this check actually catches the case where the cell is occupied by a non-space — though the logic is slightly fragile depending on print order.

Sound Effects

Sound is tightly coupled to game state. During descent, BEEP .005,24*drop-12 and BEEP .005,24*drop-10 (lines 1221–1222) produce a two-tone beep only when drop=1 (since multiplying by 0 yields a very low tone rather than silence). The ship-loss sequence at lines 6000–6005 uses a FOR loop with increasing/decreasing pitch to simulate an explosion, followed by a sustained low tone.

Surface Rendering

The lunar surface is drawn in subroutine at line 4000 using a combination of UDG characters and embedded control codes to produce a layered, terraced landscape at rows 17–21. Astronaut positions are stored in a DIM m(10) array populated from DATA (line 9070: positions 4, 5, 6, 11, 14, 15, 17, 23, 23, 26), and drawn as UDG \\l at row 20 in INK 6 (line 4005–4006). Rescued astronauts have their entry zeroed out (LET m(nman)=0 at line 2005).

Game State Management

Ship losses increment ship (counting from 1), and the game ends at line 40 when ship>=4 (three lives used) or man>=10 (all astronauts processed). The high score is preserved across rounds within a session and triggers a celebratory BORDER flash loop at line 8010 if beaten. The RESTORE/GO SUB pattern at lines 2–5 ensures UDGs are only loaded once (with high reset), while game variables are re-initialized on each new game via line 5’s GO SUB 9000.

Notable Anomalies

  • Lines 1999, 2999, and 8999 contain bare STOP statements, serving as unreachable guards between code sections — a common defensive idiom to prevent accidental fall-through.
  • The DIM a$(5,32) and DIM i$(5,32) declarations appear both in line 9000 (initialization) and line 1002 (stage 1 reset), which re-initializes them to spaces each mission — intentional, as new obstacle patterns must be generated.
  • Line 9015 builds display strings using embedded CHR$ ink/paper control codes directly into s$, m$, and n$, pre-formatting colored sprite strings for efficient repeated printing.

Image Gallery

Source Code

    1 POKE 23658,0:CLS :PRINT AT 10,11;"\{20}\{0}\{20}\{1}MOONRESCUE\{20}\{0}"; AT 15,0;"DIRECTIONS: PRESS <q> OR <e>----LOWER CASE----TO GUIDE RESCUER  LEFT OR RIGHT RESPECTIVELY; <0> IS THE RELEASE KEY FOR DROPS TO THE SURFACE OR RISING TO SHIP.":PAUSE 480
    2 RESTORE 9900:GO SUB 9100
    5 RESTORE :GO SUB 9000
    7 GO SUB wait
   10 GO TO stage1
   20 GO TO stage2
   30 IF ship<4 AND man<10 THEN GO TO 10
   40 GO TO endgame
 1000 REM stage1
 1001 LET up=0
 1002 LET x=2:LET y=3:LET mothx=0:DIM a$(5,32):DIM i$(5,32)
 1005 LET man= man+1
 1007 LET pointer=1
 1008 LET inc=1
 1010 FOR c=1 TO 5:FOR d=1 TO 32
 1020 IF RND*(11-man)<.4 THEN LET a$(c,d)="\g"
 1030 NEXT d:NEXT c
 1035 CLS 
 1040 GO SUB surface
 1050 GO SUB topprint
 1060 PRINT AT 0,6; INK 6;score; AT 0,17; INK 5;high; AT 0,30; INK 7;ship
 1070 FOR c=1 TO 5:PRINT AT 4+c*2,0; INK 6;a$(c):NEXT c
 1080 PRINT AT 2,0;m$; AT 3,0;n$; AT y,x
 1085 PRINT AT 3,3;s$
 1200 LET mothx=mothx+inc:IF NOT drop THEN LET x=x+inc
 1201 IF mothx=26 THEN LET inc=-1
 1202 IF mothx=1 THEN LET inc=1
 1205 PRINT AT 2,mothx;m$; AT 3,mothx;n$:PRINT AT y,x;s$
 1210 LET pointer=pointer+pinc:IF pointer>32 THEN LET pointer=1
 1215 FOR c=1 TO 5:PRINT INK 6; AT 4+c*2,0;a$(c,pointer TO );a$(c, TO pointer-1):NEXT c
 1220 IF INKEY$="0" THEN LET drop=1
 1221 BEEP .005,24*drop-12:BEEP .005,24*drop -10
 1223 PRINT AT y,x;" "
 1225 LET y=y+drop:IF y=21 THEN GO TO shiploss
 1227 IF NOT drop THEN GO TO 1200
 1230 LET x=x-(INKEY$="q" AND x>0)+(INKEY$="e" AND x<32)
 1235 IF ATTR (y,x)=3 THEN GO TO 20
 1240 IF SCREEN$ (y,x)="" THEN GO TO shiploss
 1300 GO TO 1200
 1999 STOP 
 2000 PRINT AT y-1,x;s$:FOR c=1 TO 10:BEEP .1,-10+c:BEEP .1,-10-c:NEXT c
 2001 LET up=0:LET drop=0
 2002 LET pointer=1
 2005 LET m(nman)=0:LET nman=nman+1
 2010 CLS :GO SUB surface:GO SUB topprint
 2020 LET y=y-1:PRINT AT y,x;s$
 2030 FOR c=1 TO 5:FOR d=1 TO 32
 2040 IF RND*(11-man)<.4 THEN LET i$(c,d)="\h"
 2050 BEEP .005,30:NEXT d:NEXT c
 2055 LET score=score+250
 2060 PRINT AT 0,6; INK 6;score; AT 0,17; INK 5;high; AT 0,30; INK 7;ship
 2070 FOR c=1 TO 5:PRINT AT 4+c*2,0; INK 5;i$(c):NEXT c
 2080 PRINT AT 2,mothx;m$; AT 3,mothx;n$
 2100 LET mothx=mothx+inc
 2101 IF mothx=28 THEN LET inc=-1
 2102 IF mothx=1 THEN LET inc=1
 2105 PRINT AT 2,mothx;m$; AT 3,mothx;n$
 2106 PRINT AT y,x;s$
 2110 LET pointer=pointer+pinc:IF pointer>32 THEN LET pointer=1
 2115 FOR c=1 TO 5:PRINT INK 5; AT 4+c*2,0;i$(c,pointer TO );i$(c, TO pointer-1):NEXT c
 2120 IF INKEY$="0" THEN LET up=1
 2130 BEEP .005,24*up-12:BEEP .005,24*up-10
 2140 PRINT AT y,x;" "
 2150 LET y=y-up:IF y<3 THEN GO TO shiploss
 2160 IF y=3 AND x=mothx+2 THEN GO TO home
 2170 IF NOT up THEN GO TO 2100
 2180 LET x=x-(INKEY$="q" AND x>0)+(INKEY$="e" AND x<32)
 2190 IF SCREEN$ (y,x)="" THEN GO TO shiploss
 2200 GO TO 2100
 2999 STOP 
 3000 INK 7:PAPER 0:BORDER 0:CLS 
 3010 FOR c=0 TO 50:PLOT RND*255, RND*175:NEXT c
 3030 PRINT AT 16,0; FLASH 1; BRIGHT 1;"PRESS ANY KEY WHEN YOU ARE READY"
 3040 IF INKEY$="" THEN GO TO 3040
 3045 PRINT AT 16,0; OVER 1;"PRESS ANY KEY WHEN YOU ARE READY"
 3050 RETURN 
 4000 INK 4:PRINT AT 17,0;"\i                              \m"
 4001 PRINT AT 18,0;"\{20}\{1}\  \{20}\{1}\{20}\{0}\i                            \m\{20}\{1}\  \{20}\{0}"
 4002 PRINT AT 19,0;"\{20}\{1}\  \  \{20}\{0}\i                          \m\{20}\{1}\  \  \{20}\{0}"
 4003 PRINT AT 20,0;"\{20}\{1}\  \  \  \{20}\{0}\i   "; INK 3;"\k\{20}\{1}\  \  \{20}\{0}\n"; INK 4;"        "; INK 3;"\k\{20}\{1}\  \  \{20}\{0}\n"
 4004 PRINT AT 21,0;"\{20}\{1}\  \  \  \  \{20}\{0}\j\j\j\j\{20}\{1}\  \  \{20}\{1}\{20}\{0}\j\j\j\j\j\j\j\j\j\j\{20}\{1}\  \  \{20}\{0}\j\j\j\j\j\j\{20}\{1}\  \  \  \  \{20}\{0}"
 4005 FOR c=1 TO 10:IF m(c)>0 THEN PRINT AT 20,m(c); INK 6;"\l"
 4006 NEXT c
 4010 INK 7:RETURN 
 5000 PRINT AT 0,0; INK 6; BRIGHT 1;"SCORE:      "; INK 5;"HIGH:        "; INK 7;"SHIP:  "
 5010 PRINT INK 3;"\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''"
 5020 RETURN 
 5500 FOR c=0 TO 7 STEP .5:PRINT AT y,x; INK c;"\e":BEEP .01,c*5:BEEP .01,c*-5:NEXT c
 5510 LET score=score+300:GO TO 30
 6000 FOR c=y TO 20:PRINT AT c,x; INK RND*7;"\e"; OVER 1; AT c,x;"\e":BEEP .01,-c:NEXT c
 6005 BEEP 1,-20:PAUSE 50
 6010 LET ship=ship+1:LET drop=0:LET up=0:CLS :GO TO 30
 8000 PRINT AT 6,11; FLASH 1;"GAME OVER"
 8010 IF score>high THEN LET high=score:FOR c=1 TO 10:BEEP .01, RND*50:BORDER RND*7:NEXT c:BORDER 0:PRINT AT 11,0; BRIGHT 1; FLASH 1;"WELL DONE-A NEW HIGH SCORE!!!!"
 8020 PAUSE 250:CLS :GO TO 5
 8999 STOP 
 9000 DIM a$(5,32):DIM i$(5,32)
 9010 LET ship=1:LET man=0:LET x=2:LET y=3:LET nman=1
 9015 LET s$= CHR$ 19+ CHR$ 1+"\e":LET m$=" "+ CHR$ 16+ CHR$ 2+"\a\b\c ":LET n$=" "+ CHR$ 16+ CHR$ 2+"\d \f "
 9020 LET mothx=0:LET drop=0:LET score=0
 9030 LET stage1=1000:LET stage2=2000:LET endgame=8000
 9040 LET wait=3000
 9042 LET pinc=2
 9045 LET surface=4000
 9047 LET topprint=5000:LET shiploss=6000
 9048 LET home=5500
 9050 DIM m(10):FOR c=1 TO 10
 9060 READ m(c):NEXT c
 9070 DATA 4,5,6,11,14,15,17,23,23,26
 9080 RETURN 
 9100 FOR c= USR "a" TO USR "n"+7:READ a:POKE c,a:NEXT c
 9101 LET high=0
 9899 RETURN 
 9900 DATA BIN 0, BIN 100, BIN 00111111, BIN 1111111, BIN 1100000, BIN 11110110, BIN 11111111, BIN 11111111
 9901 DATA BIN 11000, BIN 1111110, BIN 11111111, BIN 11111111, BIN 0, BIN 11111111, BIN 11100111, BIN 11000011
 9902 DATA BIN 0, BIN 100000, BIN 11111100, BIN 11111110, BIN 111, BIN 1101111, BIN 11111111, BIN 11111111
 9903 DATA BIN 11111000, BIN 11111000, BIN 11111000, BIN 1111100, BIN 111111, BIN 11111, BIN 1111, BIN 111
 9904 DATA BIN 1000, BIN 11100, BIN 1111111, BIN 1001001, BIN 1011101, BIN 111110, BIN 100010, BIN 1000001
 9905 DATA BIN 11111, BIN 11111, BIN 11111, BIN 111110, BIN 11111100, BIN 11111000, BIN 11110000, BIN 11100000
 9906 DATA BIN 11000, BIN 1111100, BIN 11111110, BIN 1111111, BIN 1111111, BIN 1111110, BIN 111000, BIN 10000
 9907 DATA BIN 0, BIN 111100, BIN 1100110, BIN 11111111, BIN 11111111, BIN 10111101, BIN 1000010, BIN 100100
 9908 DATA BIN 10000000, BIN 11000000, BIN 11100000, BIN 11110000, BIN 11111000, BIN 11111000, BIN 11111110, BIN 11111111
 9909 DATA BIN 1, BIN 10110011, BIN 11111111,255,255,255,255,255
 9910 DATA BIN 1111111, BIN 111111, BIN 11111, BIN 1111, BIN 111, BIN 111, BIN 1, BIN 0
 9911 DATA BIN 0, BIN 11100, BIN 11100, BIN 1001001, BIN 111110, BIN 1000, BIN 10100, BIN 100010
 9912 DATA BIN 1, BIN 11, BIN 11, BIN 1111, BIN 11111, BIN 111111, BIN 1111111, BIN 11111111
 9913 DATA BIN 11111110, BIN 11111100, BIN 11111100, BIN 11110000, BIN 11100000, BIN 11000000, BIN 10000000,0
 9989 RETURN 
 9992 REM CLEAR :RANDOMIZE USR 100:SAVE "MNRESQ.B1" LINE 1
 9998 REM CLEAR :RANDOMIZE USR 100:LOAD "L.B1"

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