A bouncing ball
The same program in M68K, MIPS, RISC-V.
A ball crosses the screen and turns round at every edge, and a bar along the top grows with the time the program has been running. It never stops on its own: press Run, watch it, and press Stop when you have had enough.
Drawing shapes on the screen drew one picture and stopped. This one draws a new picture forty or fifty times a second, which brings two problems with it: the reader must never see a half drawn frame, and the ball must move at the same speed whatever the machine underneath is doing.
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_FRAME equ 0x51 ; reading it waits for the next animation frame
P_TIME equ 0x52 ; one byte of the hundredths since the run started
C_RECT equ 4
C_ELLIPSE equ 6
C_CLEAR equ 9
C_BUF_ON equ 11
C_PRESENT equ 13
SIZE equ 24
LIMITX equ 256 - SIZE
LIMITY equ 192 - SIZE
SKY equ 0x03
BALL equ 0xFC
BAR equ 0x92
WHITE equ 0xFF
.org 0x8000
ld a, C_BUF_ON
out (P_CMD), a ; every drawing goes to an off-screen copy from here on
frame:
ld a, SKY
out (P_FILL), a
ld a, C_CLEAR
out (P_CMD), a ; wipe the off-screen image, not the one you can see
ld a, BALL
out (P_FILL), a
ld a, WHITE
out (P_PEN), a ; a white rim on the ball
ld a, (ballx)
out (P_X), a
add a, SIZE
out (P_X2), a
ld a, (bally)
out (P_Y), a
add a, SIZE
out (P_Y2), a
ld a, C_ELLIPSE
out (P_CMD), a ; the ellipse that fits in that box, which is a circle
ld b, 0 ; byte 0 of the time, the lowest eight bits
ld c, P_TIME
in a, (c) ; hundredths of a second since the run started
ld e, a ; the bar's right hand end
ld a, BAR
out (P_FILL), a
out (P_PEN), a
xor a
out (P_X), a
out (P_Y), a ; from the top left corner
ld a, e
out (P_X2), a
ld a, 6
out (P_Y2), a
ld a, C_RECT
out (P_CMD), a ; a bar as wide as the program has been running
ld a, C_PRESENT
out (P_CMD), a ; the whole frame becomes visible here, at once
in a, (P_FRAME) ; and the program waits for the next one
ld hl, stepx
ld a, (ballx)
add a, (hl) ; x = x + dx
cp LIMITX
jr c, keepx ; still inside
ld a, (hl)
neg
ld (hl), a ; dx = -dx, turning it round at the edge
ld a, (ballx) ; and stay where we were this frame
keepx:
ld (ballx), a
ld hl, stepy
ld a, (bally)
add a, (hl) ; y = y + dy
cp LIMITY
jr c, keepy
ld a, (hl)
neg
ld (hl), a
ld a, (bally)
keepy:
ld (bally), a
jp frame
.org 0x9000
ballx: .db 100
bally: .db 60
stepx: .db 3
stepy: .db 2
The frame is four steps and they are always in this order: clear the image, draw everything on it, show it with command 13, and wait for the next frame. Command 9 is the clear, and it wipes text and graphics together, because they are one image. Without command 11 at the top the same four steps would draw straight onto what you are looking at, and you would watch the screen go blue and the ball appear, forty times a second, which is what flicker is.
in a, (P_FRAME) is the pacing. Reading port 0x51 suspends the program until the display's next
frame and gives back 0, so one read per pass is what makes the ball move at the same speed on a fast
machine and a slow one. It suspends the program without freezing the editor, so Stop still answers
and the Screen still repaints, and inside a testcase it returns at once so a test of an animation
does not take a minute.
Port 0x52 is the same clock read a different way: it gives one byte of the hundredths of a second
since the run started, and b chooses which byte, so ld b, 0 asks for the lowest eight bits.
That byte is the width of the bar, and it wraps back round to zero all by itself, because a byte
counts to 255 and the Screen is 256 pixels wide. No arithmetic is needed to keep the bar on screen:
the size of the register does it.
The ball's position and step are four bytes in memory, because the drawing already uses a for
every out, b and c for the time port and e to carry the bar's width across it.
ld hl, stepx and add a, (hl) is the step being read where it lies, and the three instructions
under the jr c negate it in place, which turns the ball round without either edge knowing which
way it was going.
cp LIMITX catches both edges with one unsigned comparison. A step that would take x past 232
fails it the obvious way, and a step that would take x below zero wraps the byte round past 250,
which fails it as well. LIMITX equ 256 - SIZE is worked out by the assembler out of the Screen
width and the ball, so neither number appears anywhere else.