Game 1 Unit 15 of 64 1 hr learning time
Game States
Add a state machine with title screen, game over, and win states.
23% of Neon Nexus
What You’re Building
A complete game flow. Title screen. Playing. Game over. Win.

The game now starts at a title screen. Press Start to play.
The State Machine
Four states control the game:
STATE_TITLE = 0
STATE_PLAYING = 1
STATE_GAMEOVER = 2
STATE_WIN = 3
Main Loop
The main loop dispatches to state handlers:
main_loop:
jsr read_controller
lda game_state
cmp #STATE_TITLE
beq @title_state
cmp #STATE_PLAYING
beq @playing_state
cmp #STATE_GAMEOVER
beq @gameover_state
cmp #STATE_WIN
beq @win_state
jmp main_loop
@title_state:
jsr handle_title
jmp main_loop
Each state has its own handler. Clean separation.
Button Edge Detection
We want to detect a new press, not a held button:
BTN_START = %00010000
handle_title:
lda buttons
and #BTN_START
beq @no_start
lda buttons_prev
and #BTN_START
bne @no_start ; Already pressed
; New press! Start the game
jsr init_game
lda #STATE_PLAYING
sta game_state
@no_start:
lda buttons
sta buttons_prev
rts
Compare current buttons with previous frame. Only act on the rising edge.
State Transitions
| From | To | Trigger |
|---|---|---|
| TITLE | PLAYING | Start pressed |
| PLAYING | GAMEOVER | Lives reach 0 |
| PLAYING | WIN | All items collected |
| GAMEOVER | TITLE | Start pressed |
| WIN | TITLE | Start pressed |
Screen Displays
Each state shows different sprites:
- Title: Player with decorative items
- Playing: Full game (player, enemies, items, lives)
- Game Over: Enemy sprites as defeat indicator
- Win: Player surrounded by items
The Code
; =============================================================================
; NEON NEXUS - Unit 15: Game States
; =============================================================================
; Add a state machine with title screen, playing, game over, and win states.
; =============================================================================
PPUCTRL = $2000
PPUMASK = $2001
PPUSTATUS = $2002
OAMADDR = $2003
PPUSCROLL = $2005
PPUADDR = $2006
PPUDATA = $2007
OAMDMA = $4014
APUSTATUS = $4015
JOYPAD1 = $4016
; APU Pulse 1 registers
APU_PULSE1_CTRL = $4000
APU_PULSE1_SWEEP = $4001
APU_PULSE1_LO = $4002
APU_PULSE1_HI = $4003
BTN_START = %00010000
BTN_UP = %00001000
BTN_DOWN = %00000100
BTN_LEFT = %00000010
BTN_RIGHT = %00000001
; Game states
STATE_TITLE = 0
STATE_PLAYING = 1
STATE_GAMEOVER = 2
STATE_WIN = 3
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
buttons_prev: .res 1 ; Previous frame buttons (for edge detection)
temp: .res 1
row_counter: .res 1
frame_count: .res 1
lives: .res 1
invuln_timer: .res 1
game_state: .res 1 ; Current game state
items_collected: .res 1
score_lo: .res 1
score_hi: .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
.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
; Hide all sprites initially
lda #$FF
ldx #0
@hide_all:
sta oam_buffer, x
inx
bne @hide_all
lda #0
sta PPUSCROLL
sta PPUSCROLL
; Enable APU channels
lda #%00000001
sta APUSTATUS
; Start in title state
lda #STATE_TITLE
sta game_state
lda #%10000000
sta PPUCTRL
lda #%00011110
sta PPUMASK
; =============================================================================
; Main Loop - State Machine
; =============================================================================
main_loop:
jsr read_controller
lda game_state
cmp #STATE_TITLE
beq @title_state
cmp #STATE_PLAYING
beq @playing_state
cmp #STATE_GAMEOVER
beq @gameover_state
cmp #STATE_WIN
beq @win_state
jmp main_loop
@title_state:
jsr handle_title
jmp main_loop
@playing_state:
jsr handle_playing
jmp main_loop
@gameover_state:
jsr handle_gameover
jmp main_loop
@win_state:
jsr handle_win
jmp main_loop
; =============================================================================
; State Handlers
; =============================================================================
handle_title:
; Show title screen sprites
jsr show_title_sprites
; Check for Start button press (new press only)
lda buttons
and #BTN_START
beq @no_start
lda buttons_prev
and #BTN_START
bne @no_start ; Already pressed last frame
; Start pressed! Begin game
jsr init_game
lda #STATE_PLAYING
sta game_state
jsr play_collect_sound ; Start game sound
@no_start:
; Store previous buttons
lda buttons
sta buttons_prev
rts
handle_playing:
jsr move_player
jsr move_enemies
jsr check_enemy_collisions
jsr check_item_collisions
jsr check_win_condition
jsr update_all_sprites
; Store previous buttons
lda buttons
sta buttons_prev
rts
handle_gameover:
; Show game over display
jsr show_gameover_sprites
; Check for Start to restart
lda buttons
and #BTN_START
beq @no_restart
lda buttons_prev
and #BTN_START
bne @no_restart
; Restart game
lda #STATE_TITLE
sta game_state
@no_restart:
lda buttons
sta buttons_prev
rts
handle_win:
; Show win display
jsr show_win_sprites
; Check for Start to restart
lda buttons
and #BTN_START
beq @no_restart
lda buttons_prev
and #BTN_START
bne @no_restart
; Back to title
lda #STATE_TITLE
sta game_state
@no_restart:
lda buttons
sta buttons_prev
rts
; =============================================================================
; Display Routines
; =============================================================================
show_title_sprites:
; Hide all sprites first
lda #$FF
ldx #0
@hide_loop:
sta oam_buffer, x
inx
bne @hide_loop
; Show player sprite in center as "logo"
lda #100 ; Y position
sta oam_buffer+0
lda #SPRITE_PLAYER
sta oam_buffer+1
lda #0
sta oam_buffer+2
lda #124 ; X position
sta oam_buffer+3
; Show some items as decoration
lda #100
sta oam_buffer+4
lda #SPRITE_ITEM
sta oam_buffer+5
lda #%00000010
sta oam_buffer+6
lda #100
sta oam_buffer+7
lda #100
sta oam_buffer+8
lda #SPRITE_ITEM
sta oam_buffer+9
lda #%00000010
sta oam_buffer+10
lda #148
sta oam_buffer+11
rts
show_gameover_sprites:
; Hide all sprites
lda #$FF
ldx #0
@hide_loop:
sta oam_buffer, x
inx
bne @hide_loop
; Show enemies as "defeat" indicator
lda #100
sta oam_buffer+0
lda #SPRITE_ENEMY
sta oam_buffer+1
lda #%00000001
sta oam_buffer+2
lda #112
sta oam_buffer+3
lda #100
sta oam_buffer+4
lda #SPRITE_ENEMY
sta oam_buffer+5
lda #%00000001
sta oam_buffer+6
lda #128
sta oam_buffer+7
lda #100
sta oam_buffer+8
lda #SPRITE_ENEMY
sta oam_buffer+9
lda #%00000001
sta oam_buffer+10
lda #144
sta oam_buffer+11
rts
show_win_sprites:
; Hide all sprites
lda #$FF
ldx #0
@hide_loop:
sta oam_buffer, x
inx
bne @hide_loop
; Show player and items as "victory" indicator
lda #100
sta oam_buffer+0
lda #SPRITE_PLAYER
sta oam_buffer+1
lda #0
sta oam_buffer+2
lda #124
sta oam_buffer+3
; Items around player
lda #92
sta oam_buffer+4
lda #SPRITE_ITEM
sta oam_buffer+5
lda #%00000010
sta oam_buffer+6
lda #116
sta oam_buffer+7
lda #92
sta oam_buffer+8
lda #SPRITE_ITEM
sta oam_buffer+9
lda #%00000010
sta oam_buffer+10
lda #132
sta oam_buffer+11
lda #108
sta oam_buffer+12
lda #SPRITE_ITEM
sta oam_buffer+13
lda #%00000010
sta oam_buffer+14
lda #116
sta oam_buffer+15
lda #108
sta oam_buffer+16
lda #SPRITE_ITEM
sta oam_buffer+17
lda #%00000010
sta oam_buffer+18
lda #132
sta oam_buffer+19
rts
; =============================================================================
; Game Initialisation
; =============================================================================
init_game:
lda #STARTING_LIVES
sta lives
lda #0
sta invuln_timer
sta items_collected
sta score_lo
sta score_hi
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:
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
; =============================================================================
; Collision Detection
; =============================================================================
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
lda score_lo
clc
adc #100
sta score_lo
lda score_hi
adc #0
sta score_hi
jsr play_collect_sound
@next_item:
inx
cpx #NUM_ITEMS
bne @check_item
rts
player_hit:
jsr play_death_sound
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 #STATE_GAMEOVER
sta game_state
rts
; =============================================================================
; Win Condition
; =============================================================================
check_win_condition:
lda items_collected
cmp #NUM_ITEMS
bne @not_yet
lda game_state
cmp #STATE_WIN
beq @not_yet ; Already won
lda #STATE_WIN
sta game_state
jsr play_victory_sound
@not_yet:
rts
; =============================================================================
; Sound Effects
; =============================================================================
play_collect_sound:
lda #%10011111
sta APU_PULSE1_CTRL
lda #0
sta APU_PULSE1_SWEEP
lda #$C4
sta APU_PULSE1_LO
lda #%00001000
sta APU_PULSE1_HI
rts
play_death_sound:
lda #%10011111
sta APU_PULSE1_CTRL
lda #%10001111
sta APU_PULSE1_SWEEP
lda #$00
sta APU_PULSE1_LO
lda #%00001011
sta APU_PULSE1_HI
rts
play_victory_sound:
lda #%10011111
sta APU_PULSE1_CTRL
lda #%10000111
sta APU_PULSE1_SWEEP
lda #$FF
sta APU_PULSE1_LO
lda #%00000011
sta APU_PULSE1_HI
rts
; =============================================================================
; Sprite Updates
; =============================================================================
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_state
cmp #STATE_PLAYING
bne @hide
lda invuln_timer
beq @show
and #%00000100
beq @show
@hide:
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
@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
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
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
; =============================================================================
; Enemy Movement
; =============================================================================
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
; =============================================================================
; PPU Setup
; =============================================================================
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
; =============================================================================
; Input
; =============================================================================
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 Handler
; =============================================================================
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
.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"
; Tile 0: Empty
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
; Tile 1: Border
.byte %11111111,%10000001,%10000001,%11111111,%11111111,%00010001,%00010001,%11111111
.byte %00000000,%01111110,%01111110,%00000000,%00000000,%11101110,%11101110,%00000000
; Tile 2: Floor
.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%10000001
.byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000
; Tile 3: Corner TL
.byte %11111111,%11000000,%10100000,%10010000,%10001000,%10000100,%10000010,%10000001
.byte %00000000,%00111111,%01011111,%01101111,%01110111,%01111011,%01111101,%01111110
; Tile 4: Corner TR
.byte %11111111,%00000011,%00000101,%00001001,%00010001,%00100001,%01000001,%10000001
.byte %00000000,%11111100,%11111010,%11110110,%11101110,%11011110,%10111110,%01111110
; Tile 5: Corner BL
.byte %10000001,%10000010,%10000100,%10001000,%10010000,%10100000,%11000000,%11111111
.byte %01111110,%01111101,%01111011,%01110111,%01101111,%01011111,%00111111,%00000000
; Tile 6: Corner BR
.byte %10000001,%01000001,%00100001,%00010001,%00001001,%00000101,%00000011,%11111111
.byte %01111110,%10111110,%11011110,%11101110,%11110110,%11111010,%11111100,%00000000
; Tile 7: Player (sprite)
.byte %00011000,%00011000,%00111100,%01111110,%11111111,%10111101,%00100100,%00100100
.byte %00000000,%00011000,%00011000,%00111100,%01000010,%01000010,%00011000,%00000000
; Tile 8: Enemy (sprite)
.byte %00011000,%00111100,%01111110,%11111111,%11111111,%01111110,%00111100,%00011000
.byte %00000000,%00011000,%00100100,%01000010,%01000010,%00100100,%00011000,%00000000
; Tile 9: Life icon (sprite)
.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
The game now has complete flow. Title, play, end, restart.
Next
Unit 16 brings Phase 1 to a close. Time to review what we’ve built.
What Changed
Unit 14 → Unit 15
+346-67
| 1 | 1 | ; ============================================================================= | |
| 2 | - | ; NEON NEXUS - Unit 14: Win Condition | |
| 2 | + | ; NEON NEXUS - Unit 15: Game States | |
| 3 | 3 | ; ============================================================================= | |
| 4 | - | ; Add a win condition - collecting all items completes the level. | |
| 4 | + | ; Add a state machine with title screen, playing, game over, and win states. | |
| 5 | 5 | ; ============================================================================= | |
| 6 | 6 | | |
| 7 | 7 | PPUCTRL = $2000 | |
| ... | |||
| 21 | 21 | APU_PULSE1_LO = $4002 | |
| 22 | 22 | APU_PULSE1_HI = $4003 | |
| 23 | 23 | | |
| 24 | + | BTN_START = %00010000 | |
| 24 | 25 | BTN_UP = %00001000 | |
| 25 | 26 | BTN_DOWN = %00000100 | |
| 26 | 27 | BTN_LEFT = %00000010 | |
| 27 | 28 | BTN_RIGHT = %00000001 | |
| 29 | + | | |
| 30 | + | ; Game states | |
| 31 | + | STATE_TITLE = 0 | |
| 32 | + | STATE_PLAYING = 1 | |
| 33 | + | STATE_GAMEOVER = 2 | |
| 34 | + | STATE_WIN = 3 | |
| 28 | 35 | | |
| 29 | 36 | PLAYER_START_X = 124 | |
| 30 | 37 | PLAYER_START_Y = 116 | |
| ... | |||
| 63 | 70 | player_x: .res 1 | |
| 64 | 71 | player_y: .res 1 | |
| 65 | 72 | buttons: .res 1 | |
| 73 | + | buttons_prev: .res 1 ; Previous frame buttons (for edge detection) | |
| 66 | 74 | temp: .res 1 | |
| 67 | 75 | row_counter: .res 1 | |
| 68 | 76 | frame_count: .res 1 | |
| 69 | 77 | lives: .res 1 | |
| 70 | 78 | invuln_timer: .res 1 | |
| 71 | - | game_over: .res 1 | |
| 79 | + | game_state: .res 1 ; Current game state | |
| 72 | 80 | items_collected: .res 1 | |
| 73 | - | score_lo: .res 1 ; Score low byte | |
| 74 | - | score_hi: .res 1 ; Score high byte (for scores > 255) | |
| 75 | - | level_complete: .res 1 ; Non-zero when all items collected | |
| 81 | + | score_lo: .res 1 | |
| 82 | + | score_hi: .res 1 | |
| 76 | 83 | | |
| 77 | 84 | enemy_x: .res NUM_ENEMIES | |
| 78 | 85 | enemy_y: .res NUM_ENEMIES | |
| ... | |||
| 81 | 88 | | |
| 82 | 89 | item_x: .res NUM_ITEMS | |
| 83 | 90 | item_y: .res NUM_ITEMS | |
| 84 | - | item_active: .res NUM_ITEMS ; Non-zero if item exists | |
| 91 | + | item_active: .res NUM_ITEMS | |
| 85 | 92 | | |
| 86 | 93 | .segment "OAM" | |
| 87 | 94 | oam_buffer: .res 256 | |
| ... | |||
| 129 | 136 | jsr load_palette | |
| 130 | 137 | jsr draw_arena | |
| 131 | 138 | jsr set_attributes | |
| 132 | - | jsr init_game | |
| 133 | 139 | | |
| 140 | + | ; Hide all sprites initially | |
| 134 | 141 | lda #$FF | |
| 135 | 142 | ldx #0 | |
| 136 | 143 | @hide_all: | |
| 137 | 144 | sta oam_buffer, x | |
| 138 | 145 | inx | |
| 139 | 146 | bne @hide_all | |
| 140 | - | | |
| 141 | - | jsr update_all_sprites | |
| 142 | 147 | | |
| 143 | 148 | lda #0 | |
| 144 | 149 | sta PPUSCROLL | |
| 145 | 150 | sta PPUSCROLL | |
| 146 | 151 | | |
| 147 | 152 | ; Enable APU channels | |
| 148 | - | lda #%00000001 ; Enable pulse 1 | |
| 153 | + | lda #%00000001 | |
| 149 | 154 | sta APUSTATUS | |
| 155 | + | | |
| 156 | + | ; Start in title state | |
| 157 | + | lda #STATE_TITLE | |
| 158 | + | sta game_state | |
| 150 | 159 | | |
| 151 | 160 | lda #%10000000 | |
| 152 | 161 | sta PPUCTRL | |
| 153 | 162 | lda #%00011110 | |
| 154 | 163 | sta PPUMASK | |
| 155 | 164 | | |
| 165 | + | ; ============================================================================= | |
| 166 | + | ; Main Loop - State Machine | |
| 167 | + | ; ============================================================================= | |
| 156 | 168 | main_loop: | |
| 157 | - | lda game_over | |
| 158 | - | bne @game_over_loop | |
| 159 | - | lda level_complete | |
| 160 | - | bne @level_complete_loop | |
| 161 | 169 | jsr read_controller | |
| 170 | + | | |
| 171 | + | lda game_state | |
| 172 | + | cmp #STATE_TITLE | |
| 173 | + | beq @title_state | |
| 174 | + | cmp #STATE_PLAYING | |
| 175 | + | beq @playing_state | |
| 176 | + | cmp #STATE_GAMEOVER | |
| 177 | + | beq @gameover_state | |
| 178 | + | cmp #STATE_WIN | |
| 179 | + | beq @win_state | |
| 180 | + | jmp main_loop | |
| 181 | + | | |
| 182 | + | @title_state: | |
| 183 | + | jsr handle_title | |
| 184 | + | jmp main_loop | |
| 185 | + | | |
| 186 | + | @playing_state: | |
| 187 | + | jsr handle_playing | |
| 188 | + | jmp main_loop | |
| 189 | + | | |
| 190 | + | @gameover_state: | |
| 191 | + | jsr handle_gameover | |
| 192 | + | jmp main_loop | |
| 193 | + | | |
| 194 | + | @win_state: | |
| 195 | + | jsr handle_win | |
| 196 | + | jmp main_loop | |
| 197 | + | | |
| 198 | + | ; ============================================================================= | |
| 199 | + | ; State Handlers | |
| 200 | + | ; ============================================================================= | |
| 201 | + | | |
| 202 | + | handle_title: | |
| 203 | + | ; Show title screen sprites | |
| 204 | + | jsr show_title_sprites | |
| 205 | + | | |
| 206 | + | ; Check for Start button press (new press only) | |
| 207 | + | lda buttons | |
| 208 | + | and #BTN_START | |
| 209 | + | beq @no_start | |
| 210 | + | lda buttons_prev | |
| 211 | + | and #BTN_START | |
| 212 | + | bne @no_start ; Already pressed last frame | |
| 213 | + | | |
| 214 | + | ; Start pressed! Begin game | |
| 215 | + | jsr init_game | |
| 216 | + | lda #STATE_PLAYING | |
| 217 | + | sta game_state | |
| 218 | + | jsr play_collect_sound ; Start game sound | |
| 219 | + | | |
| 220 | + | @no_start: | |
| 221 | + | ; Store previous buttons | |
| 222 | + | lda buttons | |
| 223 | + | sta buttons_prev | |
| 224 | + | rts | |
| 225 | + | | |
| 226 | + | handle_playing: | |
| 162 | 227 | jsr move_player | |
| 163 | 228 | jsr move_enemies | |
| 164 | 229 | jsr check_enemy_collisions | |
| 165 | 230 | jsr check_item_collisions | |
| 166 | 231 | jsr check_win_condition | |
| 167 | 232 | jsr update_all_sprites | |
| 168 | - | jmp main_loop | |
| 169 | 233 | | |
| 170 | - | @game_over_loop: | |
| 171 | - | jmp @game_over_loop | |
| 234 | + | ; Store previous buttons | |
| 235 | + | lda buttons | |
| 236 | + | sta buttons_prev | |
| 237 | + | rts | |
| 172 | 238 | | |
| 173 | - | @level_complete_loop: | |
| 174 | - | jsr update_all_sprites ; Keep showing win state | |
| 175 | - | jmp @level_complete_loop | |
| 239 | + | handle_gameover: | |
| 240 | + | ; Show game over display | |
| 241 | + | jsr show_gameover_sprites | |
| 242 | + | | |
| 243 | + | ; Check for Start to restart | |
| 244 | + | lda buttons | |
| 245 | + | and #BTN_START | |
| 246 | + | beq @no_restart | |
| 247 | + | lda buttons_prev | |
| 248 | + | and #BTN_START | |
| 249 | + | bne @no_restart | |
| 250 | + | | |
| 251 | + | ; Restart game | |
| 252 | + | lda #STATE_TITLE | |
| 253 | + | sta game_state | |
| 254 | + | | |
| 255 | + | @no_restart: | |
| 256 | + | lda buttons | |
| 257 | + | sta buttons_prev | |
| 258 | + | rts | |
| 259 | + | | |
| 260 | + | handle_win: | |
| 261 | + | ; Show win display | |
| 262 | + | jsr show_win_sprites | |
| 263 | + | | |
| 264 | + | ; Check for Start to restart | |
| 265 | + | lda buttons | |
| 266 | + | and #BTN_START | |
| 267 | + | beq @no_restart | |
| 268 | + | lda buttons_prev | |
| 269 | + | and #BTN_START | |
| 270 | + | bne @no_restart | |
| 271 | + | | |
| 272 | + | ; Back to title | |
| 273 | + | lda #STATE_TITLE | |
| 274 | + | sta game_state | |
| 275 | + | | |
| 276 | + | @no_restart: | |
| 277 | + | lda buttons | |
| 278 | + | sta buttons_prev | |
| 279 | + | rts | |
| 280 | + | | |
| 281 | + | ; ============================================================================= | |
| 282 | + | ; Display Routines | |
| 283 | + | ; ============================================================================= | |
| 284 | + | | |
| 285 | + | show_title_sprites: | |
| 286 | + | ; Hide all sprites first | |
| 287 | + | lda #$FF | |
| 288 | + | ldx #0 | |
| 289 | + | @hide_loop: | |
| 290 | + | sta oam_buffer, x | |
| 291 | + | inx | |
| 292 | + | bne @hide_loop | |
| 293 | + | | |
| 294 | + | ; Show player sprite in center as "logo" | |
| 295 | + | lda #100 ; Y position | |
| 296 | + | sta oam_buffer+0 | |
| 297 | + | lda #SPRITE_PLAYER | |
| 298 | + | sta oam_buffer+1 | |
| 299 | + | lda #0 | |
| 300 | + | sta oam_buffer+2 | |
| 301 | + | lda #124 ; X position | |
| 302 | + | sta oam_buffer+3 | |
| 303 | + | | |
| 304 | + | ; Show some items as decoration | |
| 305 | + | lda #100 | |
| 306 | + | sta oam_buffer+4 | |
| 307 | + | lda #SPRITE_ITEM | |
| 308 | + | sta oam_buffer+5 | |
| 309 | + | lda #%00000010 | |
| 310 | + | sta oam_buffer+6 | |
| 311 | + | lda #100 | |
| 312 | + | sta oam_buffer+7 | |
| 313 | + | | |
| 314 | + | lda #100 | |
| 315 | + | sta oam_buffer+8 | |
| 316 | + | lda #SPRITE_ITEM | |
| 317 | + | sta oam_buffer+9 | |
| 318 | + | lda #%00000010 | |
| 319 | + | sta oam_buffer+10 | |
| 320 | + | lda #148 | |
| 321 | + | sta oam_buffer+11 | |
| 322 | + | rts | |
| 323 | + | | |
| 324 | + | show_gameover_sprites: | |
| 325 | + | ; Hide all sprites | |
| 326 | + | lda #$FF | |
| 327 | + | ldx #0 | |
| 328 | + | @hide_loop: | |
| 329 | + | sta oam_buffer, x | |
| 330 | + | inx | |
| 331 | + | bne @hide_loop | |
| 332 | + | | |
| 333 | + | ; Show enemies as "defeat" indicator | |
| 334 | + | lda #100 | |
| 335 | + | sta oam_buffer+0 | |
| 336 | + | lda #SPRITE_ENEMY | |
| 337 | + | sta oam_buffer+1 | |
| 338 | + | lda #%00000001 | |
| 339 | + | sta oam_buffer+2 | |
| 340 | + | lda #112 | |
| 341 | + | sta oam_buffer+3 | |
| 342 | + | | |
| 343 | + | lda #100 | |
| 344 | + | sta oam_buffer+4 | |
| 345 | + | lda #SPRITE_ENEMY | |
| 346 | + | sta oam_buffer+5 | |
| 347 | + | lda #%00000001 | |
| 348 | + | sta oam_buffer+6 | |
| 349 | + | lda #128 | |
| 350 | + | sta oam_buffer+7 | |
| 351 | + | | |
| 352 | + | lda #100 | |
| 353 | + | sta oam_buffer+8 | |
| 354 | + | lda #SPRITE_ENEMY | |
| 355 | + | sta oam_buffer+9 | |
| 356 | + | lda #%00000001 | |
| 357 | + | sta oam_buffer+10 | |
| 358 | + | lda #144 | |
| 359 | + | sta oam_buffer+11 | |
| 360 | + | rts | |
| 361 | + | | |
| 362 | + | show_win_sprites: | |
| 363 | + | ; Hide all sprites | |
| 364 | + | lda #$FF | |
| 365 | + | ldx #0 | |
| 366 | + | @hide_loop: | |
| 367 | + | sta oam_buffer, x | |
| 368 | + | inx | |
| 369 | + | bne @hide_loop | |
| 370 | + | | |
| 371 | + | ; Show player and items as "victory" indicator | |
| 372 | + | lda #100 | |
| 373 | + | sta oam_buffer+0 | |
| 374 | + | lda #SPRITE_PLAYER | |
| 375 | + | sta oam_buffer+1 | |
| 376 | + | lda #0 | |
| 377 | + | sta oam_buffer+2 | |
| 378 | + | lda #124 | |
| 379 | + | sta oam_buffer+3 | |
| 380 | + | | |
| 381 | + | ; Items around player | |
| 382 | + | lda #92 | |
| 383 | + | sta oam_buffer+4 | |
| 384 | + | lda #SPRITE_ITEM | |
| 385 | + | sta oam_buffer+5 | |
| 386 | + | lda #%00000010 | |
| 387 | + | sta oam_buffer+6 | |
| 388 | + | lda #116 | |
| 389 | + | sta oam_buffer+7 | |
| 390 | + | | |
| 391 | + | lda #92 | |
| 392 | + | sta oam_buffer+8 | |
| 393 | + | lda #SPRITE_ITEM | |
| 394 | + | sta oam_buffer+9 | |
| 395 | + | lda #%00000010 | |
| 396 | + | sta oam_buffer+10 | |
| 397 | + | lda #132 | |
| 398 | + | sta oam_buffer+11 | |
| 399 | + | | |
| 400 | + | lda #108 | |
| 401 | + | sta oam_buffer+12 | |
| 402 | + | lda #SPRITE_ITEM | |
| 403 | + | sta oam_buffer+13 | |
| 404 | + | lda #%00000010 | |
| 405 | + | sta oam_buffer+14 | |
| 406 | + | lda #116 | |
| 407 | + | sta oam_buffer+15 | |
| 408 | + | | |
| 409 | + | lda #108 | |
| 410 | + | sta oam_buffer+16 | |
| 411 | + | lda #SPRITE_ITEM | |
| 412 | + | sta oam_buffer+17 | |
| 413 | + | lda #%00000010 | |
| 414 | + | sta oam_buffer+18 | |
| 415 | + | lda #132 | |
| 416 | + | sta oam_buffer+19 | |
| 417 | + | rts | |
| 418 | + | | |
| 419 | + | ; ============================================================================= | |
| 420 | + | ; Game Initialisation | |
| 421 | + | ; ============================================================================= | |
| 176 | 422 | | |
| 177 | 423 | init_game: | |
| 178 | 424 | lda #STARTING_LIVES | |
| 179 | 425 | sta lives | |
| 180 | 426 | lda #0 | |
| 181 | - | sta game_over | |
| 182 | - | sta level_complete | |
| 183 | 427 | sta invuln_timer | |
| 184 | 428 | sta items_collected | |
| 185 | 429 | sta score_lo | |
| ... | |||
| 232 | 476 | rts | |
| 233 | 477 | | |
| 234 | 478 | init_items: | |
| 235 | - | ; Place 4 items around the arena | |
| 236 | 479 | lda #80 | |
| 237 | 480 | sta item_x+0 | |
| 238 | 481 | lda #64 | |
| ... | |||
| 261 | 504 | lda #1 | |
| 262 | 505 | sta item_active+3 | |
| 263 | 506 | rts | |
| 507 | + | | |
| 508 | + | ; ============================================================================= | |
| 509 | + | ; Collision Detection | |
| 510 | + | ; ============================================================================= | |
| 264 | 511 | | |
| 265 | 512 | check_enemy_collisions: | |
| 266 | 513 | lda invuln_timer | |
| ... | |||
| 330 | 577 | cmp #COLLECT_DIST | |
| 331 | 578 | bcs @next_item | |
| 332 | 579 | | |
| 333 | - | ; Collected! Add 100 points | |
| 580 | + | ; Collected! | |
| 334 | 581 | lda #0 | |
| 335 | 582 | sta item_active, x | |
| 336 | 583 | inc items_collected | |
| 337 | - | ; Add 100 to score (100 = $64) | |
| 338 | 584 | lda score_lo | |
| 339 | 585 | clc | |
| 340 | 586 | adc #100 | |
| ... | |||
| 366 | 612 | rts | |
| 367 | 613 | | |
| 368 | 614 | @game_over: | |
| 369 | - | lda #1 | |
| 370 | - | sta game_over | |
| 615 | + | lda #STATE_GAMEOVER | |
| 616 | + | sta game_state | |
| 371 | 617 | rts | |
| 372 | 618 | | |
| 373 | - | ; ----------------------------------------------------------------------------- | |
| 619 | + | ; ============================================================================= | |
| 620 | + | ; Win Condition | |
| 621 | + | ; ============================================================================= | |
| 622 | + | | |
| 623 | + | check_win_condition: | |
| 624 | + | lda items_collected | |
| 625 | + | cmp #NUM_ITEMS | |
| 626 | + | bne @not_yet | |
| 627 | + | lda game_state | |
| 628 | + | cmp #STATE_WIN | |
| 629 | + | beq @not_yet ; Already won | |
| 630 | + | lda #STATE_WIN | |
| 631 | + | sta game_state | |
| 632 | + | jsr play_victory_sound | |
| 633 | + | @not_yet: | |
| 634 | + | rts | |
| 635 | + | | |
| 636 | + | ; ============================================================================= | |
| 374 | 637 | ; Sound Effects | |
| 375 | - | ; ----------------------------------------------------------------------------- | |
| 638 | + | ; ============================================================================= | |
| 639 | + | | |
| 376 | 640 | play_collect_sound: | |
| 377 | - | ; High-pitched short beep | |
| 378 | - | lda #%10011111 ; Duty 50%, length counter disabled, constant vol, vol 15 | |
| 641 | + | lda #%10011111 | |
| 379 | 642 | sta APU_PULSE1_CTRL | |
| 380 | 643 | lda #0 | |
| 381 | 644 | sta APU_PULSE1_SWEEP | |
| 382 | - | lda #$C4 ; Low byte of period (high pitch) | |
| 645 | + | lda #$C4 | |
| 383 | 646 | sta APU_PULSE1_LO | |
| 384 | - | lda #%00001000 ; Length counter load, high bits of period | |
| 647 | + | lda #%00001000 | |
| 385 | 648 | sta APU_PULSE1_HI | |
| 386 | 649 | rts | |
| 387 | 650 | | |
| 388 | 651 | play_death_sound: | |
| 389 | - | ; Low-pitched descending sound | |
| 390 | - | lda #%10011111 ; Duty 50%, constant vol, vol 15 | |
| 652 | + | lda #%10011111 | |
| 391 | 653 | sta APU_PULSE1_CTRL | |
| 392 | - | lda #%10001111 ; Sweep enabled, down, fast | |
| 654 | + | lda #%10001111 | |
| 393 | 655 | sta APU_PULSE1_SWEEP | |
| 394 | - | lda #$00 ; Low byte of period (low pitch) | |
| 656 | + | lda #$00 | |
| 395 | 657 | sta APU_PULSE1_LO | |
| 396 | - | lda #%00001011 ; Length counter load, high bits | |
| 658 | + | lda #%00001011 | |
| 397 | 659 | sta APU_PULSE1_HI | |
| 398 | 660 | rts | |
| 399 | 661 | | |
| 400 | 662 | play_victory_sound: | |
| 401 | - | ; Ascending triumphant sound | |
| 402 | - | lda #%10011111 ; Duty 50%, constant vol, vol 15 | |
| 663 | + | lda #%10011111 | |
| 403 | 664 | sta APU_PULSE1_CTRL | |
| 404 | - | lda #%10000111 ; Sweep enabled, up, fast | |
| 665 | + | lda #%10000111 | |
| 405 | 666 | sta APU_PULSE1_SWEEP | |
| 406 | - | lda #$FF ; Low byte of period (start low) | |
| 667 | + | lda #$FF | |
| 407 | 668 | sta APU_PULSE1_LO | |
| 408 | - | lda #%00000011 ; Length counter load, high bits | |
| 669 | + | lda #%00000011 | |
| 409 | 670 | sta APU_PULSE1_HI | |
| 410 | 671 | rts | |
| 411 | 672 | | |
| 412 | - | ; ----------------------------------------------------------------------------- | |
| 413 | - | ; Win Condition Check | |
| 414 | - | ; ----------------------------------------------------------------------------- | |
| 415 | - | check_win_condition: | |
| 416 | - | lda items_collected | |
| 417 | - | cmp #NUM_ITEMS ; Have we collected all items? | |
| 418 | - | bne @not_yet | |
| 419 | - | lda level_complete ; Already triggered? | |
| 420 | - | bne @not_yet | |
| 421 | - | lda #1 | |
| 422 | - | sta level_complete | |
| 423 | - | jsr play_victory_sound | |
| 424 | - | @not_yet: | |
| 425 | - | rts | |
| 673 | + | ; ============================================================================= | |
| 674 | + | ; Sprite Updates | |
| 675 | + | ; ============================================================================= | |
| 426 | 676 | | |
| 427 | 677 | update_all_sprites: | |
| 428 | 678 | jsr update_player_sprite | |
| ... | |||
| 432 | 682 | rts | |
| 433 | 683 | | |
| 434 | 684 | update_player_sprite: | |
| 435 | - | lda game_over | |
| 436 | - | beq @alive | |
| 437 | - | lda #$FF | |
| 438 | - | sta oam_buffer+0 | |
| 439 | - | rts | |
| 685 | + | lda game_state | |
| 686 | + | cmp #STATE_PLAYING | |
| 687 | + | bne @hide | |
| 440 | 688 | | |
| 441 | - | @alive: | |
| 442 | 689 | lda invuln_timer | |
| 443 | 690 | beq @show | |
| 444 | 691 | and #%00000100 | |
| 445 | 692 | beq @show | |
| 693 | + | | |
| 694 | + | @hide: | |
| 446 | 695 | lda #$FF | |
| 447 | 696 | sta oam_buffer+0 | |
| 448 | 697 | rts | |
| ... | |||
| 481 | 730 | | |
| 482 | 731 | update_item_sprites: | |
| 483 | 732 | ldx #0 | |
| 484 | - | ldy #20 ; OAM offset after enemies | |
| 733 | + | ldy #20 | |
| 485 | 734 | @loop: | |
| 486 | 735 | lda item_active, x | |
| 487 | 736 | beq @hide_item | |
| ... | |||
| 492 | 741 | lda #SPRITE_ITEM | |
| 493 | 742 | sta oam_buffer, y | |
| 494 | 743 | iny | |
| 495 | - | lda #%00000010 ; Palette 2 (green) | |
| 744 | + | lda #%00000010 | |
| 496 | 745 | sta oam_buffer, y | |
| 497 | 746 | iny | |
| 498 | 747 | lda item_x, x | |
| ... | |||
| 515 | 764 | rts | |
| 516 | 765 | | |
| 517 | 766 | update_lives_display: | |
| 518 | - | ldy #36 ; OAM offset for lives | |
| 767 | + | ldy #36 | |
| 519 | 768 | ldx lives | |
| 520 | 769 | beq @hide_all | |
| 521 | 770 | | |
| ... | |||
| 567 | 816 | @hide_rest: | |
| 568 | 817 | @hide_all: | |
| 569 | 818 | rts | |
| 819 | + | | |
| 820 | + | ; ============================================================================= | |
| 821 | + | ; Enemy Movement | |
| 822 | + | ; ============================================================================= | |
| 570 | 823 | | |
| 571 | 824 | move_enemies: | |
| 572 | 825 | ldx #0 | |
| ... | |||
| 617 | 870 | cpx #NUM_ENEMIES | |
| 618 | 871 | bne @enemy_loop | |
| 619 | 872 | rts | |
| 873 | + | | |
| 874 | + | ; ============================================================================= | |
| 875 | + | ; PPU Setup | |
| 876 | + | ; ============================================================================= | |
| 620 | 877 | | |
| 621 | 878 | load_palette: | |
| 622 | 879 | bit PPUSTATUS | |
| ... | |||
| 754 | 1011 | dex | |
| 755 | 1012 | bne @attr_bottom | |
| 756 | 1013 | rts | |
| 1014 | + | | |
| 1015 | + | ; ============================================================================= | |
| 1016 | + | ; Input | |
| 1017 | + | ; ============================================================================= | |
| 757 | 1018 | | |
| 758 | 1019 | read_controller: | |
| 759 | 1020 | lda #1 | |
| ... | |||
| 811 | 1072 | sta player_x | |
| 812 | 1073 | @done: | |
| 813 | 1074 | rts | |
| 1075 | + | | |
| 1076 | + | ; ============================================================================= | |
| 1077 | + | ; NMI Handler | |
| 1078 | + | ; ============================================================================= | |
| 814 | 1079 | | |
| 815 | 1080 | nmi: | |
| 816 | 1081 | pha | |
| ... | |||
| 835 | 1100 | | |
| 836 | 1101 | irq: | |
| 837 | 1102 | rti | |
| 1103 | + | | |
| 1104 | + | ; ============================================================================= | |
| 1105 | + | ; Data | |
| 1106 | + | ; ============================================================================= | |
| 838 | 1107 | | |
| 839 | 1108 | palette_data: | |
| 840 | 1109 | .byte BG_COLOUR, $11, $21, $31 | |
| ... | |||
| 852 | 1121 | .word irq | |
| 853 | 1122 | | |
| 854 | 1123 | .segment "CHARS" | |
| 1124 | + | ; Tile 0: Empty | |
| 855 | 1125 | .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 | |
| 1126 | + | ; Tile 1: Border | |
| 856 | 1127 | .byte %11111111,%10000001,%10000001,%11111111,%11111111,%00010001,%00010001,%11111111 | |
| 857 | 1128 | .byte %00000000,%01111110,%01111110,%00000000,%00000000,%11101110,%11101110,%00000000 | |
| 1129 | + | ; Tile 2: Floor | |
| 858 | 1130 | .byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%10000001 | |
| 859 | 1131 | .byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000 | |
| 1132 | + | ; Tile 3: Corner TL | |
| 860 | 1133 | .byte %11111111,%11000000,%10100000,%10010000,%10001000,%10000100,%10000010,%10000001 | |
| 861 | 1134 | .byte %00000000,%00111111,%01011111,%01101111,%01110111,%01111011,%01111101,%01111110 | |
| 1135 | + | ; Tile 4: Corner TR | |
| 862 | 1136 | .byte %11111111,%00000011,%00000101,%00001001,%00010001,%00100001,%01000001,%10000001 | |
| 863 | 1137 | .byte %00000000,%11111100,%11111010,%11110110,%11101110,%11011110,%10111110,%01111110 | |
| 1138 | + | ; Tile 5: Corner BL | |
| 864 | 1139 | .byte %10000001,%10000010,%10000100,%10001000,%10010000,%10100000,%11000000,%11111111 | |
| 865 | 1140 | .byte %01111110,%01111101,%01111011,%01110111,%01101111,%01011111,%00111111,%00000000 | |
| 1141 | + | ; Tile 6: Corner BR | |
| 866 | 1142 | .byte %10000001,%01000001,%00100001,%00010001,%00001001,%00000101,%00000011,%11111111 | |
| 867 | 1143 | .byte %01111110,%10111110,%11011110,%11101110,%11110110,%11111010,%11111100,%00000000 | |
| 1144 | + | ; Tile 7: Player (sprite) | |
| 868 | 1145 | .byte %00011000,%00011000,%00111100,%01111110,%11111111,%10111101,%00100100,%00100100 | |
| 869 | 1146 | .byte %00000000,%00011000,%00011000,%00111100,%01000010,%01000010,%00011000,%00000000 | |
| 1147 | + | ; Tile 8: Enemy (sprite) | |
| 870 | 1148 | .byte %00011000,%00111100,%01111110,%11111111,%11111111,%01111110,%00111100,%00011000 | |
| 871 | 1149 | .byte %00000000,%00011000,%00100100,%01000010,%01000010,%00100100,%00011000,%00000000 | |
| 1150 | + | ; Tile 9: Life icon (sprite) | |
| 872 | 1151 | .byte %00000000,%00100100,%01111110,%01111110,%00111100,%00011000,%00000000,%00000000 | |
| 873 | 1152 | .byte %00000000,%00000000,%00000000,%00100100,%00011000,%00000000,%00000000,%00000000 | |
| 874 | 1153 | ; Tile 10: Item (data core - diamond) |