Draw the craft and its flame
Replace those symbols with a craft we design ourselves, keeping the same one-cell footprint.
Continue with Read the shape of the site. The terrain and flight rules work with A and *. Replace those symbols with a craft we design ourselves, keeping the same one-cell footprint.
Eight rows of eight pixels
A user-defined character occupies an 8×8 cell. Each row is one byte: its eight bit weights, from left to right, are 128, 64, 32, 16, 8, 4, 2 and 1. A set bit uses INK; a clear bit leaves PAPER visible.
Here is the resting craft. A dot means a clear pixel and # means a set one.
| Pixel row | Byte value |
|---|---|
...##... |
24 |
..####.. |
60 |
..####.. |
60 |
.######. |
126 |
.#.##.#. |
90 |
.#....#. |
66 |
........ |
0 |
........ |
0 |
For example, row 24 sets weights 16 and 8. Row 66 sets 64 and 2. The byte is a compact description of a whole pixel row; its decimal value is not a colour or a character number.
Add lines 40, 9010. Replace lines 140, 375, 700.
40 FOR c=0 TO 7: READ b: POKE USR "a"+c,b: NEXT c
140 PRINT AT r,x;CHR$ 144
375 LET a$=CHR$ 144: IF burn=1 THEN LET a$="*"
700 PRINT AT r,x;CHR$ 144
9010 DATA 24,60,60,126,90,66,0,0
Put the rows in character memory
The height loop has already read the 32 terrain values. The next eight READ operations take the craft’s bytes from DATA at 9010. READ continues from its current position; it does not restart at the first DATA line automatically.
USR "a" gives the address of the first byte of user-defined character A. The string argument matters: the numeric form of USR has a different job, calling machine code. We are using the character-address form.
POKE stores a byte at an address. As c runs from 0 to 7, USR "a"+c selects successive pixel rows. Keep each byte between 0 and 255 and keep this loop within those eight rows. We are constructing one specific graphic, not treating arbitrary memory writes as harmless drawing commands.
CHR$ 144 creates the character string for the first user-defined character. PRINT can use it where it previously used "A". The provisional * still marks thrust in this checkpoint. RUN reconstructs the graphic from its DATA, so the tape need not rely on a previous session’s character memory.
Why are the row value 24 and the character code 144 not interchangeable?
Show the explanation
24 describes which pixels are set in one row of the design. 144 identifies the completed custom character when printing it. Eight row bytes define that character; CHR$ selects it for output.
Complete program for this step
10 BORDER 0: PAPER 0: INK 7: BRIGHT 1: CLS
20 DIM h(32): RESTORE 9000
30 FOR c=1 TO 32: READ h(c): NEXT c
40 FOR c=0 TO 7: READ b: POKE USR "a"+c,b: NEXT c
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=6: LET y=400: LET v=0: LET fuel=45: LET r=4: LET burn=0
140 PRINT AT r,x;CHR$ 144
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 side=0: LET limit=100*(h(nx+1)-1)
320 IF nx<>x AND y>=limit THEN LET contact=1: LET side=1: LET nx=x
330 IF ny>=limit THEN LET contact=1
340 IF contact=1 THEN LET ny=limit
345 IF side=1 THEN LET ny=y
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$=CHR$ 144: 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=h(c+1) TO 21: PRINT PAPER 4;INK 0;AT j,c;" ";: NEXT j
620 PRINT PAPER 4;INK 0;AT h(c+1),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;CHR$ 144
710 LET m$="Terrain collision."
720 IF side=0 AND x>=20 AND x<=27 THEN LET m$="Too fast for the pad."
730 IF side=0 AND 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
9000 DATA 18,18,18,19,19,19,19,19,18,17,16,15,14,14,14,15,16,17,18,19,20,20,20,20,20,20,20,20,19,18,18,18
9010 DATA 24,60,60,126,90,66,0,0
Keep the flame inside the cell
Replace lines 40, 380, 9010. Delete line 375.
40 FOR c=0 TO 15: READ b: POKE USR "a"+c,b: NEXT c
380 PRINT AT r,x;" ";AT nr,nx;CHR$ (144+burn)
9010 DATA 24,60,60,126,90,66,0,0,24,60,60,126,90,66,24,36
The second eight DATA values define character B immediately after A. Its body is the same, but the final two rows become 24 and 36: ...##... and ..#..#... They form the flame beneath the feet while remaining inside the cell.
The loop now stores sixteen bytes. CHR$ (144+burn) selects A when burn=0 and B when burn=1. The result routine prints A again after contact, so a last paid-for burn does not leave the engine running in the result picture. This keeps the existing rule that a single burn decision controls velocity, fuel and appearance.
The clear pixels use the sky’s black PAPER, and the erase/draw pair still handles just one cell. Adding a flame in the cell below would require restoring its background and accounting for nearby terrain; it is not a free extra row of artwork.
Try changing one row byte and predict the shape before running it. Inspect the craft in flight and beside the pad, with and without thrust. A still image can judge its outline; use actual movement to judge whether the changing states are readable.
Complete program for this step
10 BORDER 0: PAPER 0: INK 7: BRIGHT 1: CLS
20 DIM h(32): RESTORE 9000
30 FOR c=1 TO 32: READ h(c): NEXT c
40 FOR c=0 TO 15: READ b: POKE USR "a"+c,b: NEXT c
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=6: LET y=400: LET v=0: LET fuel=45: LET r=4: LET burn=0
140 PRINT AT r,x;CHR$ 144
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 side=0: LET limit=100*(h(nx+1)-1)
320 IF nx<>x AND y>=limit THEN LET contact=1: LET side=1: LET nx=x
330 IF ny>=limit THEN LET contact=1
340 IF contact=1 THEN LET ny=limit
345 IF side=1 THEN LET ny=y
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;" "
380 PRINT AT r,x;" ";AT nr,nx;CHR$ (144+burn)
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=h(c+1) TO 21: PRINT PAPER 4;INK 0;AT j,c;" ";: NEXT j
620 PRINT PAPER 4;INK 0;AT h(c+1),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;CHR$ 144
710 LET m$="Terrain collision."
720 IF side=0 AND x>=20 AND x<=27 THEN LET m$="Too fast for the pad."
730 IF side=0 AND 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
9000 DATA 18,18,18,19,19,19,19,19,18,17,16,15,14,14,14,15,16,17,18,19,20,20,20,20,20,20,20,20,19,18,18,18
9010 DATA 24,60,60,126,90,66,0,0,24,60,60,126,90,66,24,36
Sources
Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 6 (DATA and READ), chapter 14 (user-defined characters, USR, POKE and CHR$), and chapter 16 (INK and PAPER).