Adding speech to MMBasic

Yup, it’s another “let’s wire up a SYN6988 board” thing, this time for MMBasic running on the Armmite STM32F407 Module (aka ‘Armmite F4’). This board is also known as the BLACK_F407VE, which also makes a nice little MicroPython platform.

Uh, let’s not dwell too much on how the SYN6988 seems to parse 19:51 as “91 minutes to 20” …

Wiring

SYN6988Armmite F4
RXPA09 (COM1 TX)
TXPA10 (COM1 RX)
RDYPA08
your choice of 3.3 V and GND connections, of course

Where to buy: AliExpress — KAIKAI Electronics Wholesale Store : High-end Speech Synthesis Module Chinese/English Speech Synthesis XFS5152 Real Pronunciation TTS

Yes, I know it says it’s an XFS5152, but I got a SYN6988 and it seems to be about as reliable a source as one can find. The board is marked YS-V6E-V1.03, and even mentions SYN6988 on the rear silkscreen:

Code

REM                 SYN6988 speech demo - MMBasic / Armmite F4
REM                 scruss, 2023-07

OPEN "COM1:9600" AS #5
REM                 READY line on PA8
SETPIN PA8, DIN, PULLUP

REM    you can ignore font/text commands
CLS
FONT 1
TEXT 0,15,"[v1]Hello - this is a speech demo."
say("[v1]Hello - this is a speech demo.")
TEXT 0,30,"[x1]soundy[d]"
say("[x1]soundy[d]"): REM    chimes
TEXT 0,45,"The time is "+LEFT$(TIME$,5)+"."
say("The time is "+LEFT$(TIME$,5)+".")
END

