Skip to content
Game 1 Unit 9 of 64 1 hr learning time

Lives System

Three lives. Sprite icons. Game over when empty. Stakes.

14% of Signal

What You’re Building

Stakes.

Infinite continues means no tension. This unit adds a lives system: start with 3 lives, lose one per death, game over when they’re gone. Small frog icons in the corner show your remaining chances.

By the end of this unit:

  • Start with 3 lives
  • Frog icons show remaining lives
  • Lives decrease on death
  • Game over state when lives = 0
  • Fire button restarts

Unit 9 Screenshot

The Lives Variable

The lives system starts with simple constants and a counter:

; Lives System Constants
; Start with 3 lives, game over when all lost

; Lives configuration
START_LIVES     equ 3

; Game states (extended)
STATE_IDLE      equ 0
STATE_HOPPING   equ 1
STATE_DYING     equ 2
STATE_GAMEOVER  equ 3           ; New: no lives remaining

; Life icon display
LIFE_ICON_W     equ 1           ; 16 pixels = 1 word
LIFE_ICON_H     equ 8
LIFE_ICON_X     equ 8           ; Left margin
LIFE_ICON_SPACING equ 20        ; Pixels between icons

; Variables
lives:          dc.w    3       ; Current life count

We decrement on death, and when lives reaches zero, the game enters the GAMEOVER state.

The Game Over Loop

When in game over state, we ignore normal gameplay and wait for restart:

mainloop:
    bsr     wait_vblank

    cmp.w   #STATE_GAMEOVER,frog_state
    beq     .game_over_loop

    ; Normal game loop...
    bra     mainloop

.game_over_loop:
    ; Wait for fire button (active low at bit 7 of $BFE001)
    btst    #7,$bfe001
    bne.s   mainloop            ; Not pressed, keep waiting

    ; Restart game
    move.w  #START_LIVES,lives
    bsr     reset_frog
    bsr     update_life_display
    bsr     clear_screen

    bra     mainloop

Life Icons with Sprites

We use sprites 1-3 to display small frog icons. Each icon is a simple 8-pixel tall shape:

; Life Icon Sprites
; Small frog shapes displayed using hardware sprites 1-3

; Sprite 1 (first life icon)
            even
life_icon_1:
            dc.w    $0000,$0000         ; Control words (set by code)
            dc.w    $0000,$0000
            dc.w    $1800,$0000         ; ..##............
            dc.w    $3c00,$0000         ; .####...........
            dc.w    $7e00,$0000         ; .######.........
            dc.w    $7e00,$0000         ; .######.........
            dc.w    $3c00,$0000         ; .####...........
            dc.w    $1800,$0000         ; ..##............
            dc.w    $0000,$0000
            dc.w    $0000,$0000         ; End marker

; Sprite 2 (second life icon)
            even
life_icon_2:
            dc.w    $0000,$0000
            dc.w    $0000,$0000
            dc.w    $1800,$0000
            dc.w    $3c00,$0000
            dc.w    $7e00,$0000
            dc.w    $7e00,$0000
            dc.w    $3c00,$0000
            dc.w    $1800,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

; Sprite 3 (third life icon)
            even
life_icon_3:
            dc.w    $0000,$0000
            dc.w    $0000,$0000
            dc.w    $1800,$0000
            dc.w    $3c00,$0000
            dc.w    $7e00,$0000
            dc.w    $7e00,$0000
            dc.w    $3c00,$0000
            dc.w    $1800,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

Updating the Display

We show or hide sprites based on remaining lives by setting or clearing the sprite control words:

; Update Life Display
; Show/hide sprite icons based on lives count

update_life_display:
            ; Position life icons at top of screen
            ; VSTART = 30, VSTOP = 38 (8 pixels tall)
            ; HSTART varies: 64, 80, 96 (sprite coords)

            ; Life 1 (show if lives >= 1)
            lea     life_icon_1,a0
            move.w  lives,d0
            cmp.w   #1,d0
            blt.s   .hide_life1
            move.w  #$1e20,$0(a0)       ; VSTART=30, HSTART=64
            move.w  #$2600,$2(a0)       ; VSTOP=38
            bra.s   .life2
.hide_life1:
            clr.l   (a0)                ; Hide: zero control words

.life2:
            ; Life 2 (show if lives >= 2)
            lea     life_icon_2,a0
            cmp.w   #2,d0
            blt.s   .hide_life2
            move.w  #$1e28,$0(a0)       ; VSTART=30, HSTART=80
            move.w  #$2600,$2(a0)
            bra.s   .life3
.hide_life2:
            clr.l   (a0)

.life3:
            ; Life 3 (show if lives >= 3)
            lea     life_icon_3,a0
            cmp.w   #3,d0
            blt.s   .hide_life3
            move.w  #$1e30,$0(a0)       ; VSTART=30, HSTART=96
            move.w  #$2600,$2(a0)
            rts
.hide_life3:
            clr.l   (a0)
            rts

Setting control words to zero effectively hides the sprite (VSTART=0 and VSTOP=0 means no visible lines).

Sprite Palette Sharing

Sprites 0-1 share colours 16-19. Since sprite 0 is our main frog and sprites 1, 2, 3 use the same palette, the life icons automatically appear in the same green colour. No extra palette setup needed.

Separating Reset Logic

We refactor respawn into a reusable function:

reset_frog:
    move.w  #START_GRID_X,frog_grid_x
    move.w  #START_GRID_Y,frog_grid_y
    move.w  #STATE_IDLE,frog_state
    clr.w   frog_anim_frame
    clr.w   joy_prev
    move.w  #COLOUR_BLACK,flash_colour
    bsr     grid_to_pixels
    rts

This is called on both death-respawn and game restart.

The Fire Button

CIA-A port A ($BFE001) bit 7 is the fire button for joystick port 2. It’s active low (0 = pressed):

