Tap Space: the turn counter advances. Hold it down: the loop keeps sampling, but the world takes just one turn. Release it and press again for the next move.
That gives us a useful split. The caller owns pacing, input and drawing. A small callable routine decides whether the current key sample represents a new press. You can keep that routine when the game around it grows.

Enter the working program
Enter the caller, then the routines below, as one numbered BASIC program. Type RUN. Use a stock Electron with its own MOS and BASIC; this caller selects MODE 6 and keeps normal operating-system interrupt handling.
The complete caller: wait, sample, apply a turn and draw changes
10 MODE 6:HIMEM=&3000
20 N%=0:S%=1:P%=FALSE:F%=0:C%=0:?&3004=165:?&3005=90:PRINT "TURN ";N%
30 GOSUB 1000
40 K%=INKEY(-99)
50 GOSUB 1100
60 F%=(F%+1)AND65535
70 IF C% THEN PRINT TAB(0,0);"TURN ";N%;" "
80 ?&3000=N%:?&3001=F%AND255:?&3002=F%DIV256:?&3003=C%
90 GOTO 30Startup happens once. Each pass waits through MOS, reads Space with INKEY(-99), applies the rule and redraws only if a turn was accepted. F% counts loop iterations; it is not the turn counter and is not a reliable elapsed-time clock.
The addresses above HIMEM=&3000 are a diagnostic mirror: &3000 holds the counter, &3001–&3002 the iteration count and &3003 the accepted flag. Two guard bytes follow at &3004–&3005. The reusable routines do not require those six bytes. Remove the mirror writes if your game does not need them, and reserve any memory you do keep against BASIC’s growing program and variable workspace.
The reusable pieces
Copy lines 1000–1130 to free line numbers, or renumber them and the call sites together. GOSUB 1000 performs the wait. GOSUB 1100 applies the press rule.
MOS pacing and a reusable press-edge turn update
1000 *FX 19
1010 RETURN
1100 C%=0
1110 IF K% AND NOT P% THEN N%=(N%+S%)AND255:C%=1
1120 P%=K%
1130 RETURN*FX 19 uses the same documented MOS synchronisation service as the assembly wait. It passes through the MOS command interpreter, so it costs more than a short assembly call. Keep the normal IRQ service running; this is not a replacement for a takeover game’s timing code.
For the rule update, put a TRUE/FALSE sample in K%. Keep P%, the previous sample, between calls. The key is newly pressed when it is held now and was not held previously.
| Global | Ownership and result |
|---|---|
N% |
Counter; new presses add the step modulo 256 |
S% |
Step; input, preserved |
K% |
Current TRUE/FALSE sample; input, preserved |
P% |
Previous sample; replaced with K% on every call |
C% |
Result: 1 for a new accepted press, 0 otherwise |
Initialise P%=FALSE once. Do not clear it inside the loop, or every held sample looks new. Consume C% before another routine reuses that global. These are BASIC globals, not local variables; rename them consistently to avoid collisions. Each GOSUB uses an interpreter return frame. The press routine owns no display, keyboard driver, heap or interrupt handler.
Customise the useful parts
Change S%=1 to S%=2. Two presses now produce 0 → 2 → 4. Holding the first press still adds only two once. No extra routine or state variable is needed.

Start N% at 254 to check 254 → 255 → 0. The mask makes this a byte counter even though BASIC integers are wider. If you need a larger move count, change the representation and wrap policy deliberately.
To use another key, replace the negative INKEY key number with the documented one. An ASCII code is a different kind of query. A short tap entirely between samples can be missed; an input queue or interrupt capture needs a different contract.
Common mistake: treating held as newly pressed
This replacement is deliberately broken. It looks only at the current sample, so holding Space pours turns into the counter.
Broken routine: held input becomes repeated turns
1000 *FX 19
1010 RETURN
1100 C%=0
1110 IF K% THEN N%=(N%+S%)AND255:C%=1
1120 P%=K%
1130 RETURN
Restore the original routine and keep P% between calls. The native checks run the repair afresh: press once, hold, release and press again. The working progression is 0 → 1 → 1 → 2.
Common mistake: removing the wait
The next replacement removes the MOS wait while keeping the press rule intact. The iteration counter now follows interpreter work rather than display fields.
Broken pacing: RETURN without waiting for a field
1000 RETURN
1010 REM No field wait: this counts BASIC iterations.
1100 C%=0
1110 IF K% AND NOT P% THEN N%=(N%+S%)AND255:C%=1
1120 P%=K%
1130 RETURNThe tested faulty Electron caller completes 43 iterations in 30 native fields, so the same field-rate guard rejects it. Restore *FX 19 in the wait routine. A program that prints its speed to the screen can be misleading: printing itself adds work and can conceal the missing wait.
Keep enough time for the work
A wait supplies a synchronisation point; it cannot make slow work fit. In our diagnostic, redrawing the text every pass made the Electron miss alternate fields. Drawing only when state changes restores 30 idle iterations in 30 native fields on each tested machine. A frame containing a redraw can still take longer. That is a workload observation, not a promise for every game.
The Electron’s display and CPU share access to RAM. Similar BASIC and MOS interfaces do not give it the BBC Micro’s available CPU budget. Keep expensive drawing separate from rules, measure your real workload, and choose an elapsed-time source if the game has timed turns. A missed field is not a reason to take extra player turns.
Choose how your game advances separates progression from timing and presentation. The other Acorn BASIC port uses the same maintained source and has its own native execution record.
What we checked
Code status: firmware-entered and emulator-executed. Released Emu198x 0.44.0 cold-boots the Electron, types the whole program and drives actual key press/release events. The native record identifies its own tool binary, firmware, source and captures.
Seven fresh cases cover default and step-two input, a nonzero start, byte wrap, the held-key fault, a repaired run and an isolated missing-wait fault. Checks observe guest state and guards, measure field pacing and reject idle advancement or repeated working turns. The retained checker rejects incomplete cases and altered inputs; it does not stand in for fresh emulation. No original-hardware or complete-game performance claim is implied.
Primary references
Acorn’s BBC Microcomputer User Guide, INKEY, its negative-key table and *FX 19; Acorn’s Electron User Guide, the corresponding BASIC and operating-system commands.