Skip to content

Wait for a command on Amstrad CPC

Read a command, update the world once and wait again, with a callable BASIC routine you can put in your own game.

Type GO and press Return. The screen changes from TURN 0 to TURN 1. Make another move and it becomes TURN 2. You can take your time choosing: the rules wait for you.

That is enough of a game loop for an untimed command game. The useful split is between getting a command, deciding what it does and showing the result. We can replace any one of those without hiding the others inside a giant routine.

The working Amstrad CPC caller after two accepted commands, showing TURN 2.

Put it in a program

Enter the caller below, then the routine underneath it, into a CPC464’s Locomotive BASIC. They form one numbered program. Type RUN after entering both. Use uppercase commands as shown; this small reader compares the exact string GO, not a full adventure vocabulary. NO is a useful rejected command. Other strings leave the counter unchanged.

A complete caller: initialise, draw, read, update, repeat

caller.bas
10 MEMORY 39999:MODE 1
20 N=0:C=0:S=1:POKE 40000,N:POKE 40001,C
30 CLS:PRINT "TURN";N
40 INPUT A$
50 GOSUB 1000
60 POKE 40000,N:POKE 40001,C
70 GOTO 30

The line numbered 20 initialises the state once. The loop returns to the drawing line, then waits at INPUT. There is no game clock here. The machine’s own services continue, but our rules do not advance until a command is submitted.

The reusable routine

Copy lines 1000–1040 into free line numbers in your program. Set N to the current counter and S to the amount an accepted command should add. Put the submitted command in A$ and call GOSUB 1000.

A rule update with no input or screen ownership

routines.bas
1000 C=0
1010 IF A$<>"GO" THEN RETURN
1020 N=(N+S) AND 255
1030 C=1
1040 RETURN
State Meaning
N Current progress; updated only for an accepted command
S Amount to add; preserved by the routine
A$ Submitted command; preserved by the routine
C Result: 1 for accepted, 0 for rejected; replaced on every call

C lets your caller decide whether to move enemies, spend a move or redraw a room. Check it before another routine reuses that variable. The counter demonstrates the update point; a complete adventure still needs its own world state and command rules.

Use integer N and S values from 0 to 255. AND 255 wraps the result into that range, so 255 plus 1 becomes 0. This is a rule for the counter, not a claim that a BASIC numeric variable occupies one byte.

Own the state and line numbers

The four variables are BASIC globals. The routine uses the interpreter’s GOSUB/RETURN stack, changes only N and C, and assumes a normal single caller. Do not jump into it with GOTO, or nest another owner of these same variables while a call is in progress.

The caller uses MEMORY 39999 to reserve addresses 40000 and 40001, then mirrors N and C there for the verifier. The routine itself never touches them. Your game can remove both POKEs and the reservation together. If you keep them, reserve those bytes against every other user. MODE 1 and CLS belong to this caller’s screen setup, not to the reusable rule update.

When combining programs, keep these line numbers free or change every call target along with them. Rename globals consistently if they clash. In particular, the map-query routines also use C: consume or save the acceptance result before asking a map question.

Make it your own

Use this caller in place of the first one and keep the same routines.bas. The change is S=2. Two accepted commands now show TURN 2, then TURN 4.

Add two progress units per accepted command

customise.bas
10 MEMORY 39999:MODE 1
20 N=0:C=0:S=2:POKE 40000,N:POKE 40001,C
30 CLS:PRINT "TURN";N
40 INPUT A$
50 GOSUB 1000
60 POKE 40000,N:POKE 40001,C
70 GOTO 30

The customised Amstrad CPC caller after two commands, showing TURN 4.

Each submitted command still causes one call. We changed the amount added; we did not read a second command or update a whole world twice. That distinction matters when a move spends two energy units but gives enemies only one response.

This changes an initial value, with no extra routine, scratch variable or artwork. The screen is still cleared and reprinted after each command. Avoid treating the short rule update as a measurement of how quickly your eventual game can redraw.

Common mistake: starting again every move

The next caller is deliberately broken. It resets N immediately before the call. The first move seems fine. The second leaves the screen at TURN 1 again.

Broken caller: state reset inside the loop

reset-each-turn.bas
10 MEMORY 39999:MODE 1
20 N=0:C=0:S=1:POKE 40000,N:POKE 40001,C
30 CLS:PRINT "TURN";N
40 INPUT A$
50 N=0:GOSUB 1000
60 POKE 40000,N:POKE 40001,C
70 GOTO 30

The faulty Amstrad CPC caller after two accepted commands, still showing TURN 1.

The routine cannot remember state that its caller has just erased. A rejected command is worse: it resets the count to zero without taking a turn. Keep the startup assignment before the loop and use the original caller above. Its two accepted commands give 0 → 1 → 2; the broken one gives 0 → 1 → 1. The captures and measured states come from separate cold runs of those callers.

When a blocking reader stops being enough

This loop is useful while the world can wait. A countdown, animated cursor or sound player that needs regular calls cannot rely on INPUT returning soon. Keep the rule routine, but give that work a non-blocking input loop or an explicitly owned interrupt service, and choose what happens when input and a deadline coincide. That is an extension needing its own checks.

Choose how your game advances separates progression, clocks and presentation, and links callers that service a deadline while waiting. A turn-based game can have a timer; this particular caller deliberately has none.

Take the code and check it

Code status: firmware-entered and emulator-executed, using Emu198x 0.44.0, CPC464 PAL, MODE 1 and the exact firmware/tool identities in the native record.

The checks wait 120 native frames before and after commands, submit rejected input, observe accepted and rejected flags, and repeat with N=37. The step-two caller and the reset mistake run independently. The CPC check also executes the 254 → 255 → 0 wrap. The record checker rejects missing source bindings and falsified progression; it checks retained evidence, while the native verifier reruns the machine. These observations establish this workload, not physical-hardware timing or a complete emulator.

Callers, routine, reproduction instructions and native captures are kept together. The deliberately broken caller stays in mistakes/, separate from the recommended one.

Language reference

Amstrad, CPC464 User Manual, BASIC keywords: GOSUB, RETURN, INPUT, MEMORY, POKE and AND.