Let the player steer
Read the keyboard without stopping for a typed answer and check movement boundaries.
So far, the program chooses every move. We’ll keep it running while a player uses o for left and p for right. Use lower-case letters without Shift.
Continue with Make it move. We are replacing its counted journey with a keyboard loop. If entering this listing into an empty program, its setup selects white on black, clears the screen and places the marker on row 10.
Read, propose, check, draw
Replace 50, 70, 80, 90 and 95. Delete 57 and 58. Add 56, 75, 85 and 100–150. Keep the other lines:
10 PAPER 0
20 INK 7
30 BORDER 0
40 CLS
50 PRINT "o left p right"
55 LET row=10
56 LET col=15
60 PRINT AT row,col;"O"
70 LET k$=INKEY$
75 LET nextcol=col
80 IF k$="o" THEN LET nextcol=col-1
85 IF k$="p" THEN LET nextcol=col+1
90 IF nextcol<1 THEN GO TO 70
95 IF nextcol>30 THEN GO TO 70
100 IF nextcol=col THEN GO TO 70
110 PRINT AT row,col;" "
120 LET col=nextcol
130 PRINT AT row,col;"O"
140 PAUSE 5
150 GO TO 70
INKEY$ reads the keyboard at that moment. It gives a string containing a key
character, or an empty string "" when there is no recognised character.
Unlike INPUT, it does not wait for a whole typed answer and Enter. Repeatedly
checking for input is called polling.
To enter INKEY$, press and release Shift+left Option/Alt, then N. In line
70, LET k$=INKEY$ stores one reading. All the decisions in this visit use that
same value, rather than taking several readings that might disagree as a key changes.
Line 75 begins by proposing no change. Lines 80 and 85 change nextcol only
for o or p. For any other key, or no key, it stays equal to col.
Lines 90 and 95 reject a proposed position outside 1–30. They return to reading input before anything is drawn. Line 100 also returns if there is no change. We keep the chosen boundary narrower than the hardware’s legal 0–31 columns so the marker has a margin.
Only an accepted move reaches 110. We erase the old cell, store the accepted position and draw the new one. Line 140 pauses briefly, then 150 reads again. The pause helps separate repeated moves, but a key press can shorten it.
Foundations: A Box That Grows From Itself
explains updates such as col+1. The extra proposed value lets us check a move
before committing it to the state used for drawing.
Try the controls
Run, then release Enter. Tap o and p, hold each in turn, and press an unrelated key. With a direction held, repeated passes can move the marker several times. A short tap may be missed between readings; this small polling loop is not a promise to capture every possible key event.
Hold left until the marker reaches column 1. Keep holding: it must stay there. Do the same at column 30. Press keys one at a time; this example is not intended to interpret combinations of several direction keys.
To return to editing, use Spectrum BREAK: Shift+Space in Host Keyboard
mode. Hold the chord until the program stops. A BREAK report is expected.
We’ll add a normal quit key in the next lesson.

Why erase before replacing col with nextcol?
The old value tells us which cell contains the marker. Replacing it first would lose that information and erase the new cell instead, leaving a trail behind. The order connects the stored state to the picture we actually drew.
The loop already has input, state updates and output. More complex games use that same separation, but have more to update than one marker. Our next change asks a design question: should holding a button repeat an action, or should each action require a new press?
Next, One press, one step continues this program.
Sources
Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 18, “Motion”, pp. 129–131; appendix C, INKEY$; chapter 15, PRINT AT.