SUB say(a$)
  LOCAL dl%,maxlof%
  REM     data length is text length + 2 (for the 1 and 0 bytes)
  dl%=2+LEN(a$)
  maxlof%=LOF(#5)
  REM     SYN6988 simple data packet
  REM      byte  1 : &HFD
  REM      byte  2 : data length (high byte)
  REM      byte  3 : data length (low byte)
  REM      byte  4 : &H01
  REM      byte  5 : &H00
  REM      bytes 6-: ASCII string data
  PRINT #5, CHR$(&hFD)+CHR$(dl%\256)+CHR$(dl% MOD 256)+CHR$(1)+CHR$(0)+a$;
  DO WHILE LOF(#5)<maxlof%
  REM       pause while sending text
    PAUSE 5
  LOOP
  DO WHILE PIN(PA8)<>1
    REM       wait until RDY is high
    PAUSE 5
  LOOP
  DO WHILE PIN(PA8)<>0
    REM       wait until SYN6988 signals READY
    PAUSE 5
  LOOP
END SUB

For more commands, please see Embedded text commands

Heres the auto-translated manual for the SYN6988:

Autumn in Canada: PicoMite version

more leaves

So I ported autumn in canada from OpenProcessing to PicoMite BASIC on the Raspberry Pi Pico:

a small black screen images with text in the centre: autumn in canada scruss, 2021-11 just watch ...
no leaves
a small black screen images with text in the centre: autumn in canada scruss, 2021-11 just watch ... with one red and one orange maple leaf sitting on top of it
a couple of leaves
a small black screen images with text in the centre: autumn in canada scruss, 2021-11 just watch ... with four red/yellow/orange maple leaves sitting on top of it
more leaves
a small black screen images with text in the centre: autumn in canada scruss, 2021-11 just watch ... with sixteen simulated fallen maple leaves mostly covering it
plenty of leaves
a small black screen image completely covered with many simulated fallen maple leaves
far too many leaves

The biggest thing that tripped me up was that PicoMite BASIC starts arrays at 0. OPTION BASE 1 fixes that oversight. It would have been nice to have OpenProcessing’s HSV colour space, and an editor that could handle lines longer than 80 characters that didn’t threaten to bomb out if you hit the End key, but it’ll serve.

Source below:

' autumn in canada
' scruss, 2021-11
' a take on my https://openprocessing.org/sketch/995420 for picomite

OPTION base 1
RANDOMIZE TIMER
' *** initialize polar coords of leaf polygon and colour array
DIM leaf_rad(24), leaf_ang(24), px%(24), py%(24)
FOR i=1 TO 24
    READ leaf_rad(i)
NEXT i
FOR i=1 TO 24
    READ x
    leaf_ang(i)=RAD(x)
NEXT i

DIM integer c%(8)
FOR i=1 TO 8
    READ r%, g%, b%
    c%(i)=RGB(r%,g%,b%)
NEXT i

' *** set up some limits
min_scale%=INT(MIN(MM.HRES, MM.VRES)/8)
max_scale%=INT(MIN(MM.HRES, MM.VRES)/6)
min_angle=-30
max_angle=30
min_x%=min_scale%
min_y%=min_x%
max_x%=MM.HRES - min_x%
max_y%=MM.VRES - min_y%

CLS
TEXT MM.HRES/2, INT(MM.VRES/3), "autumn in canada", "CM"
TEXT MM.HRES/2, INT(MM.VRES/2), "scruss, 2021-11", "CM"
TEXT MM.HRES/2, INT(2*MM.VRES/3), "just watch ...", "CM"

kt%=0
DO
    cx% = min_x% + INT(RND * (max_x% - min_x%))
    cy% = min_y% + INT(RND * (max_y% - min_y%))
    angle = min_angle + RND * (max_angle - min_angle)
    sc% = min_scale% + INT(RND * (max_scale% - min_scale%))
    col% = 1 + INT(RND * 7)
    leaf cx%, cy%, sc%, angle, c%(7), c%(col%)
    kt% = kt% + 1
LOOP UNTIL kt% >= 1024

END

SUB leaf x%, y%, scale%, angle, outline%, fill%
    FOR i=1 TO 24
        px%(i) = INT(x% + scale% * leaf_rad(i) * COS(RAD(angle)+leaf_ang(i)))
        py%(i) = INT(y% - scale% * leaf_rad(i) * SIN(RAD(angle)+leaf_ang(i)))
    NEXT i
    POLYGON 24, px%(), py%(), outline%, fill%
END SUB

' radii
DATA 0.536, 0.744, 0.608, 0.850, 0.719
DATA 0.836, 0.565, 0.589, 0.211, 0.660, 0.515
DATA 0.801, 0.515, 0.660, 0.211, 0.589, 0.565
DATA 0.836, 0.719, 0.850, 0.608, 0.744, 0.536, 1.000
' angles
DATA 270.000, 307.249, 312.110, 353.267, 356.540
DATA 16.530, 18.774, 33.215, 3.497, 60.659, 72.514
DATA 90.000, 107.486, 119.341, 176.503, 146.785, 161.226
DATA 163.470, 183.460, 186.733, 227.890, 232.751, 270.000, 270.000
' leaf colours
DATA 255,0,0, 255,36,0, 255,72,0, 255,109,0
DATA 255,145,0, 255,182,0, 255,218,0, 255,255,0

You could probably use AUTOSAVE and paste the text into the PicoMite REPL. I used an ILI9341 SPI TFT LCD Touch Panel with my Raspberry Pi Pico along with some rather messy breadboard wiring.

Fun fact: the maple leaf polygon points are derived from the official definition of the flag of Canada.

Another Raspberry Pi Pico language: MMBasic

It’s very much a work in progress, but Geoff Graham and Peter Mather’s MMBasic runs nicely on the Raspberry Pi Pico. Development is mostly coordinated on TheBackShed.com forum.

It supports an impressive range of displays and peripherals. The project gives me a very distinct “This is how we do things” vibe, and it’s quite unlike any other Raspberry Pi Pico project.

To show you what MMBasic code looks like, here’s a little demo that uses one of those “Open Smart” LED traffic lights on physical pins 14-16 which cycles through the phases every few seconds:

' traffic light on gp10-12 (green, yellow, red), pins 14-16

' set up ports for output
FOR i=14 TO 16
  SETPIN i, DOUT
  PIN(i)=0
NEXT i

red=16
amber=15
green=14

DO
  ' green on for 5 seconds
  PIN(red)=0
  PIN(green)=1
  PAUSE 5000
  ' amber on for 3 seconds
  PIN(green)=0
  PIN(amber)=1
  PAUSE 3000
  ' red on for 5 seconds
  PIN(amber)=0
  PIN(red)=1
  PAUSE 5000
LOOP

Tetris for Applesoft BASIC

a tetris game tableau, paused, with a completed line of bricks about to be removed
a very paused game

Paleotronic’s Tetris for Applesoft BASIC is a surprisingly decent version for something that’s written in an interpreted language. It’s not what anyone would call zippy, but it’s not so slow that you want to give up.

Paleotronic want you to type it in, but life’s too short for that. You can play it in your browser on the Internet Archive: Tetris for Applesoft BASIC by Mark Stock

Or download an auto-booting Apple II disk image:

Keys:

  • , — move left
  • . — move right
  • A — rotate left
  • R — rotate right
  • Z — drop
  • P — pause
  • Q — quit

Life’s also too short for correcting OCR errors in BASIC code. Tesseract is hilariously bad at recognizing source code, so I had to go through this several times. AppleCommander’s BASIC Tools was very handy for catching the last of the errors with its variable dump: caught the cases of the TO keyword converted to the variable T0 … and frankly, I am no fan of SmartQuotes when applied to source code, either.

Here’s the source, straight from the interpreter with all its weird spacing:

10  GOSUB 1000
100 W = W +1: IF W >LV  THEN W = 0: GOSUB 350
110 K =  PEEK(KB): IF K > = H  THEN  POKE KC,H:K = K -H: GOSUB 300
190  GOTO 100
200 PY = PY *A2: VLIN PY,PY +A1 AT PX: RETURN 
225 PY = PY *A2: HLIN X1,X2 AT PY: HLIN X1,X2 AT PY +A1: RETURN 
300  ON E(K) GOTO 30000,330,340,350,360,30100
310  RETURN 
330 X = X -1: GOTO 400
340 X = X +1: GOTO 400
350 DN = 1:Y = Y +1: GOSUB 400:DN = 0: RETURN 
360 S = S +1: IF S/4 =  INT(S/4)  THEN S = S -4
400  GOSUB 500
410  GOSUB 800: IF F = 0  THEN X = XX:Y = YY:S = SS: GOSUB 420: IF DN  THEN  GOSUB 900
420  COLOR= CF: FOR PP = 1 TO 4:PX = X +X(S,PP):PY = Y +Y(S,PP): GOSUB 200: NEXT PP:XX = X:YY = Y:SS = S:D = 0: RETURN 
500  IF DD  THEN  RETURN 
510  COLOR= CB: FOR PP = 1 TO 4:PX = XX +X(SS,PP):PY = YY +Y(SS,PP): GOSUB 200: NEXT PP:DD = 0: RETURN 
800 F = 1: FOR PP = 1 TO 4:PY = Y +Y(SS,PP): ON ( FN PC(X +X(S,PP)) >0) GOTO 805: NEXT PP: RETURN 
805 F = 0: RETURN 
850 F = 1: RETURN 
900 P = 10: GOSUB 30300
905 RN = 0:Y = YM
910 X = XL
920 PY = Y: IF  FN PC(X) = CB  THEN 950
930 X = X +1: IF X < = XR  THEN 920
940 R(RN) = Y:RN = RN +1
950 Y = Y -1: IF Y > = 0  THEN 910
960  IF RN  THEN  GOSUB 30400
970 Y = 0
980 X =  INT((XR -XL)/2) +XL
985 S =  INT( RND(1) *NS):CF = C(S):S = S *4
990  GOSUB 800: IF F  THEN  RETURN 
995  GOTO 31000
1000  DIM E(127),X(27,4),Y(27,4),R(40)
1010  TEXT : HOME : GR 
1011  PRINT "WELCOME..."
1014 LM = 10
1015 XM = 10:YM = 15
1016 XL =  INT((40 -XM)/2)
1017 XR = XL +XM -1
1021 A1 = 1
1022 A2 = 2
1030  DEF  FN PC(X) =  SCRN( X,PY *A2)
1040 CB = 0
1050 XX = 20:YY = 0:SS = 0
1100 KB =  -16384
1110 KC =  -16368
1120 H = 128
1129  REM KEYBOARD ACTIONS
1130  REM QUIT
1131 E( ASC("Q")) = 1
1132 E( ASC("Q") -64) = 1
1140  REM MOVE LEFT
1141 E(8) = 2
1142 E( ASC(",")) = 2
1150  REM MOVE RIGHT
1151 E(21) = 3
1152 E( ASC(".")) = 3
1160  REM MOVE DOWN
1161 E(32) = 4
1162 E( ASC("Z")) = 4
1170  REM ROTATE
1171 E( ASC("R")) = 5
1172 E(13) = 5
1173 E( ASC("A")) = 5
1179  REM PAUSE GAME
1180 E( ASC("P")) = 6
1181 E( ASC("P") -64) = 6
1185  GOSUB 2000
1186  GOSUB 1300
1190  PRINT "PRESS ANY KEY TO START..."
1191  PRINT 
1192  PRINT "PRESS Q TO QUIT."
1193  GOTO 31020
1299  REM DRAW THE GAME
1300  COLOR= 4: FOR I = 0 TO 19:X1 = 0:X2 = 39:PY = I: GOSUB 225: NEXT 
1320  COLOR= CB: FOR I = 0 TO YM:X1 = XL:X2 = XR:PY = I: GOSUB 225: NEXT 
1350  RETURN 
1400  DATA 1
1401  DATA 0,0,1,0,0,1,1,1
1402  DATA 0,0,1,0,0,1,1,1
1403  DATA 0,0,1,0,0,1,1,1
1404  DATA 0,0,1,0,0,1,1,1
1410  DATA 2
1411  DATA 0,1,1,1,2,1,3,1
1412  DATA 1,0,1,1,1,2,1,3
1413  DATA 0,1,1,1,2,1,3,1
1414  DATA 1,0,1,1,1,2,1,3
1420  DATA 12
1421  DATA 1,1,0,1,1,0,2,1
1422  DATA 1,1,0,1,1,0,1,2
1423  DATA 1,1,0,1,2,1,1,2
1424  DATA 1,1,1,0,2,1,1,2
1430  DATA 13
1431  DATA 1,1,0,1,2,1,0,2
1432  DATA 1,1,1,0,1,2,2,2
1433  DATA 1,1,0,1,2,1,2,0
1434  DATA 1,1,1,0,1,2,0,0
1440  DATA 9
1441  DATA 1,1,0,1,2,1,2,2
1442  DATA 1,1,1,0,1,2,2,0
1443  DATA 1,1,0,1,2,1,0,0
1444  DATA 1,1,1,0,1,2,0,2
1450  DATA 3
1451  DATA 1,1,1,0,0,0,2,1
1452  DATA 1,1,1,0,0,1,0,2
1453  DATA 1,1,1,0,0,0,2,1
1454  DATA 1,1,1,0,0,1,0,2
1460  DATA 6
1461  DATA 1,1,0,1,1,0,2,0
1462  DATA 1,1,0,1,0,0,1,2
1463  DATA 1,1,0,1,1,0,2,0
1464  DATA 1,1,0,1,0,0,1,2
1990  DATA  -1
2000 X = 0:Y = 0
2010 NS = 0
2020  READ C: IF C < > -1  THEN C(NS) = C: FOR J = 0 TO 3: FOR I = 1 TO 4: READ X(NS *4 +J,I): READ Y(NS *4 +J,I): NEXT I: NEXT J:NS = NS +1: GOTO 2020
2030  RETURN 
21210 P = 1: RETURN 
30000  TEXT : HOME : END 
30100  HOME 
30110  PRINT "GAME PAUSED. PRESS P TO CONTINUE..."
30120 P = 1
30130 K =  PEEK(KB): IF K > = H  THEN  POKE KC,H:K = K -H: GOSUB 30200
30140  IF P  THEN 30130
30150  HOME 
30160  PRINT "SCORE ";SC; TAB( 21);"LEVEL ";LM -LV +1
30170  RETURN 
30200  ON E(K) GOTO 30000,30210,30210,30210,30210,30220
30210  RETURN 
30220 P = 0
30230  RETURN 
30300 SC = SC +P
30310  VTAB 21: HTAB 7
30320  PRINT SC;
30330  RETURN 
30400 RN = RN -1
30410  FOR C = 0 TO 32
30415  COLOR= C
30420  FOR I = 0 TO RN:X1 = XL:X2 = XR:PY = R(I): GOSUB 225: NEXT I
30430  FOR I = 0 TO 2: NEXT I
30440  NEXT C
30450  FOR I = 0 TO RN
30460 Y = R(I) +I
30470 YP = Y -1: FOR X = XL TO XR:PY = YP: COLOR=  FN PC(X):PX = X:PY = Y: GOSUB 200: NEXT X:Y = Y -1: IF Y >0  THEN 30470
30480 P = 100: GOSUB 30300
30490  NEXT I
30495  RETURN 
31000  VTAB 22: PRINT 
31010  PRINT "              GAME OVER"
31020 P = 1
31030 K =  PEEK(KB): IF K > = H  THEN  POKE KC,H:K = K -H: GOSUB 31200
31040  IF P  THEN 31030
31050 D = 1
31060 SC = 0:LV = LM
31070  GOSUB 30150
31080  GOSUB 1300
31090  GOTO 905
31200  ON E(K) GOTO 30000
31210 P = 0: RETURN 
32000  REM END OF LISTING

If only AppleSoft had a RENUM command, the code might not look so messy

Niche Knowledge: Z80 parallel port SD card on Zeta2

green circuit board with secure digital card slot on left and 40-pin parallel interface connectors on the right
Mini PPISD board: a slow SD card mass-storage system for 8-bit computers

Almost no-one will need this knowledge, but I might need to remember it. In order to add Mini PPISD support to a RomWBW 3.01-supported system, you need to create a file called something like Source/HBIOS/Config/ZETA2_ppisd.asm (for yes, I’m using a Zeta SBC V2) containing:

#include "cfg_zeta2.asm"
UARTCFG		.SET	UARTCFG | SER_RTS
CRTACT		.SET	TRUE		
PPIDEENABLE	.SET	FALSE		
SDENABLE	.SET	TRUE		
PPPENABLE	.SET	FALSE		
PPISD		.EQU	TRUE

Running make from the top-level directory should create a ROM image called Binary/ZETA2_ppisd.rom for you to write to flash. Since my floppy drive isn’t feeling too happy, I had to resort to buying a TL866II Plus programmer to write the chip.

And it worked!

 RomWBW HBIOS v3.0.1, 2021-03-12

 ZETA V2 Z80 @ 8.000MHz
 0 MEM W/S, 1 I/O W/S, INT MODE 2
 512KB ROM, 512KB RAM

 CTC: MODE=Z2 IO=0x20
 UART0: IO=0x68 16550A MODE=38400,8,N,1
 DSRTC: MODE=STD IO=0x70 Sun 2021-03-14 17:47:13 CHARGE=OFF
 MD: UNITS=2 ROMDISK=384KB RAMDISK=384KB
 FD: IO=0x30 UNITS=2
 SD: MODE=PPI IO=0x60 DEVICES=1
 SD0: SDSC NAME=SD    BLOCKS=0x003C7800 SIZE=1935MB

 Unit        Device      Type              Capacity/Mode
 ----------  ----------  ----------------  --------------------
 Char 0      UART0:      RS-232            38400,8,N,1
 Disk 0      MD1:        RAM Disk          384KB,LBA
 Disk 1      MD0:        ROM Disk          384KB,LBA
 Disk 2      FD0:        Floppy Disk       3.5",DS/HD,CHS
 Disk 3      FD1:        Floppy Disk       3.5",DS/HD,CHS
 Disk 4      SD0:        SD Card           1935MB,LBA

 ZETA V2 Boot Loader

 ROM: (M)onitor (C)P/M (Z)-System (F)orth (B)ASIC (T)-BASIC (P)LAY (U)SER ROM  
 Disk: (0)MD1 (1)MD0 (2)FD0 (3)FD1 (4)SD0 

 Boot Selection? 

I was pleasantly surprised how easy it is to use a TL866 programmer under Linux. minipro does all the work, though. To write and verify the whole 512K Flash ROM, it’s:

minipro -p SST39SF040 -w ZETA2_ppisd.rom

The programmer supports over 16000 devices, of which around 10000 are variants (form factor, programming voltage, speed, OTP, etc). It’ll also verify over 100 different 74-series logic chips. It’s not a super cheap device (mine was a little over $80, from Simcoe Diy) but it does a lot for that price.

Next stop: try rebuilding BBC BASIC with RomWBW’s timer support included ..

bench64: a new BASIC benchmark index for 8-bit computers

Nobody asked for this. Nobody needs this. But here we are …

commodore 64 screen shot showing benchmark results:
basic bench index
>i good. ntsc c64=100

1/8 - for:
 60 s; 674.5 /s; i= 100
2/8 - goto:
 60 s; 442.3 /s; i= 100
3/8 - gosub:
 60 s; 350.8 /s; i= 100
4/8 - if:
 60 s; 242.9 /s; i= 100
5/8 - fn:
 60 s; 60.7 /s; i= 100
6/8 - maths:
 60 s; 6.4 /s; i= 100
7/8 - string:
 60 s; 82.2 /s; i= 100
8/8 - array:
 60 s; 27.9 /s; i= 100

overall index= 100


ready.
bench64 running on the reference system, an NTSC Commodore 64c

Inspired by J. G. Harston’s clever but domain-specific ClockSp benchmark, I set out to write a BASIC benchmark suite that was:

  1. more portable;
  2. based on a benchmark system that more people might own;
  3. and a bunch of other less important ideas.

Since I already had a Commodore 64, and seemingly several million other people did too, it seemed like a fair choice to use as the reference system. But the details, so many details …

basic bench index
>i good. ntsc c64=100

1/8 - for:
 309.5 s; 130.8 /s; i= 19 
2/8 - goto:
 367.8 s; 72.1 /s; i= 16 
3/8 - gosub:
 340.9 s; 61.7 /s; i= 18 
4/8 - if:
 181.8 s; 80.1 /s; i= 33 
5/8 - fn:
 135.3 s; 26.9 /s; i= 44 
6/8 - maths:
 110.1 s; 3.5 /s; i= 54 
7/8 - string:
 125.8 s; 39.2 /s; i= 48 
8/8 - array:
 103 s; 16.3 /s; i= 58 

overall index= 29
It was entirely painful running the same code on a real ZX Spectrum at under â…“ the speed of a C64

(I mean: who knew that Commodore PET BASIC could run faster or slower depending on how your numbered your lines? Not me — until today, that is.)

While the benchmark doesn’t scale well for BASIC running on modern computers — the comparisons between a simple 8-bit processor at a few MHz and a multi-core wildly complex modern CPU at many GHz just aren’t applicable — it turns out I may have one of the fastest 8-bit BASIC computers around in the matchbox-sized shape of the MinZ v1.1 (36.864 Z180, CP/M 2.2, BBC BASIC [Z80] v3):

BASIC BENCH INDEX
>I GOOD. NTSC C64=100

1/8 - FOR:
 3.2 S; 12778 /S; I= 1895 
2/8 - GOTO:
 6.1 S; 4324.5 /S; I= 978 
3/8 - GOSUB:
 3.1 S; 6789 /S; I= 1935 
4/8 - IF:
 2.9 S; 4966.9 /S; I= 2046 
5/8 - FN:
 3.5 S; 1030.6 /S; I= 1698 
6/8 - MATHS:
 1.5 S; 255.3 /S; I= 4000 
7/8 - STRING:
 2.6 S; 1871.6 /S; I= 2279 
8/8 - ARRAY:
 3.1 S; 540.3 /S; I= 1935 

OVERALL INDEX= 1839 

That’s more than 9× the speed of a BBC Micro Model B.

Github link: bench64 – a new BASIC benchmark index for 8-bit computers.

Archive download:

For completeness: Level 9 “Snowball” bitmap font

screenshot from an emulated Amstrad CPC464 with yellow text on a blue background. The font is some horrific "futuristic" thing that looks like the numbers on the bottom of paper cheques, for some reason.

The first lines of text say

 *** It's the Level 9 font ***
 ***    from  Snowball!    ***
Dug up by scruss, 2020-01

In blue text, there are a few lines of generic "lorem ipsum" filler text. The last line says "arfle barfle gloop?", which was the Level9 parser's error message. Three characters of the Level9 logo follow: a capital L and digit 9 kerned into the same character cell

Under this, in yellow again, an ASCII chart of the characters from space (32) to DEL (127)
The Snowball font on the Amstrad CPC 464

Oooh blecch — did I really like this when I was a young ‘un? Following on from the same process as in The coolest font (when I was 15, that is) here’s the bitmap font from Level 9’s Snowball.

 10 REM    *** L9SFONT.BAS ***
 15 REM bitmap font from Level 9's
 20 REM Snowball text adventure
 30 REM  on the Amstrad CPC 464
 40 REM (it was so cool at the time ...)
 50 REM Dug up by scruss, 2020-01
 60 REM ==============================
 100 SYMBOL AFTER 32
 110 MODE 1
 120 GOSUB 1000
 130 PRINT" *** It's the Level 9 font ***"
 140 PRINT" ***    from  Snowball!    ***"
 150 PRINT"   Dug up by scruss, 2020-01"
 160 PRINT
 170 PEN 2
 180 PRINT"Lorem ipsum dolor sit amet, consectetur"
 190 PRINT"adipiscing elit, sed do eiusmod tempor"
 200 PRINT"incididunt ut labore et dolore magna"
 210 PRINT"aliqua. Ut enim ad minim veniam, quis"
 220 PRINT"nostrud exercitation ullamco laboris"
 230 PRINT"nisi ut aliquip ex ea commodo consequat"
 240 PRINT"arfle barfle gloop? | | |"
 250 PRINT
 260 PEN 1
 270 FOR i%=32 TO 127
 280 PRINT CHR$(i%); " ";
 290 NEXT i%
 300 PRINT
 310 PRINT
 990 END
 1000 SYMBOL 33,&10,&28,&28,&28,&10,0,&38,0
 1010 SYMBOL 34,&66,&66,&44,&88,0,0,0,0
 1020 SYMBOL 35,0,&24,&7e,&24,&24,&7e,&24,0
 1030 SYMBOL 36,&10,&7c,&40,&7c,&04,&7c,&10,0
 1040 SYMBOL 37,&e4,&a4,&e8,&10,&2e,&4a,&4e,0
 1050 SYMBOL 38,&70,&88,&88,&72,&84,&88,&76,0
 1060 SYMBOL 39,&0c,&0c,&08,&10,0,0,0,0
 1070 SYMBOL 40,&0c,&10,&30,&30,&20,&14,&0c,0
 1080 SYMBOL 41,&30,&28,&04,&0c,&0c,&08,&30,0
 1090 SYMBOL 42,&10,&54,&38,&fe,&38,&54,&10,0
 1100 SYMBOL 43,0,&10,&10,&7c,&10,&10,0,0
 1110 SYMBOL 44,0,0,0,0,&18,&18,&10,&20
 1120 SYMBOL 45,0,0,0,&fc,0,0,0,0
 1130 SYMBOL 46,0,0,0,0,&1c,&14,&1c,0
 1140 SYMBOL 47,0,&06,&0e,&08,&10,&70,&60,0
 1150 SYMBOL 48,&fc,&8c,&8c,&84,&c4,&c4,&fc,0
 1160 SYMBOL 49,&30,&10,&10,&10,&18,&18,&38,0
 1170 SYMBOL 50,&f8,&98,&08,&f8,&80,&c8,&f8,0
 1180 SYMBOL 51,&7c,&64,&04,&3c,&34,&04,&7c,0
 1190 SYMBOL 52,&c8,&88,&88,&f8,&08,&18,&18,0
 1200 SYMBOL 53,&f8,&98,&80,&f0,&08,&c8,&f0,0
 1210 SYMBOL 54,&78,&98,&80,&f8,&98,&c8,&f8,0
 1220 SYMBOL 55,&fc,&c4,&04,&08,&08,&18,&18,0
 1230 SYMBOL 56,&fc,&c4,&8c,&fc,&84,&9c,&fc,0
 1240 SYMBOL 57,&fc,&e4,&84,&fc,&04,&0c,&0c,0
 1250 SYMBOL 58,&38,&28,&38,0,&38,&28,&38,0
 1260 SYMBOL 59,&18,&18,0,&18,&18,&10,&20,0
 1270 SYMBOL 60,&04,&08,&10,&20,&10,&08,&04,0
 1280 SYMBOL 61,0,0,&7e,0,&7e,0,0,0
 1290 SYMBOL 62,&80,&40,&20,&10,&20,&40,&80,0
 1300 SYMBOL 63,&7c,&04,&1c,&10,&10,0,&10,0
 1310 SYMBOL 64,&3e,&22,&2e,&2a,&2e,&20,&3e,0
 1320 SYMBOL 65,&fc,&c4,&8c,&fc,&84,&c4,&c4,0
 1330 SYMBOL 66,&f8,&84,&e4,&f8,&84,&9c,&f8,0
 1340 SYMBOL 67,&fc,&8c,&80,&80,&c0,&c4,&fc,0
 1350 SYMBOL 68,&f8,&64,&64,&44,&4c,&4c,&f8,0
 1360 SYMBOL 69,&fc,&8c,&c0,&f8,&c0,&8c,&fc,0
 1370 SYMBOL 70,&fc,&9c,&80,&f8,&80,&c0,&c0,0
 1380 SYMBOL 71,&fc,&9c,&80,&9c,&c4,&c4,&fc,0
 1390 SYMBOL 72,&c4,&c4,&84,&fc,&84,&8c,&8c,0
 1400 SYMBOL 73,&7c,&14,&10,&10,&10,&50,&7c,0
 1410 SYMBOL 74,&7c,&54,&10,&18,&18,&90,&60,0
 1420 SYMBOL 75,&c8,&90,&a0,&d0,&88,&8c,&8c,0
 1430 SYMBOL 76,&c0,&c0,&c0,&80,&80,&98,&f8,0
 1440 SYMBOL 77,&82,&ee,&92,&92,&ba,&82,&c6,0
 1450 SYMBOL 78,&8c,&c4,&c4,&b4,&8c,&8c,&84,0
 1460 SYMBOL 79,&fc,&c4,&c4,&84,&8c,&8c,&fc,0
 1470 SYMBOL 80,&fc,&c4,&8c,&fc,&80,&c0,&c0,0
 1480 SYMBOL 81,&fc,&c4,&c4,&84,&94,&88,&f4,0
 1490 SYMBOL 82,&fc,&c4,&8c,&fc,&90,&c8,&cc,0
 1500 SYMBOL 83,&fc,&84,&80,&78,&04,&84,&fc,0
 1510 SYMBOL 84,&7c,&10,&10,&10,&30,&30,&30,0
 1520 SYMBOL 85,&c4,&c4,&84,&8c,&8c,&8c,&fc,0
 1530 SYMBOL 86,&c4,&84,&84,&48,&48,&30,&30,0
 1540 SYMBOL 87,&84,&84,&84,&b4,&b4,&cc,&84,0
 1550 SYMBOL 88,&cc,&cc,&48,&30,&48,&cc,&cc,0
 1560 SYMBOL 89,&86,&86,&44,&28,&10,&18,&18,0
 1570 SYMBOL 90,&fc,&84,&08,&30,&40,&84,&fc,0
 1580 SYMBOL 91,&7c,&60,&60,&40,&40,&4c,&7c,0
 1590 SYMBOL 92,0,&60,&30,&10,&08,&0c,&06,0
 1600 SYMBOL 93,&3e,&32,&02,&02,&06,&06,&3e,0
 1610 SYMBOL 94,&18,&24,&42,&42,0,0,0,0
 1620 SYMBOL 95,0,0,0,0,0,0,&ee,&bb
 1630 SYMBOL 96,&3c,&22,&78,&20,&78,&20,&7e,0
 1640 SYMBOL 97,0,0,&f8,&98,&88,&cc,&fc,0
 1650 SYMBOL 98,&80,&80,&f8,&98,&88,&c8,&f8,0
 1660 SYMBOL 99,0,0,&f8,&88,&c0,&c8,&f8,0
 1670 SYMBOL 100,&08,&08,&f8,&98,&88,&c8,&f8,0
 1680 SYMBOL 101,0,0,&f8,&80,&e0,&80,&f8,0
 1690 SYMBOL 102,0,&1c,&10,&38,&38,&10,&70,0
 1700 SYMBOL 103,0,0,&f8,&98,&88,&f8,&08,&78
 1710 SYMBOL 104,&c0,&c0,&80,&f8,&88,&cc,&cc,0
 1720 SYMBOL 105,0,&60,0,&60,&60,&70,&70,0
 1730 SYMBOL 106,&08,0,&18,&18,&08,&08,&68,&78
 1740 SYMBOL 107,&c0,&c0,&cc,&d8,&f0,&d8,&cc,0
 1750 SYMBOL 108,&30,&30,&30,&30,&30,&30,&30,0
 1760 SYMBOL 109,0,0,&cc,&b4,&b4,&84,&84,0
 1770 SYMBOL 110,0,0,&f8,&98,&88,&cc,&cc,0
 1780 SYMBOL 111,0,0,&f8,&98,&88,&c8,&f8,0
 1790 SYMBOL 112,0,0,&f8,&98,&88,&f8,&80,&80
 1800 SYMBOL 113,0,0,&f8,&88,&c8,&f8,&0c,&0c
 1810 SYMBOL 114,0,0,&f8,&98,&80,&c0,&c0,0
 1820 SYMBOL 115,0,0,&f8,&80,&70,&08,&f8,0
 1830 SYMBOL 116,&60,&60,&20,&78,&20,&28,&38,0
 1840 SYMBOL 117,0,0,&c8,&c8,&98,&98,&f8,0
 1850 SYMBOL 118,0,0,&cc,&cc,&88,&70,&20,0
 1860 SYMBOL 119,0,0,&84,&84,&b4,&b4,&cc,0
 1870 SYMBOL 120,0,0,&cc,&48,&30,&48,&cc,0
 1880 SYMBOL 121,0,0,&88,&c8,&f8,&08,&08,&78
 1890 SYMBOL 122,0,0,&f8,&90,&20,&48,&f8,0
 1900 SYMBOL 123,&0c,&30,&10,&60,&10,&30,&0c,0
 1910 SYMBOL 124,&df,&db,&db,&9f,&83,&9b,&fb,0
 1920 SYMBOL 125,&30,&0c,&08,&06,&08,&0c,&30,0
 1930 SYMBOL 126,&7c,&82,&ba,&a2,&ba,&82,&7c,0
 1940 SYMBOL 127,&FF,&FF,&FF,&FF,&FF,&FF,&FF,&FF
 1950 RETURN

Process was the same:

  1. Load the disk image into an emulator
  2. Load the game
  3. Save the game as a v2 snapshot – these are uncompressed memory dumps
  4. Dump the snapshot to a png using phooky’s PROM/bin2png.py
  5. Crop that image down to just the character data and save as a plain PBM
  6. For each column, create the hex dump using shell code a bit like this:
sc=32; pnminvert snowball_font.pbm | pnmnoraw | tail +3 | cut --character="1-8"|while read f; do echo "printf '&%02x, \n' "\"\$((2#${f}))\"; done | bash | fmt -w43 | sed 's/,$//;' | while read g; do echo SYMBOL $sc", "$g; sc=$((sc + 1)); done | awk '{print 10 * (NR + 0) " " $0;}' > schars032to063.bas

TBH, the 901447-10m third-party font for the PET did the Data 70/Westminster faux-MICR thing better, and likely earlier too.

screenshot from an emulated Commodore PET with green text on a black background. The font is some horrific "futuristic" thing that looks like the numbers on the bottom of paper cheques, for some reason.

The first line of text says

 *** 901447-10m Char ROM ***

Below this, there are a few lines of generic "lorem ipsum" filler text. The last line says "arfle barfle gloop?", which was the Level9 parser's error message. 

Under this, an ASCII chart of the characters from space (32) to DEL (127)

The coolest font (when I was 15, that is)

vaguely uncial style bitmap font from old 8-bit Level 9 text adventure
The Colossal Cave font on the Amstrad CPC 464

Though I didn’t really have the patience for text adventures, Level 9 used what I thought was the coolest font (circa 1985). After checking through them all on the Internet Archive Amstrad CPC software library, I couldn’t find a version that used this bitmap font. I eventually found it on nvg. After lots of messing about, I extracted it and present it here. I’m sure I’ll make a TTF of it soon enough.

Tastes change a bit, don’t they?

 10 REM     *** L9FONT.BAS ***
 15 REM bitmap font from Level 9's
 20 REM Colossal Cave adventure
 30 REM  on the Amstrad CPC 464
 40 REM (it was so cool at the time…)
 50 REM Dug up by scruss, 2019-12
 60 REM ==============================
 100 SYMBOL AFTER 32
 110 MODE 1
 120 GOSUB 1000
 130 PRINT" *** It's the Level 9 font ***"
 140 PRINT" ***  from Colossal Cave!  ***"
 150 PRINT"   Dug up by scruss, 2019-12"
 160 PRINT
 170 PEN 2
 180 PRINT"Lorem ipsum dolor sit amet, consectetur"
 190 PRINT"adipiscing elit, sed do eiusmod tempor"
 200 PRINT"incididunt ut labore et dolore magna"
 210 PRINT"aliqua. Ut enim ad minim veniam, quis"
 220 PRINT"nostrud exercitation ullamco laboris"
 230 PRINT"nisi ut aliquip ex ea commodo consequat"
 240 PRINT"arfle barfle gloop? | | |"
 250 PRINT
 260 PEN 1
 270 FOR i%=32 TO 127
 280 PRINT CHR$(i%); " ";
 290 NEXT i%
 300 PRINT
 310 PRINT
 990 END
 1000 SYMBOL 33,&18,&24,&24,&24,&18,&0,&18,&0
 1010 SYMBOL 34,&66,&66,&44,&88,&0,&0,&0,&0
 1020 SYMBOL 35,&0,&24,&7E,&24,&24,&7E,&24,&0
 1030 SYMBOL 36,&12,&7C,&D0,&7C,&16,&FC,&10,&0
 1040 SYMBOL 37,&E4,&A4,&E8,&10,&2E,&4A,&4E,&0
 1050 SYMBOL 38,&70,&D8,&D8,&72,&D6,&CC,&76,&0
 1060 SYMBOL 39,&30,&30,&20,&40,&0,&0,&0,&0
 1070 SYMBOL 40,&1C,&38,&70,&70,&70,&38,&1C,&0
 1080 SYMBOL 41,&70,&38,&1C,&1C,&1C,&38,&70,&0
 1090 SYMBOL 42,&10,&54,&38,&FE,&38,&54,&10,&0
 1100 SYMBOL 43,&0,&10,&10,&7C,&10,&10,&0,&0
 1110 SYMBOL 44,&0,&0,&0,&0,&30,&30,&20,&40
 1120 SYMBOL 45,&0,&0,&0,&F8,&0,&0,&0,&0
 1130 SYMBOL 46,&0,&0,&0,&0,&0,&60,&60,&0
 1140 SYMBOL 47,&0,&4,&8,&10,&20,&40,&0,&0
 1150 SYMBOL 48,&7C,&C6,&CE,&D6,&E6,&C6,&7C,&0
 1160 SYMBOL 49,&8,&18,&38,&18,&18,&18,&3C,&0
 1170 SYMBOL 50,&3C,&66,&C,&18,&30,&62,&7E,&0
 1180 SYMBOL 51,&7E,&4C,&18,&3C,&6,&66,&3C,&0
 1190 SYMBOL 52,&4,&C,&1C,&2C,&7E,&C,&1E,&0
 1200 SYMBOL 53,&3E,&66,&60,&7C,&6,&6,&7C,&0
 1210 SYMBOL 54,&3C,&66,&60,&7C,&66,&66,&3C,&0
 1220 SYMBOL 55,&7E,&46,&6,&C,&C,&18,&18,&0
 1230 SYMBOL 56,&3C,&66,&34,&18,&2C,&66,&3C,&0
 1240 SYMBOL 57,&3C,&66,&66,&3E,&6,&66,&3C,&0
 1250 SYMBOL 58,&0,&30,&30,&0,&0,&30,&30,&0
 1260 SYMBOL 59,&0,&30,&30,&0,&30,&30,&20,&40
 1270 SYMBOL 60,&1C,&30,&60,&C0,&60,&30,&1C,&0
 1280 SYMBOL 61,&0,&0,&F8,&0,&F8,&0,&0,&0
 1290 SYMBOL 62,&E0,&30,&18,&C,&18,&30,&E0,&0
 1300 SYMBOL 63,&7C,&64,&C,&18,&10,&0,&10,&0
 1310 SYMBOL 64,&7C,&C6,&DE,&D2,&DE,&C0,&7E,&0
 1320 SYMBOL 65,&18,&6C,&C6,&C6,&FE,&66,&F6,&0
 1330 SYMBOL 66,&FC,&C6,&C6,&FC,&C6,&C6,&FC,&0
 1340 SYMBOL 67,&3C,&66,&C0,&C0,&C0,&66,&3C,&0
 1350 SYMBOL 68,&D8,&EC,&C6,&C6,&C6,&EC,&D8,&0
 1360 SYMBOL 69,&FE,&62,&60,&78,&60,&62,&FE,&0
 1370 SYMBOL 70,&FE,&62,&60,&78,&60,&60,&E0,&0
 1380 SYMBOL 71,&3C,&66,&C0,&CE,&C6,&66,&3C,&0
 1390 SYMBOL 72,&C6,&C6,&C6,&FE,&C6,&C6,&C6,&0
 1400 SYMBOL 73,&7E,&18,&18,&18,&18,&18,&7E,&0
 1410 SYMBOL 74,&FE,&8C,&C,&C,&C,&CC,&78,&0
 1420 SYMBOL 75,&E6,&CC,&D8,&F0,&D8,&CC,&C6,&0
 1430 SYMBOL 76,&E0,&C0,&C0,&C0,&C0,&C2,&FE,&0
 1440 SYMBOL 77,&C6,&EE,&FE,&D6,&C6,&C6,&CC,&0
 1450 SYMBOL 78,&CE,&E6,&F6,&DE,&CE,&C6,&C6,&0
 1460 SYMBOL 79,&38,&6C,&C6,&C6,&C6,&6C,&38,&0
 1470 SYMBOL 80,&DC,&E6,&C6,&C6,&FC,&C0,&C0,&0
 1480 SYMBOL 81,&38,&6C,&C6,&C6,&CA,&64,&3A,&0
 1490 SYMBOL 82,&DC,&E6,&C6,&C6,&FC,&CC,&C6,&0
 1500 SYMBOL 83,&7C,&C6,&C0,&7C,&6,&C6,&7C,&0
 1510 SYMBOL 84,&FE,&B2,&30,&30,&30,&30,&30,&0
 1520 SYMBOL 85,&E6,&66,&C6,&C6,&C6,&C6,&7C,&0
 1530 SYMBOL 86,&E6,&66,&C6,&C6,&CC,&78,&30,&0
 1540 SYMBOL 87,&EC,&66,&C6,&C6,&D6,&D6,&6C,&0
 1550 SYMBOL 88,&EE,&C6,&6C,&38,&6C,&C6,&EE,&0
 1560 SYMBOL 89,&EE,&C6,&2C,&18,&18,&18,&18,&0
 1570 SYMBOL 90,&FE,&8C,&18,&30,&60,&C2,&FE,&0
 1580 SYMBOL 91,&7C,&64,&60,&60,&60,&60,&7C,&0
 1590 SYMBOL 92,&0,&60,&30,&10,&8,&C,&6,&0
 1600 SYMBOL 93,&3E,&6,&6,&6,&6,&26,&3E,&0
 1610 SYMBOL 94,&18,&24,&42,&42,&0,&0,&0,&0
 1620 SYMBOL 95,&0,&0,&0,&0,&0,&0,&EE,&BB
 1630 SYMBOL 96,&3C,&22,&78,&20,&78,&20,&7E,&0
 1640 SYMBOL 97,&0,&0,&74,&DC,&C4,&CC,&74,&0
 1650 SYMBOL 98,&C0,&C0,&DC,&E6,&C6,&E6,&DC,&0
 1660 SYMBOL 99,&0,&0,&78,&CC,&C0,&CC,&78,&0
 1670 SYMBOL 100,&0,&70,&18,&7C,&CC,&CC,&78,&0
 1680 SYMBOL 101,&0,&0,&78,&CC,&FC,&C0,&7C,&0
 1690 SYMBOL 102,&68,&74,&60,&F8,&60,&60,&60,&C0
 1700 SYMBOL 103,&0,&0,&78,&CC,&C0,&CC,&7C,&C
 1710 SYMBOL 104,&C0,&C0,&D8,&EC,&CC,&D8,&DC,&0
 1720 SYMBOL 105,&C,&0,&38,&18,&18,&18,&38,&0
 1730 SYMBOL 106,&6,&0,&1C,&C,&C,&C,&4C,&38
 1740 SYMBOL 107,&C0,&C0,&CC,&D8,&F0,&D8,&CE,&0
 1750 SYMBOL 108,&30,&30,&30,&30,&30,&36,&3E,&0
 1760 SYMBOL 109,&0,&0,&AC,&D6,&D6,&C6,&CC,&0
 1770 SYMBOL 110,&0,&0,&BC,&C6,&C6,&CC,&DE,&0
 1780 SYMBOL 111,&0,&0,&7C,&C6,&C6,&C6,&7C,&0
 1790 SYMBOL 112,&0,&0,&DC,&E6,&C6,&E6,&DC,&C0
 1800 SYMBOL 113,&0,&0,&76,&CE,&C6,&CE,&76,&6
 1810 SYMBOL 114,&0,&0,&DC,&E6,&C6,&FC,&C6,&0
 1820 SYMBOL 115,&0,&0,&3C,&60,&3C,&8E,&7C,&0
 1830 SYMBOL 116,&18,&30,&FC,&30,&30,&32,&1C,&0
 1840 SYMBOL 117,&0,&0,&E6,&66,&C6,&C6,&7A,&0
 1850 SYMBOL 118,&0,&0,&EC,&66,&C6,&EC,&38,&0
 1860 SYMBOL 119,&0,&0,&EC,&C6,&D2,&7C,&28,&0
 1870 SYMBOL 120,&0,&0,&EE,&6C,&38,&6C,&EE,&0
 1880 SYMBOL 121,&0,&0,&EC,&C6,&6C,&18,&30,&E0
 1890 SYMBOL 122,&0,&0,&FE,&9C,&30,&62,&FE,&0
 1900 SYMBOL 123,&C,&30,&30,&60,&30,&30,&C,&0
 1910 SYMBOL 124,&CF,&DB,&DB,&CF,&C3,&DB,&FB,&0
 1920 SYMBOL 125,&60,&18,&18,&C,&18,&18,&60,&0
 1930 SYMBOL 126,&7C,&C6,&BA,&A2,&BA,&C6,&7C,&0
 1940 SYMBOL 127,&FF,&FF,&FF,&FF,&FF,&FF,&FF,&FF
 1950 RETURN

Taxman – a BASIC game from 1973

Back in 1973, the future definitely wasn’t equally distributed. While in Scotland we had power cuts, the looming three-day week and Miners’ Strike I, in California, the People’s Computer Company (PCC) was giving distributed computer access, teaching programming and publishing computer magazines. I don’t think we got that kind of access until (coincidentally) Miners’ Strike II a little over 10 years later.

taxman drawn image from People's Computer Company magazine (1973) , with "1 for you 19 for me" quote from The Beatles song "Taxman"
flares? platforms? centre parting? bow tie? It was 1973 after all

But the People’s Computer Company magazine archive is a sunny thing, overfilled with joyful amateur enthusiasm and thousands of lines of code fit to make Edsger Dijkstra explode. Of course it was written for the local few who had access to mainframes and terminals, but it hardly seems to come from the same world as the dark evenings in Scotland spent cursing the smug neighbours’ house with all the lights on, their diesel generator putt-putting into the night.

Lots of these games from the PCC era are forgettable now. The raw challenge of guessing a number on a text screen has paled somewhat in the face of 4K photo-realistic rendering. One game I found is still a little challenging, at least until you work out the trick of it: Taxman (or as the authors tried to rename it later, Factor Monster). Here’s a tiny sample game transcript:

Hi, I'm the taxman
Do you want the regulations?
(1=Yes, 0=No)? 0

How many numbers do you want
in the list? 6

The list is: 1  2  3  4  5  6 

You take? 5
Your total is  5 
I get  1 
My total is  1 

New list:  2  3  4  6 

You take? 6
Your total is  11 
I get  2  3 
My total is  6 

New list:  4 
I get  4 
because no factors of any number
are left.
My total is  10 

You  11  Taxman  10 
You win !!!

Again (1=yes, 0=no)?

Seems I sneaked a lucky win there, but it’s harder than it looks. The rules are simple:

  • Start with a list of consecutive numbers
  • You choose a number, but it has to have some factors in the list
  • The taxman (or the factor monster, a concept I much prefer as it doesn’t reinforce the Helmsley Doctrine) takes all the remaining factors of your number from the list
  • You get to choose a number from the list, which is now missing your previous choice and all of its factors, and repeat
  • Once the list has no multiples of any other number, the taxman/FM takes the rest
  • The winner is whoever has the largest sum.

For such a simple game (or perhaps, such a simple me) the computer wins surprisingly often. Since I find it fun to play, I thought I’d share the 1973 love as much as possible by porting to all of the BASIC dialects that I knew.

Plain text BASICtaxman.bas – runs under interpreters such as bas. Almost verbatim from the 1973 publication. May not allow you to play again on some interpreters; you might want to try my slightly rearranged 40 column version that should run on systems that don’t allow a variable to be dimensioned twice.

taxman on Amstrad CPC: starting with numbers 1-6, player has taken 4, so taxman takes 1 & 2, leaving 3, 5 and 6
taxman on Amstrad CPC: how BASIC programs look to me, yellow on blue 4 lyfe

Amstrad CPC Locomotive BASICtaxman.dsk – or as I call it, BASIC. 40 columns yellow on blue is how BASIC should look.

taxman on BBC Micro, showing games tart for 1-6. Adjacent numbers are a full column apart
taxman on BBC, Mode 7: dig the weird spacing

BBC BASICtaxman.ssd – for all the boopBeep fans out there. You can actually play this one in your browser, too. Yes, the number formatting is weird, but BBC BASIC was always its own master.

taxman: Commodore 64 showing the instructions
taxman on C64

Commodore 64taxman.prg – very very upper case for this dinosaur of a BASIC.

taxman running on Apple II: loaded from disk, started with 6 numbers
taxman running on Apple II

Apple II AppleSoft BASICTAXMAN.DSK – lots of fiddling with import tools and dialect weirdness because Apple.

taxman: end of game on ZX spectrum
taxman: end of game on ZX spectrum

ZX Spectrum (Sinclair BASIC)taxman.tap – 32 columns plus a very special dialect (no END, GOTO and GOSUB are GO TO and GO SUB) meant this took a while, but it was quite rewarding to get going.

taxman - BASIC program listing on ZX-81 running under sz81 emulator, Linux window borders visible
Taxman on ZX81: more SCROLLs than the Dead Sea

Sinclair ZX81 (16 K) – taxman.p – this one was a fight. The ZX81 didn’t scroll automatically, so you have to invoke SCROLL before every newline-generating PRINT or else your program will stop. For some reason this version gets unbearably slow near the end of long games, but it does complete.

Lovely automata: bbcbasicbot

bbcbasicbot rendering of my one-liner

BBC BASIC bot [beta2] on Twitter is lovely. You tweet a BBC BASIC program to it and it replies with an animation rendering of what your program would look like on a BBC Micro.

I sent it this:

1MODE4:VDU23,224,24,48,96,193,131,6,12,24:VDU23,225,24,12,6,131,193,96,48,24
2PRINTCHR$(224.5+RND(1));:GOTO2

which readers might recognize as 10 PRINT, the endless random maze one-liner for the C64. This program even inspired its own book – also called 10 PRINT CHR$(205.5+RND(1)); : GOTO 10 – about simple generative art.

You can run it in your browser thanks to the amazing JSBeeb.

eben’s bbc basic programmes

I wrote this as a comment to Learn to write games for the BBC Micro with Eben – Raspberry Pi, but it didn’t seem to save:

BeebEm? Lawks, that’s a bit old (2006). All the cool (*cough*) kids are running b-em – https://github.com/stardot/b-em – these days. It’s lovingly maintain by Stardot forum members. It’s a little crashy on some Linux platforms, but seems stable on the Raspberry Pi and Raspbian. You may need to install the liballegro5-dev and zlib1g-dev packages to get it to compile.

If you want a native version of BBC BASIC, Richard Russell’s version is pretty neat: http://www.bbcbasic.co.uk/bbcsdl/ . You’ll most likely need to change line 280 to use some variant of the WAIT command to make it playable.

Another native interpreter is Brandy. There’s an ancient one in the repos, but I’m completely taken with the Matrix Brandy fork: https://github.com/stardot/MatrixBrandy . It may need a few packages installed to get it to build (libsdl1.2-dev might be a good first try), but it’s really fast. For cross-platform happiness, change line 280 to WAIT 10. If you stick to using a FOR loop, you might have to have it as high as 2,000,000 on a fast computer!

Lastly, if you want to run the game in a browser, JSBeeb to the rescue: https://bbc.godbolt.org/?autorun&loadBasic=https://gist.githubusercontent.com/scruss/f5a8eb83f28b85d6399142cac460c806/raw/74c4e39de7661bb2e3dd7f435840dd8db7172589/helicopter.bbc
It’s a bit slow in Chromium on a Raspberry Pi, but it does work!

Comprehensive Uncle TechTip Simulator

Around 1988–1991 there was a weekly computer magazine in the UK called New Computer Express. This period coincided roughly with the time I was a freelance writer in the same field.

For childish reasons now lost to time, a group of us freelancers had a major hate-on for  NCE’s advice columnist. Writing under the name Uncle TechTip, this columnist seemed to answer most questions with something like “Hmm, I don’t know anything about _____. Maybe a reader can help?” Almost without fail, he’d have readers write in answers for next week’s issue.

Not realizing that Uncle TT’s economy of response was a sly precursor to crowdsourcing websites, the neophyte journo brigade were incensed by his lack of knowledge. One of us wrote an Uncle TechTip Simulator in BASIC, which I recreate from memory for your enjoyment:

10 CLS
15 PRINT " *** Uncle TechTip Simulator ***"
20 PRINT
25 INPUT "What is your question for Uncle TechTip";a$
30 PRINT
35 PRINT "Uncle TechTip's Answer: "
40 PRINT
45 PRINT "Hmm, I don't know anything about"
50 PRINT " ";a$;" ..."
55 PRINT "Maybe a reader can help?"

“space acid poisoning …”

Thanks to users sbadger and lurkio on the stardot forum, I’ve been reunited the original BBC BASIC one-liner that begat 2d Star Dodge/Stardodger: Asterisk Tracker!

game screen with zigzag line (you) vs static white asterisks and a frame around the screen. The object is to get through a small gap in the right hand side of the fram and avoid all the asterisks, using only one key
Asterisk Tracker

It was published in the December 1984 edition of BEEBUG Magazine (vol. 3, issue 7; page 9) and is credited to N. Silver.

It’s impossibly short:

1L=0:REP.L=L+3:MO.4:DR.1279,0:DR.1279,452:MOVE1279,572:DR.1279,1023:DR.0,1023:F.I=1TOL:V.31,RND(32)+5,RND(31),42,30:N.:P.(L-3)/3:X=0:Y=512:REP.PL.69,X,Y:X=X+4:Y=Y-(INKEY-74+.5)*8:U.PO.X,Y)=1ORX=1280:U.X<1280:V.7:REP.U.INKEY-99:RUN

It makes extensive use of BBC BASIC’s abbreviations, and the writeup even warns

… Here the programs are extensively abbreviated so that the line will fit into Basic’s keyboard buffer. Because of this, you cannot edit a LISTed version, and so, to allow for errors, it is best to spool out a copy of the text to tape/disc initially. This can be achieved as follows:

*SPOOL PROGRAM
type in program
*SPOOL

The program unwinds to something much more understandable:


   10 L=0
   20 REPEAT
   25   L=L+3
   30   MODE 4
   40   DRAW 1279,0
   50   DRAW 1279,452
   60   MOVE 1279,572
   70   DRAW 1279,1023
   80   DRAW 0,1023
   90   FOR I=1 TO L
  100     VDU 31,RND(32)+5,RND(31),42,30
  110   NEXT
  120   PRINT (L-3)/3
  130   X=0
  140   Y=512
  150   REPEAT
  155     PLOT 69,X,Y
  160     X=X+4
  170     Y=Y-(INKEY(-74)+.5)*8
  180   UNTIL POINT(X,Y)=1 OR X=1280
  190 UNTIL X<1280
  200 VDU 7
  210 REPEAT UNTIL INKEY(-99)
  220 RUN

The instructions are typical of the day:

The first game (called “Asterisk Tracker”) is a very simple game in which you have to guide a “snake” across the screen, whilst avoiding the stars. As the game progresses, more and more stars will be displayed, and the ease of the game rapidly disappears. The Return key guides the “snake” upwards, but it moves down if Return is not pressed. Aim your “snake” for the gap in the wall, and don’t touch any objects as this causes instant death from space acid poisoning!

Um, yeah, N. Silver, whatevs …

It’s pretty amazing that three type-ins could fit on a page: especially when you consider that the BEEBUG magazine was A5!

page of text from Beebug magazine, including full program text and description of the game
beebug vol 3 issue 7 page 9

If you want to play it (and who wouldn’t? We wasted days on this game) you can either run this Asterisk Tracker alone in the browser: Asterisk Tracker, or lurkio has combined them into one, and put them here: Beebug One-Line Games (Asterisk Tracker, a Truffle Hunt clone, and a treasure hunt).

I just wish Graeme Smith were still with us to play this.

Rob Manuel’s British Council Tile / Bus Fabric Sim

Rob’s British Council Tile / Bus Fabric Sim — described here: Amstrad BASIC that approximates the tiling schemes that a local council might have used for a municipal building in the 1970s — is a joy. So few colours!

No, really: this *was* the seat pattern on Western SMT buses circa 1979

Because I care (and don’t if you don’t), here’s the Locomotive BASIC source, lovingly typed into the Caprice32 emulator then extracted as text using iDsk:


10 ' British Council Tile / Bus Fabric Sim
20 ' by Rob Manuel 2018
30 '
40 ' z/x - change char up/down (ascii)
50 ' space - random palette
60 ' c - show ascii val, inks & pause
70 ' v - random character (128+ ascii)
80 ' b - random char and cols
90 ' n - fill with same line & pause
100 'i - input ascii value
110 '
120 ON BREAK GOSUB 260:MODE 1:LOCATE 1,26
130 DEF FNs=INT(RND*255)
140 SYMBOL 255,FNs,FNs,FNs,FNs,FNs,FNs,FNs,FNs
150 DEF FNp=INT(RND*4)
160 DEF FNi=INT(RND*26)
170 GOSUB 470
180 GOSUB 270
190 o$="":i$=INKEY$
200 IF i$<>"" THEN GOSUB 380
210 FOR i=1 TO 40
220 w$=CHR$(14)+CHR$(FNp)+CHR$(15)+CHR$(FNp)
230 w$=w$+CHR$(c):o$=o$+w$:NEXT i
240 store$=o$
250 PRINT o$;:GOTO 190
260 CALL &BC02:PAPER 0:PEN 1:END
270 aa=FNi:bb=FNi:cc=FNi:dd=FNi
280 INK 0,aa:INK 1,bb:INK 2,cc:INK 3,dd
290 BORDER aa
300 GOSUB 320:RETURN
310 IF c>255 THEN c=32:IF c<32 THEN c=255
320 LOCATE 1,1:PAPER 0:PEN 1
330 PRINT "C:"c;
340 PRINT CHR$(c);
350 PRINT " ";
360 PRINT "I:"aa;bb;cc;dd;
370 LOCATE 1,26:RETURN
380 IF i$=" " THEN GOSUB 270:RETURN
390 IF i$="z" THEN c=c-1:GOSUB 310:RETURN
400 IF i$="x" THEN c=c+1:GOSUB 310:RETURN
410 IF i$="c" THEN GOSUB 310:CALL &BB18:RETURN
420 IF i$="i" THEN LOCATE 1,1:INPUT "ASCII?",c:GOSUB 310:RETURN
430 IF i$="v" THEN GOSUB 470:GOSUB 310:RETURN
440 IF i$="b" THEN GOSUB 270:GOSUB 470:GOSUB 310:RETURN
450 IF i$="n" THEN FOR i=1 TO 25:PRINT store$;:NEXT:CALL &BB18:RETURN
460 RETURN
470 c=INT(RND*128)+127:RETURN

And if you really care, here’s an emulator snapshot — BritishCouncilTileSim.zip

Update: I modified the code slightly (essentially, all INT(RND*n) to RND MOD n) so it would compile with Hisoft Turbo Basic. It works! It’s faster!

Snapshot: BritishCouncilTileSimCompiled.zip

BASIC on the 6502 badge

As if it weren’t nerdy enough, the 6502 40th Anniversary Computer Badge runs Lee Davison’s EhBASIC. There are 1024 whole bytes free for your programs, so it’s not exactly spacious. It’s got useful floating point support, though:

Yup, that’s the second most boring BASIC example program, after the quadratic root finder.

100 REM HERON ROOTS
110 EP=0.0001
120 INPUT "X";X
130 N=1:RN=X/2
140 PRINT"COUNT","ROOT","DELTA":PRINT"======","======","======"
150 DE=ABS(RN*RN-X)
160 PRINT N,RN,DE
170 RN=(RN+X/RN)/2
180 N=N+1
190 IF DE>EP THEN GOTO 150

Update: Josh got my badge working again (it wasn’t, for $reasons …) and I re-ran this code. If you try the code for X=100000 and larger, it won’t converge. You might want to add:

185 IF N>25 THEN PRINT "EPSILON TOO LARGE, EXITING":END

so that the loop will exit after 25 times. Alternatively, make the value of EP depend upon the size of X. Aren’t numerical analysis and floating point foibles fun?

importing Applesoft BASIC programs on the Apple IIe

Just what no-one has needed since about 1979 or so …

BASIC on the Apple II has no easy way to import text as a program. When you LOAD a file, it must be in Apple’s tokenized format. While Apple DOS has the EXEC facility to run script files as if they were typed from the keyboard, it’s very picky about the file format:

  1. There must be a carriage return character (CR, ASCII 13) before the first line
  2. All line numbers must have an extra space before and after them
  3. All tokens must be in upper case
  4. All tokens (keywords and functions) must have a space after them.

The right way to do this conversion would be to write a tokenizer that spits out the correct binary file. But you can (kinda) fudge it with this shell command, operating from BASIC source PROG.BAS:

sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/$/ /;s/[:()]/ & /g;' PROG.BAS | tr '\n' '\r' | ac.sh -p EG.dsk PROG T

ac.sh is the command line version of AppleCommander, and the file EG.dsk referred to above is an Apple DOS 3.3 image created with

ac.sh -dos140 EG.dsk

It still needs work, as there are functions that will mess this up, and Applesoft’s parser makes a mess of code like IF A THEN …, turning it into IF AT HEN ….

So if I wanted to import the following futile program:

10 REM A FUTILE PROGRAM BY SCRUSS
20 HOME
30 FOR X=1 TO 20
40 PRINT SPC(X);"FUTILE"
50 NEXT X

Run through the script (but before EOL conversion) it would look like this:

 10  REM A FUTILE PROGRAM BY SCRUSS 
 20  HOME 
 30  FOR X=1 TO 20 
 40  PRINT SPC ( X ) ;"FUTILE" 
 50  NEXT X

Make a disk and put the modified program text on it:

ac.sh -dos140 futile.dsk
sed 's/^[0-9][0-9]*/& /;s/^/ /;1s/^/\n/;s/$/ /;s/[:()]/ & /g;' futile.bas | tr '\n' '\r' | ac.sh -p futile.dsk FUT T

Load the disk into your Apple II, clear out the init program, and import the code with EXEC FUT:

If all you get is ] cursors printed and no syntax errors, then something might be working. List it:

