# Flappy ASM

[Courses](https://asm-editor.specy.app/learn/courses.md) › [M68K assembly](https://asm-editor.specy.app/learn/courses/m68k.md) › Examples

Web page: https://asm-editor.specy.app/learn/courses/m68k/examples/flappy-asm

> A game with a state machine in it: three states, gravity counted in sixteenths of a pixel, a press that only counts on the frame it began, and three pipes recycled into an endless course.

The same program in: [MIPS](https://asm-editor.specy.app/learn/courses/mips/examples/flappy-asm.md), [RISC-V](https://asm-editor.specy.app/learn/courses/risc-v/examples/flappy-asm.md), [Z80](https://asm-editor.specy.app/learn/courses/z80/examples/flappy-asm.md)

Build a small game in which a tap lifts the bird and gravity pulls it back down. Pipes scroll in
from the right. Passing one earns a point; touching a pipe or the ground ends the round.

Choose **Build**, then **Run**. Click inside the **Screen** so it has keyboard focus. Tap Space, W,
Enter or the up arrow to start and flap; clicking the Screen also flaps. Release between taps: holding
a key or mouse button gives only one flap. After a collision, tap again to restart. Choose **Stop**
when you are done.

The listing is long because it draws the scene as well as running the game. Use this map before
reading it:

| Follow this part           | What it does                                                       |
| -------------------------- | ------------------------------------------------------------------ |
| `frame` → `readflap`       | Find out whether a new tap began.                                  |
| `READY`, `playing`, `dead` | Start a round, advance it, or let the bird land.                   |
| `movepipes` → `hittest`    | Move the obstacles, award points and check collisions during play. |
| `draw`                     | Draw the scene, show it with task 94, then pause with task 23.     |

Start with `frame`, `readflap`, `movepipes` and `hittest`. You can skim the shape and colour helpers
under `Drawing` on a first pass.

```m68k
    ORG     $1000

start:
    move.b  #92,d0
    move.b  #17,d1
    trap    #15                 ; mode 17: draw off screen until task 94

    move.b  #93,d0
    move.b  #1,d1
    trap    #15                 ; a one pixel pen for every outline

newgame:
    bsr     reset

* --- one frame ---------------------------------------------------------------
* d7 carries "a flap began this frame" from the input read to the state machine,
* so nothing called from here is allowed to touch it.
frame:
    bsr     readflap
    move.w  d0,d7

    move.w  state,d0
    cmp.w   #PLAYING,d0
    beq     playing
    cmp.w   #DEAD,d0
    beq     dead

* --- waiting to start: the bird hangs still until the first flap --------------
    tst.w   d7
    beq     draw
    move.b  #8,d0
    trap    #15                 ; hundredths of a second since the run started
    move.w  d1,seed             ; so the course depends on when the player began
    bsr     newpipes
    move.w  #PLAYING,state
    move.w  #FLAPV,birdv
    bra     draw

* --- playing -----------------------------------------------------------------
playing:
    tst.w   d7
    beq     fall
    move.w  #FLAPV,birdv        ; one tap is one flap, however long the key is held
    clr.w   wing                ; and the wing beat starts again with it
fall:
    move.w  birdv,d0
    add.w   #GRAV,d0
    cmp.w   #MAXFALL,d0
    ble     capped
    move.w  #MAXFALL,d0
capped:
    move.w  d0,birdv
    ext.l   d0
    add.l   d0,birdy            ; birdy counts sixteenths of a pixel

    tst.l   birdy
    bge     insky
    clr.l   birdy               ; the top of the screen is a bump, not a death
    clr.w   birdv
insky:
    bsr     movepipes
    bsr     hittest
    tst.w   d0
    beq     draw

    move.w  #DEAD,state
    move.w  points,d0
    cmp.w   record,d0
    ble     draw
    move.w  d0,record
    bra     draw

* --- dead: the bird drops to the ground, then waits for a flap ----------------
dead:
    move.w  birdv,d0
    add.w   #GRAV,d0
    cmp.w   #MAXFALL,d0
    ble     dcapped
    move.w  #MAXFALL,d0
dcapped:
    move.w  d0,birdv
    ext.l   d0
    add.l   d0,birdy

    move.l  birdy,d0
    asr.l   #4,d0
    cmp.w   #RESTY,d0
    blt     dfalling
    move.l  #RESTY16,birdy
    clr.w   birdv
dfalling:
    tst.w   d7
    beq     draw
    bra     newgame

* --- the frame is painted back to front, then shown all at once ---------------
draw:
    bsr     drawsky
    bsr     drawpipes
    bsr     drawground
    bsr     drawbird
    bsr     drawhud

    move.b  #94,d0
    trap    #15                 ; the finished frame appears here, all at once

    move.b  #23,d0
    move.l  #DELAY,d1
    trap    #15                 ; wait at least DELAY hundredths before the next frame
    bra     frame

*-----------------------------------------------------------------------------
* Input
*-----------------------------------------------------------------------------
* Answers d0.w = 1 on the frame a flap begins. Both tasks report what is held
* right now, so the "began" part is this program's: a press only counts when
* the frame before it had nothing pressed.
readflap:
    move.b  #19,d0
    move.l  #KEYLIST,d1
    trap    #15                 ; one $FF/$00 byte per key, in the same order
    move.l  d1,d2

    btst    #24,d2              ; space
    bne     pressed
    btst    #16,d2              ; the up arrow
    bne     pressed
    btst    #8,d2               ; w
    bne     pressed
    btst    #0,d2               ; enter
    bne     pressed

    move.b  #61,d0
    move.b  #0,d1
    trap    #15                 ; the mouse as it is right now
    btst    #0,d0               ; bit 0 of the flags byte is the left button
    bne     pressed

    clr.w   held
    moveq   #0,d0
    rts
pressed:
    tst.w   held
    bne     stillheld
    move.w  #1,held
    moveq   #1,d0               ; this frame is where the press began
    rts
stillheld:
    moveq   #0,d0
    rts

*-----------------------------------------------------------------------------
* The world
*-----------------------------------------------------------------------------
* A new game, keeping the best score of the session.
reset:
    move.w  #READY,state
    move.l  #STARTY16,birdy
    clr.w   birdv
    clr.w   points
    clr.w   wing
    clr.w   scroll
    bsr     newpipes
    rts

* Three pipes, evenly spaced off to the right, each with a gap of its own.
newpipes:
    lea     pipes,a0
    move.w  #FIRSTX,d2
    moveq   #2,d3
npnext:
    move.w  d2,(a0)+            ; where its left edge is
    bsr     randgap
    move.w  d0,(a0)+            ; the middle of its gap
    clr.w   (a0)+               ; and it has not been counted yet
    add.w   #SPACING,d2
    dbra    d3,npnext
    rts

* The middle of the next gap, from a sixteen bit congruential generator: one
* seed always lays out the same course, and the seed comes from the clock.
* Answers in d0.w and leaves every other register alone.
randgap:
    moveq   #0,d0
    move.w  seed,d0
    mulu    #25173,d0
    add.l   #13849,d0
    move.w  d0,seed             ; the low word of the product is the next seed
    moveq   #0,d0
    move.w  seed,d0
    lsr.l   #8,d0               ; its high byte is the half worth using
    mulu    #GAPSPAN,d0
    lsr.l   #8,d0               ; 0 to GAPSPAN-1, without dividing
    add.w   #GAPMIN,d0
    rts

* Everything scrolls left. A pipe that has left the screen jumps a whole cycle
* to the right with a new gap, which is why three pipes are an endless course.
movepipes:
    move.w  scroll,d0
    sub.w   #SPEED,d0
    bge     scrolled
    add.w   #TUFTGAP,d0         ; the tufts repeat, so this wrap is invisible
scrolled:
    move.w  d0,scroll

    lea     pipes,a0
    moveq   #2,d3
mpnext:
    move.w  (a0),d1
    sub.w   #SPEED,d1
    cmp.w   #NEGPIPE,d1
    bgt     mpmoved
    add.w   #CYCLE,d1
    bsr     randgap
    move.w  d0,2(a0)
    clr.w   4(a0)
mpmoved:
    move.w  d1,(a0)

    tst.w   4(a0)               ; already counted?
    bne     mpdone
    move.w  d1,d2
    add.w   #PIPEW,d2
    cmp.w   #BIRDX,d2
    bge     mpdone
    move.w  #1,4(a0)
    addq.w  #1,points           ; its right edge has passed the bird
mpdone:
    lea     6(a0),a0
    dbra    d3,mpnext
    rts

* Answers d0.w = 1 when the bird touches a pipe or the ground. Only the pipe
* test uses an inset box; the ground checks the bird's full height.
hittest:
    move.l  birdy,d1
    asr.l   #4,d1
    move.w  d1,d2
    add.w   #BIRDH,d2
    cmp.w   #GROUNDY,d2
    bge     hit                 ; the ground is met by the whole bird

    add.w   #HITIN,d1           ; the top of the box the pipes are tested with
    sub.w   #HITIN,d2           ; and its bottom

    lea     pipes,a0
    moveq   #2,d3
htnext:
    move.w  (a0),d4
    move.w  d4,d5
    add.w   #PIPEW,d5
    cmp.w   #HITR,d4
    bge     htskip              ; this pipe is still to the right of the bird
    cmp.w   #HITL,d5
    ble     htskip              ; and this one is already behind it

    move.w  2(a0),d6
    sub.w   #HALFGAP,d6
    cmp.w   d6,d1
    blt     hit                 ; above the gap
    add.w   #GAPH,d6
    cmp.w   d6,d2
    bgt     hit                 ; below it
htskip:
    lea     6(a0),a0
    dbra    d3,htnext

    moveq   #0,d0
    rts
hit:
    moveq   #1,d0
    rts

*-----------------------------------------------------------------------------
* Drawing
*-----------------------------------------------------------------------------
* The sky, and two clouds drifting across it.
drawsky:
    move.l  #SKY,d1
    move.b  #80,d0
    trap    #15
    move.l  #SKY,d1
    move.b  #81,d0
    trap    #15
    moveq   #0,d1
    moveq   #0,d2
    move.w  #WIDTH,d3
    move.w  #HEIGHT,d4
    move.b  #87,d0
    trap    #15

    move.l  #CLOUDC,d1
    move.b  #80,d0
    trap    #15
    move.l  #CLOUDC,d1
    move.b  #81,d0
    trap    #15                 ; pen and fill alike, so the lobes have no seams

    move.w  cloud1x,d5
    subq.w  #1,d5
    cmp.w   #NEGCLOUD,d5
    bgt     cl1ok
    move.w  #WIDTH,d5           ; gone on the left, back in on the right
cl1ok:
    move.w  d5,cloud1x
    move.w  #56,d6
    bsr     cloud

    move.w  cloud2x,d5
    subq.w  #1,d5
    cmp.w   #NEGCLOUD,d5
    bgt     cl2ok
    move.w  #WIDTH,d5
cl2ok:
    move.w  d5,cloud2x
    move.w  #120,d6
    bsr     cloud
    rts

* One cloud: three overlapping discs, at d5.w across and d6.w down.
cloud:
    move.w  d5,d1
    move.w  d6,d2
    move.w  d1,d3
    add.w   #70,d3
    move.w  d2,d4
    add.w   #34,d4
    move.b  #88,d0
    trap    #15

    move.w  d5,d1
    add.w   #40,d1
    move.w  d6,d2
    sub.w   #16,d2
    move.w  d1,d3
    add.w   #60,d3
    move.w  d2,d4
    add.w   #50,d4
    move.b  #88,d0
    trap    #15

    move.w  d5,d1
    add.w   #86,d1
    move.w  d6,d2
    add.w   #6,d2
    move.w  d1,d3
    add.w   #54,d3
    move.w  d2,d4
    add.w   #28,d4
    move.b  #88,d0
    trap    #15
    rts

* The three pipes. a0 walks the table, one pipe per call.
drawpipes:
    move.l  #PIPEDK,d1
    move.b  #80,d0
    trap    #15
    move.l  #PIPEC,d1
    move.b  #81,d0
    trap    #15

    lea     pipes,a0
    bsr     onepipe
    bsr     onepipe
    bsr     onepipe
    rts

* A column down from the top and one up from the ground, each capped with a
* wider lip, then a0 moves on to the pipe after it.
onepipe:
    move.w  (a0),d5             ; its left edge
    move.w  2(a0),d6            ; and the middle of its gap

    move.w  d5,d1
    moveq   #0,d2
    move.w  d5,d3
    add.w   #PIPEW,d3
    move.w  d6,d4
    sub.w   #HALFGAP,d4         ; down to the top of the gap
    move.b  #87,d0
    trap    #15

    move.w  d5,d1
    sub.w   #LIPOUT,d1
    move.w  d4,d2
    sub.w   #LIPH,d2
    move.w  d5,d3
    add.w   #PIPER,d3
    move.b  #87,d0
    trap    #15                ; the lip, still ending at the gap

    move.w  d5,d1
    move.w  d6,d2
    add.w   #HALFGAP,d2         ; from the bottom of the gap
    move.w  d5,d3
    add.w   #PIPEW,d3
    move.w  #GROUNDY,d4
    move.b  #87,d0
    trap    #15

    move.w  d5,d1
    sub.w   #LIPOUT,d1
    move.w  d6,d2
    add.w   #HALFGAP,d2
    move.w  d5,d3
    add.w   #PIPER,d3
    move.w  d2,d4
    add.w   #LIPH,d4
    move.b  #87,d0
    trap    #15

    lea     6(a0),a0
    rts

* Sand, a band of grass on top of it, and tufts that scroll with the pipes.
drawground:
    move.l  #SAND,d1
    move.b  #80,d0
    trap    #15
    move.l  #SAND,d1
    move.b  #81,d0
    trap    #15
    moveq   #0,d1
    move.w  #GROUNDY,d2
    move.w  #WIDTH,d3
    move.w  #HEIGHT,d4
    move.b  #87,d0
    trap    #15

    move.l  #GRASS,d1
    move.b  #80,d0
    trap    #15
    move.l  #GRASS,d1
    move.b  #81,d0
    trap    #15
    moveq   #0,d1
    move.w  #GROUNDY,d2
    move.w  #WIDTH,d3
    move.w  #GRASSB,d4
    move.b  #87,d0
    trap    #15

    move.l  #GRASSDK,d1
    move.b  #80,d0
    trap    #15
    move.l  #GRASSDK,d1
    move.b  #81,d0
    trap    #15

* one tuft further left than the screen, so the row never runs out on that side
    move.w  scroll,d5
    sub.w   #TUFTGAP,d5
    moveq   #11,d6
gtnext:
    move.w  d5,d1
    move.w  #TUFTY,d2
    move.w  d5,d3
    add.w   #TUFTW,d3
    move.w  #TUFTB,d4
    move.b  #87,d0
    trap    #15
    add.w   #TUFTGAP,d5
    dbra    d6,gtnext
    rts

* The bird: a body, a wing that beats through three positions, an eye and a
* beak. Only the body's box is what `hittest` measures.
drawbird:
    move.l  birdy,d5
    asr.l   #4,d5               ; sixteenths of a pixel back down to pixels

    move.l  #INK,d1
    move.b  #80,d0
    trap    #15                 ; one dark outline for all of it

    move.l  #BODY,d1
    move.b  #81,d0
    trap    #15
    move.w  #BIRDX,d1
    move.w  d5,d2
    move.w  #BIRDR,d3
    move.w  d5,d4
    add.w   #BIRDH,d4
    move.b  #88,d0
    trap    #15

    moveq   #0,d6
    move.w  wing,d6
    move.w  state,d0
    cmp.w   #DEAD,d0
    beq     wingset             ; a dead bird stops beating
    addq.w  #1,d6
    cmp.w   #12,d6
    blt     wingheld
    moveq   #0,d6
wingheld:
    move.w  d6,wing
wingset:
    lsr.w   #2,d6               ; twelve frames, three positions
    mulu    #7,d6
    add.w   #WINGTOP,d6

    move.l  #WINGC,d1
    move.b  #81,d0
    trap    #15
    move.w  #WINGL,d1
    move.w  d5,d2
    add.w   d6,d2
    move.w  #WINGR,d3
    move.w  d2,d4
    add.w   #13,d4
    move.b  #88,d0
    trap    #15

    move.l  #PAPER,d1
    move.b  #81,d0
    trap    #15
    move.w  #EYEL,d1
    move.w  d5,d2
    add.w   #7,d2
    move.w  #EYER,d3
    move.w  d2,d4
    add.w   #14,d4
    move.b  #88,d0
    trap    #15

    move.l  #INK,d1
    move.b  #81,d0
    trap    #15
    move.w  #PUPL,d1
    move.w  d5,d2
    add.w   #11,d2
    move.w  #PUPR,d3
    move.w  d2,d4
    add.w   #7,d4
    move.b  #88,d0
    trap    #15

    move.l  #BEAKC,d1
    move.b  #81,d0
    trap    #15
    move.w  #BEAKL,d1
    move.w  d5,d2
    add.w   #19,d2
    move.w  #BEAKR,d3
    move.w  d2,d4
    add.w   #9,d4
    move.b  #87,d0
    trap    #15
    rts

* The score, and whatever state the game is in has to say.
drawhud:
    move.w  state,d0
    cmp.w   #READY,d0
    beq     hudready

    move.l  #INK,d1
    move.b  #81,d0
    trap    #15
    move.l  #PAPER,d1
    move.b  #80,d0
    trap    #15
    move.w  #PLATEL,d1
    move.w  #14,d2
    move.w  #PLATER,d3
    move.w  #46,d4
    move.b  #87,d0
    trap    #15

    lea     line,a1
    moveq   #0,d1
    move.w  points,d1
    bsr     appnum
    lea     line,a1
    move.w  #22,d6
    bsr     ctext

    move.w  state,d0
    cmp.w   #DEAD,d0
    beq     huddead
    rts

hudready:
    move.w  #250,d1
    move.w  #392,d2
    bsr     plate

    lea     title,a1
    move.w  #266,d6
    bsr     ctext
    lea     hint1,a1
    move.w  #302,d6
    bsr     ctext
    lea     hint2,a1
    move.w  #326,d6
    bsr     ctext
    lea     hint3,a1
    move.w  #358,d6
    bsr     ctext
    rts

huddead:
    move.w  #150,d1
    move.w  #330,d2
    bsr     plate

    lea     overmsg,a1
    move.w  #172,d6
    bsr     ctext

    lea     line,a1
    lea     pointsmsg,a0
    bsr     append
    moveq   #0,d1
    move.w  points,d1
    bsr     appnum
    lea     line,a1
    move.w  #210,d6
    bsr     ctext

    lea     line,a1
    lea     recordmsg,a0
    bsr     append
    moveq   #0,d1
    move.w  record,d1
    bsr     appnum
    lea     line,a1
    move.w  #238,d6
    bsr     ctext

    lea     againmsg,a1
    move.w  #286,d6
    bsr     ctext
    rts

* A plate across the middle of the screen, from d1.w down to d2.w.
plate:
    move.w  d1,d5
    move.w  d2,d6
    move.l  #INK,d1
    move.b  #81,d0
    trap    #15
    move.l  #PAPER,d1
    move.b  #80,d0
    trap    #15
    move.w  #PANELL,d1
    move.w  d5,d2
    move.w  #PANELR,d3
    move.w  d6,d4
    move.b  #87,d0
    trap    #15
    rts

*-----------------------------------------------------------------------------
* Text
*-----------------------------------------------------------------------------
* Draws the NULL terminated string at (a1) centred across the screen with its
* top at d6.w. The screen's font is an 8 by 16 cell, so half a character is the
* four pixels each one takes off the centre.
ctext:
    movea.l a1,a0
    moveq   #0,d5
ctlen:
    move.b  (a0)+,d0
    beq     ctdraw
    addq.w  #1,d5
    bra     ctlen
ctdraw:
    asl.w   #2,d5
    move.w  #CENTREX,d1
    sub.w   d5,d1
    move.w  d6,d2
    move.b  #95,d0
    trap    #15
    rts

* Appends the NULL terminated string at (a0) to the one at (a1), leaving a1 on
* the new terminator so the next call carries straight on from there.
append:
    move.b  (a0)+,d0
    beq     appdone
    move.b  d0,(a1)+
    bra     append
appdone:
    clr.b   (a1)
    rts

* Appends d1.l, a number up to 65535, as decimal at (a1). Digits come out least
* significant first, so they are written backwards into a scratch buffer and
* the whole thing is appended at the end.
appnum:
    lea     numend,a0
    clr.b   -(a0)
annext:
    divu    #10,d1
    move.l  d1,d2
    swap    d2                  ; the remainder is this digit
    add.w   #$30,d2
    move.b  d2,-(a0)
    andi.l  #$FFFF,d1           ; and the quotient is what is left to do
    bne     annext
    bra     append

*-----------------------------------------------------------------------------
* State
*-----------------------------------------------------------------------------
state:      dc.w    0
birdy:      dc.l    STARTY16    ; sixteenths of a pixel, so gravity is smooth
birdv:      dc.w    0
points:     dc.w    0
record:     dc.w    0
wing:       dc.w    0
held:       dc.w    0           ; was a flap key or button down last frame?
scroll:     dc.w    0
cloud1x:    dc.w    70
cloud2x:    dc.w    390
seed:       dc.w    $1D4B

* one pipe is its left edge, the middle of its gap, and whether it is counted
pipes:      dc.w    0,0,0
            dc.w    0,0,0
            dc.w    0,0,0

title:      dc.b    'FLAPPY ASM',0
hint1:      dc.b    'TAP SPACE, W, ENTER OR THE UP ARROW',0
hint2:      dc.b    'OR CLICK ON THE SCREEN ITSELF',0
hint3:      dc.b    'CLICK THE SCREEN PANEL SO IT TAKES THE KEYS',0
overmsg:    dc.b    'GAME OVER',0
pointsmsg:  dc.b    'SCORE ',0
recordmsg:  dc.b    'BEST ',0
againmsg:   dc.b    'FLAP TO PLAY AGAIN',0

line:       ds.b    48
numbuf:     ds.b    8
numend:     ds.b    1

*-----------------------------------------------------------------------------
* Tunables
*-----------------------------------------------------------------------------
WIDTH   equ     640
HEIGHT  equ     480
CENTREX equ     320
GROUNDY equ     416             ; the top of the ground
GRASSH  equ     10
GRASSB  equ     426             ; GROUNDY+GRASSH
TUFTY   equ     410
TUFTB   equ     418
TUFTW   equ     20
TUFTGAP equ     56
CLOUDW  equ     140
NEGCLOUD equ    -140            ; -CLOUDW, where a cloud comes back on the right

READY   equ     0
PLAYING equ     1
DEAD    equ     2

BIRDX   equ     150             ; the bird only ever moves up and down
BIRDW   equ     46
BIRDH   equ     34
BIRDR   equ     196             ; BIRDX+BIRDW
HITIN   equ     6               ; the pipe hit box is smaller by this much on each side
HITL    equ     156             ; BIRDX+HITIN
HITR    equ     190             ; BIRDX+BIRDW-HITIN
STARTY  equ     196
STARTY16 equ    3136            ; STARTY*16
RESTY   equ     382             ; GROUNDY-BIRDH, where a dead bird lands
RESTY16 equ     6112            ; RESTY*16

GRAV    equ     5               ; sixteenths of a pixel, per frame, per frame
FLAPV   equ     -80             ; the speed one flap gives, upwards
MAXFALL equ     150

WINGTOP equ     5
WINGL   equ     159             ; BIRDX+9
WINGR   equ     183             ; BIRDX+33
EYEL    equ     174             ; BIRDX+24
EYER    equ     188             ; BIRDX+38
PUPL    equ     180             ; BIRDX+30
PUPR    equ     187             ; BIRDX+37
BEAKL   equ     190             ; BIRDX+40
BEAKR   equ     208             ; BIRDX+58

PIPEW   equ     70
PIPER   equ     76              ; PIPEW+LIPOUT
NEGPIPE equ     -70             ; -PIPEW, where a pipe is recycled
LIPH    equ     18
LIPOUT  equ     6
GAPH    equ     140
HALFGAP equ     70
SPEED   equ     4               ; pixels a frame, for everything that scrolls
SPACING equ     240             ; between one pipe and the next
CYCLE   equ     720             ; SPACING*3, what a recycled pipe jumps by
FIRSTX  equ     620
GAPMIN  equ     120             ; the highest the middle of a gap goes
GAPSPAN equ     180             ; the offset ranges from 0 through 179

DELAY   equ     3               ; hundredths of a second a frame
KEYLIST equ     $2026570D       ; space, up arrow, w, enter

PLATEL  equ     284
PLATER  equ     356
PANELL  equ     104
PANELR  equ     536

* colors are $00BBGGRR longs: blue high, then green, then red
SKY     equ     $00E8C04E
CLOUDC  equ     $00F8FBFF
PIPEC   equ     $0039BB58
PIPEDK  equ     $00347A29
SAND    equ     $0095D8DE
GRASS   equ     $0058C474
GRASSDK equ     $0040A04C
BODY    equ     $003ECEFA
WINGC   equ     $00D2F5FF
BEAKC   equ     $002882F0
INK     equ     $00101820
PAPER   equ     $00FFFFFF
```

*Testcase for the program above (JSON):*

```json
{ "runFor": 20000 }
```

## Follow one frame

`state` tells `frame` which part of the game to run. In `READY`, the bird waits for the first flap.
That flap seeds a new set of pipes, sets the upward speed and changes `state` to `PLAYING`. On later
frames, `playing` applies gravity, moves the pipes and checks for a hit. A hit changes `state` to
`DEAD`; the bird then falls to the ground and waits for another flap. Every path reaches `draw`,
which shows the completed picture and loops back to `frame`.

```m68k
frame:
    bsr     readflap
    move.w  d0,d7

    move.w  state,d0
    cmp.w   #PLAYING,d0
    beq     playing
    cmp.w   #DEAD,d0
    beq     dead
```

`readflap` returns 1 in `d0` when a new flap begins. The loop copies that answer to `d7` before
using `d0` to inspect `state`. The routines called later leave `d7` alone, so the state code can
still test the flap after those calls.

## Turn a held input into one flap

Task 19 reports which keys are down, and task 61 reports whether the mouse button is down. Both
describe the current state. If a key stays down for several frames, `readflap` must return 1 only
on the first of them. The word `held` remembers whether any flap input was down on the previous
read:

```m68k
    clr.w   held
    moveq   #0,d0
    rts
pressed:
    tst.w   held
    bne     stillheld
    move.w  #1,held
    moveq   #1,d0
    rts
```

When all inputs are released, `held` becomes 0. On the next pressed read, the code sets `held` to
1 and returns a flap. Further reads while it stays pressed return 0. This is why you release and
tap again for each lift.

## Move in sixteenths of a pixel

`birdy` stores the vertical position in sixteenths of a pixel, and `birdv` stores the amount added
to it each frame. Smaller screen Y values are higher up. A flap sets `birdv` to `FLAPV = -80`, an
upward speed of 5 pixels per frame. During each `PLAYING` frame, gravity adds `GRAV = 5` to that
speed _before_ the speed is added to `birdy`.

The starting position is `STARTY16 = 196 × 16 = 3136`. The first flap in `READY` sets the speed
but leaves the position at 3136. If no further flap occurs, the next three frames are:

| `PLAYING` frame | `birdv` after gravity | `birdy` after movement | Drawn Y (`birdy >> 4`) |
| --------------- | --------------------: | ---------------------: | ---------------------: |
| 1               |                   -75 |                   3061 |                    191 |
| 2               |                   -70 |                   2991 |                    186 |
| 3               |                   -65 |                   2926 |                    182 |

The fraction stays in `birdy` even though `drawbird` uses `asr.l #4` to get a whole pixel. That
lets the speed change by 5 sixteenths each frame. Once gravity makes `birdv` positive, the bird
descends; `MAXFALL` limits that downward speed.

## Reuse three pipe records

`pipes` contains three records, each six bytes long. `a0` points to one record at a time:

| Offset from `a0` | Word       | Meaning                                                    |
| ---------------- | ---------- | ---------------------------------------------------------- |
| `0`              | X          | Left edge of the pipe.                                     |
| `2`              | Gap centre | Vertical centre of its opening.                            |
| `4`              | Counted    | 0 until its right edge passes the bird and scores a point. |

`newpipes` starts their X positions at 620, 860 and 1100: `SPACING = 240` pixels apart. Each
`PLAYING` frame, `movepipes` subtracts `SPEED = 4` from every X. Once a pipe has gone one width
past the left edge, it jumps right by `CYCLE = 720`, which is three spacings. Its gap is chosen
again and its counted word is cleared. It therefore joins the end of the same three-pipe course.

`randgap` changes a word called `seed` with multiplication and addition, then uses its high byte
to choose the next gap centre. The offset is 0 through 179, so `GAPMIN = 120` puts the centre at
**120 through 299**. The first flap gets a seed from task 8's elapsed time, making the course
depend on when you start. `GAPH = 140` makes each opening 140 pixels high, with `HALFGAP = 70`
above and below its centre. If you change the opening height, change its half-height too.

## Check hits and show the result

`hittest` checks the ground first. It uses the bird's full height: when `birdy / 16 + BIRDH`
reaches `GROUNDY`, the bird hits the ground. For a pipe, it uses a smaller rectangle. `HITL` and
`HITR` inset the bird's horizontal span by `HITIN = 6` pixels, and the tested top and bottom are
also inset by 6. A pipe can cause a hit only while its X span overlaps this rectangle and the
rectangle extends above or below the gap.

Task 92 mode 17 sets up a hidden drawing image. `draw` paints sky, pipes, ground, bird and text
in that order, then task 94 shows the completed image. The drawing helpers supply the clouds,
grass, wing and score; their details can wait until you want to change the artwork. Task 23 then
waits at least `DELAY = 3` hundredths of a second before the next frame. Drawing and instructions
take time too, so this does not set an exact frame rate on every machine.

Try one small change: set `DELAY` to 6. Before running it, predict how the game will feel. Will
the bird and pipes move a different number of pixels _per frame_?

<details>
<summary>Show what to expect</summary>

The minimum pause doubles, so the game will usually look slower in real time. `GRAV`, `birdv` and
`SPEED` have not changed, so their movement per frame stays the same. The exact speed change in
real time depends on the drawing and instruction time as well as the pause.

</details>

## Instructions in this Lecture

- M68K: [`move`](https://asm-editor.specy.app/documentation/m68k/instruction/move.md), [`trap`](https://asm-editor.specy.app/documentation/m68k/instruction/trap.md), [`bsr`](https://asm-editor.specy.app/documentation/m68k/instruction/bsr.md), [`cmp`](https://asm-editor.specy.app/documentation/m68k/instruction/cmp.md), [`beq`](https://asm-editor.specy.app/documentation/m68k/instruction/beq.md), [`tst`](https://asm-editor.specy.app/documentation/m68k/instruction/tst.md), [`bra`](https://asm-editor.specy.app/documentation/m68k/instruction/bra.md), [`clr`](https://asm-editor.specy.app/documentation/m68k/instruction/clr.md), [`add`](https://asm-editor.specy.app/documentation/m68k/instruction/add.md), [`ble`](https://asm-editor.specy.app/documentation/m68k/instruction/ble.md), [`ext`](https://asm-editor.specy.app/documentation/m68k/instruction/ext.md), [`bge`](https://asm-editor.specy.app/documentation/m68k/instruction/bge.md), [`asr`](https://asm-editor.specy.app/documentation/m68k/instruction/asr.md), [`blt`](https://asm-editor.specy.app/documentation/m68k/instruction/blt.md), [`btst`](https://asm-editor.specy.app/documentation/m68k/instruction/btst.md), [`bne`](https://asm-editor.specy.app/documentation/m68k/instruction/bne.md), [`moveq`](https://asm-editor.specy.app/documentation/m68k/instruction/moveq.md), [`rts`](https://asm-editor.specy.app/documentation/m68k/instruction/rts.md), [`lea`](https://asm-editor.specy.app/documentation/m68k/instruction/lea.md), [`dbra`](https://asm-editor.specy.app/documentation/m68k/instruction/dbra.md), [`mulu`](https://asm-editor.specy.app/documentation/m68k/instruction/mulu.md), [`lsr`](https://asm-editor.specy.app/documentation/m68k/instruction/lsr.md), [`sub`](https://asm-editor.specy.app/documentation/m68k/instruction/sub.md), [`bgt`](https://asm-editor.specy.app/documentation/m68k/instruction/bgt.md), [`addq`](https://asm-editor.specy.app/documentation/m68k/instruction/addq.md), [`subq`](https://asm-editor.specy.app/documentation/m68k/instruction/subq.md), [`movea`](https://asm-editor.specy.app/documentation/m68k/instruction/movea.md), [`asl`](https://asm-editor.specy.app/documentation/m68k/instruction/asl.md), [`divu`](https://asm-editor.specy.app/documentation/m68k/instruction/divu.md), [`swap`](https://asm-editor.specy.app/documentation/m68k/instruction/swap.md), [`andi`](https://asm-editor.specy.app/documentation/m68k/instruction/andi.md)

Previous: [The snake game](https://asm-editor.specy.app/learn/courses/m68k/examples/snake.md)

Next: [Play a WAV and a MIDI file](https://asm-editor.specy.app/learn/courses/m68k/examples/play-sound-files.md)
