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

Collectibles

Add data cores that the player can collect.

17% of Neon Nexus

What You’re Building

Data cores. Collect them to win.

Collectibles

Green diamonds scattered around the arena. Touch them and they disappear.

Item Data

Each item has a position and an active flag:

NUM_ITEMS = 4

item_x:       .res NUM_ITEMS
item_y:       .res NUM_ITEMS
item_active:  .res NUM_ITEMS   ; Non-zero if item exists

Collection Detection

Same distance check as enemy collision, but the result is different:

check_item_collisions:
    ldx #0
@check_item:
    lda item_active, x
    beq @next_item      ; Skip if already collected

    ; Distance check (same as enemy collision)
    ; ...

    ; Collected!
    lda #0
    sta item_active, x   ; Deactivate item
    inc items_collected

@next_item:
    inx
    cpx #NUM_ITEMS
    bne @check_item
    rts

Item Sprites

Items use sprite palette 2 (green) to distinguish them from enemies (red):

update_item_sprites:
    ldx #0
    ldy #20             ; OAM offset

@loop:
    lda item_active, x
    beq @hide_item

    lda item_y, x
    sta oam_buffer, y
    iny
    lda #SPRITE_ITEM
    sta oam_buffer, y
    iny
    lda #%00000010      ; Palette 2 (green)
    sta oam_buffer, y
    ; ...

The Code

; =============================================================================
; NEON NEXUS - Unit 11: Collectibles
; =============================================================================
; Add data cores that the player can collect.
; =============================================================================

PPUCTRL   = $2000
PPUMASK   = $2001
PPUSTATUS = $2002
OAMADDR   = $2003
PPUSCROLL = $2005
PPUADDR   = $2006
PPUDATA   = $2007
OAMDMA    = $4014
JOYPAD1   = $4016

BTN_UP     = %00001000
BTN_DOWN   = %00000100
BTN_LEFT   = %00000010
BTN_RIGHT  = %00000001

PLAYER_START_X  = 124
PLAYER_START_Y  = 116
PLAYER_SPEED    = 2
ENEMY_SPEED     = 1
NUM_ENEMIES     = 4
NUM_ITEMS       = 4
STARTING_LIVES  = 3
COLLISION_DIST  = 6
COLLECT_DIST    = 8
INVULN_TIME     = 90

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
SPRITE_ITEM    = 10

ARENA_LEFT   = 16
ARENA_RIGHT  = 232
ARENA_TOP    = 24
ARENA_BOTTOM = 208

DIR_RIGHT = 1
DIR_LEFT  = $FF
DIR_DOWN  = 1
DIR_UP    = $FF
BG_COLOUR = $0F

.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
game_over:      .res 1
items_collected: .res 1

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

item_x:         .res NUM_ITEMS
item_y:         .res NUM_ITEMS
item_active:    .res NUM_ITEMS   ; Non-zero if item exists

.segment "OAM"
oam_buffer:     .res 256

.segment "BSS"

.segment "HEADER"
    .byte "NES", $1A, 2, 1, $01, $00, 0,0,0,0,0,0,0,0

.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

    lda #$FF
    ldx #0
@hide_all:
    sta oam_buffer, x
    inx
    bne @hide_all

    jsr update_all_sprites

    lda #0
    sta PPUSCROLL
    sta PPUSCROLL

    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_enemy_collisions
    jsr check_item_collisions
    jsr update_all_sprites
    jmp main_loop

@game_over_loop:
    jmp @game_over_loop

init_game:
    lda #STARTING_LIVES
    sta lives
    lda #0
    sta game_over
    sta invuln_timer
    sta items_collected

    lda #PLAYER_START_X
    sta player_x
    lda #PLAYER_START_Y
    sta player_y

    jsr init_enemies
    jsr init_items
    rts

init_enemies:
    lda #48
    sta enemy_x+0
    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

init_items:
    ; Place 4 items around the arena
    lda #80
    sta item_x+0
    lda #64
    sta item_y+0
    lda #1
    sta item_active+0

    lda #168
    sta item_x+1
    lda #64
    sta item_y+1
    lda #1
    sta item_active+1

    lda #80
    sta item_x+2
    lda #160
    sta item_y+2
    lda #1
    sta item_active+2

    lda #168
    sta item_x+3
    lda #160
    sta item_y+3
    lda #1
    sta item_active+3
    rts

