Skip to content
Game 3

Steer and thrust together

We will inspect the keys separately before changing the flight loop.

Continue with Reach a landing pad. The world already updates both coordinates. The remaining restriction is how we read the keyboard. We will inspect the keys separately before changing the flight loop.

Look at the controls without flying

Save the landing program. Enter NEW and run this separate diagnostic. It prints three values; 1 means the named control is pressed and 0 means it is released.

10 BORDER 0: PAPER 0: INK 7: CLS
20 PRINT AT 0,1;"O LEFT  P RIGHT  SPACE THRUST"
30 PRINT AT 2,1;"Hold combinations. Q quits."
100 IF INKEY$="q" THEN GO TO 200
110 LET keys=IN 57342
120 LET right=1-(keys-2*INT (keys/2))
130 LET left=1-(INT (keys/2)-2*INT (keys/4))
140 LET keys=IN 32766
150 LET thrust=1-(keys-2*INT (keys/2))
160 PRINT AT 5,1;"LEFT: ";left;AT 7,1;"RIGHT: ";right;AT 9,1;"THRUST: ";thrust
170 GO TO 100
200 IF INKEY$<>"" THEN GO TO 200
210 STOP

The Spectrum groups its keyboard into half-rows of five keys. IN reads an input port: its number selects hardware, and the returned number reports what that hardware is doing. These are two different numbers with different jobs.

Port address Selected half-row Bits used here
57342 P, O, I, U, Y Bit 0 for P; bit 1 for O
32766 Space, Symbol Shift, M, N, B Bit 0 for Space

A byte has eight bits, with weights 1, 2, 4, 8, 16, 32, 64 and 128. The port number’s low byte is 254. Its high byte selects a half-row: 223 for the P row, or 127 for the Space row. Thus 254+256*223 gives 57342 and 254+256*127 gives 32766. In binary those high bytes are 11011111 and 01111111: each has a different selection bit cleared. These addresses come from the Spectrum’s wiring, not a search for convenient magic numbers.

The returned byte uses zero for a pressed key. A selected half-row’s other keys and returned bits must not be mistaken for our controls. In particular, the returned EAR input bit can vary, so equality with one whole-byte value is not a reliable key test.

Extract the bit we need

keys-2*INT (keys/2) removes all complete groups of two, leaving a remainder of 0 or 1. That is the lowest bit. For a numerical example, 190 divides into 95 complete pairs and remainder 0; 191 has the same 95 pairs and remainder 1. Those values illustrate the arithmetic, not a promise of a fixed keyboard byte.

To inspect the next bit, first discard the lowest one with INT (keys/2). Then remove its complete pairs. Written together, that is INT (keys/2)-2*INT (keys/4). The diagnostic subtracts each extracted bit from 1 so its displayed values mean pressed=1 rather than pressed=0.

O/P low two bits, high bit first O P
11 released released
10 released pressed
01 pressed released
00 pressed pressed

Hold each key, then pairs, then all three controls. Left and right should both display 1 when O and P are held. Space should change thrust independently. Release everything and check that all three values return to zero. Q alone quits after release.

Why can’t we use AND 1 as a shortcut for extracting the lowest bit?

Show the explanation

Sinclair BASIC’s AND combines logical conditions; it is not the bit-masking operation used by some other languages. The grouping and remainder expressions here extract the actual bits we want.

Return to the landing game

Reload the complete landing program from the previous lesson. Do not apply these edits to the diagnostic.

Replace lines 70, 210, 220, 230, 260, 270.

70 PRINT AT 8,2;"SPACE: thrust (with steering)"
210 LET keys=IN 57342: LET nx=x
220 IF INT (keys/2)-2*INT (keys/4)=0 THEN LET nx=nx-1
230 IF keys-2*INT (keys/2)=0 THEN LET nx=nx+1
260 LET burn=0: LET keys=IN 32766
270 IF keys-2*INT (keys/2)=0 AND fuel>0 THEN LET burn=1: LET fuel=fuel-1

The two steering tests now inspect separate bits. If both O and P are down, one subtracts from nx and the other adds to it, so they cancel before the boundary clamps. A separate IN reads Space. Fuel, burn, velocity and contact keep their existing rules.

