This program implements a Lunar Excursion Module (LEM) landing simulator called “Moon Lander,” written by Peter Cooke. The player controls thrust (keys 1/2) and rotation angle (keys 0/9) through eight discrete orientations to land on a marked pad. The game relies heavily on machine code routines loaded from a separate CODE block at address 40000, called via RANDOMIZE USR to handle sprite drawing, collision detection (returning a value from USR), and explosion/landing effects. Physics are computed in BASIC using trigonometric functions: horizontal and vertical velocity components are updated each frame using COS and SIN of the angle scaled to 2*PI/8 radians per step. The program saves itself as a two-part tape file—a BASIC autostart loader at line 620 and the machine code block “moonland C” at 40000, 2768 bytes long.
Program Analysis
Program Structure
The program is organized into a main game loop and several subroutines. Execution is divided broadly as follows:
- Lines 10–70: Title screen — flashing “STOP THE TAPE” banner using alternating INK/PAPER colors, waiting for a keypress via
RANDOMIZE USR 26727(a fast halt/keypress wait routine) andINKEY$. - Lines 80–90: Display instructions (GO SUB 370) and initialize the game screen and variables (GO SUB 520, GO SUB 400).
- Lines 100–290: Main game loop — reads input, updates physics, POKEs sprite positions into machine code data areas, calls USR routines for rendering and collision detection.
- Lines 300–360: Landing/crash evaluation and restart logic.
- Lines 370–390: Instructions screen subroutine.
- Lines 400–510: Screen setup — draws border, lunar terrain using PLOT/DRAW arcs, initializes machine code sprite tables, prints HUD.
- Lines 520–610: Variable initialization subroutine.
- Lines 620–630: Loader/saver block — loads the CODE file on first run; saves both BASIC and CODE parts to tape.
Machine Code Integration
The game depends entirely on a 2768-byte machine code block loaded at address 40000. BASIC calls it repeatedly via RANDOMIZE USR at several entry points:
| Address | Purpose |
|---|---|
26727 | Fast keypress wait / halt loop (ROM utility) |
40000 | Erase old sprite at previous position |
40145 | Draw sprite at new position / collision detect (returns 0 if collision) |
40300 | Explosion animation frame |
40400 | Terrain painter / fill routine (called with POKEd parameters) |
40451 | Landing pad drawing routine (yellow pad) |
40550 | Crash/explosion sequence frame |
Sprite position data is communicated to the machine code by POKEing x and y coordinates into fixed addresses. The current position is stored at 40183 (x) and 40165 (y), while the previous position (for erasure) is held at 40038 and 40020. The rotation angle is communicated via 40006 (current) and 40151 (previous), enabling the machine code to draw the correctly rotated LEM sprite.
Physics Model
The LEM uses a simple Euler-integration physics model. The angle is quantized into 8 directions (0–7), and thrust is decomposed into X and Y velocity increments:
sx = sx + 0.03 - 0.01 * thrust * COS(2*PI*angle/8)— horizontal velocity with constant rightward drift (simulating gravity-free horizontal drift)sy = sy + 0.01 * thrust * SIN(2*PI*angle/8)— vertical velocity (no gravitational constant is added, so “gravity” is implicitly the absence of upward thrust)
The 0.03 constant in the X update provides a persistent rightward screen drift, simulating orbital motion rather than planetary gravity. The lack of a downward gravity term means the lander coasts freely unless thrust is applied, which is an unconventional but coherent model for a top-down or side-scrolling orbital context.
Collision and Landing Detection
Collision detection is performed by the machine code at 40145, which returns 0 in the USR value when a collision with terrain is detected. Line 290 checks IF v=0 THEN GO TO 100 to continue if no collision. Line 300 then evaluates safe-landing criteria:
(INT x) = 48— LEM must be over the pad (X position check)ybetween 170 and 204 — within vertical landing zonesx <= 0.3— horizontal speed within safe limitsy <= 0.2— vertical speed within safe limit
If any condition fails, the crash sequence runs: it cycles through explosion frames (addresses 40300 and 40000) for angles 8–11, then plays a descending BEEP sequence, and prints “CRASH !!!!”.
Notable BASIC Techniques
- Color cycling on title screen: Lines 20–40 swap INK and PAPER values using a temporary variable
cto alternate two colors across 22 rows, creating a striped effect without a lookup table. - Fuel-linked audio: Line 100 uses
BEEP 0.05, 0.05 + (12 AND fuel<100). The expression12 AND fuel<100evaluates to 12 (a high pitch) when fuel is critical and 0 otherwise, giving an audio warning. - PAUSE NOT PI: Used in lines 310, 340, 390 as a wait-for-keypress idiom.
NOT PIevaluates to 0, soPAUSE 0waits indefinitely until a key is pressed. - Variable initialization compaction: In subroutine 520–610, variables are initialized by chaining assignments:
LET y=x,LET angle=thrust,LET sy=sx— all set to the same value without redundant literals. - OVER 1 screen clear: Line 320 uses
OVER 1with INK 7 and PAPER 0 to XOR-erase previously drawn graphics from rows 0–18, effectively clearing the play area withoutCLS. - Terrain drawing with arcs: Lines 420–430 use the Spectrum’s
DRAW x,y,anglearc form to render curved mountain terrain, a technique requiring careful precalculation of arc parameters.
Two-Part Tape Loading
Line 620 is the autostart loader: it clears memory below 40000, prints a loading message, loads the CODE block “moonland C”, sets system variables to point to the machine code area (POKEing 23675/23676 to set the RAMTOP area), and then RUNs from line 10. Line 630 saves both parts: the BASIC program with LINE 620 autostart and the 2768-byte machine code block starting at 40000.
Potential Anomalies
- Line 450 is a
REM-commented POKE/USR block that was evidently an earlier or alternative terrain-fill call, left in as a development artifact. - The
REMon line 1 contains what appears to be loader/development notes embedded as a REM string, a common practice for embedding metadata or copy-protection hints in BASIC programs. - The landing X-position check
(INT x)<>48uses integer truncation rather than rounding, which could cause asymmetric pad detection depending on approach direction.
Content
Source Code
1 REM USE LIST 2d RETURN :H\ NEW o AND g GO SUB i{= CLEAR GO SUB a{= CLEAR LIST \ RETURN :H\ NEW o AND g GO SUB i{= CLEAR GO SUB a{= CLEAR LIST <>oo
10 BORDER 5: PAPER 5: LET a=2: LET b=7: CLS
20 FOR i=0 TO 21
30 PRINT AT i,8; INK a; PAPER b;" STOP THE TAPE "
40 LET c=a: LET a=b: LET b=c: NEXT i
50 PRINT #1;AT 1,0; INK 9;" PRESS ANY KEY "
60 RANDOMIZE USR 26727
70 IF INKEY$="" THEN GO TO 60
80 GO SUB 370
90 GO SUB 520: GO SUB 400
100 LET z$=INKEY$: BEEP 0.05,.05+(12 AND fuel<100)
110 LET angle=angle+(z$="0")-(z$="9")
120 IF angle=8 THEN LET angle=0
130 IF angle=-1 THEN LET angle=7
140 POKE 40006,PEEK 40151: POKE 40151,angle
150 LET thrust=thrust+(z$="1")-(z$="2" AND thrust>0)
160 IF fuel<=0 THEN LET fuel=0: LET thrust=0
170 LET sx=sx+.03-.01*thrust*COS (2*PI*angle/8)
180 LET sy=sy+.01*thrust*SIN (2*PI*angle/8)
190 LET x=x+sx
200 LET y=y+sy
210 POKE 40038,PEEK 40183
220 POKE 40020,PEEK 40165
230 POKE 40183,x
240 POKE 40165,y
250 LET fuel=fuel-thrust
260 PRINT AT 20,2; PAPER 1;fuel;" ";AT 20,9;INT (sx*10);" ";AT 20,15;INT (sy*10);" ";AT 20,21;thrust;" "
270 LET v=USR 40000
280 LET v=USR 40145
290 IF v=0 THEN GO TO 100
300 IF (INT x)<>48 OR y<170 OR y>204 OR sx>.3 OR sy>.2 THEN LET v=USR 40145: FOR n=8 TO 11: POKE 40006,n: LET v=USR 40000: LET v=USR 40300: LET v=USR 40000: NEXT n: FOR n=1 TO 21: LET v=USR 40550: BEEP 0.02,21-n: NEXT n: PRINT #1;AT 0,0; INK 2; PAPER 6;" CRASH !!!! Press a key ": PAUSE NOT PI: GO TO 320
310 PRINT #1;AT 0,0; INK 2; PAPER 6;" ******* SAFE LANDING ******* ": PAUSE NOT PI
320 FOR n=0 TO 18: PRINT AT n,0; OVER 1; PAPER 0; INK 7,,: NEXT n
330 IF INKEY$<>"" THEN GO TO 330
340 PRINT #1;AT 0,0;" Q to QUIT or ANY to play again ": PAUSE NOT PI
350 IF INKEY$="Q" OR INKEY$="q" THEN STOP
360 GO SUB 520: INPUT "": GO SUB 470: GO TO 100
370 BORDER 0: PAPER 0: INK 9: CLS : PRINT AT 0,0;" *** M O O N L A N D E R *** "''" By PETER COOKE"
380 PRINT AT 5,0;"Use the keys "'"9 turn left"'"0 turn right"'"1 increase thrust"'"2 decrease thrust"''"land your L.E.M. on the pad (in yellow)."
390 PRINT AT 20,0;" press a key to start ": PAUSE NOT PI: RETURN
400 PAPER 0: INK 7: BORDER 0: CLS : FOR n=19 TO 21: PRINT AT n,0; PAPER 1,,: NEXT n
410 PLOT 0,0: DRAW 255,0: DRAW 0,175: DRAW -255,0: DRAW 0,-175
420 PLOT 30,175: DRAW 70,-110,.9: DRAW 30,70,.9: DRAW 60,30,-1: DRAW 65,-60,-1.3
430 PLOT 1,160: DRAW 100,-130,1.3: DRAW 70,90,1: DRAW 40,0: DRAW 44,-60,1
440 POKE 40452,31: POKE 40454,074: RANDOMIZE USR 40451
450 REM POKE 40401,51: POKE 40403,175: RANDOMIZE USR 40400
460 POKE 40401,1: POKE 40403,25: RANDOMIZE USR 40400
470 PRINT AT 19,2; PAPER 1;"fuel hsi vsi thrust"
480 PRINT AT 20,2; PAPER 1;fuel;" ";AT 20,9;sx;" ";AT 20,15;sy;" ";AT 20,21;thrust;" "
490 RANDOMIZE USR 40145
500 PRINT AT 7,21; INK 6; OVER 1;" "
510 RETURN
520 LET x=2: LET y=x
530 LET fuel=2000
540 LET thrust=0
550 LET angle=thrust
560 LET sx=0: LET sy=sx
570 POKE 40006,angle
580 POKE 40151,angle
590 POKE 40020,y: POKE 40165,y
600 POKE 40038,x: POKE 40183,x
610 RETURN
620 CLEAR 39999: PRINT INK 9;AT 10,2;" HANG ABOUT - STILL LOADING ": LOAD "moonland C"CODE : POKE 23676,166: POKE 23675,104: RUN
630 SAVE "Moonland" LINE 620: BEEP .2,22: SAVE "moonland C"CODE 40000,2768: BEEP .2,22
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.


