So back when I was young, my school had a network of BBC Micro computers (8-bit 6502 based machines; unlike a lot of the 8-bits of the time, they had a real operating system and were really rather nice to use). The file server that drove all this was a beefy twin-processor BBC Master with a whole 20MB Winchester disk. I hate to think how much it cost the school.
I wrote loads of stuff for it and I think I was eventually using over half of that Winchester with my own software.
Then it got struck by lightning.
Recently I found a floppy disk at the bottom of a box which I hadn't seen for years. This disk contained a few programs I'd sent to a computer magazine, hoping to sell them. They'd returned it, of course, with 'does not meet our requirements at this time' checked on the form letter. But it contains pretty much the last remaining vestige of this 10MB or so of terrible BBC Micro software that I'd written. (10MB is over 50 floppy disks, you know!)
Load and run the disk in Matt Godbolt's amazing browser-based BBC emulator.
The game that will run by default is Snail Trail, an excruciatingly bad Tron clone. But enough from me; let 15-year-old-me describe it in his own words:
The year is 19910, and the barbaric custom of settling disputes with legal processes has been replaced by the civilised method of trial by snail. You and your partner are dropped onto your duelling snails in the middle of the arena, and the fight begins! Each snail is specially genetically engineered; they travel at a constant speed of 100 kph, and leave a trail of corrosive slime behind them. If you or your opponent's snail hits one of these, it will burst into flames and explode. However, to make things simpler, there are two special features: at the edge of the arena, there are four teleport gates. Any snail entering one of these will be instantaneously transported to the opposite gate. Also, each of you has a device that will either drop a pod (a "mine") full of corrosive slime behind you, which your opponent can run into, or else blast a hole in a corrosive trail you are about to hit. However, mining or blasting will cost you. Each person starts with a certain number of credits; mining uses a small amount, blasting uses a lot more. If you run out of credits, your device will deactivate itself. For the really bad crimes, income tax evasion and the such like, the Referees will withhold the mining and bombing devices... The program is for one or two players. If the one player option is selected, the computer will play as Yellow. The keys are as follows: Up A Down Z Left X Right C Blast V Mine CTRL Yellow Up * Down ? Left < Right > Blast M Mine ] Options such as sound, music, blasting & mining (special effects), number of games in a match and so on, can be changed from the menu at the beginning.
On a modern UK PC keyboard, Yellow's up key is @
.
If you don't like the game, and I don't blame you, you can press
ESCAPE
to exit. Press F12 to reinitialise the disk filing
system; there are a few other programs on the disk, none of which are
of any particular interest (and one doesn't work at all).
Snail Trail is 368 lines of BBC Basic. This is a structured Basic with named procedures, proper control flow expression, etc. It's actually quite well written, and 15-year-old-me thoughtfully provided an overview. Although he didn't get the line numbers right.
Program Description =================== Procedures used: 250 DEFPROCinitgame Draws screen for start of game 580 DEFPROCinitprog Defines envelopes, characters, etc 770 DEFPROCwrite(T$,X%,Y%,C1%,C2%) Displays text T$ at X%,Y% in C1% for background and C2% for foreground 890 DEFFNn(A%,P%) Pads A% with spaces to string length P% 930 DEFPROCplayer1 Moves player 1 (red) 1130 DEFPROCplayer2 Moves player 2 (yellow) 1330 DEFPROCexplode(X%,Y%) Draws an explosion at X%,Y% 1600 DEFPROCtitle Displays title and options menu 1990 DEFFNyn(fx%) Outputs Y for fx%=TRUE, otherwise N 2020 DEFPROCbomb(X%,Y%,C%) Draws a mine of colour C% at X%,Y% 2070 DEFPROCblast(X%,Y%) Deletes a square at X%,Y% 2170 DEFPROCupdate Updates credit display at top of screen 2240 DEFPROCendgame Displays GAME OVER, says who won 2520 DEFPROCchangegames Changes the number of games in a match 2640 DEFPROCchangeeffects Toggles special effects on/off 2700 DEFPROCchangemusic Toggles music on/off 2770 DEFPROCchangeplayers Toggles players 1/2 2830 DEFPROCcomputer Moves computer (yellow) 3000 DEFPROCturn Detects a free direction for computer turn to 3140 DEFPROCchangesound Toggles sound on/off 3220 DEFPROCcountdown Start of game routine 3380 DEFPROCtune Tests if a note in the tune is forthcoming, if so plays it 3470 DEFPROCvolume(V%) Defines envelope 4 with volume V% 3510 DEFPROCmovedown Download routine
For those of you who don't know BBC Basic, each DEFPROC
statement defines a named subroutine which can be called later with
PROC
. There are a few slightly clever tricks in there:
the background music, which will make you scream if you have to
listen to it for more than a few minutes, is driven by
PROCtune
which polls the audio buffer to see if it needs
another note. The music data itself is a single string on line 740.
It's poked into a dynamically allocated buffer in memory so
individual characters can be read efficiently using BBC Basic's
equivalent of PEEK
(see lines 3470 to 3490). Probably
wasn't worth the effort.
It's quite portable, and I suspect would run almost unmodified on an
Archimedes or a modern PC with BBC Basic. (I did try with Brandy, an
open source interpreter, but Snail Trail crashed it) The program is,
though, too big to fit in an unexpanded BBC Micro with the high
resolution graphics and a disk filesystem active, so there's a
relocation stage (PROCmovedown
). This actually overwrites
part of the currently running program! There's then some really hacky
code in lines 3660 to 3670 which change Basic's working space base
address, then poke characters into the keyboard buffer to reset Basic,
reload the program in its new home in RAM, and then run it again...
The main thing of note is the AI for the computer player, which is really stupid but rather interesting to watch. It's in lines 2880 to 3170; 29 actual lines of code.
The whole program listing is included below. I should add that despite the statement in line 30, this program is not the property of The Micro User and is, in fact, © 1991 David Given.
See if you can spot the bugs!
10 REM Snail Trail 20 REM by D Given 30 REM (c) 1991 The Micro User 40 : 50 IF PAGE>&E00 MODE7:PROCmovedown 60 MODE1:VDU23;8202;0;0;0; 70 PROCinitprog 80 REPEAT 90 PROCtitle 100 REPEAT 110 PROCinitgame 120 PROCcountdown 130 REPEAT 140 PROCplayer1 150 PROCtune 160 IF pl%=2 PROCplayer2 ELSE PROCcomputer 170 UNTIL p1% OR p2% 180 IF p1% PROCexplode(X1%,Y1%) 190 IF p2% PROCexplode(X2%,Y2%) 200 G1%=G1%-(p2%=TRUE) 210 G2%=G2%-(p1%=TRUE) 220 UNTIL G1%+G2%>=max% 230 PROCendgame 240 UNTIL FALSE 250 : 260 DEFPROCinitgame 270 CLS:VDU23,0,6,0,0;0;0; 280 X1%=440:Y1%=425 290 X2%=840:Y2%=425 300 D1%=RND(4):D2%=RND(4) 310 p1%=0:p2%=0 320 C1%=650:C2%=650 330 GCOL0,2 340 MOVE 50,50:DRAW 1230,50 350 DRAW 1230,800:DRAW 50,800 360 DRAW 50,50 370 PROCtune 380 GCOL0,1 390 MOVE X1%-12,Y1%+12:VDU5,128,4 400 GCOL0,2 410 MOVE X2%-12,Y2%+12:VDU5,128,4 420 PROCwrite("S N A I L T R A I L",320,1000,1,2) 430 PROCwrite("Credits",50,900,1,0) 440 PROCwrite("Credits",50,850,2,0) 450 PROCwrite("Games",974,900,1,0) 460 PROCwrite("Games",974,850,2,0) 470 PROCwrite(FNn(G1%,2),1166,900,2,1) 480 PROCwrite(FNn(G2%,2),1166,850,2,1) 490 MOVE 288,836:GCOL0,3 500 PLOT1,C2%,0 510 MOVE 288,886:PLOT1,C1%,0 520 GCOL0,0:MOVE 590,50:PLOT1,100,0 530 MOVE 590,800:PLOT1,100,0 540 MOVE 50,375:PLOT1,0,100 550 MOVE 1230,375:PLOT1,0,100 560 VDU23,0,6,32,0;0;0; 570 ENDPROC 580 : 590 DEFPROCinitprog 600 LOCAL A% 610 VDU23,128,238,238,238,0,238,238,238,0 620 VDU23,129,255,255,255,255,255,255,255,255 630 ENVELOPE 1,1,-6,6,0,2,2,0,110,-2,3,10,110,0 640 ENVELOPE 2,1,0,0,0,0,0,0,20,0,0,-1,120,0 650 ENVELOPE 3,5,1,-1,0,1,1,0,127,-10,0,-10,126,0 660 DIM DX%(4),DY%(4),data% 100 670 DATA 0,0,0,4,4,0,0,-4,-4,0 680 RESTORE 670 690 FOR A%=0 TO 4 700 READ DX%(A%),DY%(A%) 710 NEXT 720 max%=10:fx%=TRUE:pl%=1 730 snd%=TRUE:*FX210,0 740 $data%="AAQQAQ]AQQAQAAQQAQ]AQQAQUUeeUeAUeeUeUUeeUeAUeeUeAAQQAQ]AQQAQAAQQAQ]AQQAQA]mm]mI]mm]mUUeeUeAUeeUe" 750 TIME=0:ptr%=-3:tune%=TRUE 760 ENDPROC 770 : 780 DEFPROCwrite(T$,X%,Y%,C1%,C2%) 790 GCOL 0,C1% 800 VDU5 810 MOVE X%-4,Y%:PRINTT$;:PROCtune 820 MOVE X%,Y%+4:PRINTT$;:PROCtune 830 MOVE X%+4,Y%:PRINTT$;:PROCtune 840 MOVE X%,Y%-4:PRINTT$;:PROCtune 850 GCOL 0,C2% 860 MOVE X%,Y%:PRINTT$;:PROCtune 870 VDU4 880 ENDPROC 890 : 900 DEFFNn(A%,P%) 910 =RIGHT$(STRING$(P%,"0")+STR$A%,P%) 920 : 930 DEFPROCplayer1 940 LOCAL C% 950 X1%=X1%+DX%(D1%) 960 Y1%=Y1%+DY%(D1%) 970 IF INKEY-66 AND D1%<>3 D1%=1 980 IF INKEY-98 AND D1%<>1 D1%=3 990 IF INKEY-67 AND D1%<>2 D1%=4 1000 IF INKEY-83 AND D1%<>4 D1%=2 1010 IF INKEY-2 AND fx% AND C1%>50 PROCbomb(X1%,Y1%,1):C1%=C1%-50:PROCupdate 1020 IF INKEY-100 AND fx% AND C1%>200 PROCblast(X1%,Y1%):C1%=C1%-200:PROCupdate 1030 IF X1%<50 X1%=1230 1040 IF X1%>1230 X1%=50 1050 IF Y1%>800 Y1%=50 1060 IF Y1%<50 Y1%=800 1070 GCOL0,1:PLOT 69,X1%,Y1% 1080 IF D1%=0 ENDPROC 1090 C%=POINT(X1%+DX%(D1%),Y1%+DY%(D1%)) 1100 IF C%=1 OR C%=2 p1%=TRUE 1110 ENDPROC 1120 : 1130 DEFPROCplayer2 1140 LOCAL C% 1150 X2%=X2%+DX%(D2%) 1160 Y2%=Y2%+DY%(D2%) 1170 IF INKEY-73 AND D2%<>3 D2%=1 1180 IF INKEY-105 AND D2%<>1 D2%=3 1190 IF INKEY-103 AND D2%<>2 D2%=4 1200 IF INKEY-104 AND D2%<>4 D2%=2 1210 IF INKEY-89 AND fx% AND C2%>50 PROCbomb(X2%,Y2%,2):C2%=C2%-50:PROCupdate 1220 IF INKEY-102 AND fx% AND C2%>200 PROCblast(X2%,Y2%):C2%=C2%-200:PROCupdate 1230 IF X2%<50 X2%=1230 1240 IF X2%>1230 X2%=50 1250 IF Y2%>800 Y2%=50 1260 IF Y2%<50 Y2%=800 1270 GCOL0,2:PLOT 69,X2%,Y2% 1280 IF D2%=0 ENDPROC 1290 C%=POINT(X2%+DX%(D2%),Y2%+DY%(D2%)) 1300 IF C%=1 OR C%=2 p2%=TRUE 1310 ENDPROC 1320 : 1330 DEFPROCexplode(X%,Y%) 1340 LOCAL C%,D%,E% 1350 GCOL3,3:VDU5 1360 SOUND&10,2,6,10 1370 FOR E%=0 TO 1 1380 MOVE X%-16,Y%+16 1390 VDU255 1400 C%=16:D%=16 1410 REPEAT 1420 MOVE X%-16-C%,Y%+16 1430 PLOT 1,0,-32 1440 MOVE X%+16+C%,Y%+16 1450 PLOT 1,0,-32 1460 MOVE X%-16,Y%+16+C% 1470 PLOT 1,32,0 1480 MOVE X%-16,Y%-16-C% 1490 PLOT 1,32,0 1500 D%=D%-1:C%=C%+D% 1510 PROCtune 1520 UNTIL D%=0 1530 NEXT:VDU4 1540 FOR C%=1 TO 25 1550 *FX19 1560 PROCtune 1570 NEXT 1580 ENDPROC 1590 : 1600 DEFPROCtitle 1610 LOCAL C%,A% 1620 FOR A%=50 TO 100 1630 PROCvolume(A%):*FX19 1640 PROCtune:NEXT 1650 PROCvolume(100) 1660 CLS:VDU23,0,6,0,0;0;0; 1670 PROCwrite("S N A I L T R A I L",320,1000,1,2) 1680 PROCwrite("Program by D Given",352,900,3,0) 1690 PROCwrite("Music by A Clark",384,850,3,0) 1700 PROCwrite("1.",256,650,1,0) 1710 PROCwrite("Current options set:",320,750,2,0) 1720 PROCwrite("Number of games:",336,650,3,0) 1730 PROCwrite(FNn(max%,2),880,650,2,1) 1740 PROCwrite("2.",256,600,1,0) 1750 PROCwrite("Special effects:",336,600,3,0) 1760 PROCwrite(FNyn(fx%),912,600,2,1) 1770 PROCwrite("3.",256,550,1,0) 1780 PROCwrite("Players:",336,550,3,0) 1790 PROCwrite(FNn(pl%,1),912,550,2,1) 1800 PROCwrite("4.",256,500,1,0) 1810 PROCwrite("Sound:",336,500,3,0) 1820 PROCwrite(FNyn(snd%),912,500,2,1) 1830 PROCwrite("5.",256,450,1,0) 1840 PROCwrite("Music:",336,450,3,0) 1850 PROCwrite(FNyn(tune%),912,450,2,1) 1860 PROCwrite("Press SPACE to start",320,200,3,1) 1870 PROCwrite("or a number to change the setup",144,164,3,1) 1880 VDU23,0,6,32,0;0;0; 1890 REPEAT 1900 C%=INKEY(0):PROCtune 1910 IF C%=49 PROCchangegames 1920 IF C%=50 PROCchangeeffects 1930 IF C%=51 PROCchangeplayers 1940 IF C%=52 PROCchangesound 1950 IF C%=53 PROCchangemusic 1960 UNTIL C%=32 1970 G1%=0:G2%=0 1980 CLS 1990 FOR A%=100 TO 50 STEP -1 2000 PROCvolume(A%):*FX19 2010 PROCtune:NEXT 2020 ENDPROC 2030 : 2040 DEFFNyn(fx%) 2050 IF fx% THEN ="Y" ELSE ="N" 2060 : 2070 DEFPROCbomb(X%,Y%,C%) 2080 GCOL0,C%:SOUND &11,1,40,10 2090 MOVE X%-12,Y%+12:VDU5,128,4 2100 ENDPROC 2110 : 2120 DEFPROCblast(X%,Y%) 2130 LOCAL A% 2140 SOUND&10,3,4,7 2150 FOR A%=1 TO 2 2160 GCOL0,-3*(A%=1) 2170 MOVE X%-16,Y%+16:VDU5,129,4 2180 *FX19 2190 NEXT 2200 ENDPROC 2210 : 2220 DEFPROCupdate 2230 GCOL0,3:MOVE 288,836 2240 PLOT1,C2%,0:GCOL0,0:DRAW 938,836 2250 GCOL0,3:MOVE 288,886 2260 PLOT1,C1%,0:GCOL0,0:DRAW 938,886 2270 ENDPROC 2280 : 2290 DEFPROCendgame 2300 LOCAL A%,A$,tune% 2310 FOR A%=1 TO 50 2320 *FX19 2330 NEXT 2340 FOR A%=1 TO 25 2350 COLOUR 3:COLOUR 128 2360 PRINTTAB(15,10)"GAME OVER" 2370 SOUND &11,-15,100,3 2380 SOUND &12,-15,148,3 2390 *FX19 2400 *FX19 2410 COLOUR 0:COLOUR 131 2420 PRINTTAB(15,10)"GAME OVER" 2430 SOUND &11,-15,108,3 2440 SOUND &12,-15,156,3 2450 *FX19 2460 *FX19 2470 NEXT:COLOUR 3:COLOUR 128 2480 SOUND&11,0,0,0:SOUND&12,0,0,0 2490 IF G1%>G2% THEN A$="Player 1 wins!":C%=1 2500 IF G2%>G1% THEN A$="Player 2 wins!":C%=2 2510 PROCwrite(A$,416,500,C%,0) 2520 *FX21 2530 A%=INKEY(500) 2540 ptr%=-3:TIME=0 2550 ENDPROC 2560 : 2570 DEFPROCchangegames 2580 PROCwrite(FNn(max%,2),880,650,0,0) 2590 REPEAT 2600 MOVE 880,650:GCOL 3,1 2610 VDU5:INPUT "" max% 2620 MOVE 880,650:PRINT ;max%; 2630 VDU4 2640 UNTIL max%>0 AND max%<100 2650 ptr%=-3:TIME=0 2660 PROCwrite(FNn(max%,2),880,650,2,1) 2670 ENDPROC 2680 : 2690 DEFPROCchangeeffects 2700 PROCwrite(FNyn(fx%),912,600,0,0) 2710 fx%=NOT fx% 2720 PROCwrite(FNyn(fx%),912,600,2,1) 2730 ENDPROC 2740 : 2750 DEFPROCchangemusic 2760 PROCwrite(FNyn(tune%),912,450,0,0) 2770 tune%=NOT tune% 2780 PROCwrite(FNyn(tune%),912,450,2,1) 2790 ptr%=-3:TIME=0 2800 ENDPROC 2810 : 2820 DEFPROCchangeplayers 2830 PROCwrite(FNn(pl%,1),912,550,0,0) 2840 pl%=pl%+1:IF pl%=3 pl%=1 2850 PROCwrite(FNn(pl%,1),912,550,2,1) 2860 ENDPROC 2870 : 2880 DEFPROCcomputer 2890 LOCAL C% 2900 IF RND(50)=1 PROCturn 2910 C%=POINT(X2%+DX%(D2%)*2,Y2%+DY%(D2%)) 2920 IF C% PROCturn 2930 X2%=X2%+DX%(D2%) 2940 Y2%=Y2%+DY%(D2%) 2950 C%=POINT(X2%,Y2%) 2960 IF C% p2%=TRUE 2970 IF X2%<50 X2%=1230 2980 IF X2%>1230 X2%=50 2990 IF Y2%>800 Y2%=50 3000 IF Y2%<50 Y2%=800 3010 GCOL0,2 3020 PLOT 69,X2%,Y2% 3030 ENDPROC 3040 : 3050 DEFPROCturn 3060 LOCAL D%,OD%,n% 3070 n%=0 3080 D%=SGN(RND) 3090 OD%=D2% 3100 D2%=D2%+D% 3110 n%=n%+1 3120 IF D2%=0 D2%=4 3130 IF D2%=5 D2%=1 3140 C%=POINT(X2%+DX%(D2%)*2,Y2%+DY%(D2%)) 3150 IF C% AND n%=1 D2%=OD%-D%:GOTO 3110 3160 IF C% AND n%=2 AND C2%>200 AND fx% PROCblast(X2%,Y2%):C2%=C2%-200:PROCupdate:GOTO 3110 3170 ENDPROC 3180 : 3190 DEFPROCchangesound 3200 PROCwrite(FNyn(snd%),912,500,0,0) 3210 snd%=NOT snd% 3220 *FX210,1 3230 IF snd% THEN *FX210,0 3240 PROCwrite(FNyn(snd%),912,500,2,1) 3250 ENDPROC 3260 : 3270 DEFPROCcountdown 3280 LOCAL A%,B% 3290 PROCwrite("Prepare to start",384,600,1,2) 3300 FOR B%=5 TO 1 STEP -1 3310 PROCwrite(STR$(B%+1),624,500,0,0) 3320 PROCwrite(STR$(B%),624,500,3,0) 3330 SOUND&11,3,100,20 3340 FOR A%=1 TO 25 3350 *FX19 3360 PROCtune 3370 NEXT 3380 NEXT 3390 PROCwrite("Prepare to start",384,600,0,0) 3400 SOUND&11,3,100,20 3410 PROCwrite("1",624,500,0,0) 3420 ENDPROC 3430 : 3440 DEFPROCtune 3450 IF TIME<25 OR tune%=0 ENDPROC 3460 TIME=TIME-25:ptr%=ptr%+3 3470 IF ADVAL-6=15 SOUND 1,4,data%?ptr%,5 3480 SOUND 2,4,data%?(1+ptr%),5 3490 SOUND 3,4,data%?(2+ptr%),5 3500 IF ptr%=93 ptr%=-3 3510 ENDPROC 3520 : 3530 DEFPROCvolume(V%) 3540 ENVELOPE 4,3,0,0,0,0,0,0,121,-10,-5,-2,V%,V% 3550 ENDPROC 3560 : 3570 DEFPROCmovedown 3580 PRINTTAB(12,11);CHR$132;CHR$157;CHR$135;"Please wait";CHR$32;CHR$156; 3590 VDU23;8202;0;0;0; 3600 PRINTTAB(0,20)STRING$(40,CHR$255) 3610 *TAPE 3620 FOR A%=0 TO TOP-PAGE STEP 4 3630 A%!&E00=A%!PAGE 3640 VDU31,40*(A%/(TOP-PAGE)),20,32 3650 NEXT 3660 PAGE=&E00:*KEY 0 OLD|MRUN|F|M 3670 VDU21:*FX138,0,128 3680 END