Tunnel Run is a four-stage space navigation game in which the player pilots a ship through a missile tunnel, an asteroid field, a winding tunnel, and finally a landing pad. Each stage uses a different subset of keys: 5 and 8 for lateral movement, 0 to fire in the asteroid section, and R for retro-boost during the landing approach. The program defines two custom UDG characters at startup by POKEing eight bytes each into the UDG area starting at USR “\\l”, creating a ship sprite and an asteroid graphic. Collision detection is performed using ATTR to read the color attributes of the cell occupied by the player’s ship, with each stage expecting a specific ink/paper color combination to confirm a safe position. Score is tracked throughout all four sections, with a persistent high score maintained across playthroughs within the same session.
Program Structure
The program is divided into clearly sequential sections separated by border/paper color changes used as visual cues for each stage:
- Initialization and title screen (lines 10–110): UDG setup, instructions, and wait for keypress.
- Stage 1 — Missile Tunnel (lines 120–450): Blue border, 200-frame loop; player moves laterally across row 0 while a scrolling floor is printed at row 21. ATTR-based collision checks for ink 0/paper 1 (value 8) or ink 4/paper 1 (value 12).
- Stage 2 — Asteroids (lines 460–770): Red border; player can fire vertically with key 0, scoring −20 per shot and +100 per hit on target asteroid at column
c. - Stage 3 — Winding Tunnel (lines 780–980): Yellow border; a scrolling gap
tudrifts randomly; enemy craft appear at random within the gap. - Stage 4 — Landing Pad (lines 990–1230): Black background; player descends vertically (
xincrements each frame), must align with the yellow landing square at columng. - End screens (lines 1240–1290): Crash message or success display with high score and CIRCLE graphic; option to replay from line 90.
UDG Initialization
Lines 10–50 define two custom characters. The loop runs f=0 TO 15, POKEing 16 bytes total into USR "\\l" + f. The first eight bytes (from the DATA at line 30: 0,66,153,189,255,153,66,0) define the UDG at \\l (the ship sprite). The second eight bytes (line 50: 126,60,24,126,126,126,90,24) define the UDG at \\m (used as the player’s ship icon printed at row 0). Note that the DATA at line 30 is interleaved with the loop structure: the READ at line 20 inside the loop reads all 16 values sequentially across both DATA statements.
Collision Detection via ATTR
Each stage uses ATTR (0,a) to determine whether the player’s ship position is safe, relying on the background color rendered at row 0:
| Stage | Expected ATTR value(s) | Meaning |
|---|---|---|
| Missile Tunnel | 8 or 12 | INK 0 or INK 4 on PAPER 1 (blue) |
| Asteroids | 16 | INK 0 on PAPER 2 (red) |
| Winding Tunnel | 24 | INK 0 on PAPER 3 (yellow) |
| Landing Pad | Column match with g | Positional, not ATTR-based |
Any mismatch in stages 1–3 branches to line 1240 (crash). Stage 4 checks a <> g at row 15.
Key BASIC Idioms
- Boolean movement:
LET a=a+(INKEY$="8")-(INKEY$="5")— a standard Sinclair BASIC idiom exploiting the fact that boolean expressions evaluate to 1 (true) or 0 (false). - POKE 23692,255: Resets the scroll counter to suppress the scroll? prompt, preventing the screen from pausing during fast-moving display loops.
- PAUSE 0 / INKEY$ loop: Lines 100–110 use the standard idiom of draining the keyboard buffer before waiting for a fresh keypress.
- Tunnel drift clamping (line 890):
LET tu=tu+(tu<1)-(tu>24)keeps the tunnel gap within screen bounds using boolean arithmetic rather than an IF statement.
Stage 2 — Asteroid Firing Mechanic
When the player presses 0, lines 580–650 draw a vertical beam of "I" characters in INK 6 from row 1 to 21 at column a, then check whether a = c (the asteroid’s column). A hit awards +100 points and flashes the asteroid position. The asteroid position c drifts each frame via LET c=c+(RND>.5)-(RND<.5), making it a moving target. A copyright symbol character (\\*) is used as a random obstacle sprite at line 700.
Stage 4 — Landing Pad
The landing section introduces vertical movement: x increments each frame simulating gravity. Pressing key 0 or r decrements x (retro-boost), allowing the player to control descent rate. The target column g is randomized at line 1010. A safe landing requires a = g when x >= 15; any other column at that row causes a crash. Horizontal wrapping is implemented at line 1090: LET a=a+(a<0)-(a>31).
Notable Techniques and Anomalies
- The
CIRCLE 125,85,80at line 1210 is used as a decorative success graphic on the win screen, one of the few uses of the graphics command in the program. - The transition loop at lines 280–380 acts as a brief “corridor” between stage 1’s main loop and stage 2, printing blank lines to scroll the tunnel display away.
- In stage 3 (line 910), enemy craft appear stochastically:
IF RND>.7 THEN PRINT AT 21,(RND*5)+tu; INK 1;"\\l"— the UDG ship character is reused as an enemy sprite. - The high score
highis initialized at line 60 and persists across replays within the session since GO TO 90 does not re-execute line 60. - Stage 1’s floor at line 210 prints a yellow
"^"character (caret, used as a missile sprite) at a random column between 10 and 16, creating an animated hazard on row 21 distinct from the wall blocks.
Source Code
10 CLS :FOR f=0 TO 15
20 READ a:POKE USR "\l"+f,a
30 DATA 0,66,153,189,255,153,66,0
40 NEXT f
50 DATA 126,60,24,126,126,126,90,24
60 LET high=0
70 PRINT " EXPEDITION"," __________"," You have been challenged to explore this exciting space mazeand return home ALIVE !"," There are 4 sections:","1> Missile tunnel. Keys 5,8. 2> Asteroids. Keys 5,8,0 to fire -20 points each shot, +100 per hit."
80 PRINT "3> Winding tunnel. Keys 5,8."," Avoid space craft.","4> Landing pad. Keys 5,8,r to retro-boost. Land on the yellow part."
90 PRINT '"Any key to start !!!"
100 IF INKEY$ <>"" THEN GO TO 100
110 PAUSE 0
120 LET a=15:LET sc=0
130 BORDER 1:PAPER 1:INK 0:CLS
140 PRINT AT 21,0;"\::\::\::\::\::\::\::\::\::\:: \::\::\::\::\::\::\::\::\::\::\::\::\::\::\::"
150 FOR f=1 TO 200
160 IF a <=9 OR a >=17 THEN GO TO 1240
170 PRINT AT 0,a;"\m"
180 POKE 23692,255
190 LET a=a+(INKEY$="8")-(INKEY$="5")
200 PRINT AT 21,0;
210 PRINT AT 21, INT (RND*7)+10; INK 6;"^"
220 PRINT INK 4;"\::\::\::\::\::\::\::\::\::\:: \::\::\::\::\::\::\::\::\::\::\::\::\::\::\::"
230 LET b= ATTR (0,a)
240 IF b <>8 AND b <>12 THEN GO TO 1240
250 LET sc=sc+1
260 BEEP .005,0:BEEP .005,-2
270 NEXT f
280 FOR f=1 TO 22
290 PRINT AT 0,a;"\m"
300 LET a=a+(INKEY$="8")-(INKEY$="5")
310 POKE 23692,255
320 PRINT AT 21,0,,
330 PRINT ,,
340 LET b= ATTR (0,a)
350 IF b <>8 AND b <>12 THEN GO TO 1240
360 LET sc=sc+1
370 BEEP .01,0:BEEP .01,-2
380 NEXT f
390 PRINT AT 0,a;"\m"
400 PRINT AT 10,10;"\{18}\{1}Score ";sc;"\{18}\{0}"
410 FOR f=1 TO 5
420 FOR n=10 TO 20
430 BEEP .1,n
440 NEXT n:NEXT f
450 PAUSE 250
460 BORDER 2:PAPER 2:CLS
470 PRINT AT 0,a;"\m"
480 FOR f=1 TO 3
490 BEEP .2,-2:BEEP .3,-2:BEEP .5,3.2
500 NEXT f
510 BEEP 1,3.2
520 LET c=20
530 FOR f=1 TO 200
540 PRINT AT 0,a;"\m"
550 LET a=a+(INKEY$="8")-(INKEY$="5")
560 IF INKEY$ <>"0" THEN GO TO 660
570 LET sc=sc-20
580 FOR n=1 TO 21
590 PRINT AT n,a; INK 6;"I"
600 NEXT n
610 IF a <>c THEN GO TO 650
620 LET sc=sc+100
630 FOR x=1 TO 30:PRINT AT 21,c; INK 6; PAPER 0; FLASH 1;"#":NEXT x
640 LET c=20
650 FOR n=1 TO 21:PRINT AT n,a;" ":NEXT n
660 POKE 23692,255
670 PRINT AT 21,c;" "
680 PRINT AT 21,0; INK 3; BRIGHT 1;"###"; AT 21,31;"#"
690 PRINT ,,
700 PRINT AT 21,(RND*30); INK 4;"\*"
710 LET b= ATTR (0,a)
720 IF b <>16 THEN GO TO 1240
730 LET c=c+(RND>.5)-(RND<.5)
740 BEEP .005,0:BEEP .005,-2
750 PRINT AT 21,c; INK 1;"\l"
760 NEXT f
770 FOR f=1 TO 22:PRINT AT 21,0,,:NEXT f
780 BORDER 3:PAPER 3:CLS
790 PRINT AT 0,a;"\m"
800 PRINT AT 10,10; FLASH 1;"Score ";sc
810 BEEP 1,9:BEEP 1,10.7:BEEP 1,9:PAUSE 130:BEEP 2,0
820 LET tu=13
830 PRINT AT 10,10;" "
840 FOR f=1 TO 172
850 PRINT AT 0,a;"\m"
860 LET a=a+(INKEY$="8")-(INKEY$="5")
870 POKE 23692,255
880 LET tu=tu+(RND>.5)-(RND<.5)
890 LET tu=tu+(tu<1)-(tu>24)
900 PRINT AT 21,tu; PAPER 3; INK 0;" "
910 IF RND>.7 THEN PRINT AT 21,(RND*5)+tu; INK 1;"\l"
920 PRINT INK 7; BRIGHT 1;"\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::"
930 IF f>149 THEN PRINT AT 21,0; PAPER 3; INK 0,,
940 LET b= ATTR (0,a)
950 IF b <>24 THEN GO TO 1240
960 LET sc=sc+1
970 BEEP .005,0:BEEP .005,-2
980 NEXT f
990 BORDER 0:PAPER 0:INK 7:CLS
1000 PRINT AT 0,a;"\m"
1010 LET g= INT (RND*30)+1
1020 PRINT AT 15,0; INK 1;"################################"
1030 FOR f=16 TO 21:PRINT AT f,0; INK 2;"\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::":NEXT f
1040 PRINT AT 15,g; INK 6;"#"
1050 LET x=0
1060 PAUSE 150
1070 PRINT AT x,a;"\m"
1080 LET a=a+(INKEY$="8")-(INKEY$="5")
1090 LET a=a+(a<0)-(a>31)
1100 LET x=x+1
1110 PAUSE 5
1120 IF INKEY$="0" OR INKEY$="r" THEN LET x=x-1
1130 IF x >=15 THEN GO TO 1150
1140 PRINT AT x,a;" ":GO TO 1070
1150 IF a <>g THEN GO TO 1240
1160 PAPER 0:INK 7:FLASH 1:CLS
1170 PRINT AT x,15;"\m"
1180 PRINT AT 17,10;"SCORE ";sc
1190 IF sc>high THEN LET high=sc
1200 PRINT AT 18,10;"HIGH ";high
1210 CIRCLE 125,85,80
1220 FOR f=1 TO 20:BEEP .01,f:NEXT f
1230 GO TO 1260
1240 CLS :PRINT AT 10,0;"You have crashed.Score:";sc;". "
1250 FOR f=0 TO -10 STEP -1:BEEP .01,f:NEXT f
1260 PRINT '"PRESS Y FOR ANOTHER GO,N TO STOP"
1270 IF INKEY$ <>"" THEN GO TO 1270
1280 PAUSE 0:FLASH 0:CLS
1290 IF INKEY$="y" OR INKEY$="Y" THEN GO TO 90
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