check_enemy_collisions:
    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

    jsr player_hit
    rts

@next_enemy:
    inx
    cpx #NUM_ENEMIES
    bne @check_enemy
    rts

check_item_collisions:
    ldx #0
@check_item:
    lda item_active, x
    beq @next_item

    lda player_x
    sec
    sbc item_x, x
    bpl @item_x_pos
    eor #$FF
    clc
    adc #1
@item_x_pos:
    cmp #COLLECT_DIST
    bcs @next_item

    lda player_y
    sec
    sbc item_y, x
    bpl @item_y_pos
    eor #$FF
    clc
    adc #1
@item_y_pos:
    cmp #COLLECT_DIST
    bcs @next_item

    ; Collected!
    lda #0
    sta item_active, x
    inc items_collected

@next_item:
    inx
    cpx #NUM_ITEMS
    bne @check_item
    rts

player_hit:
    dec lives
    lda lives
    beq @game_over

    lda #PLAYER_START_X
    sta player_x
    lda #PLAYER_START_Y
    sta player_y

    lda #INVULN_TIME
    sta invuln_timer
    rts

@game_over:
    lda #1
    sta game_over
    rts

update_all_sprites:
    jsr update_player_sprite
    jsr update_enemy_sprites
    jsr update_item_sprites
    jsr update_lives_display
    rts

update_player_sprite:
    lda game_over
    beq @alive
    lda #$FF
    sta oam_buffer+0
    rts

@alive:
    lda invuln_timer
    beq @show
    and #%00000100
    beq @show
    lda #$FF
    sta oam_buffer+0
    rts

@show:
    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:
    ldx #0
    ldy #4
@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_item_sprites:
    ldx #0
    ldy #20             ; OAM offset after enemies
@loop:
    lda item_active, x
    beq @hide_item

    lda item_y, x
    sta oam_buffer, y
    iny
    lda #SPRITE_ITEM
    sta oam_buffer, y
    iny
    lda #%00000010      ; Palette 2 (green)
    sta oam_buffer, y
    iny
    lda item_x, x
    sta oam_buffer, y
    iny
    jmp @next_item

@hide_item:
    lda #$FF
    sta oam_buffer, y
    iny
    iny
    iny
    iny

@next_item:
    inx
    cpx #NUM_ITEMS
    bne @loop
    rts

update_lives_display:
    ldy #36             ; OAM offset for lives
    ldx lives
    beq @hide_all

    lda #8
    sta oam_buffer, y
    iny
    lda #SPRITE_LIFE
    sta oam_buffer, y
    iny
    lda #0
    sta oam_buffer, y
    iny
    lda #16
    sta oam_buffer, y
    iny

    cpx #1
    beq @hide_rest

    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

    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
    rts

@hide_rest:
@hide_all:
    rts

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:
    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:
    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_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:
    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:
    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:
    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

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
    .byte BG_COLOUR, $16, $26, $36    ; Enemies
    .byte BG_COLOUR, $1A, $2A, $3A    ; Items (green)
    .byte BG_COLOUR, $30, $27, $17

.segment "VECTORS"
    .word nmi
    .word reset
    .word irq

