Move a square with the keyboard
The same program in M68K, RISC-V, Z80.
Select Open in editor, then Build and Run. Click the Screen before pressing keys;
click it again if you return to the editor. Type lowercase w, a, s or d to steer the square.
It keeps moving in the last direction you chose and reappears at the opposite edge when it crosses
the grid. Press Stop when you are done.
The Screen has a 32 by 32 grid of coloured cells. The square covers 3 by 3 cells, and its upper-left
corner starts at (14, 14). fill_rect takes x in $a0, y in $a1, width in $a2, and
height in $a3. It uses the colour in $s0 and the address of display in $s1. The program calls
it first to paint the background, then to erase and redraw the moving square.
The keyboard receiver is a device-address block beginning at 0xffff0000. Its control register is
at that address, and its data register is four bytes later, at 0xffff0004. Loads from these
addresses ask the device for input. Stores to display paint Screen cells; loads from display
read their current colour words.
# @screen unit=8 width=256 height=256 base=display
.eqv MMIO 0xffff0000
.eqv SIDE 32 # words across and down
.eqv BOX 3 # the square, in words
.eqv LAST 29 # SIDE - BOX, the largest x or y
.eqv FRAME 80 # milliseconds per frame
.eqv BACKGROUND 0x00101820
.eqv BOXCOLOUR 0x0000C060
.data
display: .space 4096 # SIDE * SIDE words, four bytes each
.text
.globl main
# fill_rect(x, y, w, h): $a0-$a3 hold the rectangle;
# $s0 holds the colour and $s1 holds the grid base
fill_rect:
move $t0, $a1 # row = y
add $t1, $a1, $a3 # one past the last row
rect_rows:
sll $t2, $t0, 5 # row * SIDE
add $t2, $t2, $a0 # + x
sll $t2, $t2, 2 # four bytes per word
add $t2, $t2, $s1
move $t3, $a2
rect_cols:
sw $s0, 0($t2)
addi $t2, $t2, 4
addi $t3, $t3, -1
bnez $t3, rect_cols
addi $t0, $t0, 1
blt $t0, $t1, rect_rows
jr $ra
main:
la $s1, display
li $s7, MMIO
li $s0, BACKGROUND # paint the grid once
li $a0, 0
li $a1, 0
li $a2, SIDE
li $a3, SIDE
jal fill_rect
li $s2, 14 # x
li $s3, 14 # y
li $s4, 1 # dx
li $s5, 0 # dy
frame:
li $s0, BACKGROUND # erase the square where it was
move $a0, $s2
move $a1, $s3
li $a2, BOX
li $a3, BOX
jal fill_rect
# --- one key sets the direction, it does not move the square -----------------
lw $t4, 0($s7) # the receiver control register
andi $t4, $t4, 1 # the Ready bit
beqz $t4, no_key
lw $t5, 4($s7) # read the waiting character
andi $t5, $t5, 0xFF
bne $t5, 'a', not_a
li $s4, -1
li $s5, 0
not_a:
bne $t5, 'd', not_d
li $s4, 1
li $s5, 0
not_d:
bne $t5, 'w', not_w
li $s4, 0
li $s5, -1
not_w:
bne $t5, 's', no_key
li $s4, 0
li $s5, 1
no_key:
# --- and the square moves on its own, coming back in at the far edge ---------
add $s2, $s2, $s4
ble $s2, LAST, x_low
li $s2, 0 # off the right edge, back at the left
x_low:
bgez $s2, x_done
li $s2, LAST # off the left edge, back at the right
x_done:
add $s3, $s3, $s5
ble $s3, LAST, y_low
li $s3, 0
y_low:
bgez $s3, y_done
li $s3, LAST
y_done:
li $s0, BOXCOLOUR # and draw it where it is now
move $a0, $s2
move $a1, $s3
li $a2, BOX
li $a3, BOX
jal fill_rect
li $v0, 32 # a frame of program time
li $a0, FRAME
syscall
j frame
Each pass through frame erases the old square, polls the keyboard, moves its upper-left corner,
wraps that corner if it crosses an edge, draws the square at its new position, and waits before the
next pass. The background is painted only once. $s2 and $s3 hold the corner's x and y;
$s4 and $s5 hold the horizontal and vertical steps. The initial steps (1, 0) move it right.
$s7 holds 0xffff0000. The load at 0($s7) reads receiver control; andi keeps Ready bit 0.
When that bit is 1, a character is waiting, so the load at 4($s7) reads receiver data. The
character is in its low byte, and reading the data consumes it. If a character is waiting, it
remains available until the program reads it. If Ready is 0, the program skips the character checks
and keeps the previous steps.
Each bne compares the received character with one of the lowercase direction letters. The
assembler accepts a character literal such as 'd' as shorthand for its numeric value. Pressing
d sets ($s4, $s5) to (1, 0): one cell right per frame and no vertical movement. Pressing w
sets it to (0, -1). Setting the unused step to zero prevents diagonal movement when you change
direction. Uppercase W does not match lowercase 'w' in this program.
The receiver supplies typed characters rather than a continuous report of held keys. A key changes the stored direction; releasing it does not change the steps, so the square keeps moving. This program checks for the four letter characters and does not use arrow-key input. The fixed testcase runs the program without typing into the receiver; use the Screen to check the steering yourself.
LAST is 29 because a 3-cell square whose left edge is at column 29 occupies columns 29–31. If a
rightward step makes x greater than 29, the code sets x to 0. A step past the left edge sets
x to 29. The same checks wrap y at the top and bottom.
Try this first: while it moves right, press w, then d. Predict the steps after each key. They
should be (0, -1) and then (1, 0), so the square turns up and then right without moving
diagonally. To change the edge behavior, replace the li $s2, 0 immediately after
ble $s2, LAST, x_low with li $s2, LAST. Build and Run again, then press d. At the right edge
the square now stays in the last valid column instead of appearing on the left; the other three
edges still wrap.