LANDER FIX is a multi-screen Moon Lander game in which the player pilots a spacecraft, controlling vertical thrust and horizontal rotation via joystick, and must touch down on marked landing pads across five distinct scrolling moonscapes. The program defines 21 user-defined graphics (UDGs \a through \u) via POKEd DATA at line 1140, forming a multi-character lander sprite that rotates through four orientations (characters 144–147). Terrain is drawn procedurally using PLOT/DRAW commands read from DATA lines 1420–1470, with boundary-crossing logic at lines 770–870 that cycles through the five terrain sections. SOUND commands provide engine noise and crash effects throughout, and meteor hazards are simulated via randomly positioned PRINT statements in the game loop. Fuel, vertical speed, horizontal speed, score, and remaining ships are tracked across landings and levels, with difficulty increasing each time all five pads are successfully used.
Program Structure
The program is organized into clearly delineated sections, with REM statements used as section headers. The overall flow is:
- Lines 10–20: Title screen and instructions
- Lines 30–80: Variable setup and intro animation
- Lines 90–140: Level initialization, terrain drawing, HUD setup
- Lines 150–530: Main game loop
- Lines 540–640: Scene/pad transition logic
- Lines 650–760: Successful landing handler
- Lines 770–870: Screen boundary (left/right scroll) logic
- Lines 880–1010: Crash/explosion sequence
- Lines 1020–1050: Game over screen
- Lines 1060–1130: Level completion and bonus
- Lines 1140–1160: UDG loader subroutine
- Lines 1170–1370: UDG pixel DATA (21 characters)
- Lines 1380–1480: Moonscape PLOT/DRAW subroutine and DATA
User-Defined Graphics
The subroutine at line 1140 POKEs 21 UDG characters (\a through \u) by reading DATA from lines 1170–1370. Each UDG is 8 bytes. The value a in the DATA lines is used as a literal token — in this context it represents the numeric value of the BASIC keyword AND (token 200 / 0xC8), serving as a compact stand-in for the value 200 in the pixel data. This is an intentional and well-known ZX Spectrum BASIC trick to embed specific byte values in DATA statements using keyword tokens.
Together the 21 UDGs form a multi-tile lander sprite with four rotation states (characters 144–147 map to UDGs \a–\d) and various explosion/debris frames used in the crash animation at lines 920–990.
Sprite Rotation and Movement
The lander’s facing direction is tracked by the variable gs, which holds the character code of the current UDG to PRINT. It starts at 147 (pointing down) and is incremented or decremented by joystick input at line 360:
LET gs=gs+(STICK (1,2)=4)-(STICK (1,2)=8)
Lines 370–380 wrap gs around the range 144–147, implementing modular rotation through the four cardinal orientations. The thrust direction and drift adjustment at lines 340–350 are then conditional on which orientation the lander is currently in.
Terrain Generation
Five moonscape sections are stored as PLOT/DRAW DATA starting at line 1420 through 1470, each prefixed by a count N and starting coordinates. The subroutine at line 1380 reads and executes these commands to draw the terrain. The variable ls tracks the current DATA pointer, allowing RESTORE ls to reload any scene. Boundary crossings (lines 770–870) advance or retreat ls by 10 to move between adjacent sections, wrapping around at the extremes.
Landing Pad Logic
Five landing pad positions (one per terrain section) are encoded as hardcoded pairs of row h and column d in lines 590–600, selected by the column range the player was in when they descended. Line 180 checks for alignment:
IF INT line=h-2 AND (INT col=d OR INT (col+.5)=d) THEN GO TO 650
A successful landing also checks gs=147 (lander pointing down) and vs<=10 (slow enough vertical speed) at line 660, otherwise it goes to the crash handler. The array c(30) records already-used pads (line 730), and attempting to re-use one triggers the “RESTRICTED LANDING PAD” message at line 680. All five pads must be used to complete a level (line 750 checks pad=5).
Physics Model
The physics are simplified but functional. Vertical speed vs and horizontal speed hs are display values only; actual position updates use lv (a fractional line-speed factor starting at 0.5) and drift. Line 330 moves the lander vertically, gated by an ATTR check to prevent passing through terrain (ATTR value 132 indicates a terrain pixel cell). Line 300 advances the column by the sign of drift. The variable down at line 350 acts as a thrust toggle, influenced by joystick fire-button state and cleared when INKEY$ is empty.
SOUND and Audio
SOUND commands are used extensively. Line 320 provides continuous engine audio modulated by column position. Lines 90 and 310 fire the SOUND chip with specific envelope parameters for engine boost. The crash sequence at line 950 uses a high-amplitude SOUND burst. Successful landings at line 730 use BEEP loops for a congratulatory tune. Fuel consumption is tracked and deducted by 10 per thrust activation (line 390), and an out-of-fuel branch exists at line 510.
HUD and Display
The status bar occupies the lower screen area (printed to stream #0) and shows fuel, vertical speed, horizontal speed, and remaining ships. Lines 140 and 170 redraw the HUD on alternate passes. Block graphics in the PRINT statements create decorative borders around the speed readouts. The INK 5*RND idiom causes a color-flicker effect on part of the speed display each frame.
Scroll and Multi-Room Design
The game world is divided into five horizontal rooms. When the lander exits left or right (lines 400–410 check col bounds), the program performs a CLS, RESTOREs the appropriate DATA block, redraws the terrain, and repositions the lander at the matching edge of the new room. This gives the illusion of a continuous scrolling world across five screens.
Level Progression
After all five pads are used, line 1060 adds remaining fuel as a bonus score. Lines 1100 increment the ship count and increase lv by 0.5, making the lander fall faster on subsequent levels, then restart the level setup. The lv factor in line 330 scales the vertical movement each tick.
Notable Techniques and Idioms
NOT PIis used throughout as a compact literal for0(sinceNOT 3.14159...evaluates to 0 in Sinclair BASIC).PIalone evaluates to approximately 3.14159, used at line 40 asINK PI(truncated to 3, cyan) and in calculations likecol/3.1.VAL "number"inPAUSE VAL "80"(line 890) is a memory optimization; storing a number as a string avoids the 5-byte floating-point representation in the token stream.- Boolean arithmetic is used heavily, e.g.
LET col=col*3.1:LET s=1after scene selection, and the multi-condition column calculations in lines 560, 590, 600, 620. - The ATTR() function at line 250 is used for collision detection against terrain (ATTR value 4 = green INK on black PAPER, matching the meteor color) and at line 330 for floor collision (value 132).
- The
c(30)array usesline+colas a combined index key for visited pads, which is a space-efficient but collision-prone hash; however, with only five pads spread across different row/column combinations, collisions are unlikely in practice.
Potential Bugs and Anomalies
- Line 940 and 990 contain
\{16}\{5}embedded mid-line in a PRINT statement outside a string — this appears to be a stray INK control code artifact that could cause unexpected display behavior or a syntax error depending on the interpreter state. - Line 1100 ends with
\{16}\{0}appended after the statement, which is likely a stray INK 0 embedded control character that may cause no visible effect but is not syntactically clean. - Lines 190 and 240 both check
STICK (2,2)=1 AND gs=147with identical PRINT effects, so the thrust-down visual fires twice in the same iteration when both conditions are met; this appears to be a redundancy rather than intentional behavior. - The
c(30)array is dimensioned at line 70, butline+colcould potentially exceed 30 given thatlineranges up to about 20 andcolup to 30, causing an out-of-bounds error. In practice, the five specific landing positions keep the sum within range.
Content
Source Code
10 INK PI:CLS :PRINT ''''" MOON LANDER"''''"\{20}\{1}Fire button\{20}\{0} is jet propulsion."'"\{20}\{1}Joystick\{20}\{0} left & right to rotate."''"Land on pads. too fast you crash"''"Watch out for green meteors"''"\{20}\{1}Land on all for next level.\{20}\{0}"
20 PRINT "Press a key to start":PAUSE NOT PI
30 REM SetupVariables
40 PAPER NOT PI:BORDER NOT PI:CLS :INK 6
50 LET lv=.5:LET sc= NOT PI:LET z=3:LET l=sc:LET vs=30:LET hs=21
60 FOR j=10 TO 85:PLOT j* RND*3,j* RND*2:NEXT j:FOR x=1 TO 6:INK x+2:CIRCLE (x* RND)*40+6,(x* RND)*25+10,x:NEXT x:GO SUB 1140
70 LET pad= NOT PI:LET f=3000:DIM c(30)
80 FOR x= NOT PI TO 14:SOUND 7,14;8,2;9,x;10,x:INK x/2:BORDER 0::SOUND 0,100+x;3,x;5,15:PRINT AT 21-x, PI+x; INK 6;"\{18}\{1}\m\{18}\{0}"; INK 7;"\c":CIRCLE 200,140,x*2:PRINT AT 21-x, PI+x;" ":NEXT x:CLS
90 IF STICK (2,2)=1 THEN }6,6;7,0;8,15;9,16;10,16;12,56;13,8:PAUSE 2:SOUND 8,0;9,0;10,0
100 LET ls=1420:LET h= NOT PI:LET d= NOT PI:LET line=2:LET col=10:LET drift=3:LET gs=147:LET down=4:LET s=h
110 GO SUB 1380
120 FOR j=40 TO 85:INK 8:PLOT j* RND* PI, VAL "j* RND*1+80":NEXT j:CIRCLE INK 6;200,140,10:
130 PRINT AT 14,4;"\{18}\{1}x"; AT 20,9;"x"; AT 17,15;"x"; AT 15,21;"x"; AT 19,24;"x\{18}\{0}"
140 FOR x= NOT PI TO 21:FOR Y= NOT PI TO 21 STEP 21:PRINT AT Y, NOT PI; PAPER PI; INK NOT PI;"\{18}\{1}\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''\..\''"; AT X,0;"\{18}\{1}\''"; AT X,31;"\..\{18}\{0}":NEXT y:NEXT x:PRINT #0; AT 1,0;; INK 2;"\{20}\{1}v/speed:\{20}\{0}"; PAPER 6;"\{18}\{1}\.'\{18}\{0}"; INK 2;vs; INK 5* RND; PAPER 6;"\{18}\{1}\.'\'.\.'\. \''\ :\'.\n\'.\{18}\{0}\{20}\{0}"; AT 1,20; INK 2; PAPER 5;"\{20}\{1}h/speed: \{20}\{0}"; AT 1,28;hs;" "; AT 0,21; INK 8;"\d\d\::\{20}\{1}Ships:\{20}\{0}"; AT 0,31;z;#0; INVERSE 1; INK 2; AT 0,0;"\{20}\{1}fuel: \{20}\{0}";f;"\{18}\{1}\{20}\{1}\'.\'.\n\'.\''\'.\j\'.\k\'.\'.\{18}\{0}\{20}\{0}"
150 REM RUN program
160 FLASH NOT PI:BORDER NOT PI:INK 7
170 PRINT #0; AT 1,0;; INK 7;"\{20}\{1}v/speed:\{20}\{0}"; PAPER 1;"\{18}\{1}\.'\{18}\{0}";vs; INK 5* RND;"\{18}\{1}\.'\'.\.'\. \''\ :\'.\n\'.\{20}\{0}\{18}\{0}"; AT 1,20; INK 1; PAPER 7;"\{20}\{1}h/speed: \{20}\{0}"; AT 1,28;hs;" "; AT 0,21;"\d\d\::\{20}\{1}Ships:\{20}\{0}"; AT 0,31;z;#0; AT 0,0; PAPER 1; INK 7;"\{20}\{1}fuel: \{20}\{0}";f;"\{18}\{1}\{20}\{1}\'.\'.\n\'.\''\'.\j\'.\k\'.\'.\{20}\{0}\{18}\{0}"
180 IF INT line=h-2 AND (INT col=d OR INT (col+.5)=d) THEN GO TO 650
190 IF STICK (2,2)=1 AND gs=147 THEN PRINT AT line+1,col;"\p":PAUSE 2:PRINT AT line+1,col;" "
200 PRINT AT line,col; CHR$ gs
210 IF STICK (2,2)=1 AND gs=145 THEN PRINT AT line-1,col;"\p":PAUSE 2:PRINT AT line-1,col;" "
220 IF STICK (2,2)=1 AND gs=146 THEN PRINT AT line,col-1;"\p":PAUSE 2:PRINT AT line,col-1;" "
230 IF STICK (2,2)=1 AND gs=144 THEN PRINT AT line,col+1;"\p":PAUSE 2:PRINT AT line,col+1;" "
240 IF STICK (2,2)=1 AND gs=147 THEN PRINT AT line+1,col;"\p":PAUSE 2:PRINT AT line+1,col;" "
250 IF ATTR (line+ SGN vs,col+ SGN drift)=4 OR ATTR (line,col+ SGN drift)=4 THEN GO TO 880
260 FOR x=1 TO RND*2
270 PRINT AT x,(30* RND)-col; INK 7* RND;" \k \h \g\h \r\g "
280 NEXT x
290 LET pc=col:LET pl=line
300 LET col=col+ SGN drift
310 IF STICK (2,2)=1 THEN }6,6;7,0;8,15;9,16;10,16;12,200;13,8
320 SOUND 0,100;2,150;5,5;7,28;8,col;9,col;10,10
330 LET line=line+lv* SGN vs*(ATTR (line+ SGN vs,col) <>132)
340 LET drift=drift+(STICK (2,2)=1 AND gs=146)-(STICK (2,2)=1 AND gs=144)
350 LET down= SGN (down+(STICK (2,2)=1 AND gs=145)-(STICK (2,2)=1 AND gs=147)-down*(INKEY$=""))
360 LET gs=gs+(STICK (1,2)=4)-(STICK (1,2)=8)
370 IF gs=148 THEN LET gs=144
380 IF gs=143 THEN LET gs=147
390 IF STICK (2,2)=1 THEN LET f=f-10:BEEP .08,20
400 IF col >=30 THEN GO TO 770
410 IF col >=0 AND col<1 THEN GO TO 830
420 IF line=1 THEN LET line=2
430 LET vs=vs+5+15*down:LET hs=drift*7
440 IF s <>0 THEN GO TO 460
450 IF line >=12 AND s=0 THEN GO TO 550
460 IF s=1 AND line <=2 THEN RESTORE 1420:LET col=(col/3.1)+22*(ls=1470)+19*(ls=1460)+11*(ls=1450)+6*(ls=1440):LET ls=1420:CLS :INK 4:LET line=11:LET s= NOT PI:GO TO 110
470 IF l=4 THEN BEEP .08,40
480 IF l=6 THEN BEEP .12,42:LET l= NOT PI
490 LET l=l+1
500 IF line <=2 THEN LET line=2
510 IF NOT f THEN GO TO 1120
520 PRINT AT pl,pc;" "
530 GO TO 150
540 REM change scenery
550 LET col= INT col
560 LET ls=1420+10*(col>0 AND col<7)+20*(col>6 AND col<13)+30*(col>12 AND col<18)+40*(col>17 AND col<22)+50*(col>21 AND col<31)
570 CLS :INK 4:LET line=2:RESTORE ls
580 GO SUB 1380
590 LET h=6*(col>0 AND col<7)+21*(col>6 AND col<13)+16*(col>12 AND col<18)+9*(col>17 AND col<22)+21*(col>21 AND col<31)
600 LET d=12*(col>0 AND col<7)+9*(col>6 AND col<13)+12*(col>12 AND col<18)+16*(col>17 AND col<22)+6*(col>21 AND col<31)
610 PRINT AT h,d;"\{18}\{1}x\{18}\{0}"
620 LET col=col-6*(col>21)-5*(col>17)-5*(col>12)-6*(col>6)
630 LET col=col*3.1:LET s=1
640 GO TO 150
650 REM landing
660 IF gs <>147 OR vs>10 THEN GO TO 880
670 IF c(line+col) <>1 THEN GO TO 720
680 PRINT AT 21,0;"RESTRICTED LANDING PAD. GO AWAY."
690 FOR i=1 TO 500
700 IF INKEY$="9" THEN LET h= NOT PI:GO TO 150
710 NEXT i:GO TO 880
720 PRINT AT 10,7;"GREAT LANDING"'" CONGRATULATIONS!"
730 LET c(line+col)=1:FOR i=1 TO 4:FOR j= NOT PI TO 10:BEEP .08,j:NEXT j:NEXT i:RESTORE 1420:CLS :INK 4
740 LET sc=sc+100
750 LET pad=pad+1:IF pad=5 THEN GO TO 1060
760 GO TO 100
770 REM crossing boundries
780 IF ls <=1420 THEN LET col=1:GO TO 520
790 LET ls=ls+10
800 IF ls >=1480 THEN LET ls=1430
810 CLS :RESTORE ls:INK 4
820 LET col=(ls=1430)+8*(ls=1440)+13*(ls=1450)+18*(ls=1460)+22*(ls=1470):GO TO 580
830 IF ls <=1420 THEN LET col=29:GO TO 520
840 LET ls=ls-10
850 IF ls <=1420 THEN LET ls=1470
860 CLS :RESTORE ls:INK 4
870 LET col=6*(ls=1430)+12*(ls=1440)+17*(ls=1450)+21*(ls=1460)+29*(ls=1470):GO TO 580
880 REM crash landing
890 LET z=z-1:BEEP .08,0:BEEP .08,15:BEEP .08, PI:PAUSE VAL "80"
900 FOR x= NOT PI TO 2
910 FLASH 1:PAPER 8:INK 6
920 PRINT AT line-x,col-x;"\g\h\i"; AT line,col-1;"\j\k\m"; AT line+x,col-x;"\o\p\q"
930 PRINT AT line-x,col-x;"\m\o\p"; AT line-(7+x),col-x;"\k\l\r"; AT line-(5+x),col-x;"\g\u\i\l"
940 PRINT AT line-(9+x),col-(4+x);"\g\k \l"; AT line-(8+x),col-(2+x);"\k \l \r"; AT line-(6+x)\{16}\{5},col+x;"\g \u\i \l"
950 :SOUND 6,6;7,7;8,16;9,16;10,16;12,250;13,8:
960 PRINT AT line-x,col-x;" "; AT line,col-1;" "; AT line+x,col-x;" "
970 PRINT AT line-x,col-x;" "; AT line-(7+x),col-x;" "; AT line-(5+x),col-x;" "
980 PRINT AT line-(9+x),col-(4+x);" "; AT line-(8+x),col-(2+x);" "; AT line-(6+x),col+x;" "
990 PRINT AT line-(9+x),col-(4+x);"\g\k \l"; AT line-(8+x),col-(2+x);"\k \l \r"; AT line-(6+x)\{16}\{5},col+x;"\g \u\i \l"
1000 NEXT x:PAUSE 60:INK 7:PAPER NOT PI:BORDER NOT PI:CLS :IF NOT z THEN GO TO 1020
1010 FLASH 0:RESTORE 1420:CLS :INK 4:GO TO 100
1020 PRINT AT 2,10;"Your Score: ";sc
1030 PRINT AT 10,0;" GAME OVER"," Push Joystick up to start"
1040 IF STICK (1,2)=1 THEN CLEAR :CLS :FLASH 0:GO TO 30
1050 IF STICK (1,2)=0 THEN GO TO 1040
1060 REM Finish Game
1070 LET sc=sc+f
1080 CLS :INK 7:PRINT AT 5,1;"Well done , you have landed on all the pads with a score of "; AT 8,12; FLASH 1;sc
1090 IF NOT z THEN PRINT AT 12, PI;"Push stickup to start":IF STICK (1,2)=1 THEN RUN
1100 LET z=z+1:LET lv=lv+.5\{16}\{0}
1110 PAUSE VAL "200":CLS :INK 4:GO TO 70
1120 PRINT AT 10,7;"OUT OF FUEL":LET z= NOT PI:PAUSE 200
1130 GO TO 1020
1140 REM lander graphics
1150 FOR n= USR "\a" TO USR "\u"+7:READ a:POKE n,a
1160 NEXT n:RETURN
1170 DATA 5,15,109,252,a,109,15,5
1180 DATA 231,66,255,126,24,60,a,24
1190 DATA 80,240,182,63,a,182,240,160
1200 DATA 24,60,a,24,126,255,66,231
1210 DATA 0,24,40,a,16,0,a,a
1220 DATA 112,136,a,68,40,48,0,a
1230 DATA 0,a,a,56,44,16,0,a
1240 DATA 0,a,30,18,34,84,232,0
1250 DATA 94,177,130,228,34,65,66,60
1260 DATA 0,62,65,66,34,17,14,0
1270 DATA 96,144,a,116,10,49,65,126
1280 DATA 62,65,242,9,247,136,144,96
1290 DATA 145,82,16,7,244,8,74,137
1300 DATA 100,24,198,0,222,0,24,102
1310 DATA 0,4,34,18,1,68,50,9
1320 DATA 9,50,68,1,18,34,4,0
1330 DATA 0,8,170,42,73,65,137,a
1340 DATA 144,76,34,128,72,68,32,0
1350 DATA 0,3,15,a,120,200,255,56
1360 DATA 0,192,240,a,30,19,255,28
1370 DATA 0,32,68,72,128,36,76,144
1380 REM Moonscapes
1390 READ N,x,y:PLOT x,y
1400 FOR n= NOT PI TO N:READ h,v:DRAW h,v:NEXT n
1410 REM \{20}\{1} ls \{20}\{0}
1420 DATA 25,0,31,16,0,16,32,8,0,8,8,16,-48,8,-8,8,0,8,16,16,8,8,16,8,-16,8,0,2,8,2,0,4,8,16,16,8,-24,8,8,8,0,16,-32,8,0,16,40,8,-8,16,-8,8,16,6,-8
1430 DATA 7,0,31,48,0,48,96,24,0,24,24,48,-128,24,-16,24,0,8,16
1440 DATA 9,0,153,48,-128,22,-18,24,0,24,48,48,24,24,48,24,-48,24,0,6,12,4,0
1450 DATA 10,0,31,48,24,24,48,22,-56,24,0,6,12,6,0,6,24,48,48,24,-72,24,24,16,0
1460 DATA 9,0,55,6,24,6,0,12,24,48,48,24,-72,24,24,24,0,48,-96,24,0,32,104
1470 DATA 6,0,103,40,-96,24,0,56,120,24,-24,48,-24,24,48,32,-24
1480 RETURN
1490 SAVE "LANDER FIX" LINE 1
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