.segment "CHARS"
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.byte %11111111,%10000001,%10000001,%11111111,%11111111,%00010001,%00010001,%11111111
.byte %00000000,%01111110,%01111110,%00000000,%00000000,%11101110,%11101110,%00000000
.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%10000001
.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000
.byte %11111111,%11000000,%10100000,%10010000,%10001000,%10000100,%10000010,%10000001
.byte %00000000,%00111111,%01011111,%01101111,%01110111,%01111011,%01111101,%01111110
.byte %11111111,%00000011,%00000101,%00001001,%00010001,%00100001,%01000001,%10000001
.byte %00000000,%11111100,%11111010,%11110110,%11101110,%11011110,%10111110,%01111110
.byte %10000001,%10000010,%10000100,%10001000,%10010000,%10100000,%11000000,%11111111
.byte %01111110,%01111101,%01111011,%01110111,%01101111,%01011111,%00111111,%00000000
.byte %10000001,%01000001,%00100001,%00010001,%00001001,%00000101,%00000011,%11111111
.byte %01111110,%10111110,%11011110,%11101110,%11110110,%11111010,%11111100,%00000000
.byte %00011000,%00011000,%00111100,%01111110,%11111111,%10111101,%00100100,%00100100
.byte %00000000,%00011000,%00011000,%00111100,%01000010,%01000010,%00011000,%00000000
.byte %00011000,%00111100,%01111110,%11111111,%11111111,%01111110,%00111100,%00011000
.byte %00000000,%00011000,%00100100,%01000010,%01000010,%00100100,%00011000,%00000000
.byte %00000000,%00100100,%01111110,%01111110,%00111100,%00011000,%00000000,%00000000
.byte %00000000,%00000000,%00000000,%00100100,%00011000,%00000000,%00000000,%00000000
; Tile 10: Item (data core - diamond)
.byte %00000000,%00011000,%00111100,%01111110,%01111110,%00111100,%00011000,%00000000
.byte %00000000,%00000000,%00011000,%00100100,%00100100,%00011000,%00000000,%00000000

.res 8192 - 176, $00

Build It

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

Collect the data cores. They vanish when touched.

Next

Collection feels good, but there’s no score yet. Unit 12 adds scoring.

What Changed