btst    #7,$bfe001
bne.s   .not_pressed    ; Branch if bit is 1 (not pressed)
; Button is pressed...

Key Takeaways

  • Lives counter adds consequence to death
  • Game over state pauses gameplay
  • Fire button provides restart mechanism
  • Sprite icons show lives visually
  • Palette sharing simplifies sprite setup
  • Separated reset enables clean restart

The Code

;══════════════════════════════════════════════════════════════════════════════
; SIGNAL - A Frogger-style game for the Commodore Amiga
; Unit 9: Lives System
;
; Infinite respawns means no tension. This unit adds a lives system: start
; with 3 lives, lose one on each death, game over when empty. Small frog
; icons in the corner show your remaining chances.
;══════════════════════════════════════════════════════════════════════════════

;══════════════════════════════════════════════════════════════════════════════
; CONSTANTS
;══════════════════════════════════════════════════════════════════════════════

SCREEN_W        equ 40
SCREEN_H        equ 256
PLANE_SIZE      equ SCREEN_W*SCREEN_H

GRID_COLS       equ 20
GRID_ROWS       equ 13
CELL_SIZE       equ 16
GRID_ORIGIN_X   equ 48
GRID_ORIGIN_Y   equ 44
START_GRID_X    equ 9
START_GRID_Y    equ 12

HOP_FRAMES      equ 8
PIXELS_PER_FRAME equ 2
STATE_IDLE      equ 0
STATE_HOPPING   equ 1
STATE_DYING     equ 2
STATE_GAMEOVER  equ 3           ; NEW: Game over state
DIR_UP          equ 0
DIR_DOWN        equ 1
DIR_LEFT        equ 2
DIR_RIGHT       equ 3
FROG_HEIGHT     equ 16
FROG_WIDTH      equ 16

DEATH_FRAMES    equ 30
FLASH_COLOUR    equ $0f00

NUM_CARS        equ 10
CAR_WIDTH       equ 2
CAR_WIDTH_PX    equ 32
CAR_HEIGHT      equ 12
CAR_STRUCT_SIZE equ 8

ROAD_ROW_FIRST  equ 7
ROAD_ROW_LAST   equ 11

