Skip to content

Move, collect and respond on the Amiga

Combine input, bounded movement, box collision, drawing and sound in a small program you can take apart.

Move onto the star. The count changes from 0 to 1, and you hear a short sound. Stay there: the star has gone, so it cannot score again. Press restart and it returns.

Those few seconds bring several useful routines together. Here is the Amiga version, with each part kept small enough to lift into a project of your own.

The Amiga example at its starting position, with the player, collectible and zero count.
Captured from the running Emu198x example.

Run it first

Target: A500 OCS PAL with Kickstart 1.3. Controls: digital joystick in physical port 2; Fire restarts. Reset the machine to leave.

With Asm198x available, keep the code-sample folders together and build:

Shell
make -C commodore-amiga/patterns/assembly/framework/collect-and-respond BUILD198X=/path/to/build198x

Boot the generated ADF. The sample folder contains the complete program, build recipe and integration notes. The comparison explains the choices across all five versions.

The player moves one cell per six accepted updates. Hold a direction to keep moving. Restart reacts to a new press, so holding it will not trap the player at the starting position.

Connect the pieces

Read the controls once. Calculate the new and released bits from that sample. Update the world, then draw its new state. Call service_sound before the new update so an effect gets its full first frame. That order is visible in the caller:

The jobs in one update

demo.asm, excerpt
main_loop:
    bsr wait_frame
    bsr read_controls
    bsr detect_edges
    bsr service_sound
    bsr update_player
    bsr draw_screen
    addq.w #1,updates

Startup owns the machine-specific work. It establishes the memory and hardware assumptions that the smaller routines rely on:

Initialise this caller

demo.asm, excerpt
start:
    lea $dff000,a5
    move.w #$7fff,$09a(a5)
    move.w #$7fff,$09c(a5)
    move.w #$7fff,$096(a5)
    bclr #7,$bfe201
    lea plane,a0
    move.l a0,d0
    lea copbpl,a1
    move.w d0,6(a1)
    swap d0
    move.w d0,2(a1)
    lea copperlist,a0
    move.l a0,$080(a5)
    move.w #0,$088(a5)
    move.w #$8380,$096(a5)
    bsr reset_game
    move.w #$cafe,ready

This program owns custom IRQ/DMA, the Copper, one bitplane, Paula channel 0 and the CIA-A fire input. A5 stays the custom-chip base. The routines together change D0/D1/D2/D6, A0/A1 and CCR across the frame. Keep competing writers out of those resources; adding a music player or IRQ handler requires an explicit agreement about who writes what.

Move, test and consume

The playfield is 16 × 10 cells. The player begins at (2,5); the star occupies (10,5). The rules keep X between 0 and 15 and Y between 0 and 9. Diagonals move on both axes. Opposing directions are processed left/right, then up/down; choose cancellation or last-direction priority if that better suits your game.

previous, pressed and released belong to input sampling. player_x/player_y, move_delay, item_active and score belong to the rules. Reset preserves input history: clearing it while a button is held would invent another press.

The collision uses half-open boxes. A cell at (x,y) has edges (x,y,x+1,y+1), so merely touching the star’s edge leaves it alone. The routine can also compare larger rectangles or pixel coordinates, provided both boxes use the same units and edge arithmetic does not wrap.

Reusable box overlap

collision.inc
; Half-open unsigned word edges [left,top,right,bottom].
; Reject empty/reversed boxes and touching edges. Returns D0=1/0; CCR changes.
; Other registers survive. Caller forms edges without word overflow.
boxes_overlap:
    move.w rect_a,d0
    cmp.w rect_a+4,d0
    bhs boxes_apart
    move.w rect_a+2,d0
    cmp.w rect_a+6,d0
    bhs boxes_apart
    move.w rect_b,d0
    cmp.w rect_b+4,d0
    bhs boxes_apart
    move.w rect_b+2,d0
    cmp.w rect_b+6,d0
    bhs boxes_apart
    move.w rect_a,d0
    cmp.w rect_b+4,d0
    bhs boxes_apart
    move.w rect_b,d0
    cmp.w rect_a+4,d0
    bhs boxes_apart
    move.w rect_a+2,d0
    cmp.w rect_b+6,d0
    bhs boxes_apart
    move.w rect_b+2,d0
    cmp.w rect_a+6,d0
    bhs boxes_apart
    moveq #1,d0
    rts
boxes_apart:
    moveq #0,d0
    rts
rect_a: dc.w 0,0,0,0
rect_b: dc.w 10,5,11,6

The update consumes the star before starting feedback. That single state change is what prevents the next frame from collecting it again.

Input edges, movement and collection

rules.inc
; Word state. Native joystick bits retained: U=$100 D=1 L=$200 R=2 Fire=$10.
; These rules change D0/D1/CCR; called from main only. Grid x=0..15,y=0..9.
detect_edges:
    move.w previous,d0
    not.w d0
    and.w controls,d0
    move.w d0,pressed
    move.w controls,d0
    not.w d0
    and.w previous,d0
    move.w d0,released
    move.w controls,previous
    rts
reset_game:
    move.w #2,player_x
    move.w #5,player_y
    move.w #1,item_active
    clr.w score
    clr.w move_delay
    rts
