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

Lives and Death

Add a lives system with death and respawn.

16% of Neon Nexus

What You’re Building

Lives. Death. Stakes.

Lives and Death

Now you can lose.

Lives System

Track lives in zero page:

STARTING_LIVES = 3

lives:        .res 1
invuln_timer: .res 1    ; Invulnerability after respawn
game_over:    .res 1

Death and Respawn

When collision is detected:

player_hit:
    dec lives
    lda lives
    beq @game_over

    ; Respawn at centre
    lda #PLAYER_START_X
    sta player_x
    lda #PLAYER_START_Y
    sta player_y

    ; Grant invulnerability
    lda #INVULN_TIME
    sta invuln_timer
    rts

@game_over:
    lda #1
    sta game_over
    rts

Invulnerability

After respawning, the player is briefly invulnerable. This prevents instant re-death:

INVULN_TIME = 90      ; 1.5 seconds at 60fps

check_collisions:
    ; Skip collision check if invulnerable
    lda invuln_timer
    beq @check
    dec invuln_timer
    rts

@check:
    ; Normal collision detection...

During invulnerability, the player flashes (same flash effect as Unit 9).

Lives Display

Show remaining lives as sprites:

update_lives_display:
    ldy #20             ; OAM offset for lives

    ldx lives
    beq @hide_all

    ; Show life 1
    lda #8              ; Y position
    sta oam_buffer, y
    iny
    lda #SPRITE_LIFE
    sta oam_buffer, y
    ; ...repeat for each life

Game Over

When lives reach zero:

main_loop:
    lda game_over
    bne @game_over_loop

    ; Normal game logic...
    jmp main_loop

@game_over_loop:
    ; Freeze - game is over
    jmp @game_over_loop

The game stops. The player sees their final position.

The Code

; =============================================================================
; NEON NEXUS - Unit 10: Lives and Death
; =============================================================================
; Add lives system with death and respawn.
; =============================================================================

; -----------------------------------------------------------------------------
; NES Hardware Addresses
; -----------------------------------------------------------------------------
PPUCTRL   = $2000
PPUMASK   = $2001
PPUSTATUS = $2002
OAMADDR   = $2003
PPUSCROLL = $2005
PPUADDR   = $2006
PPUDATA   = $2007
OAMDMA    = $4014

JOYPAD1   = $4016
JOYPAD2   = $4017

; Controller buttons
BTN_A      = %10000000
BTN_B      = %01000000
BTN_SELECT = %00100000
BTN_START  = %00010000
BTN_UP     = %00001000
BTN_DOWN   = %00000100
BTN_LEFT   = %00000010
BTN_RIGHT  = %00000001

; -----------------------------------------------------------------------------
; Game Constants
; -----------------------------------------------------------------------------
PLAYER_START_X  = 124
PLAYER_START_Y  = 116
PLAYER_SPEED    = 2
ENEMY_SPEED     = 1

NUM_ENEMIES     = 4
STARTING_LIVES  = 3
COLLISION_DIST  = 6
INVULN_TIME     = 90      ; 1.5 seconds of invulnerability

; Tile indices
TILE_EMPTY     = 0
TILE_BORDER    = 1
TILE_FLOOR     = 2
TILE_CORNER_TL = 3
TILE_CORNER_TR = 4
TILE_CORNER_BL = 5
TILE_CORNER_BR = 6

SPRITE_PLAYER  = 7
SPRITE_ENEMY   = 8
SPRITE_LIFE    = 9        ; Life icon

; Arena boundaries
ARENA_LEFT   = 16
ARENA_RIGHT  = 232
ARENA_TOP    = 24         ; Leave room for HUD
ARENA_BOTTOM = 208

DIR_RIGHT = 1
DIR_LEFT  = $FF
DIR_DOWN  = 1
DIR_UP    = $FF

BG_COLOUR = $0F

; -----------------------------------------------------------------------------
; Memory Layout
; -----------------------------------------------------------------------------
.segment "ZEROPAGE"
player_x:       .res 1
player_y:       .res 1
buttons:        .res 1
temp:           .res 1
row_counter:    .res 1
frame_count:    .res 1
lives:          .res 1
invuln_timer:   .res 1    ; Invulnerability countdown
game_over:      .res 1    ; Non-zero when game is over

enemy_x:        .res NUM_ENEMIES
enemy_y:        .res NUM_ENEMIES
enemy_dir_x:    .res NUM_ENEMIES
enemy_dir_y:    .res NUM_ENEMIES

.segment "OAM"
oam_buffer:     .res 256

.segment "BSS"

; -----------------------------------------------------------------------------
; iNES Header
; -----------------------------------------------------------------------------
.segment "HEADER"
    .byte "NES", $1A
    .byte 2
    .byte 1
    .byte $01
    .byte $00
    .byte 0,0,0,0,0,0,0,0

