A bouncing ball
The same program in M68K, MIPS, RISC-V.
A ball moves across the Screen and turns at each edge. A bar along the top grows as time passes, then starts again when its one-byte time value wraps to zero. Choose Build, then Run to watch the animation in the Screen panel. Press Stop when you are done; the program keeps running until you stop it.
Drawing shapes on the screen made one picture. This program repeats four steps for each new picture, or frame:
- Clear the off-screen image.
- Draw the ball and the time bar on it.
- Present the finished image all at once.
- Wait for the next display frame before moving the ball and starting again.
Command 11 turns on double buffering, so drawing goes to an off-screen image. Command 13 presents that image. The viewer sees complete pictures instead of the clear and the partly drawn ball.
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 ; select the lowest byte of the time value
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 ; proposed x is below LIMITX
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
Command 9 clears the off-screen image to SKY. It clears text and graphics together because they
share one image. The ball's X and Y coordinates are its upper-left corner. Both dimensions of its
bounding box use SIZE, so command 6 draws a circular ellipse. The rectangle command adds the
bar, and command 13 presents the completed frame.
in a, (P_FRAME) paces the loop. Reading port 0x51 waits for the next display frame and returns 0. One read per pass prevents a fast machine from racing through the animation. The editor stays
responsive while the program waits, so Stop still works. In a testcase the read returns at once,
letting an animation test finish without waiting for real time.
Port 0x52 gives the elapsed time in hundredths of a second. This read uses the Z80's in a, (c)
form: c holds the port number 0x52, while b, the high byte of the port address, selects which
byte of the clock to read. ld b, 0 selects the lowest byte. The code uses that byte as the bar's
right edge, starting at X = 0. Its value rises from 0 to 255, then wraps to 0 and the bar begins
growing again. No separate reset is needed.
The ball's X and Y positions and their steps are four bytes in memory. Each pass proposes a new
position by adding its step. stepx begins at 3; after neg, it holds 253 (0xFD), the
two's-complement byte pattern for -3.
ld hl, stepx lets add a, (hl) read that step directly from memory. The Y code does the same with
stepy.
LIMITX is 256 - SIZE, or 232. After cp LIMITX, jr c, keepx runs when the proposed X is
unsigned less than 232: the comparison needed a borrow, so the carry flag is set. If X is 231
and the step is +3, the proposal is 234. It is too far right, so the branch is skipped. The code
negates the step and keeps the old X for this frame.
The same test catches the left edge. If X is 1 and the step is -3, the byte addition is
1 + 253 = 254: the proposed position has wrapped around from below zero. As an unsigned byte,
254 is also above the limit, so the code
negates the step back to +3 and leaves X at 1. The Y test works in the same way with LIMITY,
which is 192 - SIZE, or 168. Keeping each proposed coordinate below its limit also lets the
second corner, position plus SIZE, fit in one byte.
Try changing stepx: .db 3 to .db 5, then Build and Run again. The ball should travel farther
across the Screen on each frame, while the bar still follows elapsed time.