LANDER FIX

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

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:

  1. Lines 10–20: Title screen and instructions
  2. Lines 30–80: Variable setup and intro animation
  3. Lines 90–140: Level initialization, terrain drawing, HUD setup
  4. Lines 150–530: Main game loop
  5. Lines 540–640: Scene/pad transition logic
  6. Lines 650–760: Successful landing handler
  7. Lines 770–870: Screen boundary (left/right scroll) logic
  8. Lines 880–1010: Crash/explosion sequence
  9. Lines 1020–1050: Game over screen
  10. Lines 1060–1130: Level completion and bonus
  11. Lines 1140–1160: UDG loader subroutine
  12. Lines 1170–1370: UDG pixel DATA (21 characters)
  13. 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 PI is used throughout as a compact literal for 0 (since NOT 3.14159... evaluates to 0 in Sinclair BASIC).
  • PI alone evaluates to approximately 3.14159, used at line 40 as INK PI (truncated to 3, cyan) and in calculations like col/3.1.
  • VAL "number" in PAUSE 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=1 after 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 uses line+col as 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=147 with 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, but line+col could potentially exceed 30 given that line ranges up to about 20 and col up to 30, causing an out-of-bounds error. In practice, the five specific landing positions keep the sum within range.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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.

People

No people associated with this content.

Scroll to Top