; -----------------------------------------------------------------------------
; Code
; -----------------------------------------------------------------------------
.segment "CODE"

reset:
    sei
    cld
    ldx #$40
    stx $4017
    ldx #$FF
    txs
    inx
    stx PPUCTRL
    stx PPUMASK
    stx $4010

@vblank1:
    bit PPUSTATUS
    bpl @vblank1

    lda #0
@clear_ram:
    sta $0000, x
    sta $0100, x
    sta $0200, x
    sta $0300, x
    sta $0400, x
    sta $0500, x
    sta $0600, x
    sta $0700, x
    inx
    bne @clear_ram

@vblank2:
    bit PPUSTATUS
    bpl @vblank2

    jsr load_palette
    jsr draw_arena
    jsr set_attributes
    jsr init_game

    ; Hide all sprites initially
    lda #$FF
    ldx #0
@hide_all:
    sta oam_buffer, x
    inx
    bne @hide_all

    jsr update_all_sprites

    lda #0
    sta PPUSCROLL
    sta PPUSCROLL
    sta frame_count

    lda #%10000000
    sta PPUCTRL
    lda #%00011110
    sta PPUMASK

main_loop:
    lda game_over
    bne @game_over_loop

    jsr read_controller
    jsr move_player
    jsr move_enemies
    jsr check_collisions
    jsr update_all_sprites
    jmp main_loop

@game_over_loop:
    ; Game over - just loop (player can see final state)
    jmp @game_over_loop

; -----------------------------------------------------------------------------
; Initialise Game
; -----------------------------------------------------------------------------
init_game:
    lda #STARTING_LIVES
    sta lives
    lda #0
    sta game_over
    sta invuln_timer

    lda #PLAYER_START_X
    sta player_x
    lda #PLAYER_START_Y
    sta player_y

    jsr init_enemies
    rts

; -----------------------------------------------------------------------------
; Initialise Enemies
; -----------------------------------------------------------------------------
init_enemies:
    lda #48
    sta enemy_x+0
    lda #48
    sta enemy_y+0
    lda #DIR_RIGHT
    sta enemy_dir_x+0
    lda #DIR_DOWN
    sta enemy_dir_y+0

    lda #200
    sta enemy_x+1
    lda #48
    sta enemy_y+1
    lda #DIR_LEFT
    sta enemy_dir_x+1
    lda #DIR_DOWN
    sta enemy_dir_y+1

    lda #48
    sta enemy_x+2
    lda #176
    sta enemy_y+2
    lda #DIR_RIGHT
    sta enemy_dir_x+2
    lda #DIR_UP
    sta enemy_dir_y+2

    lda #200
    sta enemy_x+3
    lda #176
    sta enemy_y+3
    lda #DIR_LEFT
    sta enemy_dir_x+3
    lda #DIR_UP
    sta enemy_dir_y+3
    rts

; -----------------------------------------------------------------------------
; Check Collisions
; -----------------------------------------------------------------------------
check_collisions:
    ; Skip if invulnerable
    lda invuln_timer
    beq @check
    dec invuln_timer
    rts

@check:
    ldx #0
@check_enemy:
    lda player_x
    sec
    sbc enemy_x, x
    bpl @check_x_pos
    eor #$FF
    clc
    adc #1
@check_x_pos:
    cmp #COLLISION_DIST
    bcs @next_enemy

    lda player_y
    sec
    sbc enemy_y, x
    bpl @check_y_pos
    eor #$FF
    clc
    adc #1
@check_y_pos:
    cmp #COLLISION_DIST
    bcs @next_enemy

    ; Collision! Lose a life
    jsr player_hit
    rts

@next_enemy:
    inx
    cpx #NUM_ENEMIES
    bne @check_enemy
    rts

; -----------------------------------------------------------------------------
; Player Hit - Lose life and respawn
; -----------------------------------------------------------------------------
player_hit:
    dec lives
    lda lives
    beq @game_over

    ; Respawn at start position
    lda #PLAYER_START_X
    sta player_x
    lda #PLAYER_START_Y
    sta player_y

    ; Start invulnerability
    lda #INVULN_TIME
    sta invuln_timer
    rts

@game_over:
    lda #1
    sta game_over
    rts

; -----------------------------------------------------------------------------
; Update All Sprites
; -----------------------------------------------------------------------------
update_all_sprites:
    jsr update_player_sprite
    jsr update_enemy_sprites
    jsr update_lives_display
    rts

; -----------------------------------------------------------------------------
; Update Player Sprite
; -----------------------------------------------------------------------------
update_player_sprite:
    ; Check if game over
    lda game_over
    beq @alive
    ; Hide player
    lda #$FF
    sta oam_buffer+0
    rts

@alive:
    ; Check invulnerability flash
    lda invuln_timer
    beq @no_flash
    and #%00000100
    beq @show
    ; Hide during flash
    lda #$FF
    sta oam_buffer+0
    rts