Unit 10 → Unit 11
+159-228
11 ; =============================================================================
2-; NEON NEXUS - Unit 10: Lives and Death
2+; NEON NEXUS - Unit 11: Collectibles
33 ; =============================================================================
4-; Add lives system with death and respawn.
4+; Add data cores that the player can collect.
55 ; =============================================================================
66
7-; -----------------------------------------------------------------------------
8-; NES Hardware Addresses
9-; -----------------------------------------------------------------------------
107 PPUCTRL = $2000
118 PPUMASK = $2001
129 PPUSTATUS = $2002
...
1512 PPUADDR = $2006
1613 PPUDATA = $2007
1714 OAMDMA = $4014
18-
1915 JOYPAD1 = $4016
20-JOYPAD2 = $4017
2116
22-; Controller buttons
23-BTN_A = %10000000
24-BTN_B = %01000000
25-BTN_SELECT = %00100000
26-BTN_START = %00010000
2717 BTN_UP = %00001000
2818 BTN_DOWN = %00000100
2919 BTN_LEFT = %00000010
3020 BTN_RIGHT = %00000001
3121
32-; -----------------------------------------------------------------------------
33-; Game Constants
34-; -----------------------------------------------------------------------------
3522 PLAYER_START_X = 124
3623 PLAYER_START_Y = 116
3724 PLAYER_SPEED = 2
3825 ENEMY_SPEED = 1
39-
4026 NUM_ENEMIES = 4
27+NUM_ITEMS = 4
4128 STARTING_LIVES = 3
4229 COLLISION_DIST = 6
43-INVULN_TIME = 90 ; 1.5 seconds of invulnerability
30+COLLECT_DIST = 8
31+INVULN_TIME = 90
4432
45-; Tile indices
46-TILE_EMPTY = 0
4733 TILE_BORDER = 1
4834 TILE_FLOOR = 2
4935 TILE_CORNER_TL = 3
5036 TILE_CORNER_TR = 4
5137 TILE_CORNER_BL = 5
5238 TILE_CORNER_BR = 6
53-
5439 SPRITE_PLAYER = 7
5540 SPRITE_ENEMY = 8
56-SPRITE_LIFE = 9 ; Life icon
41+SPRITE_LIFE = 9
42+SPRITE_ITEM = 10
5743
58-; Arena boundaries
5944 ARENA_LEFT = 16
6045 ARENA_RIGHT = 232
61-ARENA_TOP = 24 ; Leave room for HUD
46+ARENA_TOP = 24
6247 ARENA_BOTTOM = 208
6348
6449 DIR_RIGHT = 1
6550 DIR_LEFT = $FF
6651 DIR_DOWN = 1
6752 DIR_UP = $FF
68-
6953 BG_COLOUR = $0F
7054
71-; -----------------------------------------------------------------------------
72-; Memory Layout
73-; -----------------------------------------------------------------------------
7455 .segment "ZEROPAGE"
7556 player_x: .res 1
7657 player_y: .res 1
...
7960 row_counter: .res 1
8061 frame_count: .res 1
8162 lives: .res 1
82-invuln_timer: .res 1 ; Invulnerability countdown
83-game_over: .res 1 ; Non-zero when game is over
63+invuln_timer: .res 1
64+game_over: .res 1
65+items_collected: .res 1
8466
8567 enemy_x: .res NUM_ENEMIES
8668 enemy_y: .res NUM_ENEMIES
8769 enemy_dir_x: .res NUM_ENEMIES
8870 enemy_dir_y: .res NUM_ENEMIES
71+
72+item_x: .res NUM_ITEMS
73+item_y: .res NUM_ITEMS
74+item_active: .res NUM_ITEMS ; Non-zero if item exists
8975
9076 .segment "OAM"
9177 oam_buffer: .res 256
9278
9379 .segment "BSS"
9480
95-; -----------------------------------------------------------------------------
96-; iNES Header
97-; -----------------------------------------------------------------------------
9881 .segment "HEADER"
99- .byte "NES", $1A
100- .byte 2
101- .byte 1
102- .byte $01
103- .byte $00
104- .byte 0,0,0,0,0,0,0,0
82+ .byte "NES", $1A, 2, 1, $01, $00, 0,0,0,0,0,0,0,0
10583
106-; -----------------------------------------------------------------------------
107-; Code
108-; -----------------------------------------------------------------------------
10984 .segment "CODE"
11085
11186 reset:
...
146121 jsr set_attributes
147122 jsr init_game
148123
149- ; Hide all sprites initially
150124 lda #$FF
151125 ldx #0
152126 @hide_all:
...
159133 lda #0
160134 sta PPUSCROLL
161135 sta PPUSCROLL
162- sta frame_count
163136
164137 lda #%10000000
165138 sta PPUCTRL
...
169142 main_loop:
170143 lda game_over
171144 bne @game_over_loop
172-
173145 jsr read_controller
174146 jsr move_player
175147 jsr move_enemies
176- jsr check_collisions
148+ jsr check_enemy_collisions
149+ jsr check_item_collisions
177150 jsr update_all_sprites
178151 jmp main_loop
179152
180153 @game_over_loop:
181- ; Game over - just loop (player can see final state)
182154 jmp @game_over_loop
183155
184-; -----------------------------------------------------------------------------
185-; Initialise Game
186-; -----------------------------------------------------------------------------
187156 init_game:
188157 lda #STARTING_LIVES
189158 sta lives
190159 lda #0
191160 sta game_over
192161 sta invuln_timer
162+ sta items_collected
193163
194164 lda #PLAYER_START_X
195165 sta player_x
...
197167 sta player_y
198168
199169 jsr init_enemies
170+ jsr init_items
200171 rts
201172
202-; -----------------------------------------------------------------------------
203-; Initialise Enemies
204-; -----------------------------------------------------------------------------
205173 init_enemies:
206174 lda #48
207175 sta enemy_x+0
208- lda #48
209176 sta enemy_y+0
210177 lda #DIR_RIGHT
211178 sta enemy_dir_x+0
...
240207 sta enemy_dir_y+3
241208 rts
242209
243-; -----------------------------------------------------------------------------
244-; Check Collisions
245-; -----------------------------------------------------------------------------
246-check_collisions:
247- ; Skip if invulnerable
210+init_items:
211+ ; Place 4 items around the arena
212+ lda #80
213+ sta item_x+0
214+ lda #64
215+ sta item_y+0
216+ lda #1
217+ sta item_active+0
218+
219+ lda #168
220+ sta item_x+1
221+ lda #64
222+ sta item_y+1
223+ lda #1
224+ sta item_active+1
225+
226+ lda #80
227+ sta item_x+2
228+ lda #160
229+ sta item_y+2
230+ lda #1
231+ sta item_active+2
232+
233+ lda #168
234+ sta item_x+3
235+ lda #160
236+ sta item_y+3
237+ lda #1
238+ sta item_active+3
239+ rts
240+
241+check_enemy_collisions:
248242 lda invuln_timer
249243 beq @check
250244 dec invuln_timer
...
275269 cmp #COLLISION_DIST
276270 bcs @next_enemy
277271
278- ; Collision! Lose a life
279272 jsr player_hit
280273 rts
281274
...
285278 bne @check_enemy
286279 rts
287280
288-; -----------------------------------------------------------------------------
289-; Player Hit - Lose life and respawn
290-; -----------------------------------------------------------------------------
281+check_item_collisions:
282+ ldx #0
283+@check_item:
284+ lda item_active, x
285+ beq @next_item
286+
287+ lda player_x
288+ sec
289+ sbc item_x, x
290+ bpl @item_x_pos
291+ eor #$FF
292+ clc
293+ adc #1
294+@item_x_pos:
295+ cmp #COLLECT_DIST
296+ bcs @next_item
297+
298+ lda player_y
299+ sec
300+ sbc item_y, x
301+ bpl @item_y_pos
302+ eor #$FF
303+ clc
304+ adc #1
305+@item_y_pos:
306+ cmp #COLLECT_DIST
307+ bcs @next_item
308+
309+ ; Collected!
310+ lda #0
311+ sta item_active, x
312+ inc items_collected
313+
314+@next_item:
315+ inx
316+ cpx #NUM_ITEMS
317+ bne @check_item
318+ rts
319+
291320 player_hit:
292321 dec lives
293322 lda lives
294323 beq @game_over
295324
296- ; Respawn at start position
297325 lda #PLAYER_START_X
298326 sta player_x
299327 lda #PLAYER_START_Y
300328 sta player_y
301329
302- ; Start invulnerability
303330 lda #INVULN_TIME
304331 sta invuln_timer
305332 rts
...
309336 sta game_over
310337 rts
311338
312-; -----------------------------------------------------------------------------
313-; Update All Sprites
314-; -----------------------------------------------------------------------------
315339 update_all_sprites:
316340 jsr update_player_sprite
317341 jsr update_enemy_sprites
342+ jsr update_item_sprites
318343 jsr update_lives_display
319344 rts
320345
321-; -----------------------------------------------------------------------------
322-; Update Player Sprite
323-; -----------------------------------------------------------------------------
324346 update_player_sprite:
325- ; Check if game over
326347 lda game_over
327348 beq @alive
328- ; Hide player
329349 lda #$FF
330350 sta oam_buffer+0
331351 rts
332352
333353 @alive:
334- ; Check invulnerability flash
335354 lda invuln_timer
336- beq @no_flash
355+ beq @show
337356 and #%00000100
338357 beq @show
339- ; Hide during flash
340358 lda #$FF
341359 sta oam_buffer+0
342360 rts
343361
344362 @show:
345-@no_flash:
346363 lda player_y
347364 sta oam_buffer+0
348365 lda #SPRITE_PLAYER
...
353370 sta oam_buffer+3
354371 rts
355372
356-; -----------------------------------------------------------------------------
357-; Update Enemy Sprites
358-; -----------------------------------------------------------------------------
359373 update_enemy_sprites:
360374 ldx #0
361- ldy #4 ; Start after player sprite
362-
375+ ldy #4
363376 @loop:
364377 lda enemy_y, x
365378 sta oam_buffer, y
...
378391 bne @loop
379392 rts
380393
381-; -----------------------------------------------------------------------------
382-; Update Lives Display - Show life icons at top of screen
383-; -----------------------------------------------------------------------------
384-update_lives_display:
385- ; OAM slots 20-31 for lives display (up to 3 lives)
386- ldy #20 ; OAM offset
394+update_item_sprites:
395+ ldx #0
396+ ldy #20 ; OAM offset after enemies
397+@loop:
398+ lda item_active, x
399+ beq @hide_item
400+
401+ lda item_y, x
402+ sta oam_buffer, y
403+ iny
404+ lda #SPRITE_ITEM
405+ sta oam_buffer, y
406+ iny
407+ lda #%00000010 ; Palette 2 (green)
408+ sta oam_buffer, y
409+ iny
410+ lda item_x, x
411+ sta oam_buffer, y
412+ iny
413+ jmp @next_item
414+
415+@hide_item:
416+ lda #$FF
417+ sta oam_buffer, y
418+ iny
419+ iny
420+ iny
421+ iny
422+
423+@next_item:
424+ inx
425+ cpx #NUM_ITEMS
426+ bne @loop
427+ rts
387428
429+update_lives_display:
430+ ldy #36 ; OAM offset for lives
388431 ldx lives
389- beq @hide_all_lives
432+ beq @hide_all
390433
391- ; Show life 1
392- lda #8 ; Y position (top of screen)
434+ lda #8
393435 sta oam_buffer, y
394436 iny
395437 lda #SPRITE_LIFE
...
398440 lda #0
399441 sta oam_buffer, y
400442 iny
401- lda #16 ; X position
443+ lda #16
402444 sta oam_buffer, y
403445 iny
404446
405447 cpx #1
406448 beq @hide_rest
407449
408- ; Show life 2
409450 lda #8
410451 sta oam_buffer, y
411452 iny
...
422463 cpx #2
423464 beq @hide_rest
424465
425- ; Show life 3
426466 lda #8
427467 sta oam_buffer, y
428468 iny
...
434474 iny
435475 lda #36
436476 sta oam_buffer, y
437- iny
438477 rts
439478
440479 @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
456-
457-@done:
480+@hide_all:
458481 rts
459482
460-; -----------------------------------------------------------------------------
461-; Move Enemies
462-; -----------------------------------------------------------------------------
463483 move_enemies:
464484 ldx #0
465-
466485 @enemy_loop:
467486 lda enemy_x, x
468487 clc
469488 adc enemy_dir_x, x
470489 sta enemy_x, x
471-
472490 cmp #ARENA_LEFT
473491 bcs @check_right
474492 lda #DIR_RIGHT
...
476494 lda #ARENA_LEFT
477495 sta enemy_x, x
478496 jmp @move_y
479-
480497 @check_right:
481498 cmp #ARENA_RIGHT
482499 bcc @move_y
...
486503 sec
487504 sbc #1
488505 sta enemy_x, x
489-
490506 @move_y:
491507 lda enemy_y, x
492508 clc
493509 adc enemy_dir_y, x
494510 sta enemy_y, x
495-
496511 cmp #ARENA_TOP
497512 bcs @check_bottom
498513 lda #DIR_DOWN
...
500515 lda #ARENA_TOP
501516 sta enemy_y, x
502517 jmp @next_enemy
503-
504518 @check_bottom:
505519 cmp #ARENA_BOTTOM
506520 bcc @next_enemy
...
510524 sec
511525 sbc #1
512526 sta enemy_y, x
513-
514527 @next_enemy:
515528 inx
516529 cpx #NUM_ENEMIES
517530 bne @enemy_loop
518531 rts
519532
520-; -----------------------------------------------------------------------------
521-; Load Palette
522-; -----------------------------------------------------------------------------
523533 load_palette:
524534 bit PPUSTATUS
525535 lda #$3F
...
535545 bne @loop
536546 rts
537547
538-; -----------------------------------------------------------------------------
539-; Draw Arena
540-; -----------------------------------------------------------------------------
541548 draw_arena:
542549 bit PPUSTATUS
543550 lda #$20
544551 sta PPUADDR
545552 lda #$00
546553 sta PPUADDR
547-
548554 lda #0
549555 sta row_counter
550-
551556 @draw_row:
552557 lda row_counter
553558 cmp #0
...
559564 cmp #29
560565 beq @bottom_row
561566 jmp @middle_row
562-
563567 @top_row:
564568 lda row_counter
565569 cmp #0
...
575579 lda #TILE_CORNER_TR
576580 sta PPUDATA
577581 jmp @next_row
578-
579582 @top_row_inner:
580583 lda #TILE_BORDER
581584 ldx #32
...
584587 dex
585588 bne @top_inner_fill
586589 jmp @next_row
587-
588590 @bottom_row:
589591 lda row_counter
590592 cmp #29
...
600602 lda #TILE_CORNER_BR
601603 sta PPUDATA
602604 jmp @next_row
603-
604605 @bottom_row_inner:
605606 lda #TILE_BORDER
606607 ldx #32
...
609610 dex
610611 bne @bottom_inner_fill
611612 jmp @next_row
612-
613613 @middle_row:
614614 lda #TILE_BORDER
615615 sta PPUDATA
...
623623 lda #TILE_BORDER
624624 sta PPUDATA
625625 sta PPUDATA
626-
627626 @next_row:
628627 inc row_counter
629628 lda row_counter
630629 cmp #30
631630 beq @done_drawing
632631 jmp @draw_row
633-
634632 @done_drawing:
635633 rts
636634
637-; -----------------------------------------------------------------------------
638-; Set Attribute Table
639-; -----------------------------------------------------------------------------
640635 set_attributes:
641636 bit PPUSTATUS
642637 lda #$23
643638 sta PPUADDR
644639 lda #$C0
645640 sta PPUADDR
646-
647641 ldx #8
648642 lda #$00
649643 @attr_top:
650644 sta PPUDATA
651645 dex
652646 bne @attr_top
653-
654647 ldx #6
655648 @attr_floor:
656649 lda #$00
...
666659 sta PPUDATA
667660 dex
668661 bne @attr_floor
669-
670662 ldx #8
671663 lda #$00
672664 @attr_bottom:
...
675667 bne @attr_bottom
676668 rts
677669
678-; -----------------------------------------------------------------------------
679-; Read Controller
680-; -----------------------------------------------------------------------------
681670 read_controller:
682671 lda #1
683672 sta JOYPAD1
...
692681 bne @read_loop
693682 rts
694683
695-; -----------------------------------------------------------------------------
696-; Move Player
697-; -----------------------------------------------------------------------------
698684 move_player:
699685 lda buttons
700686 and #BTN_UP
...
705691 cmp #ARENA_TOP
706692 bcc @check_down
707693 sta player_y
708-
709694 @check_down:
710695 lda buttons
711696 and #BTN_DOWN
...
716701 cmp #ARENA_BOTTOM
717702 bcs @check_left
718703 sta player_y
719-
720704 @check_left:
721705 lda buttons
722706 and #BTN_LEFT
...
727711 cmp #ARENA_LEFT
728712 bcc @check_right
729713 sta player_x
730-
731714 @check_right:
732715 lda buttons
733716 and #BTN_RIGHT
...
738721 cmp #ARENA_RIGHT
739722 bcs @done
740723 sta player_x
741-
742724 @done:
743725 rts
744726
745-; === NMI ===
746727 nmi:
747728 pha
748729 txa
749730 pha
750731 tya
751732 pha
752-
753733 lda #0
754734 sta OAMADDR
755735 lda #>oam_buffer
756736 sta OAMDMA
757-
758737 inc frame_count
759-
760738 lda #0
761739 sta PPUSCROLL
762740 sta PPUSCROLL
763-
764741 pla
765742 tay
766743 pla
...
771748 irq:
772749 rti
773750
774-; -----------------------------------------------------------------------------
775-; Data
776-; -----------------------------------------------------------------------------
777751 palette_data:
778752 .byte BG_COLOUR, $11, $21, $31
779753 .byte BG_COLOUR, $13, $23, $33
780754 .byte BG_COLOUR, $19, $29, $39
781755 .byte BG_COLOUR, $16, $26, $36
782- .byte BG_COLOUR, $30, $27, $17 ; Player + lives
756+ .byte BG_COLOUR, $30, $27, $17 ; Player
783757 .byte BG_COLOUR, $16, $26, $36 ; Enemies
784- .byte BG_COLOUR, $30, $27, $17
758+ .byte BG_COLOUR, $1A, $2A, $3A ; Items (green)
785759 .byte BG_COLOUR, $30, $27, $17
786760
787-; -----------------------------------------------------------------------------
788-; Vectors
789-; -----------------------------------------------------------------------------
790761 .segment "VECTORS"
791762 .word nmi
792763 .word reset
793764 .word irq
794765
795-; -----------------------------------------------------------------------------
796-; CHR-ROM
797-; -----------------------------------------------------------------------------
798766 .segment "CHARS"
799-
800-; Tile 0: Empty
801-.byte $00,$00,$00,$00,$00,$00,$00,$00
802-.byte $00,$00,$00,$00,$00,$00,$00,$00
803-
804-; Tile 1: Border
805-.byte %11111111,%10000001,%10000001,%11111111
806-.byte %11111111,%00010001,%00010001,%11111111
807-.byte %00000000,%01111110,%01111110,%00000000
808-.byte %00000000,%11101110,%11101110,%00000000
809-
810-; Tile 2: Floor
811-.byte %00000000,%00000000,%00000000,%00000000
812-.byte %00000000,%00000000,%00000000,%10000001
813-.byte %00000000,%00000000,%00000000,%00000000
814-.byte %00000000,%00000000,%00000000,%00000000
815-
816-; Tile 3: Corner TL
817-.byte %11111111,%11000000,%10100000,%10010000
818-.byte %10001000,%10000100,%10000010,%10000001
819-.byte %00000000,%00111111,%01011111,%01101111
820-.byte %01110111,%01111011,%01111101,%01111110
821-
822-; Tile 4: Corner TR
823-.byte %11111111,%00000011,%00000101,%00001001
824-.byte %00010001,%00100001,%01000001,%10000001
825-.byte %00000000,%11111100,%11111010,%11110110
826-.byte %11101110,%11011110,%10111110,%01111110
827-
828-; Tile 5: Corner BL
829-.byte %10000001,%10000010,%10000100,%10001000
830-.byte %10010000,%10100000,%11000000,%11111111
831-.byte %01111110,%01111101,%01111011,%01110111
832-.byte %01101111,%01011111,%00111111,%00000000
833-
834-; Tile 6: Corner BR
835-.byte %10000001,%01000001,%00100001,%00010001
836-.byte %00001001,%00000101,%00000011,%11111111
837-.byte %01111110,%10111110,%11011110,%11101110
838-.byte %11110110,%11111010,%11111100,%00000000
839-
840-; Tile 7: Player
841-.byte %00011000,%00011000,%00111100,%01111110
842-.byte %11111111,%10111101,%00100100,%00100100
843-.byte %00000000,%00011000,%00011000,%00111100
844-.byte %01000010,%01000010,%00011000,%00000000
845-
846-; Tile 8: Enemy
847-.byte %00011000,%00111100,%01111110,%11111111
848-.byte %11111111,%01111110,%00111100,%00011000
849-.byte %00000000,%00011000,%00100100,%01000010
850-.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
767+.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
768+.byte %11111111,%10000001,%10000001,%11111111,%11111111,%00010001,%00010001,%11111111
769+.byte %00000000,%01111110,%01111110,%00000000,%00000000,%11101110,%11101110,%00000000
770+.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%10000001
771+.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000
772+.byte %11111111,%11000000,%10100000,%10010000,%10001000,%10000100,%10000010,%10000001
773+.byte %00000000,%00111111,%01011111,%01101111,%01110111,%01111011,%01111101,%01111110
774+.byte %11111111,%00000011,%00000101,%00001001,%00010001,%00100001,%01000001,%10000001
775+.byte %00000000,%11111100,%11111010,%11110110,%11101110,%11011110,%10111110,%01111110
776+.byte %10000001,%10000010,%10000100,%10001000,%10010000,%10100000,%11000000,%11111111
777+.byte %01111110,%01111101,%01111011,%01110111,%01101111,%01011111,%00111111,%00000000
778+.byte %10000001,%01000001,%00100001,%00010001,%00001001,%00000101,%00000011,%11111111
779+.byte %01111110,%10111110,%11011110,%11101110,%11110110,%11111010,%11111100,%00000000
780+.byte %00011000,%00011000,%00111100,%01111110,%11111111,%10111101,%00100100,%00100100
781+.byte %00000000,%00011000,%00011000,%00111100,%01000010,%01000010,%00011000,%00000000
782+.byte %00011000,%00111100,%01111110,%11111111,%11111111,%01111110,%00111100,%00011000
783+.byte %00000000,%00011000,%00100100,%01000010,%01000010,%00100100,%00011000,%00000000
784+.byte %00000000,%00100100,%01111110,%01111110,%00111100,%00011000,%00000000,%00000000
785+.byte %00000000,%00000000,%00000000,%00100100,%00011000,%00000000,%00000000,%00000000
786+; Tile 10: Item (data core - diamond)
787+.byte %00000000,%00011000,%00111100,%01111110,%01111110,%00111100,%00011000,%00000000
788+.byte %00000000,%00000000,%00011000,%00100100,%00100100,%00011000,%00000000,%00000000
857789
858-; Fill rest
859-.res 8192 - 160, $00
790+.res 8192 - 176, $00
860791