.png)
Twenty-six sections of parts. This one is the machine assembled: a game you can hand to someone. Rocks fall; you fly; you shoot or you dodge; the score says how it went. It is small - one rock, one bullet, two digits - but it is complete: nothing in it is a placeholder, and everything in it is something you already know.
Which is the real lesson. Almost nothing below is new. The section's work is showing how the parts hold each other up - and how little glue a game actually needs.
Setup runs once: take the screen (S17), borrow the font into bank 0 so tile number equals character code (S17), write the label and five sprite patterns, close the sprite list, point the sound chip's ports at the keyboard (S25). Then the loop - and the loop is the game:
main: CALL waitframe ; S26 - one pass per frame
CALL input ; S25 - every key that is down
CALL animate ; S22 - the flame's clock
CALL bullet ; S23 - the object's gated update
CALL fall ; the rock's update - same pattern
CALL hits ; S24 - both collision questions
CALL place ; publish the rocket
CALL apub ; publish the rock
CALL dscore ; publish the score
CALL flash ; publish the verdict light
JR main
Read the second half of that list again: from place down, the loop only
publishes. All decisions are made on variables in RAM; the last four
calls copy the results to the screen. State first, pictures second - the
habit S21 started, now holding up a whole game.
A score you can show. The borrowed font made tile number = character code, so displaying a number means adding 48 to a digit and writing one name table byte. The score is kept as two digit-counters, units and tens, carried by hand: when units passes 9 it resets and nudges tens. No division, no decimal arithmetic - a game keeps its numbers in whatever shape it means to show them.
A spawn table. Where does the next rock fall? From a table of eight
columns, walked with an index, masked with AND 7 so it wraps - S13's
lookup table, employed. It plays unpredictably enough, and it makes the
game's behaviour repeatable, which you will bless every time you chase a
bug. (The second column is 124 - the rocket's own column - so the game
opens with a rock falling straight at the player. An opening is a design
decision like any other.)
An effect timer. The ship-hit flash is one byte: set it to 25 on the hit, and each frame it counts down while the backdrop shows red, blue otherwise. Half a second, because the loop is frame-locked and 25 frames IS half a second. Any temporary effect - invulnerability, a message, a pause - is this same byte wearing a different job.
hits asks S24's box question twice and the answers DO things: bullet-hits-
rock parks the bullet, scores, and respawns; rock-reaches-ship starts the
flash and respawns. Notice the rock's update itself (fall) also ends in
spawn when it exits the bottom - three different fates, one respawn
routine, entered by CALL or by a final JR that borrows its RET.
ORG 256
; ================= SETUP =================
; --- blank tile 0 in every bank
LD A,64
CALL zero8
LD A,72
CALL zero8
LD A,80
CALL zero8
; --- clear the name table
LD A,0
OUT (9),A
LD A,120
OUT (9),A
LD BC,768
clr: LD A,0
OUT (8),A
DEC BC
LD A,B
OR C
JR NZ,clr
; --- borrow the font into bank 0: tile number = character code
LD A,0
OUT (9),A
LD A,25
OUT (9),A
LD HL,buffer
LD BC,768
rd: IN A,(8)
LD (HL),A
INC HL
DEC BC
LD A,B
OR C
JR NZ,rd
LD A,0
OUT (9),A
LD A,65
OUT (9),A
LD HL,buffer
LD BC,768
wr: LD A,(HL)
OUT (8),A
INC HL
DEC BC
LD A,B
OR C
JR NZ,wr
; --- the label, straight into the name table
LD A,2
OUT (9),A
LD A,120
OUT (9),A
LD HL,label
LD B,6
lbl: LD A,(HL)
OUT (8),A
INC HL
DJNZ lbl
; --- five sprite patterns: body, flame A, flame B, bullet, rock
LD A,0
OUT (9),A
LD A,88
OUT (9),A
LD HL,shapes
LD B,40
pat: LD A,(HL)
OUT (8),A
INC HL
DJNZ pat
; --- close the sprite list after our four
LD A,16
OUT (9),A
LD A,123
OUT (9),A
LD A,208
OUT (8),A
; --- AY: rows out, columns in
LD A,7
OUT (2),A
LD A,127
OUT (3),A
CALL spawn ; the first rock
; ================= THE LOOP =================
main: CALL waitframe
CALL input
CALL animate
CALL bullet
CALL fall
CALL hits
CALL place
CALL apub
CALL dscore
CALL flash
JR main
; --- one pass per frame
waitframe:
IN A,(9)
BIT 7,A
JR Z,waitframe
RET
; --- matrix input: act on every key that is down
input: LD C,223 ; row 5: W
CALL readrow
BIT 5,A
CALL Z,up
LD C,191 ; row 6: A, S, D
CALL readrow
LD D,A
BIT 6,D
CALL Z,left
BIT 5,D
CALL Z,down
BIT 4,D
CALL Z,right
LD C,254 ; row 0: space
CALL readrow
BIT 6,A
CALL Z,fire
RET
readrow:
LD A,14
OUT (2),A
LD A,C
OUT (3),A
LD A,15
OUT (2),A
IN A,(2)
RET
; --- the rock: one pixel every second frame, respawn off the bottom
fall: LD A,(astT)
XOR 1
LD (astT),A
RET NZ
LD A,(astY)
INC A
LD (astY),A
CP 176
RET C
JR spawn ; missed - the next rock (spawn returns for us)
; --- next rock: next column from the table, back to the top
spawn: LD A,(astI)
INC A
LD (astI),A
AND 7
LD E,A
LD D,0
LD HL,drops
ADD HL,DE
LD A,(HL)
LD (astX),A
LD A,8
LD (astY),A
RET
; --- both collision questions, and their consequences
hits: LD A,(bullA)
OR A
JR Z,ship ; no bullet in flight - just the ship question
LD A,(bullX)
LD B,A
LD A,(bullY)
LD C,A
LD A,(astX)
LD D,A
LD A,(astY)
LD E,A
CALL boxes
OR A
JR Z,ship
LD A,0 ; bullet hit the rock: park it...
LD (bullA),A
LD A,224
LD (bullY),A
CALL score1 ; ...score...
CALL spawn ; ...next rock
ship: LD A,(rockX)
LD B,A
LD A,(rockY)
LD C,A
LD A,(astX)
LD D,A
LD A,(astY)
LD E,A
CALL boxes
OR A
RET Z
LD A,25 ; the rock reached the ship: half a second of red
LD (flashT),A
JR spawn
; --- do boxes (B,C) and (D,E), both 8x8, overlap? A = 1 or 0
boxes: LD A,D
ADD A,7
CP B
JR C,bmiss
LD A,B
ADD A,7
CP D
JR C,bmiss
LD A,E
ADD A,7
CP C
JR C,bmiss
LD A,C
ADD A,7
CP E
JR C,bmiss
LD A,1
RET
bmiss: LD A,0
RET
; --- one more point, two digits, carried by hand
score1: LD A,(scoreU)
INC A
CP 10
JR NZ,su
LD A,(scoreT)
INC A
CP 10
JR NZ,st
LD A,0
st: LD (scoreT),A
LD A,0
su: LD (scoreU),A
RET
; --- publish the digits: tile number = character code
dscore: LD A,8
OUT (9),A
LD A,120
OUT (9),A
LD A,(scoreT)
ADD A,48
OUT (8),A
LD A,(scoreU)
ADD A,48
OUT (8),A
RET
; --- the collision light, with a timer
flash: LD A,(flashT)
OR A
JR Z,fblue
DEC A
LD (flashT),A
LD A,246
JR fset
fblue: LD A,244
fset: OUT (9),A
LD A,135
OUT (9),A
RET
; --- rocket, bullet, fire, animation: as S22-S26
place: LD A,0
OUT (9),A
LD A,123
OUT (9),A
LD A,(rockY)
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,0
OUT (8),A
LD A,15
OUT (8),A
LD A,(rockY)
ADD A,8
OUT (8),A
LD A,(rockX)
OUT (8),A
LD A,(flameP)
OUT (8),A
LD A,11
OUT (8),A
RET
apub: LD A,12
OUT (9),A
LD A,123
OUT (9),A
LD A,(astY)
OUT (8),A
LD A,(astX)
OUT (8),A
LD A,4
OUT (8),A
LD A,3 ; light green
OUT (8),A
RET
bullet: LD A,(bullA)
OR A
JR Z,bpub
LD A,(bullY)
SUB 3
CP 8
JR C,bstop
LD (bullY),A
JR bpub
bstop: LD A,0
LD (bullA),A
LD A,224
LD (bullY),A
bpub: LD A,8
OUT (9),A
LD A,123
OUT (9),A
LD A,(bullY)
OUT (8),A
LD A,(bullX)
OUT (8),A
LD A,3
OUT (8),A
LD A,15
OUT (8),A
RET
fire: LD A,(bullA)
OR A
RET NZ
LD A,1
LD (bullA),A
LD A,(rockY)
LD (bullY),A
LD A,(rockX)
LD (bullX),A
RET
animate:
LD A,(flick)
DEC A
LD (flick),A
RET NZ
LD A,8
LD (flick),A
LD A,(flameP)
XOR 3
LD (flameP),A
RET
up: LD A,(rockY)
CP 16 ; leave the score line alone
RET Z
DEC A
LD (rockY),A
RET
down: LD A,(rockY)
CP 175
RET Z
INC A
LD (rockY),A
RET
left: LD A,(rockX)
OR A
RET Z
DEC A
LD (rockX),A
RET
right: LD A,(rockX)
CP 248
RET Z
INC A
LD (rockX),A
RET
zero8: LD D,A
LD A,0
OUT (9),A
LD A,D
OUT (9),A
LD A,0
LD B,8
z8: OUT (8),A
DJNZ z8
RET
; ================= DATA =================
rockX: DEFB 124
rockY: DEFB 160
flameP: DEFB 1
flick: DEFB 8
bullX: DEFB 0
bullY: DEFB 224
bullA: DEFB 0
astX: DEFB 0
astY: DEFB 8
astI: DEFB 0
astT: DEFB 0
scoreT: DEFB 0
scoreU: DEFB 0
flashT: DEFB 0
label: DEFM "SCORE "
drops: DEFB 60,124,180,40,220,100,150,80
shapes: ; body
DEFB 00011000b
DEFB 00111100b
DEFB 00111100b
DEFB 01111110b
DEFB 01111110b
DEFB 11111111b
DEFB 11011011b
DEFB 10011001b
; flame frame A
DEFB 01011010b
DEFB 01111110b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00010000b
DEFB 00001000b
; flame frame B
DEFB 00111100b
DEFB 01111110b
DEFB 00111100b
DEFB 00111100b
DEFB 00011000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
; the bullet
DEFB 00011000b
DEFB 00011000b
DEFB 00011000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
DEFB 00000000b
; the rock
DEFB 00111100b
DEFB 01111110b
DEFB 11111111b
DEFB 11101111b
DEFB 11111101b
DEFB 11111111b
DEFB 01111110b
DEFB 00111100b
buffer: DEFS 768
W, A, S, D to fly, space to shoot. The rock is worth one point. The red flash means it reached you.
What you should see
SCORE 00, a rock falling from the top - straight at you, so do something -
your rocket answering every key including diagonals, bullets that climb
three pixels a frame, and the score ticking up as you hit. Rocks you miss
drop off the bottom and cost nothing but pride. Everything moves at its own
locked speed no matter how busy the screen is.