@show:
@no_flash:
    lda player_y
    sta oam_buffer+0
    lda #SPRITE_PLAYER
    sta oam_buffer+1
    lda #0
    sta oam_buffer+2
    lda player_x
    sta oam_buffer+3
    rts

; -----------------------------------------------------------------------------
; Update Enemy Sprites
; -----------------------------------------------------------------------------
update_enemy_sprites:
    ldx #0
    ldy #4              ; Start after player sprite

@loop:
    lda enemy_y, x
    sta oam_buffer, y
    iny
    lda #SPRITE_ENEMY
    sta oam_buffer, y
    iny
    lda #%00000001
    sta oam_buffer, y
    iny
    lda enemy_x, x
    sta oam_buffer, y
    iny
    inx
    cpx #NUM_ENEMIES
    bne @loop
    rts

; -----------------------------------------------------------------------------
; Update Lives Display - Show life icons at top of screen
; -----------------------------------------------------------------------------
update_lives_display:
    ; OAM slots 20-31 for lives display (up to 3 lives)
    ldy #20             ; OAM offset

    ldx lives
    beq @hide_all_lives

    ; Show life 1
    lda #8              ; Y position (top of screen)
    sta oam_buffer, y
    iny
    lda #SPRITE_LIFE
    sta oam_buffer, y
    iny
    lda #0
    sta oam_buffer, y
    iny
    lda #16             ; X position
    sta oam_buffer, y
    iny

    cpx #1
    beq @hide_rest

    ; Show life 2
    lda #8
    sta oam_buffer, y
    iny
    lda #SPRITE_LIFE
    sta oam_buffer, y
    iny
    lda #0
    sta oam_buffer, y
    iny
    lda #26
    sta oam_buffer, y
    iny

    cpx #2
    beq @hide_rest

    ; Show life 3
    lda #8
    sta oam_buffer, y
    iny
    lda #SPRITE_LIFE
    sta oam_buffer, y
    iny
    lda #0
    sta oam_buffer, y
    iny
    lda #36
    sta oam_buffer, y
    iny
    rts

@hide_rest:
    ; Hide remaining life slots
@hide_loop:
    cpy #32
    bcs @done
    lda #$FF
    sta oam_buffer, y
    iny
    iny
    iny
    iny
    jmp @hide_loop

@hide_all_lives:
    ldy #20
    jmp @hide_loop

@done:
    rts

; -----------------------------------------------------------------------------
; Move Enemies
; -----------------------------------------------------------------------------
move_enemies:
    ldx #0

@enemy_loop:
    lda enemy_x, x
    clc
    adc enemy_dir_x, x
    sta enemy_x, x

    cmp #ARENA_LEFT
    bcs @check_right
    lda #DIR_RIGHT
    sta enemy_dir_x, x
    lda #ARENA_LEFT
    sta enemy_x, x
    jmp @move_y

@check_right:
    cmp #ARENA_RIGHT
    bcc @move_y
    lda #DIR_LEFT
    sta enemy_dir_x, x
    lda #ARENA_RIGHT
    sec
    sbc #1
    sta enemy_x, x

@move_y:
    lda enemy_y, x
    clc
    adc enemy_dir_y, x
    sta enemy_y, x

    cmp #ARENA_TOP
    bcs @check_bottom
    lda #DIR_DOWN
    sta enemy_dir_y, x
    lda #ARENA_TOP
    sta enemy_y, x
    jmp @next_enemy

@check_bottom:
    cmp #ARENA_BOTTOM
    bcc @next_enemy
    lda #DIR_UP
    sta enemy_dir_y, x
    lda #ARENA_BOTTOM
    sec
    sbc #1
    sta enemy_y, x

@next_enemy:
    inx
    cpx #NUM_ENEMIES
    bne @enemy_loop
    rts

; -----------------------------------------------------------------------------
; Load Palette
; -----------------------------------------------------------------------------
load_palette:
    bit PPUSTATUS
    lda #$3F
    sta PPUADDR
    lda #$00
    sta PPUADDR
    ldx #0
@loop:
    lda palette_data, x
    sta PPUDATA
    inx
    cpx #32
    bne @loop
    rts

; -----------------------------------------------------------------------------
; Draw Arena
; -----------------------------------------------------------------------------
draw_arena:
    bit PPUSTATUS
    lda #$20
    sta PPUADDR
    lda #$00
    sta PPUADDR

    lda #0
    sta row_counter

@draw_row:
    lda row_counter
    cmp #0
    beq @top_row
    cmp #1
    beq @top_row
    cmp #28
    beq @bottom_row
    cmp #29
    beq @bottom_row
    jmp @middle_row

@top_row:
    lda row_counter
    cmp #0
    bne @top_row_inner
    lda #TILE_CORNER_TL
    sta PPUDATA
    lda #TILE_BORDER
    ldx #30
