.png)
Hold W and press D. With the key call from S8, one of them does not exist: the call hands back a single byte, so a second held key has nowhere to go. No diagonals. No firing on the move. Every action game you have ever enjoyed is impossible on that interface.
The keyboard itself has no such limit. Every key is a switch at the crossing of an 8 x 8 grid, all sixty-four readable at once - the ROM's key call is a convenience built on top of that grid, and it flattens the grid's whole truth into one byte. This section goes underneath: read the grid, and a chord of keys is as visible as one.
The keyboard is wired as eight rows of eight columns. Your program selects one row at a time, then reads back a byte in which each key of that row owns one bit. Both directions are active-low: you select row n by sending 255 with bit n removed, and a held key answers with a 0 in its bit. Nothing held reads 255.
The chip holding the grid is a guest star: the AY-3-8910, the Einstein's sound chip, whose two spare I/O ports were wired to the keyboard. (Its day job comes later in the course.) It lives behind two Z80 ports:
OUT (2),A - choose one of the AY's registersOUT (3),A - write to the chosen registerIN A,(2) - read from the chosen registerThree of its registers matter here. Register 7 is set once, to 127 - that points the row port outward and the column port inward; its other bits belong to the sound side of the chip, which stays untouched until the course gets there. Register 14 takes the row select. Register 15 answers with the columns:
readrow:
LD A,14 ; the row-select register
OUT (2),A
LD A,C ; the row byte - one bit low
OUT (3),A
LD A,15 ; the column register
OUT (2),A
IN A,(2)
RET
Which keys live where is Appendix V - measured, not copied - with the select byte for every row. The two rows a game wants most: row 5 holds Q W E R T Y U, and row 6 holds A S D F G H J. WASD needs just those two reads.
Before wiring it to the rocket, look at the whole grid live. This program takes the screen over, borrows the font, draws the matrix as an 8 x 8 grid of dots, and repaints it forever - '#' wherever a key is down:
ORG 256
; --- blank tile 0 in every bank
LD A,64
CALL zero8
LD A,72
CALL zero8
LD A,80
CALL zero8
; --- clear the name table
LD A,0
OUT (9),A
LD A,120
OUT (9),A
LD BC,768
clr: LD A,0
OUT (8),A
DEC BC
LD A,B
OR C
JR NZ,clr
; --- borrow the font: read glyphs 32-127 once...
LD A,0
OUT (9),A
LD A,25
OUT (9),A
LD HL,buffer
LD BC,768
rd: IN A,(8)
LD (HL),A
INC HL
DEC BC
LD A,B
OR C
JR NZ,rd
; --- ...and write them into bank 0 and bank 1
LD A,0
OUT (9),A
LD A,65 ; bank 0 at 256
CALL wr768
LD A,0
OUT (9),A
LD A,73 ; bank 1 at 2048+256
CALL wr768
; --- AY: port A output, port B input
LD A,7
OUT (2),A
LD A,127
OUT (3),A
; --- static text
LD HL,title
LD DE,38 ; row 1, column 6
CALL prstr
LD HL,bits
LD DE,108 ; row 3, column 12
CALL prstr
LD DE,165 ; "ROW n" labels at rows 5-12, column 5
LD B,8
rlbl: PUSH BC
PUSH DE
LD HL,rowtxt
CALL prstr
POP DE
LD HL,32
ADD HL,DE
EX DE,HL
LD A,(rowdig)
INC A
LD (rowdig),A
POP BC
DJNZ rlbl
; --- forever: scan, then paint
live: LD C,254
LD HL,keybuf
LD E,8
lrow: LD A,14
OUT (2),A
LD A,C
OUT (3),A
LD A,15
OUT (2),A
IN A,(2)
LD (HL),A
INC HL
RLC C
DEC E
JR NZ,lrow
LD DE,172 ; grid cells start at row 5, column 12
LD HL,keybuf
LD B,8
prow: PUSH BC
LD A,E
OUT (9),A
LD A,D
ADD A,120
OUT (9),A
LD C,(HL)
LD B,8
pbit: RLC C ; bit 7 first: leftmost cell
LD A,'#'
JR NC,mark ; active-low: no carry means pressed
LD A,'.'
mark: OUT (8),A
DJNZ pbit
INC HL
PUSH HL
LD HL,32
ADD HL,DE
EX DE,HL
POP HL
POP BC
DJNZ prow
JR live
; --- write a zero-terminated string at name entry DE
prstr: LD A,E
OUT (9),A
LD A,D
ADD A,120
OUT (9),A
ploop: LD A,(HL)
OR A
RET Z
OUT (8),A
INC HL
JR ploop
; --- 768 buffered bytes to the VDP (address already set)
wr768: OUT (9),A
LD HL,buffer
LD BC,768
w7: LD A,(HL)
OUT (8),A
INC HL
DEC BC
LD A,B
OR C
JR NZ,w7
RET
zero8: LD D,A
LD A,0
OUT (9),A
LD A,D
OUT (9),A
LD A,0
LD B,8
z8: OUT (8),A
DJNZ z8
RET
title: DEFM "THE KEYBOARD MATRIX"
DEFB 0
bits: DEFM "76543210"
DEFB 0
rowtxt: DEFM "ROW "
rowdig: DEFB '0'
DEFB 0
keybuf: DEFS 8
buffer: DEFS 768
What you should see
The grid, all dots. Now play: every key you press lights its own cell the instant it goes down and darkens when it comes up. Hold W - row 5 lights under the 5 column. Add A and D - two cells in row 6, at the same time, while W stays lit. Add space. Four keys, four cells, one picture: that is the fact the whole section stands on, and no single-byte key call could draw it.

