Skip to content
Game 0 Unit 8 of 16 1 hr learning time

The Machine Can Hear You

Read the keyboard the hard, honest way — pick a half-row, read a port, test one bit. Hold a key and watch the border react, every frame. The first time the machine answers to you.

50% of Meet The Machine

A program that decides things (Beat 7) gets interesting when the thing it decides on is you. So: how does the machine know a key is down?

There's no INKEY$ waiting around for you. You read the keyboard yourself, exactly the way you read any port, and test a single bit. The keys are grouped into half-rows, and which half-row you read is chosen by the address you put in BC:

  • ld bc, $7FFE — pick the half-row holding SPACE (and SYM SHIFT, M, N, B).
  • in a, (c) — read it into A. Each bit is one key in that group.
  • bit 0, a — test bit 0, which is SPACE.

There's one twist that catches everyone once: a pressed key reads as 0, not 1. The bit is cleared while the key is held. So bit 0, a sets the zero flag when SPACE is down.

What you'll see by the end

The screen surrounded by a red border, captured while SPACE is held down.
A red border — captured with SPACE held down. The program reads the key every frame and repaints to match; let go and it springs back to blue.

A red border — while SPACE is held. Let go and it springs back to blue. The program reads the key every single frame and repaints the border to match, so the colour is a live readout of whether you're pressing. (The screenshot catches it mid-press; at rest it's blue.)

Reading the key, every frame

; ============================================================================
; PRIMER — Beat 8: The Machine Can Hear You
; ============================================================================
; There's no INKEY$ waiting politely for a keypress. You READ the keyboard,
; the same way you'd read any port, and test one bit.
;
;   ld bc, $7FFE   -- pick the half-row of keys. The high byte ($7F) chooses
;                     which group; this one holds SPACE, SYM SHIFT, M, N, B.
;   in a, (c)      -- read that half-row into A. Each bit is one key.
;   bit 0, a       -- test bit 0 = SPACE.
;
; The twist: a pressed key reads as 0, not 1 -- the bit is CLEARED while held.
; So `bit 0, a` sets the zero flag when SPACE is DOWN (bit 0 = 0).
;
; We do this every frame: read SPACE, paint the border red while it's held,
; blue while it isn't. The machine is listening, frame after frame.
;
; (One key, one bit. Reading the whole keyboard -- all eight half-rows, several
; keys at once -- is the tiny game's job, where movement needs it.)
; ============================================================================

            org     32768

start:
.loop:
            ld      bc, $7FFE        ; the half-row holding SPACE
            in      a, (c)           ; read it  (a pressed key's bit = 0)
            bit     0, a             ; test bit 0 = SPACE
            jr      z, .down         ; zero flag set -> SPACE is held DOWN

            ld      a, 1             ; SPACE up   -> blue
            out     ($FE), a
            jr      .next

.down:
            ld      a, 2             ; SPACE down -> red
            out     ($FE), a

.next:
            halt                     ; wait for the frame
            jr      .loop            ; ...and read again

            end     start

It's the test-then-jump from Beat 7, now fed by the keyboard instead of a fixed value. The loop you've had since Beat 1 — halt then jr — finally earns its keep: each time round, it reads SPACE afresh and paints accordingly. That "read, decide, draw, repeat" rhythm is the heartbeat of every game; you're feeling it for the first time here.

Remember the twist: jr z, .down jumps when the zero flag is set, and bit 0, a sets it when bit 0 is clear — so "zero flag set" means "SPACE is down." Pressed is 0. It reads backwards until it doesn't.

One key now; the rest later

This reads exactly one key, one bit. The full keyboard is eight half-rows, and reading several keys at once — up, down, left, right, fire, all in a frame — is a job of its own. We're leaving it here: that belongs to the tiny first game, where moving a character needs it. For now, the machine can hear one key, and that's the whole idea.

Assemble and run

pasmonext --sna read-a-key.asm primer.sna

Load it, then hold SPACE: the border turns red, and releases back to blue when you let go. The machine is answering you in real time.

Try this: a different key

M is in the same half-row as SPACE, on bit 2. Change bit 0, a to bit 2, a and now the border answers to M instead. (SYM SHIFT is bit 1, N is bit 3, B is bit 4 — the same in a, (c), a different bit.)

Try this: invert it

Swap the two colours — ld a, 2 (red) on the "up" path and ld a, 1 (blue) on the "down" path — so the border is red until you press, then goes blue while held. Same reading, opposite reaction. Predict it, then confirm.

When it's wrong, see why

  • The border never changes when you press SPACE. Check ld bc, $7FFE exactly — the high byte $7F is what selects SPACE's half-row. A wrong high byte reads a different group of keys.
  • It's backwards — red at rest, blue when pressed. That's the active-low twist biting: pressed is 0. Make sure jr z is the branch you treat as "pressed".
  • It reacts once then freezes. The jr .loop at the end is missing or points at the wrong label, so the read never repeats. It must jump back to .loop, above the read.

What you've learnt

You read a key by reading a port (ld bc, $7FFE / in a, (c)) and testing its bit — and a held key reads as 0, not 1.

What's next

You've repeated work by looping back by hand. Next — Counting Toward Zero — we meet the proper counted loop: load a count, DJNZ, and let the machine do a thing a fixed number of times. The end of typing the same line eight times.