@top_fill:
    sta PPUDATA
    dex
    bne @top_fill
    lda #TILE_CORNER_TR
    sta PPUDATA
    jmp @next_row

@top_row_inner:
    lda #TILE_BORDER
    ldx #32
@top_inner_fill:
    sta PPUDATA
    dex
    bne @top_inner_fill
    jmp @next_row

@bottom_row:
    lda row_counter
    cmp #29
    bne @bottom_row_inner
    lda #TILE_CORNER_BL
    sta PPUDATA
    lda #TILE_BORDER
    ldx #30
@bottom_fill:
    sta PPUDATA
    dex
    bne @bottom_fill
    lda #TILE_CORNER_BR
    sta PPUDATA
    jmp @next_row

@bottom_row_inner:
    lda #TILE_BORDER
    ldx #32
@bottom_inner_fill:
    sta PPUDATA
    dex
    bne @bottom_inner_fill
    jmp @next_row

@middle_row:
    lda #TILE_BORDER
    sta PPUDATA
    sta PPUDATA
    lda #TILE_FLOOR
    ldx #28
@floor_fill:
    sta PPUDATA
    dex
    bne @floor_fill
    lda #TILE_BORDER
    sta PPUDATA
    sta PPUDATA

@next_row:
    inc row_counter
    lda row_counter
    cmp #30
    beq @done_drawing
    jmp @draw_row

@done_drawing:
    rts

; -----------------------------------------------------------------------------
; Set Attribute Table
; -----------------------------------------------------------------------------
set_attributes:
    bit PPUSTATUS
    lda #$23
    sta PPUADDR
    lda #$C0
    sta PPUADDR

    ldx #8
    lda #$00
@attr_top:
    sta PPUDATA
    dex
    bne @attr_top

    ldx #6
@attr_floor:
    lda #$00
    sta PPUDATA
    lda #%01010101
    sta PPUDATA
    sta PPUDATA
    sta PPUDATA
    sta PPUDATA
    sta PPUDATA
    sta PPUDATA
    lda #$00
    sta PPUDATA
    dex
    bne @attr_floor

    ldx #8
    lda #$00
@attr_bottom:
    sta PPUDATA
    dex
    bne @attr_bottom
    rts

; -----------------------------------------------------------------------------
; Read Controller
; -----------------------------------------------------------------------------
read_controller:
    lda #1
    sta JOYPAD1
    lda #0
    sta JOYPAD1
    ldx #8
@read_loop:
    lda JOYPAD1
    lsr a
    rol buttons
    dex
    bne @read_loop
    rts

; -----------------------------------------------------------------------------
; Move Player
; -----------------------------------------------------------------------------
move_player:
    lda buttons
    and #BTN_UP
    beq @check_down
    lda player_y
    sec
    sbc #PLAYER_SPEED
    cmp #ARENA_TOP
    bcc @check_down
    sta player_y

@check_down:
    lda buttons
    and #BTN_DOWN
    beq @check_left
    lda player_y
    clc
    adc #PLAYER_SPEED
    cmp #ARENA_BOTTOM
    bcs @check_left
    sta player_y

@check_left:
    lda buttons
    and #BTN_LEFT
    beq @check_right
    lda player_x
    sec
    sbc #PLAYER_SPEED
    cmp #ARENA_LEFT
    bcc @check_right
    sta player_x

@check_right:
    lda buttons
    and #BTN_RIGHT
    beq @done
    lda player_x
    clc
    adc #PLAYER_SPEED
    cmp #ARENA_RIGHT
    bcs @done
    sta player_x

@done:
    rts

; === NMI ===
nmi:
    pha
    txa
    pha
    tya
    pha

    lda #0
    sta OAMADDR
    lda #>oam_buffer
    sta OAMDMA

    inc frame_count

    lda #0
    sta PPUSCROLL
    sta PPUSCROLL

    pla
    tay
    pla
    tax
    pla
    rti

irq:
    rti

; -----------------------------------------------------------------------------
; Data
; -----------------------------------------------------------------------------
palette_data:
    .byte BG_COLOUR, $11, $21, $31
    .byte BG_COLOUR, $13, $23, $33
    .byte BG_COLOUR, $19, $29, $39
    .byte BG_COLOUR, $16, $26, $36
    .byte BG_COLOUR, $30, $27, $17    ; Player + lives
    .byte BG_COLOUR, $16, $26, $36    ; Enemies
    .byte BG_COLOUR, $30, $27, $17
    .byte BG_COLOUR, $30, $27, $17

; -----------------------------------------------------------------------------
; Vectors
; -----------------------------------------------------------------------------
.segment "VECTORS"
    .word nmi
    .word reset
    .word irq

; -----------------------------------------------------------------------------
; CHR-ROM
; -----------------------------------------------------------------------------
.segment "CHARS"

