Tunnel Run

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

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:

  1. Initialization and title screen (lines 10–110): UDG setup, instructions, and wait for keypress.
  2. 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).
  3. 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.
  4. Stage 3 — Winding Tunnel (lines 780–980): Yellow border; a scrolling gap tu drifts randomly; enemy craft appear at random within the gap.
  5. Stage 4 — Landing Pad (lines 990–1230): Black background; player descends vertically (x increments each frame), must align with the yellow landing square at column g.
  6. 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:

StageExpected ATTR value(s)Meaning
Missile Tunnel8 or 12INK 0 or INK 4 on PAPER 1 (blue)
Asteroids16INK 0 on PAPER 2 (red)
Winding Tunnel24INK 0 on PAPER 3 (yellow)
Landing PadColumn match with gPositional, 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,80 at 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 high is 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.

Image Gallery

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.