Run it:

Disk image: futile-AppleII-dsk.zip, containing:

$ ac.sh -l futile.dsk

DISK VOLUME #254
 T 002 FUT 
 A 002 FUTILE 
DOS 3.3 format; 134,144 bytes free; 9,216 bytes used.

“The Error Message as a bourgeois construct”

If you try to run the (unmodified) BASIC code for Oregon Trail (1975) on PDP-8 BASIC, you get this:

 DI 30
 XC 45
 XC 205
…
 IF 700
 NM 730
… (many, many more lines …)

I thought at first it was a stack trace, but nope — it’s error messages! You need to dig through your trusty language manual, and on page 132 it has a table to explain:

DI ERROR IN DIM STATEMENT
IF ERROR IN IF STATEMENT
NM MISSING LINE NUMBER
XC CHARS AFTER END OF LINE

(and yes, they’re in all-caps. Mixed case? Mixed feelings!)

So whenever Python throws a tantrum (or as it calls it, an exception) and wails at length about its problems, remember PDP-8 BASIC: Two letters + a line number. That’s all.

Fun things you learn from old computers …

The program on the left is running on the decimal interpreter, the one on the right the regular one

Microsoft used to supply two versions of its BASIC for Macintosh. One used decimal mathematics for precise tallying of small amounts. The other used the more familiar floating point mathematics, rounding errors and all. I don’t know what floating point library Microsoft used for sure — perhaps Motorola’s 32-bit Fast Floating Point system — but it introduces rounding errors pretty quickly. Modern routines don’t start displaying oddly until after 15 decimal places.

Consider this simple program:

10 LET x=36/10
20 LET a$="## #.#"
30 FOR n%=1 TO 18
40 PRINT USING a$; n%; x
50 LET a$=a$+"#"
60 NEXT n%
70 END

Along with the number of decimal places, it should print 3.6, 3.60, 3.600, 3.6000, … with an increasing line of zeroes after the 3.6. Bas makes a valiant but typical attempt:

 1 3.6
 2 3.60
 3 3.600
 4 3.6000
 5 3.60000
 6 3.600000
 7 3.6000000
 8 3.60000000
 9 3.600000000
10 3.6000000000
11 3.60000000000
12 3.600000000000
13 3.6000000000000
14 3.60000000000000
15 3.600000000000000
16 3.6000000000000001
17 3.60000000000000009
18 3.600000000000000089

Oddly enough, good old Locomotive BASIC on the Amstrad CPC does it more correctly somehow:

So the variables, they vary.