fall, skip the every-other-
frame gate whenever scoreT is not zero. What is the game like at 50
pixels a second, and what else would you now have to rebalance?fall, where the bottom respawns, flash for
ten frames too. How does knowing the difference between the two flashes
change how you play?fall and
hits and apub called for each. Count the scan lines where rocket,
flame, bullet and two rocks could meet, and check the budget before you
build it (S19: four per line).fire when the bullet leaves, a
different one from hits when a rock dies, noise instead of tone for the
ship getting hit. S27 is everything that takes; none of it touches the
loop's shape - a sound is one more thing a CALL does on its way past.| Symptom | Cause |
|---|---|
| The score shows punctuation instead of digits | The digit counters passed 9 without the hand-carry - a raw byte plus 48 walks past '9' into ':' and beyond. Carry at ten. |
| The game plays identically every run | It is meant to. The spawn table is the whole of fate here - reorder it if you want a different day. |
| A rock vanishes mid-air for no reason | The bullet's box is 8 wide and its picture is 2. S24: the hitbox is a choice - shrink the box or accept generous shooting. |
| Everything slows when things get busy | Something in the loop outgrew a frame. The frame lock absorbs spare time only; a pass longer than a frame drops one. |
The course has taught everything Rockfall is built from - S27 and S28, just
behind it, are exactly what a gunshot, an explosion or a background tune
need, and "Give it a voice" above is where to spend them. Persistence (a
high score that survives a reset), multiple screens, and a title sequence
are open ground for whoever keeps building. And when you want to see it on
a real Einstein, Appendix VIII - Running your game on a real Einstein
takes the finished .COM from here to a disc image the machine boots.
One thing worth doing before any of that: S30 splits a growing game like
this one across several files - a file for VDP routines, one for each
screen, one for sprites - joined with INCLUDE. Rockfall as written here
is exactly the kind of file that has outgrown itself.