update_player:
    move.w pressed,d0
    and.w #$10,d0
    beq movement
    bsr reset_game
    rts
movement:
    tst.w move_delay
    beq move_now
    subq.w #1,move_delay
    rts
move_now:
    move.w #5,move_delay
    move.w controls,d0
    btst #9,d0
    beq move_right
    tst.w player_x
    beq move_right
    subq.w #1,player_x
move_right:
    btst #1,d0
    beq move_up
    cmp.w #15,player_x
    beq move_up
    addq.w #1,player_x
move_up:
    btst #8,d0
    beq move_down
    tst.w player_y
    beq move_down
    subq.w #1,player_y
move_down:
    btst #0,d0
    beq test_collection
    cmp.w #9,player_y
    beq test_collection
    addq.w #1,player_y
test_collection:
    tst.w item_active
    beq collection_done
    move.w player_x,d0
    move.w d0,rect_a
    addq.w #1,d0
    move.w d0,rect_a+4
    move.w player_y,d0
    move.w d0,rect_a+2
    addq.w #1,d0
    move.w d0,rect_a+6
    bsr boxes_overlap
    tst.w d0
    beq collection_done
    clr.w item_active
    addq.w #1,score
    bsr trigger_sound
collection_done:
    rts

For this exact one-cell scene, coordinate equality would do less work. The general box routine earns its extra comparisons when objects have different sizes. Box collision and its alternatives explains when a more precise second test helps. A fast object can skip over a target between samples; that calls for a path or swept test as well.

Show the new state

This version uses eight-byte glyphs copied into a chip-RAM bitplane. old_x/old_y remember which cell to erase. The item is drawn before the player, so the player wins the draw order when they overlap.

Draw, erase and show the count

draw.inc
; D0=x,D1=y,A0=eight glyph bytes. Destroys D0/D1/D2/A0/A1/CCR.
; Draw into chip-RAM one-bitplane display, no blitter required. Row stride40.
; Called at frame boundary; four glyphs are cheap here but not a sprite engine.
put_cell:
    addq.w #6,d1
    mulu #320,d1
    add.w d0,d1
    addq.w #8,d1
    lea plane,a1
    adda.l d1,a1
    moveq #7,d2
glyph_row:
    move.b (a0)+,(a1)
    adda.w #40,a1
    dbra d2,glyph_row
    rts
draw_screen:
    move.w old_x,d0
    move.w old_y,d1
    lea blank,a0
    bsr put_cell
    move.w #10,d0
    move.w #5,d1
    lea blank,a0
    tst.w item_active
    beq target_shape
    lea target,a0
target_shape:
    bsr put_cell
    move.w player_x,d0
    move.w d0,old_x
    move.w player_y,d1
    move.w d1,old_y
    lea player,a0
    bsr put_cell
    moveq #0,d0
    moveq #0,d1
    lea zero,a0
    tst.w score
    beq score_shape
    lea one,a0
score_shape:
    bra put_cell
blank: dc.b 0,0,0,0,0,0,0,0
player: dc.b 24,60,126,219,255,90,36,0
target: dc.b 0,24,90,60,90,24,0,0
zero: dc.b 60,66,70,74,82,98,60,0
one: dc.b 24,56,24,24,24,24,126,0
    even

Painting a blank over the old position works because the background is empty. With a map behind the player, restore its tile. With overlapping objects, redraw the affected scene or choose another renderer. Keep that decision in the drawing routine so the rules can stay useful.

Make the event audible

On this machine, Paula channel 0 repeats a small sample while play continues; a frame counter stops it. The count also changes visibly, so sound carries useful feedback without being the only signal.

Collection sound

sound.inc
; Paula channel0 only, eight-frame gate. A5 custom base, changes D0/D1/D2/A0.
; Reuses play_sample/stop_sample; trigger must follow >=one off frame.
; This caller's item spacing guarantees it; a faster caller must enforce it.
trigger_sound:
    lea wave,a0
    moveq #8,d0
    move.w #424,d1
    moveq #64,d2
    bsr play_sample
    move.w #8,sound_ticks
    rts
service_sound:
    tst.w sound_ticks
    beq sound_done
    subq.w #1,sound_ticks
    bne sound_done
    bsr stop_sample
sound_done:
    rts

Take it into your game

Copy the routine you need, its state and its maintained includes. Read the declaration and caller together: the caller shows initialisation, call frequency and ownership. The sample README lists register effects and resource limits. Preserve the relative include layout, or update those paths deliberately when moving files.

These examples use whole-cell movement and a frame-based repeat counter. Fractional motion, scrolling, multiple items and sound priorities are useful next steps when a game needs them. A 50 Hz and a 60 Hz machine give this counter different wall-clock speeds.

Code status: assembled and emulator-executed for the named configuration. Checks drive real input, exercise all four bounds, reject edge-only contact, collect once, restart while held and capture the picture and PCM. A broken collection caller must fail the same guard. The execution record identifies the exact sources, binary, emulator and firmware; it establishes this caller rather than every feature of the machine.

Read the execution record · Understand the frame wait · Read the controls · Explore the sound routine

Game states and restart separate a fresh session from resuming a paused one, while preserving input history.