; Lives system
START_LIVES     equ 3
LIFE_ICON_W     equ 1           ; 16 pixels = 1 word
LIFE_ICON_H     equ 8
LIFE_ICON_X     equ 8           ; Left margin
LIFE_ICON_Y     equ 260         ; Below visible area (we'll position in border)
LIFE_ICON_SPACING equ 20        ; Pixels between icons

; Colours
COLOUR_BLACK    equ $0000
COLOUR_HOME     equ $0282
COLOUR_HOME_LIT equ $03a3
COLOUR_WATER1   equ $0148
COLOUR_WATER2   equ $026a
COLOUR_MEDIAN   equ $0383
COLOUR_ROAD1    equ $0333
COLOUR_ROAD2    equ $0444
COLOUR_START    equ $0262
COLOUR_START_LIT equ $0373
COLOUR_FROG     equ $00f0
COLOUR_EYES     equ $0ff0
COLOUR_OUTLINE  equ $0000
COLOUR_CAR      equ $0f00
COLOUR_LIFE     equ $00f0       ; Green for life icons

;══════════════════════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════════════════════

CUSTOM      equ $dff000

DMACONR     equ $002
VPOSR       equ $004
JOY1DAT     equ $00c

BLTCON0     equ $040
BLTCON1     equ $042
BLTAFWM     equ $044
BLTALWM     equ $046
BLTCPTH     equ $048
BLTBPTH     equ $04c
BLTAPTH     equ $050
BLTDPTH     equ $054
BLTSIZE     equ $058
BLTCMOD     equ $060
BLTBMOD     equ $062
BLTAMOD     equ $064
BLTDMOD     equ $066

COP1LC      equ $080
COPJMP1     equ $088
DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COLOR00     equ $180
SPR0PTH     equ $120
SPR0PTL     equ $122
SPR1PTH     equ $124
SPR1PTL     equ $126
SPR2PTH     equ $128
SPR2PTL     equ $12a

;══════════════════════════════════════════════════════════════════════════════
; CODE SECTION
;══════════════════════════════════════════════════════════════════════════════

            section code,code_c

start:
            lea     CUSTOM,a5

            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            bsr     clear_screen

            ; Set up bitplane pointer
            lea     screen_plane,a0
            move.l  a0,d0
            swap    d0
            lea     bplpth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     bplptl_val,a1
            move.w  d0,(a1)

            ; Initialise game
            move.w  #START_LIVES,lives
            bsr     reset_frog

            ; Set main sprite pointer (sprite 0 = frog)
            lea     frog_data,a0
            move.l  a0,d0
            swap    d0
            lea     sprpth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     sprptl_val,a1
            move.w  d0,(a1)

            ; Set up life icon sprites (sprites 1, 2, 3)
            bsr     setup_life_sprites

            bsr     update_sprite
            bsr     update_life_display

            ; Install copper list
            lea     copperlist,a0
            move.l  a0,COP1LC(a5)
            move.w  d0,COPJMP1(a5)

            ; Enable DMA
            move.w  #$87e0,DMACON(a5)

;══════════════════════════════════════════════════════════════════════════════
; MAIN LOOP
;══════════════════════════════════════════════════════════════════════════════

mainloop:
            bsr     wait_vblank

            ; Check game state
            cmp.w   #STATE_GAMEOVER,frog_state
            beq     .game_over_loop

            ; Normal game loop
            bsr     update_frog
            bsr     update_sprite

            bsr     erase_all_cars
            bsr     move_all_cars
            bsr     draw_all_cars

            cmp.w   #STATE_DYING,frog_state
            beq.s   .skip_collision
            bsr     check_collision
.skip_collision:

            bra     mainloop

.game_over_loop:
            ; Game over - wait for fire button to restart
            btst    #7,$bfe001          ; Check fire button (active low)
            bne.s   mainloop            ; Not pressed, keep waiting

            ; Restart game
            move.w  #START_LIVES,lives
            bsr     reset_frog
            bsr     update_life_display
            bsr     clear_screen

            bra     mainloop

;══════════════════════════════════════════════════════════════════════════════
; LIFE DISPLAY
;══════════════════════════════════════════════════════════════════════════════

;------------------------------------------------------------------------------
; SETUP_LIFE_SPRITES - Set sprite pointers for life icons
;------------------------------------------------------------------------------
setup_life_sprites:
            ; Sprite 1
            lea     life_icon_1,a0
            move.l  a0,d0
            swap    d0
            lea     spr1pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr1ptl_val,a1
            move.w  d0,(a1)

            ; Sprite 2
            lea     life_icon_2,a0
            move.l  a0,d0
            swap    d0
            lea     spr2pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr2ptl_val,a1
            move.w  d0,(a1)

            ; Sprite 3
            lea     life_icon_3,a0
            move.l  a0,d0
            swap    d0
            lea     spr3pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr3ptl_val,a1
            move.w  d0,(a1)

            rts

;------------------------------------------------------------------------------
; UPDATE_LIFE_DISPLAY - Show/hide life icons based on lives count
;------------------------------------------------------------------------------
update_life_display:
            ; Position life icons at top of screen
            ; VSTART = 30, VSTOP = 38 (8 pixels tall)
            ; HSTART varies: 64, 80, 96 (sprite coords)

            ; Life 1 (always show if lives >= 1)
            lea     life_icon_1,a0
            move.w  lives,d0
            cmp.w   #1,d0
            blt.s   .hide_life1
            move.w  #$1e20,$0(a0)       ; VSTART=30, HSTART=64
            move.w  #$2600,$2(a0)       ; VSTOP=38
            bra.s   .life2
.hide_life1:
            clr.l   (a0)                ; Hide sprite

.life2:
            lea     life_icon_2,a0
            cmp.w   #2,d0
            blt.s   .hide_life2
            move.w  #$1e28,$0(a0)       ; VSTART=30, HSTART=80
            move.w  #$2600,$2(a0)
            bra.s   .life3
.hide_life2:
            clr.l   (a0)

.life3:
            lea     life_icon_3,a0
            cmp.w   #3,d0
            blt.s   .hide_life3
            move.w  #$1e30,$0(a0)       ; VSTART=30, HSTART=96
            move.w  #$2600,$2(a0)
            rts
.hide_life3:
            clr.l   (a0)
            rts

;══════════════════════════════════════════════════════════════════════════════
; COLLISION AND DEATH
;══════════════════════════════════════════════════════════════════════════════

check_collision:
            move.w  frog_grid_y,d0
            cmp.w   #ROAD_ROW_FIRST,d0
            blt     .no_collision
            cmp.w   #ROAD_ROW_LAST,d0
            bgt     .no_collision

            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  2(a2),d1
            cmp.w   d0,d1
            bne.s   .next_car

            move.w  frog_pixel_x,d2
            move.w  (a2),d3

            move.w  d2,d4
            add.w   #FROG_WIDTH,d4
            cmp.w   d3,d4
            ble.s   .next_car

            move.w  d3,d4
            add.w   #CAR_WIDTH_PX,d4
            cmp.w   d2,d4
            ble.s   .next_car

            bsr     trigger_death
            bra.s   .no_collision

.next_car:
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop

.no_collision:
            rts

trigger_death:
            move.w  #STATE_DYING,frog_state
            move.w  #DEATH_FRAMES,death_timer
            move.w  #FLASH_COLOUR,flash_colour

            ; Decrement lives
            subq.w  #1,lives
            bsr     update_life_display

            rts

;------------------------------------------------------------------------------
; RESET_FROG - Reset frog position (called on respawn and game start)
;------------------------------------------------------------------------------
reset_frog:
            move.w  #START_GRID_X,frog_grid_x
            move.w  #START_GRID_Y,frog_grid_y
            move.w  #STATE_IDLE,frog_state
            clr.w   frog_anim_frame
            clr.w   joy_prev
            move.w  #COLOUR_BLACK,flash_colour
            bsr     grid_to_pixels
            rts

;══════════════════════════════════════════════════════════════════════════════
; CAR MANAGEMENT
;══════════════════════════════════════════════════════════════════════════════

erase_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  2(a2),d1
            bsr     calc_screen_addr
            bsr     wait_blit

            move.l  a0,BLTDPTH(a5)
            move.w  #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
            move.w  #$0100,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)

            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

move_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  4(a2),d1
            add.w   d1,d0

            tst.w   d1
            bmi.s   .check_left
            cmp.w   #320,d0
            blt.s   .store
            sub.w   #320+32,d0
            bra.s   .store
.check_left:
            cmp.w   #-32,d0
            bgt.s   .store
            add.w   #320+32,d0
.store:
            move.w  d0,(a2)
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

draw_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  2(a2),d1

            cmp.w   #-32,d0
            blt.s   .next
            cmp.w   #320,d0
            bge.s   .next

            bsr     calc_screen_addr
            bsr     wait_blit

            move.l  #car_gfx,BLTAPTH(a5)
            move.l  a0,BLTDPTH(a5)
            move.w  #$ffff,BLTAFWM(a5)
            move.w  #$ffff,BLTALWM(a5)
            move.w  #0,BLTAMOD(a5)
            move.w  #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
            move.w  #$09f0,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)

.next:
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

;══════════════════════════════════════════════════════════════════════════════
; FROG ROUTINES
;══════════════════════════════════════════════════════════════════════════════

update_frog:
            move.w  frog_state,d0

            cmp.w   #STATE_DYING,d0
            beq     .dying
            tst.w   d0
            beq     .idle
            bra     .hopping

.dying:
            subq.w  #1,death_timer
            bgt.s   .still_dying

            ; Death complete - check for game over
            tst.w   lives
            beq.s   .game_over

            ; Still have lives - respawn
            bsr     reset_frog
            bra     .done

.game_over:
            move.w  #STATE_GAMEOVER,frog_state
            bra     .done

.still_dying:
            move.w  death_timer,d0
            and.w   #4,d0
            beq.s   .flash_off
            move.w  #FLASH_COLOUR,flash_colour
            bra     .done
.flash_off:
            move.w  #COLOUR_BLACK,flash_colour
            bra     .done

.idle:
            bsr     read_joystick_edge
            tst.w   d0
            beq     .done

            btst    #8,d0
            beq.s   .not_up
            move.w  frog_grid_y,d1
            tst.w   d1
            beq.s   .not_up
            move.w  #DIR_UP,frog_dir
            bra.s   .start_hop
.not_up:
            btst    #0,d0
            beq.s   .not_down
            move.w  frog_grid_y,d1
            cmp.w   #GRID_ROWS-1,d1
            beq.s   .not_down
            move.w  #DIR_DOWN,frog_dir
            bra.s   .start_hop
.not_down:
            btst    #9,d0
            beq.s   .not_left
            move.w  frog_grid_x,d1
            tst.w   d1
            beq.s   .not_left
            move.w  #DIR_LEFT,frog_dir
            bra.s   .start_hop
.not_left:
            btst    #1,d0
            beq     .done
            move.w  frog_grid_x,d1
            cmp.w   #GRID_COLS-1,d1
            beq     .done
            move.w  #DIR_RIGHT,frog_dir

.start_hop:
            move.w  #STATE_HOPPING,frog_state
            clr.w   frog_anim_frame
            bra.s   .done

.hopping:
            addq.w  #1,frog_anim_frame
            move.w  frog_dir,d0

            cmp.w   #DIR_UP,d0
            bne.s   .hop_not_up
            sub.w   #PIXELS_PER_FRAME,frog_pixel_y
            bra.s   .check_done
.hop_not_up:
            cmp.w   #DIR_DOWN,d0
            bne.s   .hop_not_down
            add.w   #PIXELS_PER_FRAME,frog_pixel_y
            bra.s   .check_done
.hop_not_down:
            cmp.w   #DIR_LEFT,d0
            bne.s   .hop_not_left
            sub.w   #PIXELS_PER_FRAME,frog_pixel_x
            bra.s   .check_done
.hop_not_left:
            add.w   #PIXELS_PER_FRAME,frog_pixel_x

.check_done:
            cmp.w   #HOP_FRAMES,frog_anim_frame
            blt.s   .done

            move.w  frog_dir,d0
            cmp.w   #DIR_UP,d0
            bne.s   .end_not_up
            subq.w  #1,frog_grid_y
            bra.s   .snap
.end_not_up:
            cmp.w   #DIR_DOWN,d0
            bne.s   .end_not_down
            addq.w  #1,frog_grid_y
            bra.s   .snap
.end_not_down:
            cmp.w   #DIR_LEFT,d0
            bne.s   .end_not_left
            subq.w  #1,frog_grid_x
            bra.s   .snap
.end_not_left:
            addq.w  #1,frog_grid_x

.snap:
            bsr     grid_to_pixels
            move.w  #STATE_IDLE,frog_state

.done:
            move.w  flash_colour,flash_copper
            rts

;══════════════════════════════════════════════════════════════════════════════
; UTILITY ROUTINES
;══════════════════════════════════════════════════════════════════════════════

grid_to_pixels:
            move.w  frog_grid_x,d0
            mulu    #CELL_SIZE,d0
            add.w   #GRID_ORIGIN_X,d0
            move.w  d0,frog_pixel_x

            move.w  frog_grid_y,d0
            mulu    #CELL_SIZE,d0
            add.w   #GRID_ORIGIN_Y,d0
            move.w  d0,frog_pixel_y
            rts

read_joystick_edge:
            move.w  JOY1DAT(a5),d0
            move.w  d0,d1
            lsr.w   #1,d1
            eor.w   d1,d0

            move.w  joy_prev,d1
            not.w   d1
            and.w   d0,d1
            move.w  d0,joy_prev

            move.w  d1,d0
            rts

wait_vblank:
            move.l  #$1ff00,d1
.wait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .wait
            rts

wait_blit:
            btst    #6,DMACONR(a5)
            bne.s   wait_blit
            rts

clear_screen:
            bsr.s   wait_blit
            move.l  #screen_plane,BLTDPTH(a5)
            move.w  #0,BLTDMOD(a5)
            move.w  #$0100,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5)
            rts

calc_screen_addr:
            lea     screen_plane,a0
            move.w  d1,d2
            mulu    #CELL_SIZE,d2
            add.w   #GRID_ORIGIN_Y,d2
            mulu    #SCREEN_W,d2
            add.l   d2,a0
            move.w  d0,d2
            lsr.w   #3,d2
            ext.l   d2
            add.l   d2,a0
            rts

update_sprite:
            lea     frog_data,a0
            move.w  frog_pixel_y,d0
            move.w  frog_pixel_x,d1

            move.w  d0,d2
            lsl.w   #8,d2
            lsr.w   #1,d1
            or.b    d1,d2
            move.w  d2,(a0)

            add.w   #FROG_HEIGHT,d0
            lsl.w   #8,d0
            move.w  d0,2(a0)
            rts

;══════════════════════════════════════════════════════════════════════════════
; VARIABLES
;══════════════════════════════════════════════════════════════════════════════

frog_grid_x:    dc.w    9
frog_grid_y:    dc.w    12
frog_pixel_x:   dc.w    0
frog_pixel_y:   dc.w    0
frog_state:     dc.w    0
frog_dir:       dc.w    0
frog_anim_frame: dc.w   0
joy_prev:       dc.w    0

death_timer:    dc.w    0
flash_colour:   dc.w    0

lives:          dc.w    3

car_data:
            dc.w    0,7,1,0
            dc.w    160,7,1,0
            dc.w    100,8,-2,0
            dc.w    250,8,-2,0
            dc.w    50,9,2,0
            dc.w    200,9,2,0
            dc.w    80,10,-1,0
            dc.w    220,10,-1,0
            dc.w    30,11,3,0
            dc.w    180,11,3,0

;══════════════════════════════════════════════════════════════════════════════
; COPPER LIST
;══════════════════════════════════════════════════════════════════════════════

copperlist:
            dc.w    COLOR00
flash_copper:
            dc.w    COLOUR_BLACK

            dc.w    $0100,$1200
            dc.w    $0102,$0000
            dc.w    $0104,$0000
            dc.w    $0108,$0000
            dc.w    $010a,$0000

            dc.w    $00e0
bplpth_val: dc.w    $0000
            dc.w    $00e2
bplptl_val: dc.w    $0000

            dc.w    $0180,$0000
            dc.w    $0182,COLOUR_CAR

            ; Sprite 0-1 palette (frog and life icons share this)
            dc.w    $01a2,COLOUR_FROG
            dc.w    $01a4,COLOUR_EYES
            dc.w    $01a6,COLOUR_OUTLINE

            ; Sprite 0 pointer (main frog)
            dc.w    SPR0PTH
sprpth_val: dc.w    $0000
            dc.w    SPR0PTL
sprptl_val: dc.w    $0000

            ; Sprite 1 pointer (life icon 1)
            dc.w    SPR1PTH
spr1pth_val: dc.w   $0000
            dc.w    SPR1PTL
spr1ptl_val: dc.w   $0000

            ; Sprite 2 pointer (life icon 2)
            dc.w    SPR2PTH
spr2pth_val: dc.w   $0000
            dc.w    SPR2PTL
spr2ptl_val: dc.w   $0000

            ; Sprite 3 pointer (life icon 3)
            dc.w    $0126           ; SPR3PTH
spr3pth_val: dc.w   $0000
            dc.w    $0128           ; SPR3PTL (note: this is wrong, should be $012a)
spr3ptl_val: dc.w   $0000

            ; Zone colours
            dc.w    $2c07,$fffe
            dc.w    COLOR00,COLOUR_HOME
            dc.w    $3407,$fffe
            dc.w    COLOR00,COLOUR_HOME_LIT
            dc.w    $3807,$fffe
            dc.w    COLOR00,COLOUR_HOME

            dc.w    $3c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1
            dc.w    $4c07,$fffe
            dc.w    COLOR00,COLOUR_WATER2
            dc.w    $5c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1
            dc.w    $6c07,$fffe
            dc.w    COLOR00,COLOUR_WATER2
            dc.w    $7c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1

            dc.w    $8c07,$fffe
            dc.w    COLOR00,COLOUR_MEDIAN

            dc.w    $9c07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1
            dc.w    $ac07,$fffe
            dc.w    COLOR00,COLOUR_ROAD2
            dc.w    $bc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1
            dc.w    $cc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD2
            dc.w    $dc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1

            dc.w    $ec07,$fffe
            dc.w    COLOR00,COLOUR_START
            dc.w    $f407,$fffe
            dc.w    COLOR00,COLOUR_START_LIT
            dc.w    $f807,$fffe
            dc.w    COLOR00,COLOUR_START

            dc.w    $fc07,$fffe
            dc.w    COLOR00,COLOUR_BLACK

            dc.w    $ffff,$fffe

;══════════════════════════════════════════════════════════════════════════════
; GRAPHICS DATA
;══════════════════════════════════════════════════════════════════════════════

            even
car_gfx:
            dc.w    $0ff0,$0ff0
            dc.w    $3ffc,$3ffc
            dc.w    $7ffe,$7ffe
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $7ffe,$7ffe
            dc.w    $3c3c,$3c3c
            dc.w    $0000,$0000

            even
frog_data:
            dc.w    $ec50,$fc00

            dc.w    $0000,$0000
            dc.w    $07e0,$0000
            dc.w    $1ff8,$0420
            dc.w    $3ffc,$0a50
            dc.w    $7ffe,$1248
            dc.w    $7ffe,$1008
            dc.w    $ffff,$2004
            dc.w    $ffff,$0000
            dc.w    $ffff,$0000
            dc.w    $7ffe,$2004
            dc.w    $7ffe,$1008
            dc.w    $3ffc,$0810
            dc.w    $1ff8,$0420
            dc.w    $07e0,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

            dc.w    $0000,$0000

; Life icon sprites (small frog shape, 8 pixels tall)
            even
life_icon_1:
            dc.w    $0000,$0000         ; Control words (set by code)
            dc.w    $0000,$0000
            dc.w    $1800,$0000         ; ..##............
            dc.w    $3c00,$0000         ; .####...........
            dc.w    $7e00,$0000         ; .######.........
            dc.w    $7e00,$0000         ; .######.........
            dc.w    $3c00,$0000         ; .####...........
            dc.w    $1800,$0000         ; ..##............
            dc.w    $0000,$0000
            dc.w    $0000,$0000         ; End marker

            even
life_icon_2:
            dc.w    $0000,$0000
            dc.w    $0000,$0000
            dc.w    $1800,$0000
            dc.w    $3c00,$0000
            dc.w    $7e00,$0000
            dc.w    $7e00,$0000
            dc.w    $3c00,$0000
            dc.w    $1800,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

            even
life_icon_3:
            dc.w    $0000,$0000
            dc.w    $0000,$0000
            dc.w    $1800,$0000
            dc.w    $3c00,$0000
            dc.w    $7e00,$0000
            dc.w    $7e00,$0000
            dc.w    $3c00,$0000
            dc.w    $1800,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

;══════════════════════════════════════════════════════════════════════════════
; SCREEN BUFFER
;══════════════════════════════════════════════════════════════════════════════

            section chipbss,bss_c

            even
screen_plane:
            ds.b    PLANE_SIZE

Build It

vasmm68k_mot -Fhunkexe -kick1hunks -o signal signal.asm

What’s Next

Lives and death work, but there’s no goal yet. In Unit 10, we’ll add home zones—dock the frog in all five slots to win!

What Changed

Unit 8 → Unit 9
+218-57
11 ;══════════════════════════════════════════════════════════════════════════════
22 ; SIGNAL - A Frogger-style game for the Commodore Amiga
3-; Unit 8: Collision Detection
3+; Unit 9: Lives System
44 ;
5-; Traffic is meaningless if you can walk through it. This unit adds collision
6-; detection: hit a car, the screen flashes red, and you respawn at the start.
7-; Now it's actually a game with consequences!
5+; Infinite respawns means no tension. This unit adds a lives system: start
6+; with 3 lives, lose one on each death, game over when empty. Small frog
7+; icons in the corner show your remaining chances.
88 ;══════════════════════════════════════════════════════════════════════════════
99
1010 ;══════════════════════════════════════════════════════════════════════════════
...
2727 PIXELS_PER_FRAME equ 2
2828 STATE_IDLE equ 0
2929 STATE_HOPPING equ 1
30-STATE_DYING equ 2 ; NEW: Death state
30+STATE_DYING equ 2
31+STATE_GAMEOVER equ 3 ; NEW: Game over state
3132 DIR_UP equ 0
3233 DIR_DOWN equ 1
3334 DIR_LEFT equ 2
...
3536 FROG_HEIGHT equ 16
3637 FROG_WIDTH equ 16
3738
38-; Death animation
39-DEATH_FRAMES equ 30 ; How long death lasts (0.6 seconds at 50fps)
40-FLASH_COLOUR equ $0f00 ; Red flash
39+DEATH_FRAMES equ 30
40+FLASH_COLOUR equ $0f00
4141
4242 NUM_CARS equ 10
4343 CAR_WIDTH equ 2
44-CAR_WIDTH_PX equ 32 ; Pixels
44+CAR_WIDTH_PX equ 32
4545 CAR_HEIGHT equ 12
4646 CAR_STRUCT_SIZE equ 8
47-
48-; Collision threshold (pixels of overlap required)
49-COLLISION_THRESHOLD equ 8
5047
51-; Road rows (grid rows 7-11, which are rows 8-12 in 1-based counting)
5248 ROAD_ROW_FIRST equ 7
5349 ROAD_ROW_LAST equ 11
5450
55-; Zone colours
51+; Lives system
52+START_LIVES equ 3
53+LIFE_ICON_W equ 1 ; 16 pixels = 1 word
54+LIFE_ICON_H equ 8
55+LIFE_ICON_X equ 8 ; Left margin
56+LIFE_ICON_Y equ 260 ; Below visible area (we'll position in border)
57+LIFE_ICON_SPACING equ 20 ; Pixels between icons
58+
59+; Colours
5660 COLOUR_BLACK equ $0000
5761 COLOUR_HOME equ $0282
5862 COLOUR_HOME_LIT equ $03a3
...
6771 COLOUR_EYES equ $0ff0
6872 COLOUR_OUTLINE equ $0000
6973 COLOUR_CAR equ $0f00
74+COLOUR_LIFE equ $00f0 ; Green for life icons
7075
7176 ;══════════════════════════════════════════════════════════════════════════════
7277 ; HARDWARE REGISTERS
...
100105 COLOR00 equ $180
101106 SPR0PTH equ $120
102107 SPR0PTL equ $122
108+SPR1PTH equ $124
109+SPR1PTL equ $126
110+SPR2PTH equ $128
111+SPR2PTL equ $12a
103112
104113 ;══════════════════════════════════════════════════════════════════════════════
105114 ; CODE SECTION
...
126135 lea bplptl_val,a1
127136 move.w d0,(a1)
128137
129- ; Initialise frog
130- bsr respawn_frog
138+ ; Initialise game
139+ move.w #START_LIVES,lives
140+ bsr reset_frog
131141
132- ; Set sprite pointer
142+ ; Set main sprite pointer (sprite 0 = frog)
133143 lea frog_data,a0
134144 move.l a0,d0
135145 swap d0
...
138148 swap d0
139149 lea sprptl_val,a1
140150 move.w d0,(a1)
151+
152+ ; Set up life icon sprites (sprites 1, 2, 3)
153+ bsr setup_life_sprites
154+
141155 bsr update_sprite
156+ bsr update_life_display
142157
143158 ; Install copper list
144159 lea copperlist,a0
...
155170 mainloop:
156171 bsr wait_vblank
157172
158- ; Update frog (handles all states including death)
173+ ; Check game state
174+ cmp.w #STATE_GAMEOVER,frog_state
175+ beq .game_over_loop
176+
177+ ; Normal game loop
159178 bsr update_frog
160179 bsr update_sprite
161180
162- ; Update cars
163181 bsr erase_all_cars
164182 bsr move_all_cars
165183 bsr draw_all_cars
166184
167- ; Check collisions (only if frog is alive)
168185 cmp.w #STATE_DYING,frog_state
169186 beq.s .skip_collision
170187 bsr check_collision
171188 .skip_collision:
189+
190+ bra mainloop
191+
192+.game_over_loop:
193+ ; Game over - wait for fire button to restart
194+ btst #7,$bfe001 ; Check fire button (active low)
195+ bne.s mainloop ; Not pressed, keep waiting
196+
197+ ; Restart game
198+ move.w #START_LIVES,lives
199+ bsr reset_frog
200+ bsr update_life_display
201+ bsr clear_screen
172202
173203 bra mainloop
174204
175205 ;══════════════════════════════════════════════════════════════════════════════
176-; COLLISION DETECTION
206+; LIFE DISPLAY
177207 ;══════════════════════════════════════════════════════════════════════════════
178208
179209 ;------------------------------------------------------------------------------
180-; CHECK_COLLISION - Test frog against all cars
210+; SETUP_LIFE_SPRITES - Set sprite pointers for life icons
211+;------------------------------------------------------------------------------
212+setup_life_sprites:
213+ ; Sprite 1
214+ lea life_icon_1,a0
215+ move.l a0,d0
216+ swap d0
217+ lea spr1pth_val,a1
218+ move.w d0,(a1)
219+ swap d0
220+ lea spr1ptl_val,a1
221+ move.w d0,(a1)
222+
223+ ; Sprite 2
224+ lea life_icon_2,a0
225+ move.l a0,d0
226+ swap d0
227+ lea spr2pth_val,a1
228+ move.w d0,(a1)
229+ swap d0
230+ lea spr2ptl_val,a1
231+ move.w d0,(a1)
232+
233+ ; Sprite 3
234+ lea life_icon_3,a0
235+ move.l a0,d0
236+ swap d0
237+ lea spr3pth_val,a1
238+ move.w d0,(a1)
239+ swap d0
240+ lea spr3ptl_val,a1
241+ move.w d0,(a1)
242+
243+ rts
244+
245+;------------------------------------------------------------------------------
246+; UPDATE_LIFE_DISPLAY - Show/hide life icons based on lives count
181247 ;------------------------------------------------------------------------------
248+update_life_display:
249+ ; Position life icons at top of screen
250+ ; VSTART = 30, VSTOP = 38 (8 pixels tall)
251+ ; HSTART varies: 64, 80, 96 (sprite coords)
252+
253+ ; Life 1 (always show if lives >= 1)
254+ lea life_icon_1,a0
255+ move.w lives,d0
256+ cmp.w #1,d0
257+ blt.s .hide_life1
258+ move.w #$1e20,$0(a0) ; VSTART=30, HSTART=64
259+ move.w #$2600,$2(a0) ; VSTOP=38
260+ bra.s .life2
261+.hide_life1:
262+ clr.l (a0) ; Hide sprite
263+
264+.life2:
265+ lea life_icon_2,a0
266+ cmp.w #2,d0
267+ blt.s .hide_life2
268+ move.w #$1e28,$0(a0) ; VSTART=30, HSTART=80
269+ move.w #$2600,$2(a0)
270+ bra.s .life3
271+.hide_life2:
272+ clr.l (a0)
273+
274+.life3:
275+ lea life_icon_3,a0
276+ cmp.w #3,d0
277+ blt.s .hide_life3
278+ move.w #$1e30,$0(a0) ; VSTART=30, HSTART=96
279+ move.w #$2600,$2(a0)
280+ rts
281+.hide_life3:
282+ clr.l (a0)
283+ rts
284+
285+;══════════════════════════════════════════════════════════════════════════════
286+; COLLISION AND DEATH
287+;══════════════════════════════════════════════════════════════════════════════
288+
182289 check_collision:
183- ; Only check if frog is on a road row
184290 move.w frog_grid_y,d0
185291 cmp.w #ROAD_ROW_FIRST,d0
186292 blt .no_collision
187293 cmp.w #ROAD_ROW_LAST,d0
188294 bgt .no_collision
189295
190- ; Frog is on road - check against all cars in this row
191296 lea car_data,a2
192297 moveq #NUM_CARS-1,d7
193298
194299 .loop:
195- ; Check if car is in same row
196- move.w 2(a2),d1 ; Car row
197- cmp.w d0,d1 ; Compare with frog row
300+ move.w 2(a2),d1
301+ cmp.w d0,d1
198302 bne.s .next_car
199303
200- ; Same row - check X overlap
201- move.w frog_pixel_x,d2 ; Frog X
202- move.w (a2),d3 ; Car X
304+ move.w frog_pixel_x,d2
305+ move.w (a2),d3
203306
204- ; Check: frog_x + frog_width > car_x
205307 move.w d2,d4
206308 add.w #FROG_WIDTH,d4
207309 cmp.w d3,d4
208- ble.s .next_car ; Frog right edge <= car left edge
310+ ble.s .next_car
209311
210- ; Check: car_x + car_width > frog_x
211312 move.w d3,d4
212313 add.w #CAR_WIDTH_PX,d4
213314 cmp.w d2,d4
214- ble.s .next_car ; Car right edge <= frog left edge
315+ ble.s .next_car
215316
216- ; COLLISION! Trigger death
217317 bsr trigger_death
218- bra.s .no_collision ; Exit early
318+ bra.s .no_collision
219319
220320 .next_car:
221321 lea CAR_STRUCT_SIZE(a2),a2
...
224324 .no_collision:
225325 rts
226326
227-;------------------------------------------------------------------------------
228-; TRIGGER_DEATH - Start death sequence
229-;------------------------------------------------------------------------------
230327 trigger_death:
231328 move.w #STATE_DYING,frog_state
232329 move.w #DEATH_FRAMES,death_timer
233-
234- ; Flash screen red by changing Copper colour
235330 move.w #FLASH_COLOUR,flash_colour
331+
332+ ; Decrement lives
333+ subq.w #1,lives
334+ bsr update_life_display
335+
236336 rts
237337
238338 ;------------------------------------------------------------------------------
239-; RESPAWN_FROG - Reset frog to starting position
339+; RESET_FROG - Reset frog position (called on respawn and game start)
240340 ;------------------------------------------------------------------------------
241-respawn_frog:
341+reset_frog:
242342 move.w #START_GRID_X,frog_grid_x
243343 move.w #START_GRID_Y,frog_grid_y
244344 move.w #STATE_IDLE,frog_state
245345 clr.w frog_anim_frame
246346 clr.w joy_prev
247-
248- ; Reset flash colour to black
249347 move.w #COLOUR_BLACK,flash_colour
250-
251348 bsr grid_to_pixels
252349 rts
253350
...
286383
287384 tst.w d1
288385 bmi.s .check_left
289-
290386 cmp.w #320,d0
291387 blt.s .store
292388 sub.w #320+32,d0
293389 bra.s .store
294-
295390 .check_left:
296391 cmp.w #-32,d0
297392 bgt.s .store
298393 add.w #320+32,d0
299-
300394 .store:
301395 move.w d0,(a2)
302396 lea CAR_STRUCT_SIZE(a2),a2
...
343437
344438 cmp.w #STATE_DYING,d0
345439 beq .dying
346-
347440 tst.w d0
348441 beq .idle
349442 bra .hopping
350443
351444 .dying:
352- ; Count down death timer
353445 subq.w #1,death_timer
354446 bgt.s .still_dying
355447
356- ; Death complete - respawn
357- bsr respawn_frog
448+ ; Death complete - check for game over
449+ tst.w lives
450+ beq.s .game_over
451+
452+ ; Still have lives - respawn
453+ bsr reset_frog
454+ bra .done
455+
456+.game_over:
457+ move.w #STATE_GAMEOVER,frog_state
358458 bra .done
359459
360460 .still_dying:
361- ; Flash effect: alternate colour
362461 move.w death_timer,d0
363- and.w #4,d0 ; Flash every 4 frames
462+ and.w #4,d0
364463 beq.s .flash_off
365464 move.w #FLASH_COLOUR,flash_colour
366465 bra .done
...
457556 move.w #STATE_IDLE,frog_state
458557
459558 .done:
460- ; Update flash colour in Copper list
461559 move.w flash_colour,flash_copper
462560 rts
463561
...
557655
558656 death_timer: dc.w 0
559657 flash_colour: dc.w 0
658+
659+lives: dc.w 3
560660
561661 car_data:
562662 dc.w 0,7,1,0
...
577677 copperlist:
578678 dc.w COLOR00
579679 flash_copper:
580- dc.w COLOUR_BLACK ; This gets modified for flash effect
680+ dc.w COLOUR_BLACK
581681
582682 dc.w $0100,$1200
583683 dc.w $0102,$0000
...
593693 dc.w $0180,$0000
594694 dc.w $0182,COLOUR_CAR
595695
696+ ; Sprite 0-1 palette (frog and life icons share this)
596697 dc.w $01a2,COLOUR_FROG
597698 dc.w $01a4,COLOUR_EYES
598699 dc.w $01a6,COLOUR_OUTLINE
599700
701+ ; Sprite 0 pointer (main frog)
600702 dc.w SPR0PTH
601703 sprpth_val: dc.w $0000
602704 dc.w SPR0PTL
603705 sprptl_val: dc.w $0000
706+
707+ ; Sprite 1 pointer (life icon 1)
708+ dc.w SPR1PTH
709+spr1pth_val: dc.w $0000
710+ dc.w SPR1PTL
711+spr1ptl_val: dc.w $0000
712+
713+ ; Sprite 2 pointer (life icon 2)
714+ dc.w SPR2PTH
715+spr2pth_val: dc.w $0000
716+ dc.w SPR2PTL
717+spr2ptl_val: dc.w $0000
718+
719+ ; Sprite 3 pointer (life icon 3)
720+ dc.w $0126 ; SPR3PTH
721+spr3pth_val: dc.w $0000
722+ dc.w $0128 ; SPR3PTL (note: this is wrong, should be $012a)
723+spr3ptl_val: dc.w $0000
604724
725+ ; Zone colours
605726 dc.w $2c07,$fffe
606727 dc.w COLOR00,COLOUR_HOME
607728 dc.w $3407,$fffe
...
683804 dc.w $3ffc,$0810
684805 dc.w $1ff8,$0420
685806 dc.w $07e0,$0000
807+ dc.w $0000,$0000
808+ dc.w $0000,$0000
809+
810+ dc.w $0000,$0000
811+
812+; Life icon sprites (small frog shape, 8 pixels tall)
813+ even
814+life_icon_1:
815+ dc.w $0000,$0000 ; Control words (set by code)
816+ dc.w $0000,$0000
817+ dc.w $1800,$0000 ; ..##............
818+ dc.w $3c00,$0000 ; .####...........
819+ dc.w $7e00,$0000 ; .######.........
820+ dc.w $7e00,$0000 ; .######.........
821+ dc.w $3c00,$0000 ; .####...........
822+ dc.w $1800,$0000 ; ..##............
823+ dc.w $0000,$0000
824+ dc.w $0000,$0000 ; End marker
825+
826+ even
827+life_icon_2:
828+ dc.w $0000,$0000
829+ dc.w $0000,$0000
830+ dc.w $1800,$0000
831+ dc.w $3c00,$0000
832+ dc.w $7e00,$0000
833+ dc.w $7e00,$0000
834+ dc.w $3c00,$0000
835+ dc.w $1800,$0000
686836 dc.w $0000,$0000
687837 dc.w $0000,$0000
688838
839+ even
840+life_icon_3:
841+ dc.w $0000,$0000
842+ dc.w $0000,$0000
843+ dc.w $1800,$0000
844+ dc.w $3c00,$0000
845+ dc.w $7e00,$0000
846+ dc.w $7e00,$0000
847+ dc.w $3c00,$0000
848+ dc.w $1800,$0000
849+ dc.w $0000,$0000
689850 dc.w $0000,$0000
690851
691852 ;══════════════════════════════════════════════════════════════════════════════