Move a square with the keyboard
The same program in M68K, MIPS, RISC-V.
A square you steer. The arrow keys set its direction, and it keeps moving after you release them. When it leaves the play area, it reappears at the opposite edge. Choose Build, then Run, click the Screen panel so it has the keyboard focus, and press the arrows. A focus ring appears around the Screen. Press Stop when you are done; the program keeps running until you stop it.
As in A bouncing ball, the program draws and presents one picture per frame. After each frame it
checks which arrows are held. For port 0x31, put the port number in c and an arrow's key code in
b, then use in a, (c): a becomes 1 while that key is held, or 0 otherwise. The code checks
left, right, up and down in turn, using their key codes 0x25 through 0x28.
P_CHAR equ 0x10
P_PEN equ 0x20
P_FILL equ 0x21
P_X equ 0x23
P_Y equ 0x24
P_X2 equ 0x25
P_Y2 equ 0x26
P_CMD equ 0x27
P_KEY equ 0x31 ; 1 while the key whose code is in b is held down
P_FRAME equ 0x51
C_RECT equ 4
C_CLEAR equ 9
C_RESIZE equ 10
C_BUF_ON equ 11
C_PRESENT equ 13
K_LEFT equ 0x25
K_UP equ 0x26
K_RIGHT equ 0x27
K_DOWN equ 0x28
WIDTH equ 240
HEIGHT equ 192
TOP equ 16 ; the play area starts under the two text rows
CELL equ 24
STEP equ 6
RIGHT equ WIDTH - CELL
BOTTOM equ HEIGHT - CELL
BLACK equ 0x00
BOX equ 0x1F
WHITE equ 0xFF
.org 0x8000
ld a, WIDTH
out (P_X), a
ld a, HEIGHT
out (P_Y), a
ld a, C_RESIZE
out (P_CMD), a
ld a, BLACK
out (P_FILL), a
ld a, C_CLEAR
out (P_CMD), a ; black, and black becomes the text background
ld a, WHITE
out (P_PEN), a
ld hl, title
title_loop:
ld a, (hl)
or a
jr z, ready
out (P_CHAR), a ; the title, at the text cursor
inc hl
jr title_loop
ready:
ld a, C_BUF_ON
out (P_CMD), a ; the off-screen image starts as a copy of this one
frame:
ld a, BLACK
out (P_FILL), a
out (P_PEN), a
xor a
out (P_X), a
ld a, TOP
out (P_Y), a
ld a, WIDTH
out (P_X2), a
ld a, HEIGHT
out (P_Y2), a
ld a, C_RECT
out (P_CMD), a ; wipe the play area, leaving the title above it
ld a, BOX
out (P_FILL), a
ld a, WHITE
out (P_PEN), a
ld a, (boxx)
out (P_X), a
add a, CELL
out (P_X2), a
ld a, (boxy)
out (P_Y), a
add a, CELL
out (P_Y2), a
ld a, C_RECT
out (P_CMD), a
ld a, C_PRESENT
out (P_CMD), a
in a, (P_FRAME) ; one pass per animation frame
; --- the arrows set the direction, they do not move the square ---------------
ld c, P_KEY ; c is the port, b is the key code
ld b, K_LEFT
in a, (c)
or a
jr z, no_left
ld a, -STEP
ld (dx), a
xor a
ld (dy), a
no_left:
ld b, K_RIGHT
in a, (c)
or a
jr z, no_right
ld a, STEP
ld (dx), a
xor a
ld (dy), a
no_right:
ld b, K_UP
in a, (c)
or a
jr z, no_up
ld a, -STEP
ld (dy), a
xor a
ld (dx), a
no_up:
ld b, K_DOWN
in a, (c)
or a
jr z, no_down
ld a, STEP
ld (dy), a
xor a
ld (dx), a
no_down:
; --- and the square moves on its own, coming back in at the far edge ---------
ld hl, dx
ld a, (boxx)
add a, (hl)
cp RIGHT + 1
jr c, x_done ; still inside
ld a, (hl)
or a
jp p, off_right ; a positive step means it left by the right edge
ld a, RIGHT ; so a negative one left by the left edge
jr x_done
off_right:
xor a ; back in at the left
x_done:
ld (boxx), a
ld hl, dy
ld a, (boxy)
add a, (hl)
cp TOP
jr c, off_top ; above the play area
cp BOTTOM + 1
jr c, y_done ; still inside
ld a, TOP ; off the bottom, back in at the top
jr y_done
off_top:
ld a, BOTTOM ; off the top, back in at the bottom
y_done:
ld (boxy), a
jp frame
.org 0x9000
boxx: .db 108
boxy: .db 84
dx: .db STEP
dy: .db 0
title: .asciz "CLICK THE SCREEN, THEN STEER"
The four reads keep c = P_KEY and change b to select the next arrow. After each read, or a
sets the zero flag if the key is not held, so jr z skips that arrow's direction change. The
in a, (c) form matters here: b supplies the high byte of the I/O address, which this port uses
as the key code. With in a, (n), the old value of a supplies that byte instead.
The key handlers store the horizontal and vertical steps in dx and dy; the movement code adds
those stored steps to boxx and boxy every frame. Releasing a key leaves the steps alone, so the
square keeps moving. Each handler also clears the other step with xor a and ld, keeping movement
to one of the four directions.
RIGHT is 216, the last X position where a 24-pixel square fits. After adding dx, cp RIGHT + 1
keeps any proposed X below 217. A step left from X = 0 produces the byte FA (250): the negative
result wraps around to a large unsigned byte, so that same comparison catches the left edge as
well as the right. Once an edge is crossed, the code reads dx to choose where the square should
reappear. The stored -STEP is FA in hexadecimal, with its top bit set. or a sets the sign flag
from that bit without changing a; jp p, off_right jumps when the sign flag is clear, meaning
the step was positive. The Z80 has no jr p form, so this condition uses jp.
The play area starts at Y = TOP = 16, below two reserved 8-pixel text rows. The title occupies
the first of those rows; the second stays blank. An upward step below 16 can still be a small byte,
so Y needs one
comparison with TOP and another with BOTTOM + 1 to catch both edges. The title is printed
before command 11 turns on double buffering. That command starts the off-screen image as a copy of
the visible one, and each frame clears only the rectangle from Y = 16 down. The title stays in
place while the square is redrawn.
Try allowing diagonal movement. How would you change the key handlers so a horizontal arrow
changes only dx, and a vertical arrow changes only dy? After editing, Build and Run, click the
Screen, then hold right and up together. Watch how the square behaves at both edges.