; Tile 0: Empty
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00

; Tile 1: Border
.byte %11111111,%10000001,%10000001,%11111111
.byte %11111111,%00010001,%00010001,%11111111
.byte %00000000,%01111110,%01111110,%00000000
.byte %00000000,%11101110,%11101110,%00000000

; Tile 2: Floor
.byte %00000000,%00000000,%00000000,%00000000
.byte %00000000,%00000000,%00000000,%10000001
.byte %00000000,%00000000,%00000000,%00000000
.byte %00000000,%00000000,%00000000,%00000000

; Tile 3: Corner TL
.byte %11111111,%11000000,%10100000,%10010000
.byte %10001000,%10000100,%10000010,%10000001
.byte %00000000,%00111111,%01011111,%01101111
.byte %01110111,%01111011,%01111101,%01111110

; Tile 4: Corner TR
.byte %11111111,%00000011,%00000101,%00001001
.byte %00010001,%00100001,%01000001,%10000001
.byte %00000000,%11111100,%11111010,%11110110
.byte %11101110,%11011110,%10111110,%01111110

; Tile 5: Corner BL
.byte %10000001,%10000010,%10000100,%10001000
.byte %10010000,%10100000,%11000000,%11111111
.byte %01111110,%01111101,%01111011,%01110111
.byte %01101111,%01011111,%00111111,%00000000

; Tile 6: Corner BR
.byte %10000001,%01000001,%00100001,%00010001
.byte %00001001,%00000101,%00000011,%11111111
.byte %01111110,%10111110,%11011110,%11101110
.byte %11110110,%11111010,%11111100,%00000000

; Tile 7: Player
.byte %00011000,%00011000,%00111100,%01111110
.byte %11111111,%10111101,%00100100,%00100100
.byte %00000000,%00011000,%00011000,%00111100
.byte %01000010,%01000010,%00011000,%00000000

; Tile 8: Enemy
.byte %00011000,%00111100,%01111110,%11111111
.byte %11111111,%01111110,%00111100,%00011000
.byte %00000000,%00011000,%00100100,%01000010
.byte %01000010,%00100100,%00011000,%00000000

; Tile 9: Life icon (small heart/ship)
.byte %00000000,%00100100,%01111110,%01111110
.byte %00111100,%00011000,%00000000,%00000000
.byte %00000000,%00000000,%00000000,%00100100
.byte %00011000,%00000000,%00000000,%00000000

; Fill rest
.res 8192 - 160, $00

Build It

ca65 nexus.asm -o nexus.o
ld65 -C nes.cfg nexus.o -o nexus.nes

Die three times and it’s over. The game now has stakes.

Next

Death is the stick. Unit 11 adds the carrot: collectibles.

What Changed

