← Back to Courses
module
29

The first game

Why this matters

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.

The shape of it

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.

The three new pieces of glue

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.

Consequences live with the collision

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.

The code

        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.

The finished game: score line, a falling rock, and the rocket

Change one thing

  • Make the rock faster as the score grows: in 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?
  • Give a missed rock a cost: in fall, where the bottom respawns, flash for ten frames too. How does knowing the difference between the two flashes change how you play?
  • Change the spawn table to eight copies of 124. What genre did the game just become?
  • Two rocks: another set of variables, another attribute slot, 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).
  • Give it a voice. A short tone from 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.

When it goes wrong

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.

Summary

  • A complete game is the course's loop with more telephone lines: decide on variables, then publish - sprites, name table bytes, one register.
  • The glue is small: digits carried by hand and shown as tiles, a lookup table for fate, one-byte timers for effects.
  • Consequences belong beside their collisions; respawn is one routine with three doors.
  • Deterministic games are easier to build, debug - and, when you want, to make deliberately unfair.

Next

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.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.