Type your name across it. Find the arrow keys (rows 1 to 3). Notice row 5 light left-to-right as you run a finger along QWERTYU - the grid is the physical keyboard, wired.
The game loop's input routine changes from asking-for-one-key to checking
every key it cares about. BIT n,A tests one bit; CALL Z acts when it is
low. Three of WASD share row 6, so one read serves all three - park it in a
register and test it three times:
input: LD C,223 ; row 5: W lives at bit 5
CALL readrow
BIT 5,A
CALL Z,up ; active-low: zero means pressed
LD C,191 ; row 6: A, S and D live here together
CALL readrow
LD D,A ; keep the row - three tests want it
BIT 6,D
CALL Z,left
BIT 5,D
CALL Z,down
BIT 4,D
CALL Z,right
RET
Every held key gets its CALL. W and D down together means up and
right run in the same pass - and that is a diagonal, born.
ORG 256
; --- three patterns: body, flame A, flame B
LD A,0
OUT (9),A
LD A,88
OUT (9),A
LD HL,rocket
LD B,24
pat: LD A,(HL)
OUT (8),A
INC HL
DJNZ pat
; --- close the sprite list after our two
LD A,8
OUT (9),A
LD A,123
OUT (9),A
LD A,208
OUT (8),A
; --- AY: port A output, port B input
LD A,7
OUT (2),A
LD A,127
OUT (3),A
main: CALL place
CALL pause
CALL input
CALL animate
JR main
; --- the matrix replaces call 181: act on every key that is down
input: LD C,223 ; row 5: W lives at bit 5
CALL readrow
BIT 5,A
CALL Z,up ; active-low: zero means pressed
LD C,191 ; row 6: A, S and D live here together
CALL readrow
LD D,A ; keep the row - three tests want it
BIT 6,D
CALL Z,left
BIT 5,D
CALL Z,down
BIT 4,D
CALL Z,right
RET
; --- select the row in C, return its columns in A
readrow:
LD A,14
OUT (2),A
LD A,C
OUT (3),A
LD A,15
OUT (2),A
IN A,(2)
RET
place: LD A,0
OUT (9),A
LD A,123
OUT (9),A
LD A,(rockY)
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,0
OUT (8),A
LD A,15
OUT (8),A
LD A,(rockY)
ADD A,8
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,(flameP)
OUT (8),A
LD A,11
OUT (8),A
RET
animate:
LD A,(flick)
DEC A
LD (flick),A
RET NZ
LD A,8
LD (flick),A
LD A,(flameP)
XOR 3
LD (flameP),A
RET
up: LD A,(rockY)
OR A
RET Z
DEC A
LD (rockY),A
RET
down: LD A,(rockY)
CP 175
RET Z
INC A
LD (rockY),A
RET
left: LD A,(rockX)
OR A
RET Z
DEC A
LD (rockX),A
RET
right: LD A,(rockX)
CP 248
RET Z
INC A
LD (rockX),A
RET
pause: LD BC,3000
dloop: DEC BC
LD A,B
OR C
JR NZ,dloop
RET
rockX: DEFB 124
rockY: DEFB 88
flameP: DEFB 1
flick: DEFB 8
rocket: ; body
DEFB 00011000b
DEFB 00111100b
DEFB 00111100b
DEFB 01111110b
DEFB 01111110b
DEFB 11111111b
DEFB 11011011b
DEFB 10011001b
; flame frame A
DEFB 01011010b
DEFB 01111110b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00010000b
DEFB 00001000b
; flame frame B
DEFB 00111100b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
What you should see
The rocket as before - but fly it properly now. Hold W and D and it climbs
at forty-five degrees. Roll a finger from A to D without lifting and it
banks. It stops exactly at the edges, flame burning all the while. The
program is S22's with one organ transplanted: input reads the grid instead
of asking the ROM, and everything else - the loop, the clamps, the clock -
carries over untouched.
CALLs are doing to rockY each pass.CALL Z,up for CALL NZ,up. Fly. What does the rocket do when
you touch nothing, and why exactly that?JP to a bare
JP $. You have written "pause".| Symptom | Cause |
|---|---|
| A key's bit seems to be in the wrong column | A map with its columns numbered backwards - other references print this table reversed. Appendix V numbers by BIT; when in doubt, hold the key and read what the scan says. |
| Everything reads as pressed, or nothing ever does | The sense is inverted somewhere: selects are 255-minus-a-bit, and pressed is ZERO. BIT then Z for held. |
| SHIFT or CTRL never registers | They are not in the matrix. Use ordinary keys for game controls. |
IN A,(2)). Register 7 = 127, once.BIT tests and CALL Zs: act on everything down this
pass. Three keys on one row cost one read.S26. The heartbeat. The rocket answers your fingers every pass now - but the pass itself is still paced by a guess in a pause loop. The screen has a real clock, a flag that rises once per frame, and locking the loop to it is the difference between something that moves and something that moves smoothly.