Unit 9 → Unit 10
+274-151
11 ; =============================================================================
2-; NEON NEXUS - Unit 9: Collision Detection
2+; NEON NEXUS - Unit 10: Lives and Death
33 ; =============================================================================
4-; Detect when player touches enemies. Visual feedback on collision.
4+; Add lives system with death and respawn.
55 ; =============================================================================
66
77 ; -----------------------------------------------------------------------------
...
3232 ; -----------------------------------------------------------------------------
3333 ; Game Constants
3434 ; -----------------------------------------------------------------------------
35-PLAYER_START_X = 124
36-PLAYER_START_Y = 116
37-PLAYER_SPEED = 2
38-ENEMY_SPEED = 1
35+PLAYER_START_X = 124
36+PLAYER_START_Y = 116
37+PLAYER_SPEED = 2
38+ENEMY_SPEED = 1
3939
40-NUM_ENEMIES = 4
41-SPRITE_SIZE = 8 ; 8x8 pixel sprites
42-COLLISION_DIST = 6 ; Overlap distance for collision
40+NUM_ENEMIES = 4
41+STARTING_LIVES = 3
42+COLLISION_DIST = 6
43+INVULN_TIME = 90 ; 1.5 seconds of invulnerability
4344
4445 ; Tile indices
4546 TILE_EMPTY = 0
...
5253
5354 SPRITE_PLAYER = 7
5455 SPRITE_ENEMY = 8
56+SPRITE_LIFE = 9 ; Life icon
5557
5658 ; Arena boundaries
5759 ARENA_LEFT = 16
5860 ARENA_RIGHT = 232
59-ARENA_TOP = 16
61+ARENA_TOP = 24 ; Leave room for HUD
6062 ARENA_BOTTOM = 208
6163
6264 DIR_RIGHT = 1
...
7072 ; Memory Layout
7173 ; -----------------------------------------------------------------------------
7274 .segment "ZEROPAGE"
73-player_x: .res 1
74-player_y: .res 1
75-buttons: .res 1
76-temp: .res 1
77-temp2: .res 1
78-row_counter: .res 1
79-frame_count: .res 1
80-collision_flag: .res 1 ; Non-zero if collision detected
81-flash_timer: .res 1 ; Timer for visual feedback
75+player_x: .res 1
76+player_y: .res 1
77+buttons: .res 1
78+temp: .res 1
79+row_counter: .res 1
80+frame_count: .res 1
81+lives: .res 1
82+invuln_timer: .res 1 ; Invulnerability countdown
83+game_over: .res 1 ; Non-zero when game is over
8284
83-enemy_x: .res NUM_ENEMIES
84-enemy_y: .res NUM_ENEMIES
85-enemy_dir_x: .res NUM_ENEMIES
86-enemy_dir_y: .res NUM_ENEMIES
85+enemy_x: .res NUM_ENEMIES
86+enemy_y: .res NUM_ENEMIES
87+enemy_dir_x: .res NUM_ENEMIES
88+enemy_dir_y: .res NUM_ENEMIES
8789
8890 .segment "OAM"
89-oam_buffer: .res 256
91+oam_buffer: .res 256
9092
9193 .segment "BSS"
9294
...
142144 jsr load_palette
143145 jsr draw_arena
144146 jsr set_attributes
145- jsr init_enemies
146-
147- lda #PLAYER_START_X
148- sta player_x
149- lda #PLAYER_START_Y
150- sta player_y
151-
152- lda #0
153- sta collision_flag
154- sta flash_timer
155-
156- ; Player sprite
157- lda player_y
158- sta oam_buffer+0
159- lda #SPRITE_PLAYER
160- sta oam_buffer+1
161- lda #0
162- sta oam_buffer+2
163- lda player_x
164- sta oam_buffer+3
165-
166- jsr update_enemy_sprites
147+ jsr init_game
167148
168- ; Hide remaining sprites
149+ ; Hide all sprites initially
169150 lda #$FF
170- ldx #20
171-@hide_sprites:
151+ ldx #0
152+@hide_all:
172153 sta oam_buffer, x
173154 inx
174- bne @hide_sprites
155+ bne @hide_all
156+
157+ jsr update_all_sprites
175158
176159 lda #0
177160 sta PPUSCROLL
...
184167 sta PPUMASK
185168
186169 main_loop:
170+ lda game_over
171+ bne @game_over_loop
172+
187173 jsr read_controller
188174 jsr move_player
189175 jsr move_enemies
190176 jsr check_collisions
191- jsr update_player_sprite
192- jsr update_enemy_sprites
177+ jsr update_all_sprites
193178 jmp main_loop
179+
180+@game_over_loop:
181+ ; Game over - just loop (player can see final state)
182+ jmp @game_over_loop
194183
195184 ; -----------------------------------------------------------------------------
196-; Check Collisions - Player vs all enemies
185+; Initialise Game
197186 ; -----------------------------------------------------------------------------
198-check_collisions:
187+init_game:
188+ lda #STARTING_LIVES
189+ sta lives
199190 lda #0
200- sta collision_flag
191+ sta game_over
192+ sta invuln_timer
193+
194+ lda #PLAYER_START_X
195+ sta player_x
196+ lda #PLAYER_START_Y
197+ sta player_y
198+
199+ jsr init_enemies
200+ rts
201+
202+; -----------------------------------------------------------------------------
203+; Initialise Enemies
204+; -----------------------------------------------------------------------------
205+init_enemies:
206+ lda #48
207+ sta enemy_x+0
208+ lda #48
209+ sta enemy_y+0
210+ lda #DIR_RIGHT
211+ sta enemy_dir_x+0
212+ lda #DIR_DOWN
213+ sta enemy_dir_y+0
214+
215+ lda #200
216+ sta enemy_x+1
217+ lda #48
218+ sta enemy_y+1
219+ lda #DIR_LEFT
220+ sta enemy_dir_x+1
221+ lda #DIR_DOWN
222+ sta enemy_dir_y+1
223+
224+ lda #48
225+ sta enemy_x+2
226+ lda #176
227+ sta enemy_y+2
228+ lda #DIR_RIGHT
229+ sta enemy_dir_x+2
230+ lda #DIR_UP
231+ sta enemy_dir_y+2
232+
233+ lda #200
234+ sta enemy_x+3
235+ lda #176
236+ sta enemy_y+3
237+ lda #DIR_LEFT
238+ sta enemy_dir_x+3
239+ lda #DIR_UP
240+ sta enemy_dir_y+3
241+ rts
242+
243+; -----------------------------------------------------------------------------
244+; Check Collisions
245+; -----------------------------------------------------------------------------
246+check_collisions:
247+ ; Skip if invulnerable
248+ lda invuln_timer
249+ beq @check
250+ dec invuln_timer
251+ rts
201252
253+@check:
202254 ldx #0
203255 @check_enemy:
204- ; Check X overlap
205- ; |player_x - enemy_x| < COLLISION_DIST
206256 lda player_x
207257 sec
208258 sbc enemy_x, x
209- bpl @check_x_positive
210- ; Negative - negate it
259+ bpl @check_x_pos
211260 eor #$FF
212261 clc
213262 adc #1
214-@check_x_positive:
263+@check_x_pos:
215264 cmp #COLLISION_DIST
216- bcs @next_enemy ; No X overlap
265+ bcs @next_enemy
217266
218- ; Check Y overlap
219267 lda player_y
220268 sec
221269 sbc enemy_y, x
222- bpl @check_y_positive
270+ bpl @check_y_pos
223271 eor #$FF
224272 clc
225273 adc #1
226-@check_y_positive:
274+@check_y_pos:
227275 cmp #COLLISION_DIST
228- bcs @next_enemy ; No Y overlap
276+ bcs @next_enemy
229277
230- ; Collision detected!
231- lda #1
232- sta collision_flag
233- lda #30 ; Flash for 30 frames
234- sta flash_timer
235- rts ; Exit early on first collision
278+ ; Collision! Lose a life
279+ jsr player_hit
280+ rts
236281
237282 @next_enemy:
238283 inx
...
241286 rts
242287
243288 ; -----------------------------------------------------------------------------
244-; Update Player Sprite - Flash when hit
289+; Player Hit - Lose life and respawn
290+; -----------------------------------------------------------------------------
291+player_hit:
292+ dec lives
293+ lda lives
294+ beq @game_over
295+
296+ ; Respawn at start position
297+ lda #PLAYER_START_X
298+ sta player_x
299+ lda #PLAYER_START_Y
300+ sta player_y
301+
302+ ; Start invulnerability
303+ lda #INVULN_TIME
304+ sta invuln_timer
305+ rts
306+
307+@game_over:
308+ lda #1
309+ sta game_over
310+ rts
311+
312+; -----------------------------------------------------------------------------
313+; Update All Sprites
314+; -----------------------------------------------------------------------------
315+update_all_sprites:
316+ jsr update_player_sprite
317+ jsr update_enemy_sprites
318+ jsr update_lives_display
319+ rts
320+
321+; -----------------------------------------------------------------------------
322+; Update Player Sprite
245323 ; -----------------------------------------------------------------------------
246324 update_player_sprite:
247- ; Update position
248- lda player_y
325+ ; Check if game over
326+ lda game_over
327+ beq @alive
328+ ; Hide player
329+ lda #$FF
249330 sta oam_buffer+0
250- lda player_x
251- sta oam_buffer+3
331+ rts
252332
253- ; Check if flashing
254- lda flash_timer
333+@alive:
334+ ; Check invulnerability flash
335+ lda invuln_timer
255336 beq @no_flash
256-
257- ; Decrement timer
258- dec flash_timer
259-
260- ; Flash by toggling visibility every 4 frames
261337 and #%00000100
262- beq @show_player
263- ; Hide player (move off screen)
338+ beq @show
339+ ; Hide during flash
264340 lda #$FF
265341 sta oam_buffer+0
266342 rts
267343
268-@show_player:
344+@show:
345+@no_flash:
269346 lda player_y
270347 sta oam_buffer+0
348+ lda #SPRITE_PLAYER
349+ sta oam_buffer+1
350+ lda #0
351+ sta oam_buffer+2
352+ lda player_x
353+ sta oam_buffer+3
271354 rts
272355
273-@no_flash:
356+; -----------------------------------------------------------------------------
357+; Update Enemy Sprites
358+; -----------------------------------------------------------------------------
359+update_enemy_sprites:
360+ ldx #0
361+ ldy #4 ; Start after player sprite
362+
363+@loop:
364+ lda enemy_y, x
365+ sta oam_buffer, y
366+ iny
367+ lda #SPRITE_ENEMY
368+ sta oam_buffer, y
369+ iny
370+ lda #%00000001
371+ sta oam_buffer, y
372+ iny
373+ lda enemy_x, x
374+ sta oam_buffer, y
375+ iny
376+ inx
377+ cpx #NUM_ENEMIES
378+ bne @loop
274379 rts
275380
276381 ; -----------------------------------------------------------------------------
277-; Initialise Enemies
382+; Update Lives Display - Show life icons at top of screen
278383 ; -----------------------------------------------------------------------------
279-init_enemies:
280- lda #48
281- sta enemy_x+0
282- lda #48
283- sta enemy_y+0
284- lda #DIR_RIGHT
285- sta enemy_dir_x+0
286- lda #DIR_DOWN
287- sta enemy_dir_y+0
384+update_lives_display:
385+ ; OAM slots 20-31 for lives display (up to 3 lives)
386+ ldy #20 ; OAM offset
288387
289- lda #200
290- sta enemy_x+1
291- lda #48
292- sta enemy_y+1
293- lda #DIR_LEFT
294- sta enemy_dir_x+1
295- lda #DIR_DOWN
296- sta enemy_dir_y+1
388+ ldx lives
389+ beq @hide_all_lives
297390
298- lda #48
299- sta enemy_x+2
300- lda #176
301- sta enemy_y+2
302- lda #DIR_RIGHT
303- sta enemy_dir_x+2
304- lda #DIR_UP
305- sta enemy_dir_y+2
391+ ; Show life 1
392+ lda #8 ; Y position (top of screen)
393+ sta oam_buffer, y
394+ iny
395+ lda #SPRITE_LIFE
396+ sta oam_buffer, y
397+ iny
398+ lda #0
399+ sta oam_buffer, y
400+ iny
401+ lda #16 ; X position
402+ sta oam_buffer, y
403+ iny
306404
307- lda #200
308- sta enemy_x+3
309- lda #176
310- sta enemy_y+3
311- lda #DIR_LEFT
312- sta enemy_dir_x+3
313- lda #DIR_UP
314- sta enemy_dir_y+3
405+ cpx #1
406+ beq @hide_rest
407+
408+ ; Show life 2
409+ lda #8
410+ sta oam_buffer, y
411+ iny
412+ lda #SPRITE_LIFE
413+ sta oam_buffer, y
414+ iny
415+ lda #0
416+ sta oam_buffer, y
417+ iny
418+ lda #26
419+ sta oam_buffer, y
420+ iny
421+
422+ cpx #2
423+ beq @hide_rest
424+
425+ ; Show life 3
426+ lda #8
427+ sta oam_buffer, y
428+ iny
429+ lda #SPRITE_LIFE
430+ sta oam_buffer, y
431+ iny
432+ lda #0
433+ sta oam_buffer, y
434+ iny
435+ lda #36
436+ sta oam_buffer, y
437+ iny
438+ rts
439+
440+@hide_rest:
441+ ; Hide remaining life slots
442+@hide_loop:
443+ cpy #32
444+ bcs @done
445+ lda #$FF
446+ sta oam_buffer, y
447+ iny
448+ iny
449+ iny
450+ iny
451+ jmp @hide_loop
452+
453+@hide_all_lives:
454+ ldy #20
455+ jmp @hide_loop
315456
457+@done:
316458 rts
317459
318460 ; -----------------------------------------------------------------------------
...
373515 inx
374516 cpx #NUM_ENEMIES
375517 bne @enemy_loop
376- rts
377-
378-; -----------------------------------------------------------------------------
379-; Update Enemy Sprites
380-; -----------------------------------------------------------------------------
381-update_enemy_sprites:
382- ldx #0
383- ldy #4
384-
385-@loop:
386- lda enemy_y, x
387- sta oam_buffer, y
388- iny
389- lda #SPRITE_ENEMY
390- sta oam_buffer, y
391- iny
392- lda #%00000001
393- sta oam_buffer, y
394- iny
395- lda enemy_x, x
396- sta oam_buffer, y
397- iny
398- inx
399- cpx #NUM_ENEMIES
400- bne @loop
401518 rts
402519
403520 ; -----------------------------------------------------------------------------
...
535652 bne @attr_top
536653
537654 ldx #6
538-@attr_floor_rows:
655+@attr_floor:
539656 lda #$00
540657 sta PPUDATA
541658 lda #%01010101
...
548665 lda #$00
549666 sta PPUDATA
550667 dex
551- bne @attr_floor_rows
668+ bne @attr_floor
552669
553670 ldx #8
554671 lda #$00
...
661778 .byte BG_COLOUR, $11, $21, $31
662779 .byte BG_COLOUR, $13, $23, $33
663780 .byte BG_COLOUR, $19, $29, $39
664- .byte BG_COLOUR, $16, $26, $36
665- .byte BG_COLOUR, $30, $27, $17
666781 .byte BG_COLOUR, $16, $26, $36
782+ .byte BG_COLOUR, $30, $27, $17 ; Player + lives
783+ .byte BG_COLOUR, $16, $26, $36 ; Enemies
667784 .byte BG_COLOUR, $30, $27, $17
668785 .byte BG_COLOUR, $30, $27, $17
669786
...
731848 .byte %11111111,%01111110,%00111100,%00011000
732849 .byte %00000000,%00011000,%00100100,%01000010
733850 .byte %01000010,%00100100,%00011000,%00000000
851+
852+; Tile 9: Life icon (small heart/ship)
853+.byte %00000000,%00100100,%01111110,%01111110
854+.byte %00111100,%00011000,%00000000,%00000000
855+.byte %00000000,%00000000,%00000000,%00100100
856+.byte %00011000,%00000000,%00000000,%00000000
734857
735858 ; Fill rest
736-.res 8192 - 144, $00
859+.res 8192 - 160, $00
737860