Skip to content

Turn a Score into Five Digits in BBC Micro BASIC

Convert an unsigned 16-bit value into exactly five ASCII character codes, preserving its two input bytes and making leading-zero policy explicit.

Your score counter likes binary arithmetic. The player wants to read 65535. This formatter supplies five decimal character codes that a separate text routine can draw, translate to tiles or write into a record.

BBC Micro BASIC shows both retained behaviour checks passing.
The working caller completes with both saved checks equal to one.

Include and initialise

Reserve D(5), public L/H/O and private V/P/I/K. Reserve lines 3000–3150, combine routines.bas and your caller with build.py, and keep END, STOP or a jump before the helper. C64/BBC use GOSUB; Spectrum uses GO SUB. Private I must not be an enclosing caller’s FOR variable.

Call 3000 at a reset boundary. It clears L/H, validity O and every D element. Then supply unsigned low byte L and high byte H. Their combined value is L+256*H, from zero to 65,535.

Format without drawing

Call 3100. If L/H are integral bytes, O=1 and D(1)..D(5) receives exactly five ASCII codes with leading zeroes. L/H survive. Invalid input sets O=0 and preserves all D values and both inputs. This BASIC validity flag handles numeric values outside the assembly helper’s byte-input representation.

Field Use
L / H Low/high input bytes; retained
D(1)..D(5) Five output ASCII codes, no terminator
O Fresh valid flag

Zero becomes 00000. The largest input becomes 65535. There is no sixth byte to scan for a zero terminator. A consumer must use the counted five-element output. ASCII is a logical result: a character display, bitmap font or tile map may need a machine-specific translation.

The reusable routine

Reusable numbered routines

routines.bas
3000 LET L=0: LET H=0: LET O=0: FOR I=1 TO 5: LET D(I)=0: NEXT I: RETURN
3100 LET O=0
3110 IF L<0 OR L>255 OR L<>INT (L) THEN RETURN
3120 IF H<0 OR H>255 OR H<>INT (H) THEN RETURN
3130 LET V=256*H+L: LET P=10000: FOR I=1 TO 5
3140 LET K=INT (V/P): LET D(I)=48+K: LET V=V-K*P: LET P=P/10
3150 NEXT I: LET O=1: RETURN

The helper combines the input in private V, then extracts digits at place values 10,000, 1,000, 100, 10 and 1. V is a BASIC number with enough range for the 16-bit input; it is not an eight-bit work byte. It finishes at zero after all digits, while P finishes at 0.1. Private scratch is overwritten on every valid call.

Format changed values

The normal caller first formats 65,535, then zero, one,9,999 and 10,000. The custom caller begins with 4,096 and returns 04096. Every native trace checks all five character codes and preserved inputs after every call.

Example calls from the maintained caller

demo.bas, excerpt
90 GOSUB 3000: LET AA=0: LET BB=0
100 LET L=255: LET H=255
110 GOSUB 3100: GOSUB 2000
120 IF H=255 THEN LET AA=1
130 LET BB=1
140 IF D(1)<>54 THEN LET BB=0
150 IF D(2)<>53 THEN LET BB=0
160 IF D(3)<>53 THEN LET BB=0
170 IF D(4)<>51 THEN LET BB=0
180 IF D(5)<>53 THEN LET BB=0
190 LET L=0: LET H=0: GOSUB 3100: GOSUB 2000
200 LET L=1: LET H=0: GOSUB 3100: GOSUB 2000
210 LET L=15: LET H=39: GOSUB 3100: GOSUB 2000
220 LET L=16: LET H=39: GOSUB 3100: GOSUB 2000
230 PRINT "TEXT RESULTS";AA;BB
240 ?(MB+995)=AA: ?(MB+996)=BB: ?(MB+997)=GD: ?(MB+998)=WC-256*INT (WC/256): ?(MB+999)=INT (WC/256): ?(MB+1000)=239: STOP

The diagnostic’s line 2000, AA/BB/JJ/KK (Z/Q on Spectrum) and mailbox are removable. A game copies both bytes, calls 3100, checks O and passes D to its own rendering routine. Convert when the displayed score changes if doing so saves useful work.

Customise presentation

Hide leading zeroes after formatting if that suits the display, while keeping the final zero for a value of zero. Signs, separators, decimal points and larger counters require a wider output contract. They are not silently inferred from these two unsigned bytes.

BASIC’s STR$ can be convenient for a complete number, but padding, leading spaces and substring operations differ across dialects. This routine offers fixed counted ASCII output without string allocation. Packed BCD moves work into score updates; native lookup tables trade space for conversion speed. Which is best depends on how often the score changes and where it is displayed.

Mistake and repair

The wrong caller clears H before formatting.65,535 becomes 255 and the formatter correctly returns 00255. Both saved checks fail because the supplied high byte and resulting digits differ from the intended score. Copy both bytes before changing gameplay state. The repair’s source and loaded program match the normal caller.

BBC Micro BASIC shows two failed checks from the deliberate caller mistake.
The wrong caller runs to completion, but both saved behaviour checks are zero.

Cost and Maths for Games

Each valid call extracts five digits with division, subtraction and integer rounding. It writes five output cells and allocates no helper strings. The globals and array cells occupy BASIC numeric storage; they are not nine packed native bytes. No hardware-cycle claim is made.

Maths for Games connects binary storage, base-ten place values and remainders. The Craft separates the stored score, its formatted digits and the player’s visual feedback.

Machine and measured size

BBC BASIC arrays include element zero; this one-based array interface leaves it unused. Its diagnostic uses MODE 7 and HIMEM=&6000 to separate program storage, mailbox and screen. The helper itself changes neither mode nor HIMEM.

The numbered helper occupies 235 tokenised program bytes on this interpreter, including line headers. This excludes the caller, test tables, numeric scalar/array storage, interpreter stacks and ROM. Tokenised size is not machine-code size or a hardware-cycle count. No real-time or whole-game performance budget is inferred from the diagnostic. The caller’s AA/BB, JJ/KK and RAM mailbox are observation machinery, not required game state.

Execution evidence

Working, custom, deliberately wrong and exact repaired callers executed through BBC Micro’s stock BASIC interpreter under Emu198x 0.44.2. Build198x 0.2.9 tokenises C64 PRG and Spectrum TAP programs. C64 enters stock RUN, Spectrum uses ROM tape loading, and BBC enters every numbered source line through MOS/BASIC. Actual loaded program bytes, complete state packets, positive completion/count/guards and visible result pixels are checked. After the positive completion marker, the observer advances two complete display frames before capturing, so a partially scanned final character cannot masquerade as a finished picture. Firmware is not distributed.

An independent mathematical/order model checks the trace and rejects altered source, configuration, loaded program, state, completion and image evidence. Host exhaustive checks complement the native boundary/sequential examples; those host ranges are not claimed as natively exhaustive. The supplied diagnostic callers deliberately end with STOP; the interpreter report is expected. The wrong caller prints 0,0 and the normal/custom/repaired callers print 1,1.

Public CI checks retained evidence; it does not execute restricted firmware. Refresh with python3 scripts/verify-basic-records.py decimal-format bbc-micro --tools /path/to/extracted/releases and inspect with python3 scripts/check-basic-records.py decimal-format.

Explore further

Compare the assembly technique. The BASIC game-flow routine makes session boundaries explicit. Complete source and native evidence.