The game tests for a zero bit directly, whereas the diagnostic converts it to 1 for its display. Those conventions describe the same pressed state. We retain INKEY$ for Q and the single-choice menus; it remains useful where we want one answer.

Try steering while holding thrust, and releasing just one of those keys. The remaining control should keep working. We have changed the input reader, not the acceleration or pad. Compare that responsiveness with the previous version before altering any flight settings.

Complete program for this step
10 BORDER 0: PAPER 0: INK 7: BRIGHT 1: CLS
50 PRINT AT 3,9;"TOUCHDOWN"
60 PRINT AT 6,2;"O / P: steer left / right"
70 PRINT AT 8,2;"SPACE: thrust (with steering)"
80 PRINT AT 10,2;"Pad: ========  Safe: 0 to 12"
90 PRINT AT 12,2;"Empty tank? You still coast."
100 PRINT AT 15,2;"S launches. Q quits."
110 GO SUB 800: IF k$="q" THEN GO TO 850
120 CLS: GO SUB 600
130 LET x=18: LET y=400: LET v=0: LET fuel=45: LET r=4: LET burn=0
140 PRINT AT r,x;"A"
150 PRINT AT 0,1;"FUEL       SPEED       MAX 12"
160 PRINT AT 1,1;"O/P STEER  SPACE THRUST  Q QUIT"
200 LET k$=INKEY$: IF k$="q" THEN GO TO 850
210 LET keys=IN 57342: LET nx=x
220 IF INT (keys/2)-2*INT (keys/4)=0 THEN LET nx=nx-1
230 IF keys-2*INT (keys/2)=0 THEN LET nx=nx+1
240 IF nx<1 THEN LET nx=1
250 IF nx>30 THEN LET nx=30
260 LET burn=0: LET keys=IN 32766
270 IF keys-2*INT (keys/2)=0 AND fuel>0 THEN LET burn=1: LET fuel=fuel-1
280 LET v=v+2-6*burn
290 IF v>60 THEN LET v=60
300 IF v<-30 THEN LET v=-30
310 LET ny=y+v: LET contact=0: LET limit=1900
330 IF ny>=limit THEN LET contact=1
340 IF contact=1 THEN LET ny=limit
350 IF ny<300 THEN LET ny=300: LET v=0
360 LET nr=INT (ny/100)
370 PRINT AT 0,6;fuel;"  ";AT 0,19;v;"   "
375 LET a$="A": IF burn=1 THEN LET a$="*"
380 PRINT AT r,x;" ";AT nr,nx;a$
390 LET x=nx: LET y=ny: LET r=nr
400 IF contact=1 THEN GO TO 700
410 PAUSE 2: GO TO 200
600 FOR c=0 TO 31
610 FOR j=20 TO 21: PRINT PAPER 4;INK 0;AT j,c;" ";: NEXT j
620 PRINT PAPER 4;INK 0;AT 20,c;"#"
630 NEXT c
640 FOR c=20 TO 27: PRINT PAPER 6;INK 0;AT 20,c;"=": NEXT c
650 RETURN
700 PRINT AT r,x;"A"
710 LET m$="Off the pad."
720 IF x>=20 AND x<=27 THEN LET m$="Too fast for the pad."
730 IF x>=20 AND x<=27 AND v<=12 AND v>=0 THEN LET m$="Safe landing!"
740 PRINT AT 2,1;m$;"    "
770 PRINT AT 1,1;"R retries. Q quits.            "
780 IF INKEY$<>"" THEN GO TO 780
790 LET k$=INKEY$: IF k$<>"r" AND k$<>"q" THEN GO TO 790
795 IF INKEY$<>"" THEN GO TO 795
796 IF k$="q" THEN GO TO 850
797 GO TO 120
800 IF INKEY$<>"" THEN GO TO 800
810 LET k$=INKEY$: IF k$<>"s" AND k$<>"q" THEN GO TO 810
820 IF INKEY$<>"" THEN GO TO 820
830 RETURN
850 IF INKEY$<>"" THEN GO TO 850
860 PAPER 0: INK 7: CLS: PRINT "Finished.": STOP

Sources

Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 9 (INT), chapter 13 (logical AND), and chapter 23 (keyboard half-rows, ports and returned bits).