Moon Rescue is a action game in which the player pilots a lander to rescue stranded astronauts on the lunar surface and return them to a moving mothership. The game uses 14 user-defined graphics (UDGs “a” through “n”), loaded via a POKE loop at line 9100, to render the lander, mothership, and surface personnel. Scrolling alien-landscape rows are implemented by rotating string slices with the TO syntax (e.g., `a$(c,pointer TO )` concatenated with `a$(c, TO pointer-1)`), creating a parallax scrolling effect across five terrain rows. Difficulty scales with the number of missions completed: the probability of obstacle placement uses `RND*(11-man)<.4`, so terrain density increases as `man` grows. Line numbers are stored in variables (e.g., `stage1=1000`, `shiploss=6000`) and used in computed GO TO statements, a common memory-saving idiom.
Program Structure
The program is organized into well-separated functional blocks:
- Lines 2–40: Bootstrap and game-loop dispatcher. Calls initialization routines, then branches to
stage1orstage2depending on game state. - Lines 1000–1999: Stage 1 — descending phase. The player drops the lander from the mothership to pick up an astronaut on the surface.
- Lines 2000–2999: Stage 2 — ascending phase. The player lifts off with the rescued astronaut and docks with the moving mothership.
- Lines 3000–3050: Wait/title screen with starfield, awaiting a keypress before play.
- Lines 4000–4010: Surface drawing subroutine — renders the lunar terrain using UDGs and embedded control codes, plus plots rescued-astronaut positions from array
m(). - Lines 5000–5020: Top status bar subroutine — prints SCORE, HIGH, and SHIP labels with a decorative separator line of block graphics.
- Lines 5500–5510: Docking/home routine — flashes the lander in cycling INK colors with sound, awards 300 points, and exits to the game-loop dispatcher.
- Lines 6000–6010: Ship-loss routine — animates a falling explosion, sounds a long beep, increments the ship-loss counter, and returns to the dispatcher.
- Lines 8000–8999: Game-over screen, high-score check with fanfare, then restarts at line 5.
- Lines 9000–9080: Main initialization: dimensions arrays, sets all game variables and computed line-number variables, reads astronaut position data.
- Lines 9100–9899: UDG loader and high-score reset.
- Lines 9900–9989: UDG pixel data as DATA statements.
Computed GO TO via Variables
All major subroutine and jump targets are stored in plain numeric variables at lines 9030–9048:
stage1=1000,stage2=2000,endgame=8000wait=3000,surface=4000,topprint=5000shiploss=6000,home=5500
These are then used in statements like GO TO stage1 and GO SUB wait. On the Spectrum, Sinclair BASIC evaluates the variable’s numeric value as the target line, so this is functionally identical to GO TO 1000 but saves tokenized space and improves readability.
User-Defined Graphics
Fourteen UDGs (characters "a" through "n") are defined at startup. Line 9100 uses a single FOR/POKE loop spanning USR "a" to USR "n"+7 to sequentially write 8 bytes per character from the DATA blocks at lines 9900–9913. The UDGs represent: the player’s lander (\e), mothership components (\a\b\c and \d \f), terrain tiles (\g, \h), and surface/astronaut decorations (\i through \n). The mothership is printed as a two-row composite string built with embedded INK control characters in m$ and n$ at line 9015.
Scrolling Terrain Technique
Five rows of terrain are stored in the string arrays a$(5,32) (stage 1, enemy terrain) and i$(5,32) (stage 2, friendly terrain). A pointer variable pointer is advanced by pinc=2 each frame. Each row is printed as two string slices concatenated inline:
PRINT AT 4+c*2,0; a$(c,pointer TO ); a$(c, TO pointer-1)
This wraps the string around itself, producing a smooth horizontal scroll without any array shifting. When pointer exceeds 32 it resets to 1, completing the cycle.
Difficulty Scaling
Terrain density is controlled at line 1020 by:
IF RND*(11-man)<.4 THEN LET a$(c,d)="\g"
As man (mission count) increases toward 10, the divisor (11-man) shrinks, so the scaled random value is more likely to fall below 0.4, placing more obstacles. This gives an elegant single-expression difficulty curve without branching logic.
Game-Loop and Stage Transitions
The top-level dispatcher at lines 2–40 handles all inter-stage routing:
- Line 7:
GO SUB wait— shows the ready screen. - Line 10:
GO TO stage1— enter descent phase. - Line 20:
GO TO stage2— entered when the lander lands (ATTR check at line 1235). - Line 30: checks
ship<4 AND man<10; loops back to stage 1 if lives remain and missions are incomplete. - Line 40:
GO TO endgamewhen conditions fail.
Both stage routines branch directly to shiploss or home by name, bypassing the dispatcher, then the ship-loss/home routines themselves GO TO 30 to re-enter the condition check.
Collision Detection
Two complementary methods are used:
ATTR (y,x)=3at line 1235 — detects landing on the surface (green INK on black gives attribute value 3), triggering the stage 2 transition.SCREEN$ (y,x)=""at lines 1240 and 2190 — detects collision with any non-space character (terrain obstacles or other sprites), triggering ship loss. An empty string return from SCREEN$ means the cell is blank, so a non-empty result indicates a collision.- Line 2160: exact coordinate match
y=3 AND x=mothx+2— checks docking with the mothership.
Sound Design
Sound is tightly coupled to game state:
- Drop/ascent engine:
BEEP .005,24*drop-12andBEEP .005,24*drop-10— whendroporupis 0, both BEEPs play at –12 and –10 (idle hum); when 1, at +12 and +14 (thrust tone). - Rescue pickup: rising/falling tone pair in a FOR loop at line 2000.
- Docking success: cycling INK with paired BEEPs at line 5500.
- Ship loss: descending-pitch explosion at line 6000, followed by a long low BEEP.
Notable Anomalies and Details
- Line 1999 and 2999 contain
STOPstatements acting as safety guards — they are never reached in normal play but prevent fall-through between stage blocks if line targets are miscalculated. - The ship-loss counter uses
shipinitialized to 1; the condition at line 30 testsship<4, so the player effectively has 3 lives (ship becomes 2, 3, then 4). - Line 2005 sets
m(nman)=0before incrementingnman, which would zero out an already-zero slot — this appears to be a redundant but harmless reset of the astronaut position array entry for the current mission. - The
RESTORE 9900at line 2 followed byGO SUB 9100ensures the DATA pointer is at the UDG data before loading; line 5’s plainRESTOREresets to the start for the astronaut position DATA at line 9070. - The
m$mothership string at line 9015 is constructed with embeddedCHR$ 16(INK) andCHR$ 2(red) control codes inline, making the mothership always print in red regardless of surrounding INK state.
Content
Source Code
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 0,4,63,127,96,246,255,255
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
9990 SAVE "moonrescue" LINE 1
9991 GO TO 1
9992 STOP
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
