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

Song Selection Screen

Building a menu system to select songs. The structure for multiple songs, ready to expand.

27% of SID Symphony

Phase 2 begins. The complete game from Phase 1 becomes the foundation for something bigger.

This unit adds a song selection menu between the title screen and gameplay. Currently there’s only one song to choose, but the infrastructure is ready for more. When you add songs in future units, they’ll appear in this menu automatically.

Run It

Assemble and run:

acme -f cbm -o symphony.prg symphony.asm

Unit 17 Screenshot

Press fire on the title screen to reach the song selection menu. Select a song and press fire again to play.

Song selection menu — choose which song to play

The New Game Flow

The game now has five states instead of four:

TITLE → MENU → PLAYING → RESULTS/GAMEOVER → MENU

After completing or failing a song, you return to the menu—not the title. This feels more like a real game: pick a song, play it, try another.

New State Constant

We add STATE_MENU to our state machine:

STATE_TITLE   = 0               ; Title screen
STATE_MENU    = 1               ; Song selection menu (NEW!)
STATE_PLAYING = 2               ; Gameplay
STATE_RESULTS = 3               ; Success results
STATE_GAMEOVER = 4              ; Failure game over

The state numbers shifted. STATE_PLAYING is now 2 instead of 1. This matters for the main loop dispatcher.

Three new zero-page variables track menu state:

cursor_pos  = $10               ; Which song is highlighted
key_delay_count = $11           ; Prevents key repeat spam
selected_song = $12             ; Which song to play

The key delay prevents the cursor from flying through options when holding up/down. Without it, the menu would be unusable.

Drawing the Menu

The show_menu routine clears the screen and displays the song list:

show_menu:
            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_menu:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_menu

            ; Initialise cursor
            lda #0
            sta cursor_pos
            sta key_delay_count

            ; Draw menu title
            ldx #0
draw_menu_title:
            lda menu_title,x
            beq draw_menu_title_done
            sta SCREEN + (4 * 40) + 12,x
            lda #TITLE_COL
            sta COLRAM + (4 * 40) + 12,x
            inx
            jmp draw_menu_title
draw_menu_title_done:

            ; Draw song list
            jsr draw_song_list
            rts

The cursor starts at position 0 (the first song). With only one song currently, that’s the only option.

Cursor Movement

The update_menu routine handles input:

update_menu:
            ; Handle key delay
            lda key_delay_count
            beq menu_check_input
            dec key_delay_count
            jmp menu_check_fire

menu_check_input:
            ; Check joystick up
            lda CIA1_PRA
            and #$01            ; Bit 0 = up
            beq menu_up_pressed

            ; Check joystick down
            lda CIA1_PRA
            and #$02            ; Bit 1 = down
            beq menu_down_pressed

            jmp menu_check_fire

Key repeat delay works like this:

  1. If key_delay_count is non-zero, skip input checking and decrement
  2. When input detected, set key_delay_count to KEY_DELAY (10 frames)
  3. This means roughly 5 cursor movements per second maximum

Sound Feedback

Two new sounds make the menu feel responsive:

MENU_MOVE_FREQ    = $10         ; Lower pitch for cursor move
MENU_MOVE_WAVE    = $11         ; Triangle (soft)

MENU_SELECT_FREQ  = $18         ; Higher pitch for selection
MENU_SELECT_WAVE  = $41         ; Pulse (click)

The move sound is soft and low. The select sound is brighter and sharper. This audio distinction helps users understand what action they just took.

Transitioning to Game

When fire is pressed, we store the selection and start the game:

menu_fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Play select sound
            jsr play_menu_select

            ; Store selected song
            lda cursor_pos
            sta selected_song

            ; Transition to game
            jsr transition_to_game
            rts

transition_to_game:
            ; Set up the selected song
            ; In future units, this will load different song data

            ; Initialise game
            jsr init_game
            lda #STATE_PLAYING
            sta game_state
            rts

Right now transition_to_game always loads the same song. In Unit 18, we’ll use selected_song to choose which song data to use.

Returning to Menu

After completing or failing, pressing fire now returns to the menu:

update_results:
            ; Check for fire to return to menu
            lda CIA1_PRA
            and #$10
            beq results_fire
            ; ...

results_fire:
            ; ...
            ; Return to menu (not title)
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

This small change makes a big difference. Players can immediately try another song without going through the title screen.

Song Name Display

During gameplay, the selected song’s name appears in the HUD:

            ; Draw song name on gameplay screen
            ldx #0
draw_song_name:
            lda selected_song
            ; For now only song 0
            lda song1_name,x
            beq draw_song_name_done
            sta SCREEN + 28,x
            lda #11
            sta COLRAM + 28,x
            inx
            jmp draw_song_name
draw_song_name_done:

When we add more songs, this will display which one is playing.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 17: Song Selection Screen
; ============================================================================
; Adding a menu system to select songs. Currently only one song, but the
; structure is ready for multiple songs in the next unit.
;
; New concepts: Menu state, cursor tracking, key repeat delay
;
; Controls: Z = Track 1, X = Track 2, C = Track 3
;           Up/Down = Menu navigation
;           Fire/Space = Start game / Select
; ============================================================================

; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================

; Screenshot mode - set to 1 to skip title and show menu immediately
; Used for automated screenshot capture. Override with: acme -DSCREENSHOT_MODE=1
!ifndef SCREENSHOT_MODE { SCREENSHOT_MODE = 0 }

; SID Voice Settings (for note playback)
VOICE1_WAVE = $21               ; Sawtooth for track 1
VOICE2_WAVE = $41               ; Pulse for track 2
VOICE3_WAVE = $11               ; Triangle for track 3

VOICE1_FREQ = $1C
VOICE2_FREQ = $0E
VOICE3_FREQ = $07

VOICE_AD    = $09               ; Attack=0, Decay=9
VOICE_SR    = $00               ; Sustain=0, Release=0
PULSE_WIDTH = $08

; Perfect hit sound - bright and satisfying
PERFECT_SFX_FREQ  = $30         ; Higher pitch
PERFECT_SFX_WAVE  = $21         ; Sawtooth (bright)
PERFECT_SFX_AD    = $08         ; Fast attack, medium decay
PERFECT_SFX_SR    = $00         ; No sustain

; Good hit sound - positive but lesser
GOOD_SFX_FREQ     = $20         ; Lower pitch than perfect
GOOD_SFX_WAVE     = $11         ; Triangle (softer)
GOOD_SFX_AD       = $0A         ; Slightly slower decay
GOOD_SFX_SR       = $00         ; No sustain

; Miss sound settings - harsh buzz
MISS_FREQ   = $08
MISS_WAVE   = $81               ; Noise
MISS_AD     = $00               ; Instant attack
MISS_SR     = $A0               ; Quick sustain, fast release

; Menu sound settings
MENU_SELECT_FREQ  = $18         ; Menu click pitch
MENU_SELECT_WAVE  = $41         ; Pulse for click
MENU_SELECT_AD    = $00         ; Instant
MENU_SELECT_SR    = $80         ; Very short

MENU_MOVE_FREQ    = $10         ; Lower pitch for cursor move
MENU_MOVE_WAVE    = $11         ; Triangle (soft)
MENU_MOVE_AD      = $00         ; Instant
MENU_MOVE_SR      = $40         ; Very short

; Visual Settings
BORDER_COL  = 0
BG_COL      = 0

TRACK1_NOTE_COL = 10            ; Light red for high track
TRACK2_NOTE_COL = 13            ; Light green for mid track
TRACK3_NOTE_COL = 14            ; Light blue for low track

TRACK_LINE_COL = 11             ; Grey for track lines
HIT_ZONE_COL = 7                ; Yellow for hit zone

HIT_COL     = 1
PERFECT_COL = 1                 ; White flash for perfect
GOOD_COL    = 7                 ; Yellow flash for good
MISS_COL    = 2                 ; Red flash for miss

HEALTH_COL  = 5                 ; Green for health bar
PROGRESS_COL = 3                ; Cyan for progress bar
COMBO_COL   = 13                ; Light green for combo

; Title screen colours
TITLE_COL   = 1                 ; White for title
SUBTITLE_COL = 11               ; Grey for subtitle
MENU_COL    = 7                 ; Yellow for menu items
CURSOR_COL  = 1                 ; White for cursor

; ============================================================================
; SCORING SETTINGS (BALANCED)
; ============================================================================

PERFECT_SCORE = 100             ; Perfect hit value
GOOD_SCORE    = 50              ; Good hit value

; ============================================================================
; COMBO SETTINGS (BALANCED)
; ============================================================================

COMBO_TIER_2  = 10              ; 2x multiplier at 10 hits
COMBO_TIER_3  = 25              ; 3x multiplier at 25 hits
COMBO_TIER_4  = 50              ; 4x multiplier at 50 hits

; ============================================================================
; HEALTH SETTINGS (BALANCED FOR FAIRNESS)
; ============================================================================

HEALTH_MAX    = 64              ; Maximum health
HEALTH_START  = 32              ; Start at half health
HEALTH_PERFECT = 4              ; Gain 4 for perfect
HEALTH_GOOD   = 2               ; Gain 2 for good
HEALTH_MISS   = 8               ; Lose 8 for miss (forgiving)

; ============================================================================
; HIT DETECTION SETTINGS (BALANCED TIMING WINDOWS)
; ============================================================================

HIT_ZONE_MIN = 2                ; Earliest hit column
HIT_ZONE_MAX = 5                ; Latest hit column
HIT_ZONE_CENTRE = 3             ; Perfect timing column

; ============================================================================
; SONG SETTINGS
; ============================================================================

SONG_LENGTH   = 64              ; 64 beats at 120 BPM = ~32 seconds
PROGRESS_WIDTH = 16             ; Progress bar width

; ============================================================================
; MENU SETTINGS
; ============================================================================

NUM_SONGS     = 1               ; Currently only 1 song
KEY_DELAY     = 10              ; Frames between key repeats

; ============================================================================
; GAME STATES
; ============================================================================

STATE_TITLE   = 0               ; Title screen
STATE_MENU    = 1               ; Song selection menu (NEW!)
STATE_PLAYING = 2               ; Gameplay
STATE_RESULTS = 3               ; Success results
STATE_GAMEOVER = 4              ; Failure game over

; ============================================================================
; MEMORY MAP
; ============================================================================

SCREEN      = $0400
COLRAM      = $D800
BORDER      = $D020
BGCOL       = $D021
CHARPTR     = $D018

CHARSET     = $3000

; SID registers
SID         = $D400
SID_V1_FREQ_LO = $D400
SID_V1_FREQ_HI = $D401
SID_V1_PWHI = $D403
SID_V1_CTRL = $D404
SID_V1_AD   = $D405
SID_V1_SR   = $D406

SID_V2_FREQ_LO = $D407
SID_V2_FREQ_HI = $D408
SID_V2_PWHI = $D40A
SID_V2_CTRL = $D40B
SID_V2_AD   = $D40C
SID_V2_SR   = $D40D

SID_V3_FREQ_LO = $D40E
SID_V3_FREQ_HI = $D40F
SID_V3_PWHI = $D411
SID_V3_CTRL = $D412
SID_V3_AD   = $D413
SID_V3_SR   = $D414

SID_VOLUME  = $D418

; CIA keyboard and joystick
CIA1_PRA    = $DC00
CIA1_PRB    = $DC01

; Track positions (balanced spacing)
TRACK1_ROW  = 8                 ; High track
TRACK2_ROW  = 12                ; Mid track
TRACK3_ROW  = 16                ; Low track

; HUD positions
HEALTH_ROW  = 23                ; Health bar row
PROGRESS_ROW = 24               ; Progress bar row

; Combo display position
COMBO_ROW   = 2                 ; Combo display row

; Hit zone
HIT_ZONE_COLUMN = 3             ; Where notes are hit

; Custom character codes
CHAR_NOTE   = 128
CHAR_TRACK  = 129
CHAR_HITZONE = 130
CHAR_SPACE  = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132
CHAR_CURSOR = 62                ; > character for menu cursor

; Note settings
MAX_NOTES   = 8                 ; Maximum simultaneous notes
NOTE_SPAWN_COL = 37             ; Where notes appear

; Timing
FRAMES_PER_BEAT = 25            ; 50fps / 25 = 2 beats per second = 120 BPM
END_DELAY_FRAMES = 75           ; 1.5 seconds after song ends

; Zero page
ZP_PTR      = $FB
ZP_PTR_HI   = $FC

; Variables
frame_count = $02
beat_count  = $03
song_pos    = $04
song_pos_hi = $05
temp_track  = $06
key_pressed = $07
hit_quality = $08
border_flash = $09
miss_track  = $0A
game_state  = $0B
hit_note_freq = $0C
song_beat   = $0D
song_ended  = $0E
end_delay   = $0F
cursor_pos  = $10               ; Menu cursor position (NEW!)
key_delay_count = $11           ; Key repeat delay counter (NEW!)
selected_song = $12             ; Which song to play (NEW!)

; ----------------------------------------------------------------------------
; BASIC Stub
; ----------------------------------------------------------------------------

            * = $0801

            !byte $0C, $08
            !byte $0A, $00
            !byte $9E
            !text "2064"
            !byte $00
            !byte $00, $00

; ----------------------------------------------------------------------------
; Main Program
; ----------------------------------------------------------------------------

            * = $0810

start:
            jsr copy_charset
            jsr init_sid

!if SCREENSHOT_MODE = 1 {
            ; Screenshot mode: skip title, go to menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
} else {
            ; Normal mode: show title screen
            jsr show_title
            lda #STATE_TITLE
            sta game_state
}

main_loop:
            lda #$FF
wait_raster:
            cmp $D012
            bne wait_raster

            lda game_state
            cmp #STATE_TITLE
            beq do_title
            cmp #STATE_MENU
            beq do_menu
            cmp #STATE_PLAYING
            beq do_playing
            cmp #STATE_RESULTS
            beq do_results
            jmp do_gameover

do_title:
            jsr update_title
            jmp main_loop

do_menu:
            jsr update_menu
            jmp main_loop

do_playing:
            jsr update_playing
            jmp main_loop

do_results:
            jsr update_results
            jmp main_loop

do_gameover:
            jsr update_gameover
            jmp main_loop

; ----------------------------------------------------------------------------
; Show Title Screen
; ----------------------------------------------------------------------------

show_title:
            ; Clear screen
            lda #BORDER_COL
            sta BORDER
            lda #BG_COL
            sta BGCOL

            ldx #0
            lda #CHAR_SPACE
clear_title:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_title

            ; Draw big title "SID SYMPHONY"
            ldx #0
draw_title_text:
            lda title_big,x
            beq draw_title_done
            sta SCREEN + (8 * 40) + 14,x
            lda #TITLE_COL
            sta COLRAM + (8 * 40) + 14,x
            inx
            jmp draw_title_text
draw_title_done:

            ; Draw subtitle "A RHYTHM GAME"
            ldx #0
draw_subtitle:
            lda subtitle_text,x
            beq draw_subtitle_done
            sta SCREEN + (10 * 40) + 13,x
            lda #SUBTITLE_COL
            sta COLRAM + (10 * 40) + 13,x
            inx
            jmp draw_subtitle
draw_subtitle_done:

            ; Draw controls
            ldx #0
draw_controls:
            lda controls_text,x
            beq draw_controls_done
            sta SCREEN + (14 * 40) + 11,x
            lda #11
            sta COLRAM + (14 * 40) + 11,x
            inx
            jmp draw_controls
draw_controls_done:

            ; Draw track info
            ldx #0
draw_track_info:
            lda track_info,x
            beq draw_track_done
            sta SCREEN + (16 * 40) + 9,x
            lda #11
            sta COLRAM + (16 * 40) + 9,x
            inx
            jmp draw_track_info
draw_track_done:

            ; Draw "PRESS FIRE TO START"
            ldx #0
draw_press_fire:
            lda press_fire_text,x
            beq draw_press_done
            sta SCREEN + (20 * 40) + 10,x
            lda #7              ; Yellow
            sta COLRAM + (20 * 40) + 10,x
            inx
            jmp draw_press_fire
draw_press_done:

            rts

title_big:
            !scr "sid symphony"
            !byte 0

subtitle_text:
            !scr "a rhythm game"
            !byte 0

controls_text:
            !scr "controls: z / x / c"
            !byte 0

track_info:
            !scr "hit notes as they reach"
            !byte 0

press_fire_text:
            !scr "press fire to start"
            !byte 0

; ----------------------------------------------------------------------------
; Update Title State
; ----------------------------------------------------------------------------

update_title:
            ; Check for fire button (joystick port 2)
            lda CIA1_PRA
            and #$10            ; Bit 4 = fire
            beq title_fire_pressed

            ; Also check space bar as alternative
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10            ; Space
            beq title_fire_pressed

            ; No input - stay on title
            lda #$FF
            sta CIA1_PRA
            rts

title_fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Play menu select sound
            jsr play_menu_select

            ; Go to song selection menu
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Show Song Selection Menu
; ----------------------------------------------------------------------------

show_menu:
            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_menu:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_menu

            ; Initialise cursor
            lda #0
            sta cursor_pos
            sta key_delay_count

            ; Draw menu title
            ldx #0
draw_menu_title:
            lda menu_title,x
            beq draw_menu_title_done
            sta SCREEN + (4 * 40) + 12,x
            lda #TITLE_COL
            sta COLRAM + (4 * 40) + 12,x
            inx
            jmp draw_menu_title
draw_menu_title_done:

            ; Draw instructions
            ldx #0
draw_menu_instr:
            lda menu_instructions,x
            beq draw_menu_instr_done
            sta SCREEN + (22 * 40) + 8,x
            lda #SUBTITLE_COL
            sta COLRAM + (22 * 40) + 8,x
            inx
            jmp draw_menu_instr
draw_menu_instr_done:

            ; Draw song list
            jsr draw_song_list

            rts

menu_title:
            !scr "select a song"
            !byte 0

menu_instructions:
            !scr "up/down to select, fire to play"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Song List
; ----------------------------------------------------------------------------

draw_song_list:
            ; Draw each song entry
            ; For now, just one song

            ; Song 1
            ldx #0
draw_song1:
            lda song1_name,x
            beq draw_song1_done
            sta SCREEN + (10 * 40) + 14,x
            lda #MENU_COL
            sta COLRAM + (10 * 40) + 14,x
            inx
            jmp draw_song1
draw_song1_done:

            ; Draw cursor at current position
            jsr draw_cursor
            rts

; Song names
song1_name:
            !scr "first steps"
            !byte 0

; ----------------------------------------------------------------------------
; Draw Menu Cursor
; ----------------------------------------------------------------------------

draw_cursor:
            ; Clear all potential cursor positions
            lda #CHAR_SPACE
            sta SCREEN + (10 * 40) + 12
            ; (Room for more songs later)

            ; Draw cursor at current position
            lda cursor_pos
            asl                 ; Multiply by 2 (rows per song)
            clc
            adc #10             ; Start row
            tax

            ; Calculate screen position
            ; Row X * 40 = screen offset
            lda #0
            sta ZP_PTR
            lda #0
            sta ZP_PTR_HI

            ; Multiply row by 40
cursor_row_mult:
            cpx #0
            beq cursor_row_done
            lda ZP_PTR
            clc
            adc #40
            sta ZP_PTR
            lda ZP_PTR_HI
            adc #0
            sta ZP_PTR_HI
            dex
            jmp cursor_row_mult

cursor_row_done:
            ; Add column offset (12)
            lda ZP_PTR
            clc
            adc #12
            sta ZP_PTR
            lda ZP_PTR_HI
            adc #0
            sta ZP_PTR_HI

            ; Add screen base
            lda ZP_PTR
            clc
            adc #<SCREEN
            sta ZP_PTR
            lda ZP_PTR_HI
            adc #>SCREEN
            sta ZP_PTR_HI

            ; Draw cursor character
            lda #CHAR_CURSOR
            ldy #0
            sta (ZP_PTR),y

            ; Set cursor colour
            lda ZP_PTR
            sec
            sbc #<SCREEN
            clc
            adc #<COLRAM
            sta ZP_PTR
            lda ZP_PTR_HI
            sbc #>SCREEN
            adc #>COLRAM
            sta ZP_PTR_HI

            lda #CURSOR_COL
            sta (ZP_PTR),y

            rts

; ----------------------------------------------------------------------------
; Update Menu State
; ----------------------------------------------------------------------------

update_menu:
            ; Handle key delay
            lda key_delay_count
            beq menu_check_input
            dec key_delay_count
            jmp menu_check_fire

menu_check_input:
            ; Check joystick up (or W key)
            lda CIA1_PRA
            and #$01            ; Bit 0 = up
            beq menu_up_pressed

            ; Check joystick down (or S key)
            lda CIA1_PRA
            and #$02            ; Bit 1 = down
            beq menu_down_pressed

            jmp menu_check_fire

menu_up_pressed:
            lda cursor_pos
            beq menu_set_delay  ; Already at top
            dec cursor_pos
            jsr play_menu_move
            jsr draw_cursor
            jmp menu_set_delay

menu_down_pressed:
            lda cursor_pos
            cmp #NUM_SONGS-1
            bcs menu_set_delay  ; Already at bottom
            inc cursor_pos
            jsr play_menu_move
            jsr draw_cursor
            jmp menu_set_delay

menu_set_delay:
            lda #KEY_DELAY
            sta key_delay_count

menu_check_fire:
            ; Check for fire button
            lda CIA1_PRA
            and #$10
            beq menu_fire_pressed

            ; Check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq menu_fire_pressed

            lda #$FF
            sta CIA1_PRA
            rts

menu_fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Play select sound
            jsr play_menu_select

            ; Store selected song
            lda cursor_pos
            sta selected_song

            ; Transition to game
            jsr transition_to_game
            rts

; ----------------------------------------------------------------------------
; Transition to Game
; ----------------------------------------------------------------------------

transition_to_game:
            ; Set up the selected song (for now, only song 0)
            ; In future units, this will load different song data

            ; Initialise game
            jsr init_game
            lda #STATE_PLAYING
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Play Menu Move Sound
; ----------------------------------------------------------------------------

play_menu_move:
            lda #0
            sta SID_V3_FREQ_LO
            lda #MENU_MOVE_FREQ
            sta SID_V3_FREQ_HI
            lda #$08
            sta SID_V3_PWHI
            lda #MENU_MOVE_AD
            sta SID_V3_AD
            lda #MENU_MOVE_SR
            sta SID_V3_SR
            lda #MENU_MOVE_WAVE
            ora #$01            ; Gate on
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Menu Select Sound
; ----------------------------------------------------------------------------

play_menu_select:
            lda #0
            sta SID_V3_FREQ_LO
            lda #MENU_SELECT_FREQ
            sta SID_V3_FREQ_HI
            lda #$08
            sta SID_V3_PWHI
            lda #MENU_SELECT_AD
            sta SID_V3_AD
            lda #MENU_SELECT_SR
            sta SID_V3_SR
            lda #MENU_SELECT_WAVE
            ora #$01            ; Gate on
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Initialize Game (called when starting from menu)
; ----------------------------------------------------------------------------

init_game:
            jsr init_screen
            jsr init_notes
            jsr init_score
            jsr init_health
            jsr init_combo
            jsr init_song

            rts

; ----------------------------------------------------------------------------
; Initialize Combo
; ----------------------------------------------------------------------------

init_combo:
            lda #0
            sta combo
            sta max_combo
            jsr display_combo
            rts

; ----------------------------------------------------------------------------
; Update Playing State
; ----------------------------------------------------------------------------

update_playing:
            inc frame_count
            lda frame_count
            cmp #FRAMES_PER_BEAT
            bcc no_new_beat

            lda #0
            sta frame_count
            jsr advance_song
            jsr check_spawn_note

no_new_beat:
            jsr update_notes
            jsr reset_track_colours
            jsr update_border_flash
            jsr check_keys
            jsr check_song_end

            rts

; ----------------------------------------------------------------------------
; Update Results State
; ----------------------------------------------------------------------------

update_results:
            ; Check for fire to return to menu
            lda CIA1_PRA
            and #$10
            beq results_fire

            ; Also check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq results_fire

            lda #$FF
            sta CIA1_PRA
            rts

results_fire:
            lda #$FF
            sta CIA1_PRA

            jsr play_menu_select

            ; Return to menu (not title)
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Update Game Over State
; ----------------------------------------------------------------------------

update_gameover:
            ; Check for fire to return to menu
            lda CIA1_PRA
            and #$10
            beq gameover_fire

            ; Also check space bar
            lda #$7F
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            beq gameover_fire

            lda #$FF
            sta CIA1_PRA
            rts

gameover_fire:
            lda #$FF
            sta CIA1_PRA

            jsr play_menu_select

            ; Return to menu (not title)
            jsr show_menu
            lda #STATE_MENU
            sta game_state
            rts

; ----------------------------------------------------------------------------
; Initialize Screen (Gameplay)
; ----------------------------------------------------------------------------

init_screen:
            ; Enable custom charset
            lda #$1C            ; Character ROM at $3000
            sta CHARPTR

            ; Set colours
            lda #BORDER_COL
            sta BORDER
            lda #BG_COL
            sta BGCOL

            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_screen:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_screen

            ; Draw tracks
            ldx #0
draw_tracks:
            lda #CHAR_TRACK
            sta SCREEN + (TRACK1_ROW * 40),x
            sta SCREEN + (TRACK2_ROW * 40),x
            sta SCREEN + (TRACK3_ROW * 40),x
            lda #TRACK_LINE_COL
            sta COLRAM + (TRACK1_ROW * 40),x
            sta COLRAM + (TRACK2_ROW * 40),x
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne draw_tracks

            ; Draw hit zones
            lda #CHAR_HITZONE
            sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
            lda #HIT_ZONE_COL
            sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN

            ; Draw track labels
            lda #$31            ; "1"
            sta SCREEN + (TRACK1_ROW * 40)
            lda #$32            ; "2"
            sta SCREEN + (TRACK2_ROW * 40)
            lda #$33            ; "3"
            sta SCREEN + (TRACK3_ROW * 40)
            lda #1
            sta COLRAM + (TRACK1_ROW * 40)
            sta COLRAM + (TRACK2_ROW * 40)
            sta COLRAM + (TRACK3_ROW * 40)

            ; Draw HUD labels
            ldx #0
draw_score_label:
            lda score_label,x
            beq draw_miss_label_start
            sta SCREEN,x
            lda #7
            sta COLRAM,x
            inx
            jmp draw_score_label

draw_miss_label_start:
            ldx #0
draw_miss_label:
            lda miss_label,x
            beq draw_combo_label_start
            sta SCREEN + 14,x
            lda #2
            sta COLRAM + 14,x
            inx
            jmp draw_miss_label

draw_combo_label_start:
            ldx #0
draw_combo_label:
            lda combo_label,x
            beq draw_health_label_start
            sta SCREEN + (COMBO_ROW * 40) + 27,x
            lda #COMBO_COL
            sta COLRAM + (COMBO_ROW * 40) + 27,x
            inx
            jmp draw_combo_label

draw_health_label_start:
            ldx #0
draw_health_label:
            lda health_label,x
            beq draw_progress_label_start
            sta SCREEN + (HEALTH_ROW * 40),x
            lda #HEALTH_COL
            sta COLRAM + (HEALTH_ROW * 40),x
            inx
            jmp draw_health_label

draw_progress_label_start:
            ldx #0
draw_progress_label:
            lda progress_label,x
            beq labels_done
            sta SCREEN + (PROGRESS_ROW * 40),x
            lda #PROGRESS_COL
            sta COLRAM + (PROGRESS_ROW * 40),x
            inx
            jmp draw_progress_label

labels_done:
            ; Draw song name on gameplay screen
            ldx #0
draw_song_name:
            lda selected_song
            ; For now only song 0
            lda song1_name,x
            beq draw_song_name_done
            sta SCREEN + 28,x
            lda #11
            sta COLRAM + 28,x
            inx
            jmp draw_song_name
draw_song_name_done:

            rts

score_label:
            !scr "score:"
            !byte 0
miss_label:
            !scr "miss:"
            !byte 0
combo_label:
            !scr "combo:"
            !byte 0
health_label:
            !scr "health:"
            !byte 0
progress_label:
            !scr "song:"
            !byte 0

; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------

init_notes:
            ldx #0
            lda #0
clear_notes:
            sta note_track,x
            sta note_col,x
            sta note_freq,x
            inx
            cpx #MAX_NOTES
            bne clear_notes
            rts

; ----------------------------------------------------------------------------
; Initialize Score
; ----------------------------------------------------------------------------

init_score:
            lda #0
            sta score_lo
            sta score_hi
            sta miss_count
            sta perfect_count
            sta good_count
            jsr display_score
            jsr display_misses
            rts

; ----------------------------------------------------------------------------
; Initialize Health
; ----------------------------------------------------------------------------

init_health:
            lda #HEALTH_START
            sta health
            jsr display_health
            rts

; ----------------------------------------------------------------------------
; Initialize Song
; ----------------------------------------------------------------------------

init_song:
            lda #0
            sta frame_count
            sta beat_count
            sta song_beat
            sta song_ended

            ; Point to song data (for now, only one song)
            lda #<song_data
            sta song_pos
            lda #>song_data
            sta song_pos_hi

            jsr display_progress
            rts

; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------

init_sid:
            ; Clear SID
            ldx #$18
            lda #0
clear_sid:
            sta SID,x
            dex
            bpl clear_sid

            ; Set volume
            lda #$0F
            sta SID_VOLUME

            ; Set pulse widths
            lda #PULSE_WIDTH
            sta SID_V1_PWHI
            sta SID_V2_PWHI
            sta SID_V3_PWHI

            ; Set ADSR for all voices
            lda #VOICE_AD
            sta SID_V1_AD
            sta SID_V2_AD
            sta SID_V3_AD

            lda #VOICE_SR
            sta SID_V1_SR
            sta SID_V2_SR
            sta SID_V3_SR

            rts

; ----------------------------------------------------------------------------
; Copy Custom Charset
; ----------------------------------------------------------------------------

copy_charset:
            ; Copy default ROM charset to RAM first
            sei
            lda #$33
            sta $01

            ldx #0
copy_rom_chars:
            lda $D000,x
            sta CHARSET,x
            lda $D100,x
            sta CHARSET+$100,x
            lda $D200,x
            sta CHARSET+$200,x
            lda $D300,x
            sta CHARSET+$300,x
            lda $D400,x
            sta CHARSET+$400,x
            lda $D500,x
            sta CHARSET+$500,x
            lda $D600,x
            sta CHARSET+$600,x
            lda $D700,x
            sta CHARSET+$700,x
            inx
            bne copy_rom_chars

            lda #$37
            sta $01
            cli

            ; Now add custom characters

            ; Character 128 - Note (solid circle)
            lda #%00111100
            sta CHARSET + (128 * 8) + 0
            lda #%01111110
            sta CHARSET + (128 * 8) + 1
            lda #%11111111
            sta CHARSET + (128 * 8) + 2
            lda #%11111111
            sta CHARSET + (128 * 8) + 3
            lda #%11111111
            sta CHARSET + (128 * 8) + 4
            lda #%11111111
            sta CHARSET + (128 * 8) + 5
            lda #%01111110
            sta CHARSET + (128 * 8) + 6
            lda #%00111100
            sta CHARSET + (128 * 8) + 7

            ; Character 129 - Track line (dashes)
            lda #%00000000
            sta CHARSET + (129 * 8) + 0
            lda #%00000000
            sta CHARSET + (129 * 8) + 1
            lda #%00000000
            sta CHARSET + (129 * 8) + 2
            lda #%11001100
            sta CHARSET + (129 * 8) + 3
            lda #%11001100
            sta CHARSET + (129 * 8) + 4
            lda #%00000000
            sta CHARSET + (129 * 8) + 5
            lda #%00000000
            sta CHARSET + (129 * 8) + 6
            lda #%00000000
            sta CHARSET + (129 * 8) + 7

            ; Character 130 - Hit zone (vertical bars)
            lda #%10000001
            sta CHARSET + (130 * 8) + 0
            lda #%10000001
            sta CHARSET + (130 * 8) + 1
            lda #%10000001
            sta CHARSET + (130 * 8) + 2
            lda #%10000001
            sta CHARSET + (130 * 8) + 3
            lda #%10000001
            sta CHARSET + (130 * 8) + 4
            lda #%10000001
            sta CHARSET + (130 * 8) + 5
            lda #%10000001
            sta CHARSET + (130 * 8) + 6
            lda #%10000001
            sta CHARSET + (130 * 8) + 7

            ; Character 131 - Bar full (solid block)
            lda #%11111111
            sta CHARSET + (131 * 8) + 0
            sta CHARSET + (131 * 8) + 1
            sta CHARSET + (131 * 8) + 2
            sta CHARSET + (131 * 8) + 3
            sta CHARSET + (131 * 8) + 4
            sta CHARSET + (131 * 8) + 5
            sta CHARSET + (131 * 8) + 6
            sta CHARSET + (131 * 8) + 7

            ; Character 132 - Bar empty (hollow block)
            lda #%11111111
            sta CHARSET + (132 * 8) + 0
            lda #%10000001
            sta CHARSET + (132 * 8) + 1
            sta CHARSET + (132 * 8) + 2
            sta CHARSET + (132 * 8) + 3
            sta CHARSET + (132 * 8) + 4
            sta CHARSET + (132 * 8) + 5
            sta CHARSET + (132 * 8) + 6
            lda #%11111111
            sta CHARSET + (132 * 8) + 7

            rts

; ----------------------------------------------------------------------------
; Advance Song
; ----------------------------------------------------------------------------

advance_song:
            inc song_beat
            jsr display_progress
            rts

; ----------------------------------------------------------------------------
; Check Spawn Note
; ----------------------------------------------------------------------------

check_spawn_note:
            ; Check if song has ended
            lda song_ended
            bne spawn_done

            ; Get pointer to current position in song
            lda song_pos
            sta ZP_PTR
            lda song_pos_hi
            sta ZP_PTR_HI

            ; Read beat number
            ldy #0
            lda (ZP_PTR),y

            ; Check for end marker
            cmp #$FF
            beq song_end_marker

            ; Check if this beat matches
            cmp song_beat
            bne spawn_done

            ; Spawn this note
            iny
            lda (ZP_PTR),y      ; Track
            sta temp_track
            iny
            lda (ZP_PTR),y      ; Frequency

            ; Find free note slot
            ldx #0
find_slot:
            lda note_track,x
            beq found_slot
            inx
            cpx #MAX_NOTES
            bne find_slot
            jmp advance_song_ptr  ; No free slot, skip note

found_slot:
            lda temp_track
            sta note_track,x
            lda #NOTE_SPAWN_COL
            sta note_col,x
            ldy #2
            lda (ZP_PTR),y
            sta note_freq,x
            jsr draw_note

advance_song_ptr:
            ; Move to next note in song
            lda song_pos
            clc
            adc #3
            sta song_pos
            lda song_pos_hi
            adc #0
            sta song_pos_hi

            ; Check if there are more notes on this beat
            jmp check_spawn_note

song_end_marker:
            lda #1
            sta song_ended
spawn_done:
            rts

; ----------------------------------------------------------------------------
; Update Notes
; ----------------------------------------------------------------------------

update_notes:
            ldx #0
update_notes_loop:
            lda note_track,x
            beq update_next_note

            ; Erase at old position
            jsr erase_note

            ; Move note left
            dec note_col,x

            ; Check if past hit zone (missed)
            lda note_col,x
            cmp #HIT_ZONE_MIN
            bcs note_still_active

            ; Note was missed
            lda note_track,x
            sta miss_track
            lda #0
            sta note_track,x
            jsr handle_miss
            jmp update_next_note

note_still_active:
            jsr draw_note

update_next_note:
            inx
            cpx #MAX_NOTES
            bne update_notes_loop
            rts

; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------

draw_note:
            lda note_track,x
            cmp #1
            beq draw_note_t1
            cmp #2
            beq draw_note_t2
            jmp draw_note_t3

draw_note_t1:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            ; Set colour
            lda note_col,x
            clc
            adc #<(COLRAM + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK1_NOTE_COL
            sta (ZP_PTR),y
            rts

draw_note_t2:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK2_NOTE_COL
            sta (ZP_PTR),y
            rts

draw_note_t3:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_NOTE
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK3_NOTE_COL
            sta (ZP_PTR),y
            rts

; ----------------------------------------------------------------------------
; Erase Note
; ----------------------------------------------------------------------------

erase_note:
            lda note_track,x
            cmp #1
            beq erase_note_t1
            cmp #2
            beq erase_note_t2
            jmp erase_note_t3

erase_note_t1:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK1_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK1_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

erase_note_t2:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK2_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK2_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

erase_note_t3:
            lda note_col,x
            clc
            adc #<(SCREEN + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(SCREEN + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI

            lda #CHAR_TRACK
            ldy #0
            sta (ZP_PTR),y

            lda note_col,x
            clc
            adc #<(COLRAM + TRACK3_ROW * 40)
            sta ZP_PTR
            lda #>(COLRAM + TRACK3_ROW * 40)
            adc #0
            sta ZP_PTR_HI
            lda #TRACK_LINE_COL
            sta (ZP_PTR),y
            rts

; ----------------------------------------------------------------------------
; Handle Miss
; ----------------------------------------------------------------------------

handle_miss:
            jsr play_miss_sound
            jsr break_combo

            ; Decrease health
            lda #HEALTH_MISS
            jsr decrease_health

            ; Flash border red
            lda #MISS_COL
            sta BORDER
            lda #8
            sta border_flash

            ; Flash the track where miss happened
            lda miss_track
            cmp #1
            beq flash_miss_t1
            cmp #2
            beq flash_miss_t2
            jmp flash_miss_t3

flash_miss_t1:
            ldx #0
            lda #MISS_COL
flash_m1_loop:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne flash_m1_loop
            rts

flash_miss_t2:
            ldx #0
            lda #MISS_COL
flash_m2_loop:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne flash_m2_loop
            rts

flash_miss_t3:
            ldx #0
            lda #MISS_COL
flash_m3_loop:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne flash_m3_loop
            rts

; ----------------------------------------------------------------------------
; Play Miss Sound
; ----------------------------------------------------------------------------

play_miss_sound:
            inc miss_count
            jsr display_misses

            lda #0
            sta SID_V3_FREQ_LO
            lda #MISS_FREQ
            sta SID_V3_FREQ_HI
            lda #MISS_AD
            sta SID_V3_AD
            lda #MISS_SR
            sta SID_V3_SR
            lda #MISS_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Perfect Sound
; ----------------------------------------------------------------------------

play_perfect_sound:
            lda #0
            sta SID_V3_FREQ_LO
            lda #PERFECT_SFX_FREQ
            sta SID_V3_FREQ_HI
            lda #PERFECT_SFX_AD
            sta SID_V3_AD
            lda #PERFECT_SFX_SR
            sta SID_V3_SR
            lda #PERFECT_SFX_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Play Good Sound
; ----------------------------------------------------------------------------

play_good_sound:
            lda #0
            sta SID_V3_FREQ_LO
            lda #GOOD_SFX_FREQ
            sta SID_V3_FREQ_HI
            lda #GOOD_SFX_AD
            sta SID_V3_AD
            lda #GOOD_SFX_SR
            sta SID_V3_SR
            lda #GOOD_SFX_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Increase Health
; ----------------------------------------------------------------------------

increase_health:
            clc
            adc health
            cmp #HEALTH_MAX
            bcc health_ok
            lda #HEALTH_MAX
health_ok:
            sta health
            jsr display_health
            rts

; ----------------------------------------------------------------------------
; Decrease Health
; ----------------------------------------------------------------------------

decrease_health:
            sta temp_track
            lda health
            sec
            sbc temp_track
            bcs health_not_zero
            lda #0
health_not_zero:
            sta health
            jsr display_health

            ; Check for game over
            lda health
            bne no_game_over
            jsr show_gameover
            lda #STATE_GAMEOVER
            sta game_state
no_game_over:
            rts

; ----------------------------------------------------------------------------
; Display Health Bar
; ----------------------------------------------------------------------------

display_health:
            ; Calculate filled blocks (health / 4 = blocks, max 16)
            lda health
            lsr
            lsr
            sta temp_track

            ldx #0
draw_health_bar:
            cpx temp_track
            bcs draw_empty_health
            lda #CHAR_BAR_FULL
            jmp store_health_char
draw_empty_health:
            lda #CHAR_BAR_EMPTY
store_health_char:
            sta SCREEN + (HEALTH_ROW * 40) + 8,x
            lda #HEALTH_COL
            sta COLRAM + (HEALTH_ROW * 40) + 8,x
            inx
            cpx #16
            bne draw_health_bar
            rts

; ----------------------------------------------------------------------------
; Display Progress Bar
; ----------------------------------------------------------------------------

display_progress:
            ; Calculate progress (song_beat / 4)
            lda song_beat
            lsr
            lsr
            sta temp_track

            ldx #0
draw_progress_bar:
            cpx temp_track
            bcs draw_empty_progress
            lda #CHAR_BAR_FULL
            jmp store_progress_char
draw_empty_progress:
            lda #CHAR_BAR_EMPTY
store_progress_char:
            sta SCREEN + (PROGRESS_ROW * 40) + 8,x
            lda #PROGRESS_COL
            sta COLRAM + (PROGRESS_ROW * 40) + 8,x
            inx
            cpx #PROGRESS_WIDTH
            bne draw_progress_bar
            rts

; ----------------------------------------------------------------------------
; Combo System
; ----------------------------------------------------------------------------

increment_combo:
            inc combo
            lda combo
            cmp max_combo
            bcc combo_no_max
            sta max_combo
combo_no_max:
            jsr display_combo
            rts

break_combo:
            lda #0
            sta combo
            jsr display_combo
            rts

display_combo:
            ; Display combo count
            lda combo
            ldx #0
combo_div_100:
            cmp #100
            bcc combo_done_100
            sec
            sbc #100
            inx
            jmp combo_div_100
combo_done_100:
            pha
            txa
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 34

            pla
            ldx #0
combo_div_10:
            cmp #10
            bcc combo_done_10
            sec
            sbc #10
            inx
            jmp combo_div_10
combo_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 35

            pla
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 36

            ; Colour based on multiplier
            jsr get_multiplier
            cmp #1
            beq combo_col_1x
            cmp #2
            beq combo_col_2x
            cmp #3
            beq combo_col_3x
            jmp combo_col_4x

combo_col_1x:
            lda #11             ; Grey
            jmp set_combo_col
combo_col_2x:
            lda #7              ; Yellow
            jmp set_combo_col
combo_col_3x:
            lda #5              ; Green
            jmp set_combo_col
combo_col_4x:
            lda #1              ; White

set_combo_col:
            sta COLRAM + (COMBO_ROW * 40) + 34
            sta COLRAM + (COMBO_ROW * 40) + 35
            sta COLRAM + (COMBO_ROW * 40) + 36
            rts

get_multiplier:
            lda combo
            cmp #COMBO_TIER_4
            bcs mult_4x
            cmp #COMBO_TIER_3
            bcs mult_3x
            cmp #COMBO_TIER_2
            bcs mult_2x
            lda #1
            rts
mult_2x:
            lda #2
            rts
mult_3x:
            lda #3
            rts
mult_4x:
            lda #4
            rts

; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------

reset_track_colours:
            ldx #1              ; Start at column 1 (skip label)
reset_col_loop:
            lda #TRACK_LINE_COL
            sta COLRAM + (TRACK1_ROW * 40),x
            sta COLRAM + (TRACK2_ROW * 40),x
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne reset_col_loop

            ; Restore hit zone colours
            lda #HIT_ZONE_COL
            sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN

            ; Redraw notes with correct colours
            ldx #0
redraw_loop:
            lda note_track,x
            beq redraw_next
            jsr draw_note
redraw_next:
            inx
            cpx #MAX_NOTES
            bne redraw_loop
            rts

; ----------------------------------------------------------------------------
; Update Border Flash
; ----------------------------------------------------------------------------

update_border_flash:
            lda border_flash
            beq flash_done
            dec border_flash
            bne flash_done
            lda #BORDER_COL
            sta BORDER
flash_done:
            rts

; ----------------------------------------------------------------------------
; Check Keys
; ----------------------------------------------------------------------------

check_keys:
            lda #$FD
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            bne check_x_key

            lda #1
            sta key_pressed
            jsr check_hit
            bcc check_x_key
            jsr play_voice1_note
            jsr flash_track1_hit
            jsr award_points

check_x_key:
            lda #$FB
            sta CIA1_PRA
            lda CIA1_PRB
            and #$80
            bne check_c_key

            lda #2
            sta key_pressed
            jsr check_hit
            bcc check_c_key
            jsr play_voice2_note
            jsr flash_track2_hit
            jsr award_points

check_c_key:
            lda #$FB
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10
            bne check_keys_done

            lda #3
            sta key_pressed
            jsr check_hit
            bcc check_keys_done
            jsr play_voice3_note
            jsr flash_track3_hit
            jsr award_points

check_keys_done:
            lda #$FF
            sta CIA1_PRA
            rts

; ----------------------------------------------------------------------------
; Check Hit
; ----------------------------------------------------------------------------

check_hit:
            ldx #0

check_hit_loop:
            lda note_track,x
            beq check_hit_next

            cmp key_pressed
            bne check_hit_next

            lda note_col,x
            cmp #HIT_ZONE_MIN
            bcc check_hit_next
            cmp #HIT_ZONE_MAX+1
            bcs check_hit_next

            lda note_freq,x
            sta hit_note_freq

            lda note_col,x
            cmp #HIT_ZONE_CENTRE
            bcc hit_good
            cmp #HIT_ZONE_CENTRE+2
            bcs hit_good

            lda #2
            sta hit_quality
            jmp hit_found

hit_good:
            lda #1
            sta hit_quality

hit_found:
            jsr erase_note
            lda #0
            sta note_track,x
            sec
            rts

check_hit_next:
            inx
            cpx #MAX_NOTES
            bne check_hit_loop

            lda #0
            sta hit_quality
            clc
            rts

; ----------------------------------------------------------------------------
; Check Song End
; ----------------------------------------------------------------------------

check_song_end:
            lda song_ended
            beq not_ended

            ; Check if all notes cleared
            ldx #0
check_notes_clear:
            lda note_track,x
            bne not_ended
            inx
            cpx #MAX_NOTES
            bne check_notes_clear

            ; Add delay before results
            inc end_delay
            lda end_delay
            cmp #END_DELAY_FRAMES
            bcc not_ended

            ; Show results
            jsr show_results
            lda #STATE_RESULTS
            sta game_state

not_ended:
            rts

; ----------------------------------------------------------------------------
; Award Points
; ----------------------------------------------------------------------------

award_points:
            jsr increment_combo

            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit
            lda #GOOD_SCORE
            jsr apply_multiplier
            jsr add_score

            inc good_count

            ; Play good sound effect
            jsr play_good_sound

            lda #GOOD_COL
            sta BORDER
            lda #4
            sta border_flash

            lda #HEALTH_GOOD
            jsr increase_health

            jmp award_done

award_perfect:
            ; Perfect hit
            lda #PERFECT_SCORE
            jsr apply_multiplier
            jsr add_score

            inc perfect_count

            ; Play perfect sound effect
            jsr play_perfect_sound

            lda #PERFECT_COL
            sta BORDER
            lda #6
            sta border_flash

            lda #HEALTH_PERFECT
            jsr increase_health

award_done:
            jsr display_score
            rts

; ----------------------------------------------------------------------------
; Apply Multiplier
; ----------------------------------------------------------------------------

apply_multiplier:
            sta base_score
            jsr get_multiplier
            sta current_mult

            lda base_score
            sta score_add_lo
            lda #0
            sta score_add_hi

            lda current_mult
            cmp #1
            beq mult_done_apply

            dec current_mult
mult_add_loop:
            lda score_add_lo
            clc
            adc base_score
            sta score_add_lo
            lda score_add_hi
            adc #0
            sta score_add_hi
            dec current_mult
            bne mult_add_loop

mult_done_apply:
            rts

base_score:    !byte 0
current_mult:  !byte 0
score_add_lo:  !byte 0
score_add_hi:  !byte 0

; ----------------------------------------------------------------------------
; Add Score
; ----------------------------------------------------------------------------

add_score:
            lda score_lo
            clc
            adc score_add_lo
            sta score_lo
            lda score_hi
            adc score_add_hi
            sta score_hi
            rts

; ----------------------------------------------------------------------------
; Display Score
; ----------------------------------------------------------------------------

display_score:
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc done_10000
            sta work_hi
            sty work_lo
            inx
            jmp div_10000
done_10000:
            txa
            ora #$30
            sta SCREEN + 8

            ldx #0
div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc done_1000
            sta work_hi
            sty work_lo
            inx
            jmp div_1000
done_1000:
            txa
            ora #$30
            sta SCREEN + 9

            ldx #0
div_100:
            lda work_lo
            sec
            sbc #100
            bcc done_100
            sta work_lo
            inx
            jmp div_100
done_100:
            txa
            ora #$30
            sta SCREEN + 10

            ldx #0
div_10:
            lda work_lo
            sec
            sbc #10
            bcc done_10
            sta work_lo
            inx
            jmp div_10
done_10:
            txa
            ora #$30
            sta SCREEN + 11

            lda work_lo
            ora #$30
            sta SCREEN + 12

            lda #7
            sta COLRAM + 8
            sta COLRAM + 9
            sta COLRAM + 10
            sta COLRAM + 11
            sta COLRAM + 12

            rts

; ----------------------------------------------------------------------------
; Display Misses
; ----------------------------------------------------------------------------

display_misses:
            lda miss_count

            ldx #0
miss_div_10:
            cmp #10
            bcc miss_done_10
            sec
            sbc #10
            inx
            jmp miss_div_10
miss_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + 21
            pla
            ora #$30
            sta SCREEN + 22

            lda #2
            sta COLRAM + 21
            sta COLRAM + 22

            rts

work_lo:    !byte 0
work_hi:    !byte 0

; ----------------------------------------------------------------------------
; Play Voices (for note playback)
; ----------------------------------------------------------------------------

play_voice1_note:
            lda #0
            sta SID_V1_FREQ_LO
            lda hit_note_freq
            sta SID_V1_FREQ_HI
            lda #VOICE1_WAVE
            ora #$01
            sta SID_V1_CTRL
            rts

play_voice2_note:
            lda #0
            sta SID_V2_FREQ_LO
            lda hit_note_freq
            sta SID_V2_FREQ_HI
            lda #VOICE2_WAVE
            ora #$01
            sta SID_V2_CTRL
            rts

play_voice3_note:
            lda #0
            sta SID_V3_FREQ_LO
            lda hit_note_freq
            sta SID_V3_FREQ_HI
            lda #VOICE3_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Flash Tracks
; ----------------------------------------------------------------------------

flash_track1_hit:
            ldx #0
            lda #HIT_COL
flash_t1h_loop:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne flash_t1h_loop
            lda #1
            sta COLRAM + (TRACK1_ROW * 40)
            rts

flash_track2_hit:
            ldx #0
            lda #HIT_COL
flash_t2h_loop:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne flash_t2h_loop
            lda #1
            sta COLRAM + (TRACK2_ROW * 40)
            rts

flash_track3_hit:
            ldx #0
            lda #HIT_COL
flash_t3h_loop:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne flash_t3h_loop
            lda #1
            sta COLRAM + (TRACK3_ROW * 40)
            rts

; ----------------------------------------------------------------------------
; Show Results Screen
; ----------------------------------------------------------------------------

show_results:
            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_results:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_results

            ; Draw "SONG COMPLETE!"
            ldx #0
draw_complete:
            lda complete_text,x
            beq draw_results_score
            sta SCREEN + (5 * 40) + 13,x
            lda #5
            sta COLRAM + (5 * 40) + 13,x
            inx
            jmp draw_complete

draw_results_score:
            ; Draw score label
            ldx #0
draw_rs_label:
            lda results_score_label,x
            beq draw_rs_value
            sta SCREEN + (9 * 40) + 12,x
            lda #7
            sta COLRAM + (9 * 40) + 12,x
            inx
            jmp draw_rs_label

draw_rs_value:
            ; Draw score value
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
rs_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc rs_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp rs_div_10000
rs_done_10000:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 23

            ldx #0
rs_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc rs_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp rs_div_1000
rs_done_1000:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 24

            ldx #0
rs_div_100:
            lda work_lo
            sec
            sbc #100
            bcc rs_done_100
            sta work_lo
            inx
            jmp rs_div_100
rs_done_100:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 25

            ldx #0
rs_div_10:
            lda work_lo
            sec
            sbc #10
            bcc rs_done_10
            sta work_lo
            inx
            jmp rs_div_10
rs_done_10:
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 26

            lda work_lo
            ora #$30
            sta SCREEN + (9 * 40) + 27

            lda #7
            sta COLRAM + (9 * 40) + 23
            sta COLRAM + (9 * 40) + 24
            sta COLRAM + (9 * 40) + 25
            sta COLRAM + (9 * 40) + 26
            sta COLRAM + (9 * 40) + 27

            ; Draw perfect count
            ldx #0
draw_perfect_label:
            lda perfect_label,x
            beq draw_perfect_value
            sta SCREEN + (11 * 40) + 12,x
            lda #1
            sta COLRAM + (11 * 40) + 12,x
            inx
            jmp draw_perfect_label

draw_perfect_value:
            lda perfect_count
            ldx #0
pv_div_10:
            cmp #10
            bcc pv_done_10
            sec
            sbc #10
            inx
            jmp pv_div_10
pv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (11 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (11 * 40) + 24
            lda #1
            sta COLRAM + (11 * 40) + 23
            sta COLRAM + (11 * 40) + 24

            ; Draw good count
            ldx #0
draw_good_label:
            lda good_label,x
            beq draw_good_value
            sta SCREEN + (12 * 40) + 12,x
            lda #7
            sta COLRAM + (12 * 40) + 12,x
            inx
            jmp draw_good_label

draw_good_value:
            lda good_count
            ldx #0
gv_div_10:
            cmp #10
            bcc gv_done_10
            sec
            sbc #10
            inx
            jmp gv_div_10
gv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (12 * 40) + 24
            lda #7
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24

            ; Draw miss count
            ldx #0
draw_miss_label_r:
            lda miss_label_r,x
            beq draw_miss_value
            sta SCREEN + (13 * 40) + 12,x
            lda #2
            sta COLRAM + (13 * 40) + 12,x
            inx
            jmp draw_miss_label_r

draw_miss_value:
            lda miss_count
            ldx #0
mv_div_10:
            cmp #10
            bcc mv_done_10
            sec
            sbc #10
            inx
            jmp mv_div_10
mv_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (13 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (13 * 40) + 24
            lda #2
            sta COLRAM + (13 * 40) + 23
            sta COLRAM + (13 * 40) + 24

            ; Draw max combo
            ldx #0
draw_maxc_label:
            lda maxcombo_label,x
            beq draw_maxc_value
            sta SCREEN + (15 * 40) + 12,x
            lda #COMBO_COL
            sta COLRAM + (15 * 40) + 12,x
            inx
            jmp draw_maxc_label

draw_maxc_value:
            lda max_combo
            ldx #0
mc_div_100:
            cmp #100
            bcc mc_done_100
            sec
            sbc #100
            inx
            jmp mc_div_100
mc_done_100:
            pha
            txa
            ora #$30
            sta SCREEN + (15 * 40) + 23

            pla
            ldx #0
mc_div_10:
            cmp #10
            bcc mc_done_10
            sec
            sbc #10
            inx
            jmp mc_div_10
mc_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (15 * 40) + 24
            pla
            ora #$30
            sta SCREEN + (15 * 40) + 25
            lda #COMBO_COL
            sta COLRAM + (15 * 40) + 23
            sta COLRAM + (15 * 40) + 24
            sta COLRAM + (15 * 40) + 25

            ; Draw "PRESS FIRE"
            ldx #0
draw_return:
            lda return_text,x
            beq results_done
            sta SCREEN + (20 * 40) + 10,x
            lda #11
            sta COLRAM + (20 * 40) + 10,x
            inx
            jmp draw_return

results_done:
            rts

complete_text:
            !scr "song complete!"
            !byte 0

results_score_label:
            !scr "final score:"
            !byte 0

perfect_label:
            !scr "perfect:"
            !byte 0

good_label:
            !scr "good:"
            !byte 0

miss_label_r:
            !scr "misses:"
            !byte 0

maxcombo_label:
            !scr "max combo:"
            !byte 0

return_text:
            !scr "press fire to continue"
            !byte 0

; ----------------------------------------------------------------------------
; Show Game Over Screen
; ----------------------------------------------------------------------------

show_gameover:
            ; Clear screen
            ldx #0
            lda #CHAR_SPACE
clear_gameover:
            sta SCREEN,x
            sta SCREEN+$100,x
            sta SCREEN+$200,x
            sta SCREEN+$2E8,x
            inx
            bne clear_gameover

            ; Draw "GAME OVER"
            ldx #0
draw_gameover_text:
            lda gameover_text,x
            beq draw_gameover_score
            sta SCREEN + (8 * 40) + 15,x
            lda #2
            sta COLRAM + (8 * 40) + 15,x
            inx
            jmp draw_gameover_text

draw_gameover_score:
            ; Draw score
            ldx #0
draw_go_score_label:
            lda gameover_score,x
            beq draw_go_score_value
            sta SCREEN + (12 * 40) + 12,x
            lda #7
            sta COLRAM + (12 * 40) + 12,x
            inx
            jmp draw_go_score_label

draw_go_score_value:
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ldx #0
go_div_10000:
            lda work_lo
            sec
            sbc #<10000
            tay
            lda work_hi
            sbc #>10000
            bcc go_done_10000
            sta work_hi
            sty work_lo
            inx
            jmp go_div_10000
go_done_10000:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 23

            ldx #0
go_div_1000:
            lda work_lo
            sec
            sbc #<1000
            tay
            lda work_hi
            sbc #>1000
            bcc go_done_1000
            sta work_hi
            sty work_lo
            inx
            jmp go_div_1000
go_done_1000:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 24

            ldx #0
go_div_100:
            lda work_lo
            sec
            sbc #100
            bcc go_done_100
            sta work_lo
            inx
            jmp go_div_100
go_done_100:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 25

            ldx #0
go_div_10:
            lda work_lo
            sec
            sbc #10
            bcc go_done_10
            sta work_lo
            inx
            jmp go_div_10
go_done_10:
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 26

            lda work_lo
            ora #$30
            sta SCREEN + (12 * 40) + 27

            lda #7
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24
            sta COLRAM + (12 * 40) + 25
            sta COLRAM + (12 * 40) + 26
            sta COLRAM + (12 * 40) + 27

            ; Draw retry message
            ldx #0
draw_retry:
            lda retry_text,x
            beq gameover_done
            sta SCREEN + (18 * 40) + 10,x
            lda #11
            sta COLRAM + (18 * 40) + 10,x
            inx
            jmp draw_retry

gameover_done:
            rts

gameover_text:
            !scr "game over"
            !byte 0

gameover_score:
            !scr "your score:"
            !byte 0

retry_text:
            !scr "press fire to continue"
            !byte 0

; ============================================================================
; SONG DATA - First Song: "First Steps"
; ============================================================================
; Format: beat, track (1-3), SID frequency high byte
; ============================================================================

song_data:
            ; Opening - gentle introduction
            !byte 0, 1, $47          ; Beat 0: Track 1, high note
            !byte 2, 2, $2C          ; Beat 2: Track 2, mid note
            !byte 4, 3, $11          ; Beat 4: Track 3, low note

            ; Build - add more notes
            !byte 8, 1, $3B
            !byte 10, 2, $27
            !byte 12, 3, $13

            ; Complexity - overlapping patterns
            !byte 16, 1, $35
            !byte 17, 2, $2C
            !byte 18, 1, $3B
            !byte 20, 3, $16

            ; Theme repeats
            !byte 24, 1, $47
            !byte 26, 2, $35
            !byte 28, 3, $11

            ; Variation
            !byte 32, 2, $2F
            !byte 34, 1, $4F
            !byte 36, 3, $17

            ; Building intensity
            !byte 40, 1, $58
            !byte 42, 2, $2C
            !byte 44, 3, $11
            !byte 46, 2, $27

            ; Climax section
            !byte 48, 1, $6A
            !byte 49, 2, $35
            !byte 50, 1, $58
            !byte 52, 3, $1A
            !byte 54, 2, $2F

            ; Resolution
            !byte 56, 1, $47
            !byte 58, 2, $2C
            !byte 60, 3, $11
            !byte 62, 1, $35

            !byte $FF               ; End marker

; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------

note_track:
            !fill MAX_NOTES, 0

note_col:
            !fill MAX_NOTES, 0

note_freq:
            !fill MAX_NOTES, 0

; ----------------------------------------------------------------------------
; Game Variables
; ----------------------------------------------------------------------------

score_lo:     !byte 0
score_hi:     !byte 0
miss_count:   !byte 0
perfect_count: !byte 0
good_count:   !byte 0
health:       !byte 0
combo:        !byte 0
max_combo:    !byte 0

; ============================================================================
; END OF SID SYMPHONY - UNIT 17
; ============================================================================

What’s Coming

The menu system is ready for expansion:

  • Unit 18: Add Song 2 with different character
  • Unit 19: Add difficulty selection to the menu
  • Unit 20: Difficulty variants for each song
  • Unit 21: Song 3 at faster tempo

The infrastructure built here pays off quickly.

What You’ve Learnt

  • Menu state management - Adding states to the state machine
  • Cursor tracking - Position variable and bounds checking
  • Key repeat delay - Preventing input spam
  • State transitions - Menu → Game → Menu flow
  • Sound feedback - Different sounds for different actions

Phase 2 is underway.

What Changed

Unit 16 → Unit 17
+1404-1461
11 ; ============================================================================
2-; SID SYMPHONY - Unit 16: Phase 1 Complete
2+; SID SYMPHONY - Unit 17: Song Selection Screen
33 ; ============================================================================
4-; The complete Phase 1 rhythm game. Polished, balanced, and ready to play.
5-; Title screen, gameplay, results, game over - all working together.
4+; Adding a menu system to select songs. Currently only one song, but the
5+; structure is ready for multiple songs in the next unit.
66 ;
7-; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
8-; Fire/Space = Start game, return to title
7+; New concepts: Menu state, cursor tracking, key repeat delay
8+;
9+; Controls: Z = Track 1, X = Track 2, C = Track 3
10+; Up/Down = Menu navigation
11+; Fire/Space = Start game / Select
912 ; ============================================================================
1013
1114 ; ============================================================================
1215 ; CUSTOMISATION SECTION
1316 ; ============================================================================
17+
18+; Screenshot mode - set to 1 to skip title and show menu immediately
19+; Used for automated screenshot capture. Override with: acme -DSCREENSHOT_MODE=1
20+!ifndef SCREENSHOT_MODE { SCREENSHOT_MODE = 0 }
1421
1522 ; SID Voice Settings (for note playback)
1623 VOICE1_WAVE = $21 ; Sawtooth for track 1
...
4855 MENU_SELECT_WAVE = $41 ; Pulse for click
4956 MENU_SELECT_AD = $00 ; Instant
5057 MENU_SELECT_SR = $80 ; Very short
58+
59+MENU_MOVE_FREQ = $10 ; Lower pitch for cursor move
60+MENU_MOVE_WAVE = $11 ; Triangle (soft)
61+MENU_MOVE_AD = $00 ; Instant
62+MENU_MOVE_SR = $40 ; Very short
5163
5264 ; Visual Settings
5365 BORDER_COL = 0
...
7284 ; Title screen colours
7385 TITLE_COL = 1 ; White for title
7486 SUBTITLE_COL = 11 ; Grey for subtitle
87+MENU_COL = 7 ; Yellow for menu items
88+CURSOR_COL = 1 ; White for cursor
7589
7690 ; ============================================================================
7791 ; SCORING SETTINGS (BALANCED)
...
112126
113127 SONG_LENGTH = 64 ; 64 beats at 120 BPM = ~32 seconds
114128 PROGRESS_WIDTH = 16 ; Progress bar width
129+
130+; ============================================================================
131+; MENU SETTINGS
132+; ============================================================================
133+
134+NUM_SONGS = 1 ; Currently only 1 song
135+KEY_DELAY = 10 ; Frames between key repeats
115136
116137 ; ============================================================================
117138 ; GAME STATES
118139 ; ============================================================================
119140
120141 STATE_TITLE = 0 ; Title screen
121-STATE_PLAYING = 1 ; Gameplay
122-STATE_RESULTS = 2 ; Success results
123-STATE_GAMEOVER = 3 ; Failure game over
142+STATE_MENU = 1 ; Song selection menu (NEW!)
143+STATE_PLAYING = 2 ; Gameplay
144+STATE_RESULTS = 3 ; Success results
145+STATE_GAMEOVER = 4 ; Failure game over
124146
125147 ; ============================================================================
126148 ; MEMORY MAP
...
185207 CHAR_SPACE = 32
186208 CHAR_BAR_FULL = 131
187209 CHAR_BAR_EMPTY = 132
210+CHAR_CURSOR = 62 ; > character for menu cursor
188211
189212 ; Note settings
190213 MAX_NOTES = 8 ; Maximum simultaneous notes
...
213236 song_beat = $0D
214237 song_ended = $0E
215238 end_delay = $0F
239+cursor_pos = $10 ; Menu cursor position (NEW!)
240+key_delay_count = $11 ; Key repeat delay counter (NEW!)
241+selected_song = $12 ; Which song to play (NEW!)
216242
217243 ; ----------------------------------------------------------------------------
218244 ; BASIC Stub
...
236262 start:
237263 jsr copy_charset
238264 jsr init_sid
239- jsr show_title
240265
241- ; Start in TITLE state
266+!if SCREENSHOT_MODE = 1 {
267+ ; Screenshot mode: skip title, go to menu
268+ jsr show_menu
269+ lda #STATE_MENU
270+ sta game_state
271+} else {
272+ ; Normal mode: show title screen
273+ jsr show_title
242274 lda #STATE_TITLE
243275 sta game_state
276+}
244277
245278 main_loop:
246279 lda #$FF
...
251284 lda game_state
252285 cmp #STATE_TITLE
253286 beq do_title
287+ cmp #STATE_MENU
288+ beq do_menu
254289 cmp #STATE_PLAYING
255290 beq do_playing
256291 cmp #STATE_RESULTS
...
259294
260295 do_title:
261296 jsr update_title
297+ jmp main_loop
298+
299+do_menu:
300+ jsr update_menu
262301 jmp main_loop
263302
264303 do_playing:
...
384423 ; Check for fire button (joystick port 2)
385424 lda CIA1_PRA
386425 and #$10 ; Bit 4 = fire
387- beq fire_pressed
426+ beq title_fire_pressed
388427
389428 ; Also check space bar as alternative
390429 lda #$7F
391430 sta CIA1_PRA
392431 lda CIA1_PRB
393432 and #$10 ; Space
394- beq fire_pressed
433+ beq title_fire_pressed
395434
396435 ; No input - stay on title
397436 lda #$FF
398437 sta CIA1_PRA
399438 rts
400439
401-fire_pressed:
440+title_fire_pressed:
402441 lda #$FF
403442 sta CIA1_PRA
404443
405444 ; Play menu select sound
406445 jsr play_menu_select
407446
408- ; Start the game!
447+ ; Go to song selection menu
448+ jsr show_menu
449+ lda #STATE_MENU
450+ sta game_state
451+ rts
452+
453+; ----------------------------------------------------------------------------
454+; Show Song Selection Menu
455+; ----------------------------------------------------------------------------
456+
457+show_menu:
458+ ; Clear screen
459+ ldx #0
460+ lda #CHAR_SPACE
461+clear_menu:
462+ sta SCREEN,x
463+ sta SCREEN+$100,x
464+ sta SCREEN+$200,x
465+ sta SCREEN+$2E8,x
466+ inx
467+ bne clear_menu
468+
469+ ; Initialise cursor
470+ lda #0
471+ sta cursor_pos
472+ sta key_delay_count
473+
474+ ; Draw menu title
475+ ldx #0
476+draw_menu_title:
477+ lda menu_title,x
478+ beq draw_menu_title_done
479+ sta SCREEN + (4 * 40) + 12,x
480+ lda #TITLE_COL
481+ sta COLRAM + (4 * 40) + 12,x
482+ inx
483+ jmp draw_menu_title
484+draw_menu_title_done:
485+
486+ ; Draw instructions
487+ ldx #0
488+draw_menu_instr:
489+ lda menu_instructions,x
490+ beq draw_menu_instr_done
491+ sta SCREEN + (22 * 40) + 8,x
492+ lda #SUBTITLE_COL
493+ sta COLRAM + (22 * 40) + 8,x
494+ inx
495+ jmp draw_menu_instr
496+draw_menu_instr_done:
497+
498+ ; Draw song list
499+ jsr draw_song_list
500+
501+ rts
502+
503+menu_title:
504+ !scr "select a song"
505+ !byte 0
506+
507+menu_instructions:
508+ !scr "up/down to select, fire to play"
509+ !byte 0
510+
511+; ----------------------------------------------------------------------------
512+; Draw Song List
513+; ----------------------------------------------------------------------------
514+
515+draw_song_list:
516+ ; Draw each song entry
517+ ; For now, just one song
518+
519+ ; Song 1
520+ ldx #0
521+draw_song1:
522+ lda song1_name,x
523+ beq draw_song1_done
524+ sta SCREEN + (10 * 40) + 14,x
525+ lda #MENU_COL
526+ sta COLRAM + (10 * 40) + 14,x
527+ inx
528+ jmp draw_song1
529+draw_song1_done:
530+
531+ ; Draw cursor at current position
532+ jsr draw_cursor
533+ rts
534+
535+; Song names
536+song1_name:
537+ !scr "first steps"
538+ !byte 0
539+
540+; ----------------------------------------------------------------------------
541+; Draw Menu Cursor
542+; ----------------------------------------------------------------------------
543+
544+draw_cursor:
545+ ; Clear all potential cursor positions
546+ lda #CHAR_SPACE
547+ sta SCREEN + (10 * 40) + 12
548+ ; (Room for more songs later)
549+
550+ ; Draw cursor at current position
551+ lda cursor_pos
552+ asl ; Multiply by 2 (rows per song)
553+ clc
554+ adc #10 ; Start row
555+ tax
556+
557+ ; Calculate screen position
558+ ; Row X * 40 = screen offset
559+ lda #0
560+ sta ZP_PTR
561+ lda #0
562+ sta ZP_PTR_HI
563+
564+ ; Multiply row by 40
565+cursor_row_mult:
566+ cpx #0
567+ beq cursor_row_done
568+ lda ZP_PTR
569+ clc
570+ adc #40
571+ sta ZP_PTR
572+ lda ZP_PTR_HI
573+ adc #0
574+ sta ZP_PTR_HI
575+ dex
576+ jmp cursor_row_mult
577+
578+cursor_row_done:
579+ ; Add column offset (12)
580+ lda ZP_PTR
581+ clc
582+ adc #12
583+ sta ZP_PTR
584+ lda ZP_PTR_HI
585+ adc #0
586+ sta ZP_PTR_HI
587+
588+ ; Add screen base
589+ lda ZP_PTR
590+ clc
591+ adc #<SCREEN
592+ sta ZP_PTR
593+ lda ZP_PTR_HI
594+ adc #>SCREEN
595+ sta ZP_PTR_HI
596+
597+ ; Draw cursor character
598+ lda #CHAR_CURSOR
599+ ldy #0
600+ sta (ZP_PTR),y
601+
602+ ; Set cursor colour
603+ lda ZP_PTR
604+ sec
605+ sbc #<SCREEN
606+ clc
607+ adc #<COLRAM
608+ sta ZP_PTR
609+ lda ZP_PTR_HI
610+ sbc #>SCREEN
611+ adc #>COLRAM
612+ sta ZP_PTR_HI
613+
614+ lda #CURSOR_COL
615+ sta (ZP_PTR),y
616+
617+ rts
618+
619+; ----------------------------------------------------------------------------
620+; Update Menu State
621+; ----------------------------------------------------------------------------
622+
623+update_menu:
624+ ; Handle key delay
625+ lda key_delay_count
626+ beq menu_check_input
627+ dec key_delay_count
628+ jmp menu_check_fire
629+
630+menu_check_input:
631+ ; Check joystick up (or W key)
632+ lda CIA1_PRA
633+ and #$01 ; Bit 0 = up
634+ beq menu_up_pressed
635+
636+ ; Check joystick down (or S key)
637+ lda CIA1_PRA
638+ and #$02 ; Bit 1 = down
639+ beq menu_down_pressed
640+
641+ jmp menu_check_fire
642+
643+menu_up_pressed:
644+ lda cursor_pos
645+ beq menu_set_delay ; Already at top
646+ dec cursor_pos
647+ jsr play_menu_move
648+ jsr draw_cursor
649+ jmp menu_set_delay
650+
651+menu_down_pressed:
652+ lda cursor_pos
653+ cmp #NUM_SONGS-1
654+ bcs menu_set_delay ; Already at bottom
655+ inc cursor_pos
656+ jsr play_menu_move
657+ jsr draw_cursor
658+ jmp menu_set_delay
659+
660+menu_set_delay:
661+ lda #KEY_DELAY
662+ sta key_delay_count
663+
664+menu_check_fire:
665+ ; Check for fire button
666+ lda CIA1_PRA
667+ and #$10
668+ beq menu_fire_pressed
669+
670+ ; Check space bar
671+ lda #$7F
672+ sta CIA1_PRA
673+ lda CIA1_PRB
674+ and #$10
675+ beq menu_fire_pressed
676+
677+ lda #$FF
678+ sta CIA1_PRA
679+ rts
680+
681+menu_fire_pressed:
682+ lda #$FF
683+ sta CIA1_PRA
684+
685+ ; Play select sound
686+ jsr play_menu_select
687+
688+ ; Store selected song
689+ lda cursor_pos
690+ sta selected_song
691+
692+ ; Transition to game
693+ jsr transition_to_game
694+ rts
695+
696+; ----------------------------------------------------------------------------
697+; Transition to Game
698+; ----------------------------------------------------------------------------
699+
700+transition_to_game:
701+ ; Set up the selected song (for now, only song 0)
702+ ; In future units, this will load different song data
703+
704+ ; Initialise game
409705 jsr init_game
410706 lda #STATE_PLAYING
411707 sta game_state
708+ rts
709+
710+; ----------------------------------------------------------------------------
711+; Play Menu Move Sound
712+; ----------------------------------------------------------------------------
713+
714+play_menu_move:
715+ lda #0
716+ sta SID_V3_FREQ_LO
717+ lda #MENU_MOVE_FREQ
718+ sta SID_V3_FREQ_HI
719+ lda #$08
720+ sta SID_V3_PWHI
721+ lda #MENU_MOVE_AD
722+ sta SID_V3_AD
723+ lda #MENU_MOVE_SR
724+ sta SID_V3_SR
725+ lda #MENU_MOVE_WAVE
726+ ora #$01 ; Gate on
727+ sta SID_V3_CTRL
412728 rts
413729
414730 ; ----------------------------------------------------------------------------
...
432748 rts
433749
434750 ; ----------------------------------------------------------------------------
435-; Initialize Game (called when starting from title)
751+; Initialize Game (called when starting from menu)
436752 ; ----------------------------------------------------------------------------
437753
438754 init_game:
...
485801 ; ----------------------------------------------------------------------------
486802
487803 update_results:
488- ; Check for fire to return to title
804+ ; Check for fire to return to menu
489805 lda CIA1_PRA
490806 and #$10
491807 beq results_fire
...
505821 lda #$FF
506822 sta CIA1_PRA
507823
508- ; Play menu select sound
509824 jsr play_menu_select
510825
511- ; Return to title screen
512- jsr show_title
513- lda #STATE_TITLE
826+ ; Return to menu (not title)
827+ jsr show_menu
828+ lda #STATE_MENU
514829 sta game_state
515830 rts
516831
...
519834 ; ----------------------------------------------------------------------------
520835
521836 update_gameover:
522- ; Check for fire to return to title
837+ ; Check for fire to return to menu
523838 lda CIA1_PRA
524839 and #$10
525840 beq gameover_fire
526841
842+ ; Also check space bar
527843 lda #$7F
528844 sta CIA1_PRA
529845 lda CIA1_PRB
...
538854 lda #$FF
539855 sta CIA1_PRA
540856
541- ; Play menu select sound
542857 jsr play_menu_select
543-
544- ; Return to title screen
545- jsr show_title
546- lda #STATE_TITLE
547- sta game_state
548- rts
549-
550-; ----------------------------------------------------------------------------
551-; Check Song End
552-; ----------------------------------------------------------------------------
553-
554-check_song_end:
555- lda song_ended
556- beq song_not_ended
557-
558- ldx #0
559-check_notes_clear:
560- lda note_track,x
561- bne notes_still_active
562- inx
563- cpx #MAX_NOTES
564- bne check_notes_clear
565-
566- dec end_delay
567- bne song_not_ended
568858
569- jsr show_results
570- lda #STATE_RESULTS
859+ ; Return to menu (not title)
860+ jsr show_menu
861+ lda #STATE_MENU
571862 sta game_state
572-
573-notes_still_active:
574-song_not_ended:
575- rts
576-
577-; ----------------------------------------------------------------------------
578-; Initialize Song
579-; ----------------------------------------------------------------------------
580-
581-init_song:
582- lda #<song_data
583- sta song_pos
584- lda #>song_data
585- sta song_pos_hi
586-
587- lda #0
588- sta frame_count
589- sta beat_count
590- sta song_beat
591- sta border_flash
592- sta song_ended
593-
594- lda #END_DELAY_FRAMES
595- sta end_delay
596-
597- jsr display_progress
598- rts
599-
600-; ----------------------------------------------------------------------------
601-; Advance Song
602-; ----------------------------------------------------------------------------
603-
604-advance_song:
605- lda song_ended
606- bne advance_done
607-
608- inc beat_count
609- inc song_beat
610-
611- lda song_beat
612- cmp #SONG_LENGTH
613- bcc song_continues
614-
615- lda #1
616- sta song_ended
617-
618-song_continues:
619- jsr display_progress
620-
621-advance_done:
622- rts
623-
624-; ----------------------------------------------------------------------------
625-; Display Progress
626-; ----------------------------------------------------------------------------
627-
628-display_progress:
629- lda song_beat
630- lsr
631- lsr
632- sta temp_progress
633-
634- ldx #0
635- lda temp_progress
636- beq draw_empty_progress
637-
638-draw_full_progress:
639- lda #CHAR_BAR_FULL
640- sta SCREEN + (PROGRESS_ROW * 40) + 12,x
641- lda #PROGRESS_COL
642- sta COLRAM + (PROGRESS_ROW * 40) + 12,x
643- inx
644- cpx temp_progress
645- bne draw_full_progress
646-
647-draw_empty_progress:
648- cpx #PROGRESS_WIDTH
649- beq progress_done
650- lda #CHAR_BAR_EMPTY
651- sta SCREEN + (PROGRESS_ROW * 40) + 12,x
652- lda #11
653- sta COLRAM + (PROGRESS_ROW * 40) + 12,x
654- inx
655- jmp draw_empty_progress
656-
657-progress_done:
658- rts
659-
660-temp_progress: !byte 0
661-
662-; ----------------------------------------------------------------------------
663-; Get Multiplier
664-; ----------------------------------------------------------------------------
665-
666-get_multiplier:
667- lda combo
668- cmp #COMBO_TIER_4
669- bcs mult_4x
670- cmp #COMBO_TIER_3
671- bcs mult_3x
672- cmp #COMBO_TIER_2
673- bcs mult_2x
674-
675- lda #1
676- rts
677-
678-mult_2x:
679- lda #2
680- rts
681-
682-mult_3x:
683- lda #3
684- rts
685-
686-mult_4x:
687- lda #4
688- rts
689-
690-; ----------------------------------------------------------------------------
691-; Display Combo
692-; ----------------------------------------------------------------------------
693-
694-display_combo:
695- ldx #0
696-draw_combo_label:
697- lda combo_label,x
698- beq draw_combo_value
699- sta SCREEN + (COMBO_ROW * 40) + 12,x
700- lda #11
701- sta COLRAM + (COMBO_ROW * 40) + 12,x
702- inx
703- jmp draw_combo_label
704-
705-draw_combo_value:
706- lda combo
707-
708- ldx #0
709-combo_div_100:
710- cmp #100
711- bcc combo_done_100
712- sec
713- sbc #100
714- inx
715- jmp combo_div_100
716-combo_done_100:
717- pha
718- txa
719- ora #$30
720- sta SCREEN + (COMBO_ROW * 40) + 18
721- pla
722-
723- ldx #0
724-combo_div_10:
725- cmp #10
726- bcc combo_done_10
727- sec
728- sbc #10
729- inx
730- jmp combo_div_10
731-combo_done_10:
732- pha
733- txa
734- ora #$30
735- sta SCREEN + (COMBO_ROW * 40) + 19
736- pla
737-
738- ora #$30
739- sta SCREEN + (COMBO_ROW * 40) + 20
740-
741- jsr get_multiplier
742- cmp #4
743- beq combo_col_4x
744- cmp #3
745- beq combo_col_3x
746- cmp #2
747- beq combo_col_2x
748-
749- lda #11
750- jmp set_combo_col
751-
752-combo_col_2x:
753- lda #7
754- jmp set_combo_col
755-
756-combo_col_3x:
757- lda #5
758- jmp set_combo_col
759-
760-combo_col_4x:
761- lda #1
762-
763-set_combo_col:
764- sta COLRAM + (COMBO_ROW * 40) + 18
765- sta COLRAM + (COMBO_ROW * 40) + 19
766- sta COLRAM + (COMBO_ROW * 40) + 20
767-
768- jsr get_multiplier
769- ora #$30
770- sta SCREEN + (COMBO_ROW * 40) + 22
771- lda #$18
772- sta SCREEN + (COMBO_ROW * 40) + 23
773-
774- jsr get_multiplier
775- cmp #4
776- beq mult_col_4x
777- cmp #3
778- beq mult_col_3x
779- cmp #2
780- beq mult_col_2x
781-
782- lda #11
783- jmp set_mult_col
784-
785-mult_col_2x:
786- lda #7
787- jmp set_mult_col
788-
789-mult_col_3x:
790- lda #5
791- jmp set_mult_col
792-
793-mult_col_4x:
794- lda #1
795-
796-set_mult_col:
797- sta COLRAM + (COMBO_ROW * 40) + 22
798- sta COLRAM + (COMBO_ROW * 40) + 23
799-
800- rts
801-
802-combo_label:
803- !scr "combo:"
804- !byte 0
805-
806-; ----------------------------------------------------------------------------
807-; Increment Combo
808-; ----------------------------------------------------------------------------
809-
810-increment_combo:
811- inc combo
812-
813- lda combo
814- cmp max_combo
815- bcc combo_not_max
816- sta max_combo
817-combo_not_max:
818-
819- jsr display_combo
820863 rts
821864
822865 ; ----------------------------------------------------------------------------
823-; Break Combo
866+; Initialize Screen (Gameplay)
824867 ; ----------------------------------------------------------------------------
825-
826-break_combo:
827- lda combo
828- beq combo_already_zero
829-
830- lda #0
831- sta combo
832- jsr display_combo
833868
834-combo_already_zero:
835- rts
869+init_screen:
870+ ; Enable custom charset
871+ lda #$1C ; Character ROM at $3000
872+ sta CHARPTR
836873
837-; ----------------------------------------------------------------------------
838-; Show Results Screen
839-; ----------------------------------------------------------------------------
874+ ; Set colours
875+ lda #BORDER_COL
876+ sta BORDER
877+ lda #BG_COL
878+ sta BGCOL
840879
841-show_results:
880+ ; Clear screen
842881 ldx #0
843882 lda #CHAR_SPACE
844-clear_for_results:
883+clear_screen:
845884 sta SCREEN,x
846885 sta SCREEN+$100,x
847886 sta SCREEN+$200,x
848887 sta SCREEN+$2E8,x
849888 inx
850- bne clear_for_results
889+ bne clear_screen
851890
891+ ; Draw tracks
852892 ldx #0
853-draw_results_title:
854- lda results_title,x
855- beq draw_results_title_done
856- sta SCREEN + (3 * 40) + 14,x
857- lda #1
858- sta COLRAM + (3 * 40) + 14,x
893+draw_tracks:
894+ lda #CHAR_TRACK
895+ sta SCREEN + (TRACK1_ROW * 40),x
896+ sta SCREEN + (TRACK2_ROW * 40),x
897+ sta SCREEN + (TRACK3_ROW * 40),x
898+ lda #TRACK_LINE_COL
899+ sta COLRAM + (TRACK1_ROW * 40),x
900+ sta COLRAM + (TRACK2_ROW * 40),x
901+ sta COLRAM + (TRACK3_ROW * 40),x
859902 inx
860- jmp draw_results_title
861-draw_results_title_done:
903+ cpx #38
904+ bne draw_tracks
862905
863- ldx #0
864-draw_final_score_label:
865- lda final_score_label,x
866- beq draw_final_score_done
867- sta SCREEN + (6 * 40) + 10,x
868- lda #7
869- sta COLRAM + (6 * 40) + 10,x
870- inx
871- jmp draw_final_score_label
872-draw_final_score_done:
873- jsr display_final_score
906+ ; Draw hit zones
907+ lda #CHAR_HITZONE
908+ sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
909+ sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
910+ sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
911+ lda #HIT_ZONE_COL
912+ sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
913+ sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
914+ sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
874915
875- ldx #0
876-draw_perfects_label:
877- lda perfects_label,x
878- beq draw_perfects_done
879- sta SCREEN + (8 * 40) + 10,x
916+ ; Draw track labels
917+ lda #$31 ; "1"
918+ sta SCREEN + (TRACK1_ROW * 40)
919+ lda #$32 ; "2"
920+ sta SCREEN + (TRACK2_ROW * 40)
921+ lda #$33 ; "3"
922+ sta SCREEN + (TRACK3_ROW * 40)
880923 lda #1
881- sta COLRAM + (8 * 40) + 10,x
882- inx
883- jmp draw_perfects_label
884-draw_perfects_done:
885- lda perfect_count
886- jsr display_stat_at_8
924+ sta COLRAM + (TRACK1_ROW * 40)
925+ sta COLRAM + (TRACK2_ROW * 40)
926+ sta COLRAM + (TRACK3_ROW * 40)
887927
928+ ; Draw HUD labels
888929 ldx #0
889-draw_goods_label:
890- lda goods_label,x
891- beq draw_goods_done
892- sta SCREEN + (9 * 40) + 10,x
930+draw_score_label:
931+ lda score_label,x
932+ beq draw_miss_label_start
933+ sta SCREEN,x
893934 lda #7
894- sta COLRAM + (9 * 40) + 10,x
935+ sta COLRAM,x
895936 inx
896- jmp draw_goods_label
897-draw_goods_done:
898- lda good_count
899- jsr display_stat_at_9
937+ jmp draw_score_label
900938
939+draw_miss_label_start:
901940 ldx #0
902-draw_misses_label:
903- lda misses_label,x
904- beq draw_misses_done
905- sta SCREEN + (10 * 40) + 10,x
941+draw_miss_label:
942+ lda miss_label,x
943+ beq draw_combo_label_start
944+ sta SCREEN + 14,x
906945 lda #2
907- sta COLRAM + (10 * 40) + 10,x
946+ sta COLRAM + 14,x
908947 inx
909- jmp draw_misses_label
910-draw_misses_done:
911- lda miss_count
912- jsr display_stat_at_10
948+ jmp draw_miss_label
913949
950+draw_combo_label_start:
914951 ldx #0
915-draw_max_combo_label:
916- lda max_combo_label,x
917- beq draw_max_combo_done
918- sta SCREEN + (12 * 40) + 10,x
919- lda #13
920- sta COLRAM + (12 * 40) + 10,x
952+draw_combo_label:
953+ lda combo_label,x
954+ beq draw_health_label_start
955+ sta SCREEN + (COMBO_ROW * 40) + 27,x
956+ lda #COMBO_COL
957+ sta COLRAM + (COMBO_ROW * 40) + 27,x
921958 inx
922- jmp draw_max_combo_label
923-draw_max_combo_done:
924- lda max_combo
925- jsr display_stat_at_12
959+ jmp draw_combo_label
926960
961+draw_health_label_start:
927962 ldx #0
928-draw_accuracy_label:
929- lda accuracy_label,x
930- beq draw_accuracy_done
931- sta SCREEN + (14 * 40) + 10,x
932- lda #5
933- sta COLRAM + (14 * 40) + 10,x
963+draw_health_label:
964+ lda health_label,x
965+ beq draw_progress_label_start
966+ sta SCREEN + (HEALTH_ROW * 40),x
967+ lda #HEALTH_COL
968+ sta COLRAM + (HEALTH_ROW * 40),x
934969 inx
935- jmp draw_accuracy_label
936-draw_accuracy_done:
937- jsr calculate_accuracy
938- jsr display_accuracy
970+ jmp draw_health_label
939971
972+draw_progress_label_start:
940973 ldx #0
941-draw_press_key:
942- lda press_fire_results,x
943- beq draw_press_key_done
944- sta SCREEN + (18 * 40) + 9,x
945- lda #7
946- sta COLRAM + (18 * 40) + 9,x
974+draw_progress_label:
975+ lda progress_label,x
976+ beq labels_done
977+ sta SCREEN + (PROGRESS_ROW * 40),x
978+ lda #PROGRESS_COL
979+ sta COLRAM + (PROGRESS_ROW * 40),x
947980 inx
948- jmp draw_press_key
949-draw_press_key_done:
981+ jmp draw_progress_label
950982
951- lda #5 ; Green border for success
952- sta BORDER
983+labels_done:
984+ ; Draw song name on gameplay screen
985+ ldx #0
986+draw_song_name:
987+ lda selected_song
988+ ; For now only song 0
989+ lda song1_name,x
990+ beq draw_song_name_done
991+ sta SCREEN + 28,x
992+ lda #11
993+ sta COLRAM + 28,x
994+ inx
995+ jmp draw_song_name
996+draw_song_name_done:
953997
954998 rts
955-
956-results_title:
957- !scr "song complete!"
958- !byte 0
959-
960-final_score_label:
961- !scr "final score:"
962- !byte 0
963-
964-perfects_label:
965- !scr "perfects:"
966- !byte 0
967999
968-goods_label:
969- !scr "goods:"
1000+score_label:
1001+ !scr "score:"
9701002 !byte 0
971-
972-misses_label:
973- !scr "misses:"
1003+miss_label:
1004+ !scr "miss:"
9741005 !byte 0
975-
976-max_combo_label:
977- !scr "max combo:"
1006+combo_label:
1007+ !scr "combo:"
9781008 !byte 0
979-
980-accuracy_label:
981- !scr "accuracy:"
1009+health_label:
1010+ !scr "health:"
9821011 !byte 0
983-
984-press_fire_results:
985- !scr "press fire for title"
1012+progress_label:
1013+ !scr "song:"
9861014 !byte 0
9871015
9881016 ; ----------------------------------------------------------------------------
989-; Display Final Score
1017+; Initialize Notes
9901018 ; ----------------------------------------------------------------------------
991-
992-display_final_score:
993- lda score_lo
994- sta work_lo
995- lda score_hi
996- sta work_hi
997-
998- ldx #0
999-fs_div_10000:
1000- lda work_lo
1001- sec
1002- sbc #<10000
1003- tay
1004- lda work_hi
1005- sbc #>10000
1006- bcc fs_done_10000
1007- sta work_hi
1008- sty work_lo
1009- inx
1010- jmp fs_div_10000
1011-fs_done_10000:
1012- txa
1013- ora #$30
1014- sta SCREEN + (6 * 40) + 23
1015-
1016- ldx #0
1017-fs_div_1000:
1018- lda work_lo
1019- sec
1020- sbc #<1000
1021- tay
1022- lda work_hi
1023- sbc #>1000
1024- bcc fs_done_1000
1025- sta work_hi
1026- sty work_lo
1027- inx
1028- jmp fs_div_1000
1029-fs_done_1000:
1030- txa
1031- ora #$30
1032- sta SCREEN + (6 * 40) + 24
1033-
1034- ldx #0
1035-fs_div_100:
1036- lda work_lo
1037- sec
1038- sbc #100
1039- bcc fs_done_100
1040- sta work_lo
1041- inx
1042- jmp fs_div_100
1043-fs_done_100:
1044- txa
1045- ora #$30
1046- sta SCREEN + (6 * 40) + 25
10471019
1020+init_notes:
10481021 ldx #0
1049-fs_div_10:
1050- lda work_lo
1051- sec
1052- sbc #10
1053- bcc fs_done_10
1054- sta work_lo
1022+ lda #0
1023+clear_notes:
1024+ sta note_track,x
1025+ sta note_col,x
1026+ sta note_freq,x
10551027 inx
1056- jmp fs_div_10
1057-fs_done_10:
1058- txa
1059- ora #$30
1060- sta SCREEN + (6 * 40) + 26
1061-
1062- lda work_lo
1063- ora #$30
1064- sta SCREEN + (6 * 40) + 27
1065-
1066- lda #7
1067- sta COLRAM + (6 * 40) + 23
1068- sta COLRAM + (6 * 40) + 24
1069- sta COLRAM + (6 * 40) + 25
1070- sta COLRAM + (6 * 40) + 26
1071- sta COLRAM + (6 * 40) + 27
1072-
1028+ cpx #MAX_NOTES
1029+ bne clear_notes
10731030 rts
10741031
10751032 ; ----------------------------------------------------------------------------
1076-; Display Stats
1033+; Initialize Score
10771034 ; ----------------------------------------------------------------------------
1078-
1079-display_stat_at_8:
1080- ldx #0
1081-stat8_div:
1082- cmp #10
1083- bcc stat8_done
1084- sec
1085- sbc #10
1086- inx
1087- jmp stat8_div
1088-stat8_done:
1089- pha
1090- txa
1091- ora #$30
1092- sta SCREEN + (8 * 40) + 23
1093- pla
1094- ora #$30
1095- sta SCREEN + (8 * 40) + 24
1096- lda #1
1097- sta COLRAM + (8 * 40) + 23
1098- sta COLRAM + (8 * 40) + 24
1099- rts
11001035
1101-display_stat_at_9:
1102- ldx #0
1103-stat9_div:
1104- cmp #10
1105- bcc stat9_done
1106- sec
1107- sbc #10
1108- inx
1109- jmp stat9_div
1110-stat9_done:
1111- pha
1112- txa
1113- ora #$30
1114- sta SCREEN + (9 * 40) + 23
1115- pla
1116- ora #$30
1117- sta SCREEN + (9 * 40) + 24
1118- lda #7
1119- sta COLRAM + (9 * 40) + 23
1120- sta COLRAM + (9 * 40) + 24
1036+init_score:
1037+ lda #0
1038+ sta score_lo
1039+ sta score_hi
1040+ sta miss_count
1041+ sta perfect_count
1042+ sta good_count
1043+ jsr display_score
1044+ jsr display_misses
11211045 rts
11221046
1123-display_stat_at_10:
1124- ldx #0
1125-stat10_div:
1126- cmp #10
1127- bcc stat10_done
1128- sec
1129- sbc #10
1130- inx
1131- jmp stat10_div
1132-stat10_done:
1133- pha
1134- txa
1135- ora #$30
1136- sta SCREEN + (10 * 40) + 23
1137- pla
1138- ora #$30
1139- sta SCREEN + (10 * 40) + 24
1140- lda #2
1141- sta COLRAM + (10 * 40) + 23
1142- sta COLRAM + (10 * 40) + 24
1143- rts
1047+; ----------------------------------------------------------------------------
1048+; Initialize Health
1049+; ----------------------------------------------------------------------------
11441050
1145-display_stat_at_12:
1146- ldx #0
1147-stat12_div:
1148- cmp #10
1149- bcc stat12_done
1150- sec
1151- sbc #10
1152- inx
1153- jmp stat12_div
1154-stat12_done:
1155- pha
1156- txa
1157- ora #$30
1158- sta SCREEN + (12 * 40) + 23
1159- pla
1160- ora #$30
1161- sta SCREEN + (12 * 40) + 24
1162- lda #13
1163- sta COLRAM + (12 * 40) + 23
1164- sta COLRAM + (12 * 40) + 24
1051+init_health:
1052+ lda #HEALTH_START
1053+ sta health
1054+ jsr display_health
11651055 rts
11661056
11671057 ; ----------------------------------------------------------------------------
1168-; Calculate Accuracy
1058+; Initialize Song
11691059 ; ----------------------------------------------------------------------------
1170-
1171-calculate_accuracy:
1172- lda perfect_count
1173- clc
1174- adc good_count
1175- sta total_hits
1176-
1177- clc
1178- adc miss_count
1179- sta total_notes
1180-
1181- beq accuracy_zero
11821060
1183- lda total_hits
1184- sta dividend_lo
1061+init_song:
11851062 lda #0
1186- sta dividend_hi
1187-
1188- ldx #100
1189-mult_loop:
1190- dex
1191- beq mult_done
1192- lda dividend_lo
1193- clc
1194- adc total_hits
1195- sta dividend_lo
1196- lda dividend_hi
1197- adc #0
1198- sta dividend_hi
1199- jmp mult_loop
1200-mult_done:
1063+ sta frame_count
1064+ sta beat_count
1065+ sta song_beat
1066+ sta song_ended
12011067
1202- lda #0
1203- sta accuracy
1204-div_loop:
1205- lda dividend_lo
1206- sec
1207- sbc total_notes
1208- tay
1209- lda dividend_hi
1210- sbc #0
1211- bcc div_done
1212- sta dividend_hi
1213- sty dividend_lo
1214- inc accuracy
1215- jmp div_loop
1216-div_done:
1217- rts
1068+ ; Point to song data (for now, only one song)
1069+ lda #<song_data
1070+ sta song_pos
1071+ lda #>song_data
1072+ sta song_pos_hi
12181073
1219-accuracy_zero:
1220- lda #0
1221- sta accuracy
1074+ jsr display_progress
12221075 rts
1223-
1224-total_hits: !byte 0
1225-total_notes: !byte 0
1226-dividend_lo: !byte 0
1227-dividend_hi: !byte 0
1228-accuracy: !byte 0
12291076
12301077 ; ----------------------------------------------------------------------------
1231-; Display Accuracy
1078+; Initialize SID
12321079 ; ----------------------------------------------------------------------------
1233-
1234-display_accuracy:
1235- lda accuracy
12361080
1237- ldx #0
1238-acc_div_100:
1239- cmp #100
1240- bcc acc_done_100
1241- sec
1242- sbc #100
1243- inx
1244- jmp acc_div_100
1245-acc_done_100:
1246- pha
1247- txa
1248- ora #$30
1249- sta SCREEN + (14 * 40) + 23
1250- pla
1081+init_sid:
1082+ ; Clear SID
1083+ ldx #$18
1084+ lda #0
1085+clear_sid:
1086+ sta SID,x
1087+ dex
1088+ bpl clear_sid
12511089
1252- ldx #0
1253-acc_div_10:
1254- cmp #10
1255- bcc acc_done_10
1256- sec
1257- sbc #10
1258- inx
1259- jmp acc_div_10
1260-acc_done_10:
1261- pha
1262- txa
1263- ora #$30
1264- sta SCREEN + (14 * 40) + 24
1265- pla
1090+ ; Set volume
1091+ lda #$0F
1092+ sta SID_VOLUME
12661093
1267- ora #$30
1268- sta SCREEN + (14 * 40) + 25
1094+ ; Set pulse widths
1095+ lda #PULSE_WIDTH
1096+ sta SID_V1_PWHI
1097+ sta SID_V2_PWHI
1098+ sta SID_V3_PWHI
12691099
1270- lda #$25 ; % symbol
1271- sta SCREEN + (14 * 40) + 26
1100+ ; Set ADSR for all voices
1101+ lda #VOICE_AD
1102+ sta SID_V1_AD
1103+ sta SID_V2_AD
1104+ sta SID_V3_AD
12721105
1273- lda #5
1274- sta COLRAM + (14 * 40) + 23
1275- sta COLRAM + (14 * 40) + 24
1276- sta COLRAM + (14 * 40) + 25
1277- sta COLRAM + (14 * 40) + 26
1106+ lda #VOICE_SR
1107+ sta SID_V1_SR
1108+ sta SID_V2_SR
1109+ sta SID_V3_SR
12781110
12791111 rts
12801112
12811113 ; ----------------------------------------------------------------------------
1282-; Copy Character Set
1114+; Copy Custom Charset
12831115 ; ----------------------------------------------------------------------------
12841116
12851117 copy_charset:
1118+ ; Copy default ROM charset to RAM first
12861119 sei
1287-
1288- lda $01
1289- pha
1290- and #$FB
1120+ lda #$33
12911121 sta $01
12921122
12931123 ldx #0
1294-copy_loop:
1124+copy_rom_chars:
12951125 lda $D000,x
12961126 sta CHARSET,x
12971127 lda $D100,x
...
13091139 lda $D700,x
13101140 sta CHARSET+$700,x
13111141 inx
1312- bne copy_loop
1142+ bne copy_rom_chars
13131143
1314- pla
1144+ lda #$37
13151145 sta $01
1316-
13171146 cli
1318-
1319- jsr define_custom_chars
1320-
1321- lda #$1C
1322- sta CHARPTR
1323-
1324- rts
13251147
1326-; ----------------------------------------------------------------------------
1327-; Define Custom Characters
1328-; ----------------------------------------------------------------------------
1148+ ; Now add custom characters
13291149
1330-define_custom_chars:
1331- ; Note character - arrow/chevron shape
1332- lda #%00000110
1333- sta CHARSET + (CHAR_NOTE * 8) + 0
1334- lda #%00011110
1335- sta CHARSET + (CHAR_NOTE * 8) + 1
1150+ ; Character 128 - Note (solid circle)
1151+ lda #%00111100
1152+ sta CHARSET + (128 * 8) + 0
13361153 lda #%01111110
1337- sta CHARSET + (CHAR_NOTE * 8) + 2
1338- lda #%11111110
1339- sta CHARSET + (CHAR_NOTE * 8) + 3
1340- lda #%11111110
1341- sta CHARSET + (CHAR_NOTE * 8) + 4
1154+ sta CHARSET + (128 * 8) + 1
1155+ lda #%11111111
1156+ sta CHARSET + (128 * 8) + 2
1157+ lda #%11111111
1158+ sta CHARSET + (128 * 8) + 3
1159+ lda #%11111111
1160+ sta CHARSET + (128 * 8) + 4
1161+ lda #%11111111
1162+ sta CHARSET + (128 * 8) + 5
13421163 lda #%01111110
1343- sta CHARSET + (CHAR_NOTE * 8) + 5
1344- lda #%00011110
1345- sta CHARSET + (CHAR_NOTE * 8) + 6
1346- lda #%00000110
1347- sta CHARSET + (CHAR_NOTE * 8) + 7
1164+ sta CHARSET + (128 * 8) + 6
1165+ lda #%00111100
1166+ sta CHARSET + (128 * 8) + 7
13481167
1349- ; Track line character
1168+ ; Character 129 - Track line (dashes)
13501169 lda #%00000000
1351- sta CHARSET + (CHAR_TRACK * 8) + 0
1352- sta CHARSET + (CHAR_TRACK * 8) + 1
1353- sta CHARSET + (CHAR_TRACK * 8) + 2
1354- lda #%11111111
1355- sta CHARSET + (CHAR_TRACK * 8) + 3
1356- sta CHARSET + (CHAR_TRACK * 8) + 4
1170+ sta CHARSET + (129 * 8) + 0
13571171 lda #%00000000
1358- sta CHARSET + (CHAR_TRACK * 8) + 5
1359- sta CHARSET + (CHAR_TRACK * 8) + 6
1360- sta CHARSET + (CHAR_TRACK * 8) + 7
1172+ sta CHARSET + (129 * 8) + 1
1173+ lda #%00000000
1174+ sta CHARSET + (129 * 8) + 2
1175+ lda #%11001100
1176+ sta CHARSET + (129 * 8) + 3
1177+ lda #%11001100
1178+ sta CHARSET + (129 * 8) + 4
1179+ lda #%00000000
1180+ sta CHARSET + (129 * 8) + 5
1181+ lda #%00000000
1182+ sta CHARSET + (129 * 8) + 6
1183+ lda #%00000000
1184+ sta CHARSET + (129 * 8) + 7
13611185
1362- ; Hit zone character
1363- lda #%01100110
1364- sta CHARSET + (CHAR_HITZONE * 8) + 0
1365- sta CHARSET + (CHAR_HITZONE * 8) + 1
1366- sta CHARSET + (CHAR_HITZONE * 8) + 2
1367- sta CHARSET + (CHAR_HITZONE * 8) + 3
1368- sta CHARSET + (CHAR_HITZONE * 8) + 4
1369- sta CHARSET + (CHAR_HITZONE * 8) + 5
1370- sta CHARSET + (CHAR_HITZONE * 8) + 6
1371- sta CHARSET + (CHAR_HITZONE * 8) + 7
1186+ ; Character 130 - Hit zone (vertical bars)
1187+ lda #%10000001
1188+ sta CHARSET + (130 * 8) + 0
1189+ lda #%10000001
1190+ sta CHARSET + (130 * 8) + 1
1191+ lda #%10000001
1192+ sta CHARSET + (130 * 8) + 2
1193+ lda #%10000001
1194+ sta CHARSET + (130 * 8) + 3
1195+ lda #%10000001
1196+ sta CHARSET + (130 * 8) + 4
1197+ lda #%10000001
1198+ sta CHARSET + (130 * 8) + 5
1199+ lda #%10000001
1200+ sta CHARSET + (130 * 8) + 6
1201+ lda #%10000001
1202+ sta CHARSET + (130 * 8) + 7
13721203
1373- ; Full bar character
1204+ ; Character 131 - Bar full (solid block)
13741205 lda #%11111111
1375- sta CHARSET + (CHAR_BAR_FULL * 8) + 0
1376- sta CHARSET + (CHAR_BAR_FULL * 8) + 1
1377- sta CHARSET + (CHAR_BAR_FULL * 8) + 2
1378- sta CHARSET + (CHAR_BAR_FULL * 8) + 3
1379- sta CHARSET + (CHAR_BAR_FULL * 8) + 4
1380- sta CHARSET + (CHAR_BAR_FULL * 8) + 5
1381- sta CHARSET + (CHAR_BAR_FULL * 8) + 6
1382- sta CHARSET + (CHAR_BAR_FULL * 8) + 7
1206+ sta CHARSET + (131 * 8) + 0
1207+ sta CHARSET + (131 * 8) + 1
1208+ sta CHARSET + (131 * 8) + 2
1209+ sta CHARSET + (131 * 8) + 3
1210+ sta CHARSET + (131 * 8) + 4
1211+ sta CHARSET + (131 * 8) + 5
1212+ sta CHARSET + (131 * 8) + 6
1213+ sta CHARSET + (131 * 8) + 7
13831214
1384- ; Empty bar character
1215+ ; Character 132 - Bar empty (hollow block)
13851216 lda #%11111111
1386- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 0
1217+ sta CHARSET + (132 * 8) + 0
13871218 lda #%10000001
1388- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 1
1389- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 2
1390- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 3
1391- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 4
1392- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 5
1393- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 6
1219+ sta CHARSET + (132 * 8) + 1
1220+ sta CHARSET + (132 * 8) + 2
1221+ sta CHARSET + (132 * 8) + 3
1222+ sta CHARSET + (132 * 8) + 4
1223+ sta CHARSET + (132 * 8) + 5
1224+ sta CHARSET + (132 * 8) + 6
13941225 lda #%11111111
1395- sta CHARSET + (CHAR_BAR_EMPTY * 8) + 7
1396-
1397- rts
1398-
1399-; ----------------------------------------------------------------------------
1400-; Initialize Health
1401-; ----------------------------------------------------------------------------
1402-
1403-init_health:
1404- lda #HEALTH_START
1405- sta health
1406- jsr display_health
1407- rts
1408-
1409-; ----------------------------------------------------------------------------
1410-; Initialize Score
1411-; ----------------------------------------------------------------------------
1226+ sta CHARSET + (132 * 8) + 7
14121227
1413-init_score:
1414- lda #0
1415- sta score_lo
1416- sta score_hi
1417- sta miss_count
1418- sta perfect_count
1419- sta good_count
1420- jsr display_score
1421- jsr display_misses
14221228 rts
14231229
14241230 ; ----------------------------------------------------------------------------
1425-; Initialize Notes
1231+; Advance Song
14261232 ; ----------------------------------------------------------------------------
14271233
1428-init_notes:
1429- ldx #MAX_NOTES-1
1430- lda #0
1431-init_notes_loop:
1432- sta note_track,x
1433- sta note_col,x
1434- sta note_freq,x
1435- dex
1436- bpl init_notes_loop
1234+advance_song:
1235+ inc song_beat
1236+ jsr display_progress
14371237 rts
14381238
14391239 ; ----------------------------------------------------------------------------
...
14411241 ; ----------------------------------------------------------------------------
14421242
14431243 check_spawn_note:
1244+ ; Check if song has ended
14441245 lda song_ended
1445- bne spawn_done_early
1246+ bne spawn_done
1247+
1248+ ; Get pointer to current position in song
1249+ lda song_pos
1250+ sta ZP_PTR
1251+ lda song_pos_hi
1252+ sta ZP_PTR_HI
14461253
1254+ ; Read beat number
14471255 ldy #0
1256+ lda (ZP_PTR),y
14481257
1449-spawn_check_loop:
1450- lda (song_pos),y
1258+ ; Check for end marker
14511259 cmp #$FF
1452- beq spawn_song_end
1453-
1454- cmp beat_count
1455- beq spawn_match
1456- bcs spawn_done_early
1260+ beq song_end_marker
14571261
1458- jmp spawn_advance
1262+ ; Check if this beat matches
1263+ cmp song_beat
1264+ bne spawn_done
14591265
1460-spawn_match:
1266+ ; Spawn this note
14611267 iny
1462- lda (song_pos),y
1268+ lda (ZP_PTR),y ; Track
14631269 sta temp_track
14641270 iny
1465- lda (song_pos),y
1466- pha
1467- lda temp_track
1468- jsr spawn_note_with_freq
1469- pla
1470- dey
1471- dey
1472-
1473-spawn_advance:
1474- lda song_pos
1475- clc
1476- adc #3
1477- sta song_pos
1478- lda song_pos_hi
1479- adc #0
1480- sta song_pos_hi
1481- jmp spawn_check_loop
1482-
1483-spawn_song_end:
1484- lda #1
1485- sta song_ended
1486-
1487-spawn_done_early:
1488- rts
1489-
1490-; ----------------------------------------------------------------------------
1491-; Spawn Note With Frequency
1492-; ----------------------------------------------------------------------------
1493-
1494-spawn_note_with_freq:
1495- sta temp_track
1271+ lda (ZP_PTR),y ; Frequency
14961272
1273+ ; Find free note slot
14971274 ldx #0
1498-spawn_find_slot:
1275+find_slot:
14991276 lda note_track,x
1500- beq spawn_found_slot
1277+ beq found_slot
15011278 inx
15021279 cpx #MAX_NOTES
1503- bne spawn_find_slot
1504- rts
1280+ bne find_slot
1281+ jmp advance_song_ptr ; No free slot, skip note
15051282
1506-spawn_found_slot:
1283+found_slot:
15071284 lda temp_track
15081285 sta note_track,x
15091286 lda #NOTE_SPAWN_COL
15101287 sta note_col,x
1511-
1512- tsx
1513- lda $0103,x
1288+ ldy #2
1289+ lda (ZP_PTR),y
15141290 sta note_freq,x
1515-
15161291 jsr draw_note
1292+
1293+advance_song_ptr:
1294+ ; Move to next note in song
1295+ lda song_pos
1296+ clc
1297+ adc #3
1298+ sta song_pos
1299+ lda song_pos_hi
1300+ adc #0
1301+ sta song_pos_hi
1302+
1303+ ; Check if there are more notes on this beat
1304+ jmp check_spawn_note
1305+
1306+song_end_marker:
1307+ lda #1
1308+ sta song_ended
1309+spawn_done:
15171310 rts
15181311
15191312 ; ----------------------------------------------------------------------------
...
15221315
15231316 update_notes:
15241317 ldx #0
1525-
1526-update_loop:
1318+update_notes_loop:
15271319 lda note_track,x
1528- beq update_next
1320+ beq update_next_note
15291321
1322+ ; Erase at old position
15301323 jsr erase_note
15311324
1325+ ; Move note left
15321326 dec note_col,x
1533- lda note_col,x
1534- cmp #1
1535- bcc update_miss
15361327
1537- jsr draw_note
1538- jmp update_next
1328+ ; Check if past hit zone (missed)
1329+ lda note_col,x
1330+ cmp #HIT_ZONE_MIN
1331+ bcs note_still_active
15391332
1540-update_miss:
1333+ ; Note was missed
15411334 lda note_track,x
15421335 sta miss_track
15431336 lda #0
15441337 sta note_track,x
15451338 jsr handle_miss
1546-
1547-update_next:
1548- inx
1549- cpx #MAX_NOTES
1550- bne update_loop
1551- rts
1552-
1553-; ----------------------------------------------------------------------------
1554-; Handle Miss
1555-; ----------------------------------------------------------------------------
1556-
1557-handle_miss:
1558- inc miss_count
1559-
1560- jsr play_miss_sound
1561-
1562- lda #MISS_COL
1563- sta BORDER
1564- lda #8
1565- sta border_flash
1566-
1567- jsr display_misses
1568- jsr decrease_health
1569- jsr break_combo
1570-
1571- rts
1572-
1573-; ----------------------------------------------------------------------------
1574-; Decrease Health
1575-; ----------------------------------------------------------------------------
1576-
1577-decrease_health:
1578- lda health
1579- sec
1580- sbc #HEALTH_MISS
1581- bcc health_zero
1582- sta health
1583- jsr display_health
1584- jsr check_game_over
1585- rts
1586-
1587-health_zero:
1588- lda #0
1589- sta health
1590- jsr display_health
1591- jsr check_game_over
1592- rts
1593-
1594-; ----------------------------------------------------------------------------
1595-; Increase Health
1596-; ----------------------------------------------------------------------------
1597-
1598-increase_health:
1599- clc
1600- adc health
1601- cmp #HEALTH_MAX
1602- bcc health_ok
1603- lda #HEALTH_MAX
1604-health_ok:
1605- sta health
1606- jsr display_health
1607- rts
1608-
1609-; ----------------------------------------------------------------------------
1610-; Check Game Over
1611-; ----------------------------------------------------------------------------
1612-
1613-check_game_over:
1614- lda health
1615- bne not_game_over
1616-
1617- lda #STATE_GAMEOVER
1618- sta game_state
1619- jsr show_game_over
1620-
1621-not_game_over:
1622- rts
1623-
1624-; ----------------------------------------------------------------------------
1625-; Show Game Over
1626-; ----------------------------------------------------------------------------
1627-
1628-show_game_over:
1629- ; Clear screen for game over display
1630- ldx #0
1631- lda #CHAR_SPACE
1632-clear_gameover:
1633- sta SCREEN,x
1634- sta SCREEN+$100,x
1635- sta SCREEN+$200,x
1636- sta SCREEN+$2E8,x
1637- inx
1638- bne clear_gameover
1639-
1640- ; Draw "GAME OVER" in red
1641- ldx #0
1642-game_over_loop:
1643- lda game_over_text,x
1644- beq game_over_text_done
1645- sta SCREEN + (6 * 40) + 15,x
1646- lda #2 ; Red for failure
1647- sta COLRAM + (6 * 40) + 15,x
1648- inx
1649- jmp game_over_loop
1650-game_over_text_done:
1651-
1652- ; Draw "HEALTH DEPLETED"
1653- ldx #0
1654-draw_depleted:
1655- lda depleted_text,x
1656- beq draw_depleted_done
1657- sta SCREEN + (8 * 40) + 12,x
1658- lda #10 ; Light red
1659- sta COLRAM + (8 * 40) + 12,x
1660- inx
1661- jmp draw_depleted
1662-draw_depleted_done:
1663-
1664- ; Show score achieved
1665- ldx #0
1666-draw_go_score_label:
1667- lda go_score_label,x
1668- beq draw_go_score_done
1669- sta SCREEN + (12 * 40) + 11,x
1670- lda #7
1671- sta COLRAM + (12 * 40) + 11,x
1672- inx
1673- jmp draw_go_score_label
1674-draw_go_score_done:
1675- jsr display_gameover_score
1676-
1677- ; Show notes hit
1678- ldx #0
1679-draw_go_notes_label:
1680- lda go_notes_label,x
1681- beq draw_go_notes_done
1682- sta SCREEN + (14 * 40) + 11,x
1683- lda #11
1684- sta COLRAM + (14 * 40) + 11,x
1685- inx
1686- jmp draw_go_notes_label
1687-draw_go_notes_done:
1688- lda perfect_count
1689- clc
1690- adc good_count
1691- jsr display_go_stat
1692-
1693- ; "PRESS FIRE FOR TITLE"
1694- ldx #0
1695-game_over_press:
1696- lda press_fire_gameover,x
1697- beq game_over_done
1698- sta SCREEN + (18 * 40) + 10,x
1699- lda #7 ; Yellow for action
1700- sta COLRAM + (18 * 40) + 10,x
1701- inx
1702- jmp game_over_press
1703-
1704-game_over_done:
1705- lda #2 ; Red border for failure
1706- sta BORDER
1707- rts
1708-
1709-game_over_text:
1710- !scr "game over"
1711- !byte 0
1712-
1713-depleted_text:
1714- !scr "health depleted"
1715- !byte 0
1716-
1717-go_score_label:
1718- !scr "score achieved:"
1719- !byte 0
1720-
1721-go_notes_label:
1722- !scr "notes hit:"
1723- !byte 0
1724-
1725-press_fire_gameover:
1726- !scr "press fire for title"
1727- !byte 0
1728-
1729-; Display gameover score (row 12)
1730-display_gameover_score:
1731- lda score_lo
1732- sta work_lo
1733- lda score_hi
1734- sta work_hi
1735-
1736- ldx #0
1737-go_div_10000:
1738- lda work_lo
1739- sec
1740- sbc #<10000
1741- tay
1742- lda work_hi
1743- sbc #>10000
1744- bcc go_done_10000
1745- sta work_hi
1746- sty work_lo
1747- inx
1748- jmp go_div_10000
1749-go_done_10000:
1750- txa
1751- ora #$30
1752- sta SCREEN + (12 * 40) + 27
1753-
1754- ldx #0
1755-go_div_1000:
1756- lda work_lo
1757- sec
1758- sbc #<1000
1759- tay
1760- lda work_hi
1761- sbc #>1000
1762- bcc go_done_1000
1763- sta work_hi
1764- sty work_lo
1765- inx
1766- jmp go_div_1000
1767-go_done_1000:
1768- txa
1769- ora #$30
1770- sta SCREEN + (12 * 40) + 28
1771-
1772- ldx #0
1773-go_div_100:
1774- lda work_lo
1775- sec
1776- sbc #100
1777- bcc go_done_100
1778- sta work_lo
1779- inx
1780- jmp go_div_100
1781-go_done_100:
1782- txa
1783- ora #$30
1784- sta SCREEN + (12 * 40) + 29
1785-
1786- ldx #0
1787-go_div_10:
1788- lda work_lo
1789- sec
1790- sbc #10
1791- bcc go_done_10
1792- sta work_lo
1793- inx
1794- jmp go_div_10
1795-go_done_10:
1796- txa
1797- ora #$30
1798- sta SCREEN + (12 * 40) + 30
1799-
1800- lda work_lo
1801- ora #$30
1802- sta SCREEN + (12 * 40) + 31
1803-
1804- lda #7
1805- sta COLRAM + (12 * 40) + 27
1806- sta COLRAM + (12 * 40) + 28
1807- sta COLRAM + (12 * 40) + 29
1808- sta COLRAM + (12 * 40) + 30
1809- sta COLRAM + (12 * 40) + 31
1810- rts
1811-
1812-; Display notes hit stat at row 14
1813-display_go_stat:
1814- ldx #0
1815-go_stat_div:
1816- cmp #10
1817- bcc go_stat_done
1818- sec
1819- sbc #10
1820- inx
1821- jmp go_stat_div
1822-go_stat_done:
1823- pha
1824- txa
1825- ora #$30
1826- sta SCREEN + (14 * 40) + 27
1827- pla
1828- ora #$30
1829- sta SCREEN + (14 * 40) + 28
1830- lda #11
1831- sta COLRAM + (14 * 40) + 27
1832- sta COLRAM + (14 * 40) + 28
1833- rts
1834-
1835-; ----------------------------------------------------------------------------
1836-; Display Health
1837-; ----------------------------------------------------------------------------
1838-
1839-display_health:
1840- lda health
1841- lsr
1842- lsr
1843- lsr
1844- sta temp_health
1845-
1846- ldx #0
1847- lda temp_health
1848- beq draw_empty_bars
1339+ jmp update_next_note
18491340
1850-draw_full_bars:
1851- lda #CHAR_BAR_FULL
1852- sta SCREEN + (HEALTH_ROW * 40) + 12,x
1853- lda #HEALTH_COL
1854- sta COLRAM + (HEALTH_ROW * 40) + 12,x
1855- inx
1856- cpx temp_health
1857- bne draw_full_bars
1341+note_still_active:
1342+ jsr draw_note
18581343
1859-draw_empty_bars:
1860- cpx #8
1861- beq health_done
1862- lda #CHAR_BAR_EMPTY
1863- sta SCREEN + (HEALTH_ROW * 40) + 12,x
1864- lda #11
1865- sta COLRAM + (HEALTH_ROW * 40) + 12,x
1344+update_next_note:
18661345 inx
1867- jmp draw_empty_bars
1868-
1869-health_done:
1870- rts
1871-
1872-temp_health: !byte 0
1873-
1874-; ============================================================================
1875-; POLISHED SOUND EFFECTS
1876-; ============================================================================
1877-
1878-; Play Perfect Hit Sound - Bright, high, satisfying
1879-play_perfect_sound:
1880- lda #0
1881- sta SID_V3_FREQ_LO
1882- lda #PERFECT_SFX_FREQ
1883- sta SID_V3_FREQ_HI
1884- lda #$08
1885- sta SID_V3_PWHI
1886- lda #PERFECT_SFX_AD
1887- sta SID_V3_AD
1888- lda #PERFECT_SFX_SR
1889- sta SID_V3_SR
1890- lda #PERFECT_SFX_WAVE
1891- ora #$01 ; Gate on
1892- sta SID_V3_CTRL
1893- rts
1894-
1895-; Play Good Hit Sound - Positive but lesser
1896-play_good_sound:
1897- lda #0
1898- sta SID_V3_FREQ_LO
1899- lda #GOOD_SFX_FREQ
1900- sta SID_V3_FREQ_HI
1901- lda #$08
1902- sta SID_V3_PWHI
1903- lda #GOOD_SFX_AD
1904- sta SID_V3_AD
1905- lda #GOOD_SFX_SR
1906- sta SID_V3_SR
1907- lda #GOOD_SFX_WAVE
1908- ora #$01 ; Gate on
1909- sta SID_V3_CTRL
1910- rts
1911-
1912-; Play Miss Sound - Harsh buzz
1913-play_miss_sound:
1914- lda #0
1915- sta SID_V3_FREQ_LO
1916- lda #MISS_FREQ
1917- sta SID_V3_FREQ_HI
1918- lda #MISS_AD
1919- sta SID_V3_AD
1920- lda #MISS_SR
1921- sta SID_V3_SR
1922- lda #MISS_WAVE
1923- ora #$01 ; Gate on
1924- sta SID_V3_CTRL
1346+ cpx #MAX_NOTES
1347+ bne update_notes_loop
19251348 rts
19261349
19271350 ; ----------------------------------------------------------------------------
...
19341357 beq draw_note_t1
19351358 cmp #2
19361359 beq draw_note_t2
1937- cmp #3
1938- beq draw_note_t3
1939- rts
1360+ jmp draw_note_t3
19401361
19411362 draw_note_t1:
19421363 lda note_col,x
...
19471368 adc #0
19481369 sta ZP_PTR_HI
19491370
1950- ldy #0
19511371 lda #CHAR_NOTE
1372+ ldy #0
19521373 sta (ZP_PTR),y
19531374
1375+ ; Set colour
19541376 lda note_col,x
19551377 clc
19561378 adc #<(COLRAM + TRACK1_ROW * 40)
...
19711393 adc #0
19721394 sta ZP_PTR_HI
19731395
1974- ldy #0
19751396 lda #CHAR_NOTE
1397+ ldy #0
19761398 sta (ZP_PTR),y
19771399
19781400 lda note_col,x
...
19951417 adc #0
19961418 sta ZP_PTR_HI
19971419
1998- ldy #0
19991420 lda #CHAR_NOTE
1421+ ldy #0
20001422 sta (ZP_PTR),y
20011423
20021424 lda note_col,x
...
20201442 beq erase_note_t1
20211443 cmp #2
20221444 beq erase_note_t2
2023- cmp #3
2024- beq erase_note_t3
2025- rts
1445+ jmp erase_note_t3
20261446
20271447 erase_note_t1:
20281448 lda note_col,x
...
20331453 adc #0
20341454 sta ZP_PTR_HI
20351455
2036- ldy #0
20371456 lda #CHAR_TRACK
1457+ ldy #0
20381458 sta (ZP_PTR),y
20391459
20401460 lda note_col,x
...
20571477 adc #0
20581478 sta ZP_PTR_HI
20591479
2060- ldy #0
20611480 lda #CHAR_TRACK
1481+ ldy #0
20621482 sta (ZP_PTR),y
20631483
20641484 lda note_col,x
...
20811501 adc #0
20821502 sta ZP_PTR_HI
20831503
2084- ldy #0
20851504 lda #CHAR_TRACK
1505+ ldy #0
20861506 sta (ZP_PTR),y
20871507
20881508 lda note_col,x
...
20971517 rts
20981518
20991519 ; ----------------------------------------------------------------------------
2100-; Initialize Screen
1520+; Handle Miss
21011521 ; ----------------------------------------------------------------------------
2102-
2103-init_screen:
2104- lda #BORDER_COL
2105- sta BORDER
2106- lda #BG_COL
2107- sta BGCOL
2108-
2109- ldx #0
2110- lda #CHAR_SPACE
2111-clr_screen:
2112- sta SCREEN,x
2113- sta SCREEN+$100,x
2114- sta SCREEN+$200,x
2115- sta SCREEN+$2E8,x
2116- inx
2117- bne clr_screen
21181522
2119- ldx #0
2120- lda #TRACK_LINE_COL
2121-clr_colour:
2122- sta COLRAM,x
2123- sta COLRAM+$100,x
2124- sta COLRAM+$200,x
2125- sta COLRAM+$2E8,x
2126- inx
2127- bne clr_colour
1523+handle_miss:
1524+ jsr play_miss_sound
1525+ jsr break_combo
21281526
2129- jsr draw_tracks
2130- jsr draw_hit_zones
2131- jsr draw_labels
1527+ ; Decrease health
1528+ lda #HEALTH_MISS
1529+ jsr decrease_health
21321530
2133- rts
1531+ ; Flash border red
1532+ lda #MISS_COL
1533+ sta BORDER
1534+ lda #8
1535+ sta border_flash
21341536
2135-; ----------------------------------------------------------------------------
2136-; Draw Tracks
2137-; ----------------------------------------------------------------------------
1537+ ; Flash the track where miss happened
1538+ lda miss_track
1539+ cmp #1
1540+ beq flash_miss_t1
1541+ cmp #2
1542+ beq flash_miss_t2
1543+ jmp flash_miss_t3
21381544
2139-draw_tracks:
2140- lda #CHAR_TRACK
1545+flash_miss_t1:
21411546 ldx #0
2142-draw_t1:
2143- sta SCREEN + (TRACK1_ROW * 40),x
1547+ lda #MISS_COL
1548+flash_m1_loop:
1549+ sta COLRAM + (TRACK1_ROW * 40),x
21441550 inx
21451551 cpx #38
2146- bne draw_t1
1552+ bne flash_m1_loop
1553+ rts
21471554
1555+flash_miss_t2:
21481556 ldx #0
2149-draw_t2:
2150- sta SCREEN + (TRACK2_ROW * 40),x
1557+ lda #MISS_COL
1558+flash_m2_loop:
1559+ sta COLRAM + (TRACK2_ROW * 40),x
21511560 inx
21521561 cpx #38
2153- bne draw_t2
1562+ bne flash_m2_loop
1563+ rts
21541564
1565+flash_miss_t3:
21551566 ldx #0
2156-draw_t3:
2157- sta SCREEN + (TRACK3_ROW * 40),x
1567+ lda #MISS_COL
1568+flash_m3_loop:
1569+ sta COLRAM + (TRACK3_ROW * 40),x
21581570 inx
21591571 cpx #38
2160- bne draw_t3
2161-
1572+ bne flash_m3_loop
21621573 rts
21631574
21641575 ; ----------------------------------------------------------------------------
2165-; Draw Hit Zones
1576+; Play Miss Sound
21661577 ; ----------------------------------------------------------------------------
21671578
2168-draw_hit_zones:
2169- lda #CHAR_HITZONE
1579+play_miss_sound:
1580+ inc miss_count
1581+ jsr display_misses
21701582
2171- sta SCREEN + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
2172- sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
2173- sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
2174- sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
1583+ lda #0
1584+ sta SID_V3_FREQ_LO
1585+ lda #MISS_FREQ
1586+ sta SID_V3_FREQ_HI
1587+ lda #MISS_AD
1588+ sta SID_V3_AD
1589+ lda #MISS_SR
1590+ sta SID_V3_SR
1591+ lda #MISS_WAVE
1592+ ora #$01
1593+ sta SID_V3_CTRL
1594+ rts
21751595
2176- sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
2177- sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
2178- sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
1596+; ----------------------------------------------------------------------------
1597+; Play Perfect Sound
1598+; ----------------------------------------------------------------------------
21791599
2180- sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
2181- sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
2182- sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
2183- sta SCREEN + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
1600+play_perfect_sound:
1601+ lda #0
1602+ sta SID_V3_FREQ_LO
1603+ lda #PERFECT_SFX_FREQ
1604+ sta SID_V3_FREQ_HI
1605+ lda #PERFECT_SFX_AD
1606+ sta SID_V3_AD
1607+ lda #PERFECT_SFX_SR
1608+ sta SID_V3_SR
1609+ lda #PERFECT_SFX_WAVE
1610+ ora #$01
1611+ sta SID_V3_CTRL
1612+ rts
21841613
2185- lda #HIT_ZONE_COL
2186- sta COLRAM + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
2187- sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
2188- sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
2189- sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
1614+; ----------------------------------------------------------------------------
1615+; Play Good Sound
1616+; ----------------------------------------------------------------------------
21901617
2191- sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
2192- sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
2193- sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
1618+play_good_sound:
1619+ lda #0
1620+ sta SID_V3_FREQ_LO
1621+ lda #GOOD_SFX_FREQ
1622+ sta SID_V3_FREQ_HI
1623+ lda #GOOD_SFX_AD
1624+ sta SID_V3_AD
1625+ lda #GOOD_SFX_SR
1626+ sta SID_V3_SR
1627+ lda #GOOD_SFX_WAVE
1628+ ora #$01
1629+ sta SID_V3_CTRL
1630+ rts
21941631
2195- sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
2196- sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
2197- sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
2198- sta COLRAM + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
1632+; ----------------------------------------------------------------------------
1633+; Increase Health
1634+; ----------------------------------------------------------------------------
21991635
1636+increase_health:
1637+ clc
1638+ adc health
1639+ cmp #HEALTH_MAX
1640+ bcc health_ok
1641+ lda #HEALTH_MAX
1642+health_ok:
1643+ sta health
1644+ jsr display_health
22001645 rts
22011646
22021647 ; ----------------------------------------------------------------------------
2203-; Draw Labels
1648+; Decrease Health
22041649 ; ----------------------------------------------------------------------------
22051650
2206-draw_labels:
2207- ldx #0
2208-draw_score_label:
2209- lda score_label,x
2210- beq draw_score_label_done
2211- sta SCREEN + 1,x
2212- lda #1
2213- sta COLRAM + 1,x
2214- inx
2215- bne draw_score_label
2216-draw_score_label_done:
1651+decrease_health:
1652+ sta temp_track
1653+ lda health
1654+ sec
1655+ sbc temp_track
1656+ bcs health_not_zero
1657+ lda #0
1658+health_not_zero:
1659+ sta health
1660+ jsr display_health
22171661
2218- ldx #0
2219-draw_miss_label:
2220- lda miss_label,x
2221- beq draw_miss_label_done
2222- sta SCREEN + 15,x
2223- lda #2
2224- sta COLRAM + 15,x
2225- inx
2226- bne draw_miss_label
2227-draw_miss_label_done:
1662+ ; Check for game over
1663+ lda health
1664+ bne no_game_over
1665+ jsr show_gameover
1666+ lda #STATE_GAMEOVER
1667+ sta game_state
1668+no_game_over:
1669+ rts
22281670
2229- ldx #0
2230-draw_title_game:
2231- lda title_game,x
2232- beq draw_title_game_done
2233- sta SCREEN + 27,x
2234- lda #1
2235- sta COLRAM + 27,x
2236- inx
2237- bne draw_title_game
2238-draw_title_game_done:
1671+; ----------------------------------------------------------------------------
1672+; Display Health Bar
1673+; ----------------------------------------------------------------------------
22391674
2240- ldx #0
2241-draw_health_label:
2242- lda health_label,x
2243- beq draw_health_label_done
2244- sta SCREEN + (HEALTH_ROW * 40) + 4,x
2245- lda #5
2246- sta COLRAM + (HEALTH_ROW * 40) + 4,x
2247- inx
2248- bne draw_health_label
2249-draw_health_label_done:
1675+display_health:
1676+ ; Calculate filled blocks (health / 4 = blocks, max 16)
1677+ lda health
1678+ lsr
1679+ lsr
1680+ sta temp_track
22501681
22511682 ldx #0
2252-draw_progress_label:
2253- lda progress_label,x
2254- beq draw_progress_label_done
2255- sta SCREEN + (PROGRESS_ROW * 40) + 4,x
2256- lda #3
2257- sta COLRAM + (PROGRESS_ROW * 40) + 4,x
1683+draw_health_bar:
1684+ cpx temp_track
1685+ bcs draw_empty_health
1686+ lda #CHAR_BAR_FULL
1687+ jmp store_health_char
1688+draw_empty_health:
1689+ lda #CHAR_BAR_EMPTY
1690+store_health_char:
1691+ sta SCREEN + (HEALTH_ROW * 40) + 8,x
1692+ lda #HEALTH_COL
1693+ sta COLRAM + (HEALTH_ROW * 40) + 8,x
22581694 inx
2259- bne draw_progress_label
2260-draw_progress_label_done:
2261-
2262- lda #$1A ; Z label
2263- sta SCREEN + (TRACK1_ROW * 40)
2264- lda #TRACK1_NOTE_COL
2265- sta COLRAM + (TRACK1_ROW * 40)
2266-
2267- lda #$18 ; X label
2268- sta SCREEN + (TRACK2_ROW * 40)
2269- lda #TRACK2_NOTE_COL
2270- sta COLRAM + (TRACK2_ROW * 40)
2271-
2272- lda #$03 ; C label
2273- sta SCREEN + (TRACK3_ROW * 40)
2274- lda #TRACK3_NOTE_COL
2275- sta COLRAM + (TRACK3_ROW * 40)
2276-
1695+ cpx #16
1696+ bne draw_health_bar
22771697 rts
2278-
2279-score_label:
2280- !scr "score:"
2281- !byte 0
2282-
2283-miss_label:
2284- !scr "miss:"
2285- !byte 0
22861698
2287-title_game:
2288- !scr "sid symphony"
2289- !byte 0
1699+; ----------------------------------------------------------------------------
1700+; Display Progress Bar
1701+; ----------------------------------------------------------------------------
22901702
2291-health_label:
2292- !scr "health:"
2293- !byte 0
1703+display_progress:
1704+ ; Calculate progress (song_beat / 4)
1705+ lda song_beat
1706+ lsr
1707+ lsr
1708+ sta temp_track
22941709
2295-progress_label:
2296- !scr "song:"
2297- !byte 0
1710+ ldx #0
1711+draw_progress_bar:
1712+ cpx temp_track
1713+ bcs draw_empty_progress
1714+ lda #CHAR_BAR_FULL
1715+ jmp store_progress_char
1716+draw_empty_progress:
1717+ lda #CHAR_BAR_EMPTY
1718+store_progress_char:
1719+ sta SCREEN + (PROGRESS_ROW * 40) + 8,x
1720+ lda #PROGRESS_COL
1721+ sta COLRAM + (PROGRESS_ROW * 40) + 8,x
1722+ inx
1723+ cpx #PROGRESS_WIDTH
1724+ bne draw_progress_bar
1725+ rts
22981726
22991727 ; ----------------------------------------------------------------------------
2300-; Initialize SID
1728+; Combo System
23011729 ; ----------------------------------------------------------------------------
23021730
2303-init_sid:
2304- ldx #$18
1731+increment_combo:
1732+ inc combo
1733+ lda combo
1734+ cmp max_combo
1735+ bcc combo_no_max
1736+ sta max_combo
1737+combo_no_max:
1738+ jsr display_combo
1739+ rts
1740+
1741+break_combo:
23051742 lda #0
2306-clear_sid:
2307- sta SID,x
2308- dex
2309- bpl clear_sid
1743+ sta combo
1744+ jsr display_combo
1745+ rts
23101746
2311- lda #$0F
2312- sta SID_VOLUME
1747+display_combo:
1748+ ; Display combo count
1749+ lda combo
1750+ ldx #0
1751+combo_div_100:
1752+ cmp #100
1753+ bcc combo_done_100
1754+ sec
1755+ sbc #100
1756+ inx
1757+ jmp combo_div_100
1758+combo_done_100:
1759+ pha
1760+ txa
1761+ ora #$30
1762+ sta SCREEN + (COMBO_ROW * 40) + 34
23131763
2314- lda #$00
2315- sta SID_V1_FREQ_LO
2316- lda #VOICE1_FREQ
2317- sta SID_V1_FREQ_HI
2318- lda #PULSE_WIDTH
2319- sta SID_V1_PWHI
2320- lda #VOICE_AD
2321- sta SID_V1_AD
2322- lda #VOICE_SR
2323- sta SID_V1_SR
1764+ pla
1765+ ldx #0
1766+combo_div_10:
1767+ cmp #10
1768+ bcc combo_done_10
1769+ sec
1770+ sbc #10
1771+ inx
1772+ jmp combo_div_10
1773+combo_done_10:
1774+ pha
1775+ txa
1776+ ora #$30
1777+ sta SCREEN + (COMBO_ROW * 40) + 35
23241778
2325- lda #$00
2326- sta SID_V2_FREQ_LO
2327- lda #VOICE2_FREQ
2328- sta SID_V2_FREQ_HI
2329- lda #PULSE_WIDTH
2330- sta SID_V2_PWHI
2331- lda #VOICE_AD
2332- sta SID_V2_AD
2333- lda #VOICE_SR
2334- sta SID_V2_SR
1779+ pla
1780+ ora #$30
1781+ sta SCREEN + (COMBO_ROW * 40) + 36
23351782
2336- lda #$00
2337- sta SID_V3_FREQ_LO
2338- lda #VOICE3_FREQ
2339- sta SID_V3_FREQ_HI
2340- lda #PULSE_WIDTH
2341- sta SID_V3_PWHI
2342- lda #VOICE_AD
2343- sta SID_V3_AD
2344- lda #VOICE_SR
2345- sta SID_V3_SR
1783+ ; Colour based on multiplier
1784+ jsr get_multiplier
1785+ cmp #1
1786+ beq combo_col_1x
1787+ cmp #2
1788+ beq combo_col_2x
1789+ cmp #3
1790+ beq combo_col_3x
1791+ jmp combo_col_4x
1792+
1793+combo_col_1x:
1794+ lda #11 ; Grey
1795+ jmp set_combo_col
1796+combo_col_2x:
1797+ lda #7 ; Yellow
1798+ jmp set_combo_col
1799+combo_col_3x:
1800+ lda #5 ; Green
1801+ jmp set_combo_col
1802+combo_col_4x:
1803+ lda #1 ; White
1804+
1805+set_combo_col:
1806+ sta COLRAM + (COMBO_ROW * 40) + 34
1807+ sta COLRAM + (COMBO_ROW * 40) + 35
1808+ sta COLRAM + (COMBO_ROW * 40) + 36
1809+ rts
23461810
1811+get_multiplier:
1812+ lda combo
1813+ cmp #COMBO_TIER_4
1814+ bcs mult_4x
1815+ cmp #COMBO_TIER_3
1816+ bcs mult_3x
1817+ cmp #COMBO_TIER_2
1818+ bcs mult_2x
1819+ lda #1
1820+ rts
1821+mult_2x:
1822+ lda #2
1823+ rts
1824+mult_3x:
1825+ lda #3
1826+ rts
1827+mult_4x:
1828+ lda #4
23471829 rts
23481830
23491831 ; ----------------------------------------------------------------------------
...
23511833 ; ----------------------------------------------------------------------------
23521834
23531835 reset_track_colours:
2354- ldx #0
1836+ ldx #1 ; Start at column 1 (skip label)
1837+reset_col_loop:
23551838 lda #TRACK_LINE_COL
2356-reset_t1:
23571839 sta COLRAM + (TRACK1_ROW * 40),x
2358- inx
2359- cpx #38
2360- bne reset_t1
2361-
2362- ldx #0
2363-reset_t2:
23641840 sta COLRAM + (TRACK2_ROW * 40),x
2365- inx
2366- cpx #38
2367- bne reset_t2
2368-
2369- ldx #0
2370-reset_t3:
23711841 sta COLRAM + (TRACK3_ROW * 40),x
23721842 inx
23731843 cpx #38
2374- bne reset_t3
2375-
2376- lda #TRACK1_NOTE_COL
2377- sta COLRAM + (TRACK1_ROW * 40)
2378- lda #TRACK2_NOTE_COL
2379- sta COLRAM + (TRACK2_ROW * 40)
2380- lda #TRACK3_NOTE_COL
2381- sta COLRAM + (TRACK3_ROW * 40)
1844+ bne reset_col_loop
23821845
1846+ ; Restore hit zone colours
23831847 lda #HIT_ZONE_COL
23841848 sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
23851849 sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
23861850 sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
2387-
2388- jsr redraw_all_notes
2389-
2390- rts
2391-
2392-; ----------------------------------------------------------------------------
2393-; Redraw All Notes
2394-; ----------------------------------------------------------------------------
23951851
2396-redraw_all_notes:
1852+ ; Redraw notes with correct colours
23971853 ldx #0
23981854 redraw_loop:
23991855 lda note_track,x
...
25251981 lda #0
25261982 sta hit_quality
25271983 clc
1984+ rts
1985+
1986+; ----------------------------------------------------------------------------
1987+; Check Song End
1988+; ----------------------------------------------------------------------------
1989+
1990+check_song_end:
1991+ lda song_ended
1992+ beq not_ended
1993+
1994+ ; Check if all notes cleared
1995+ ldx #0
1996+check_notes_clear:
1997+ lda note_track,x
1998+ bne not_ended
1999+ inx
2000+ cpx #MAX_NOTES
2001+ bne check_notes_clear
2002+
2003+ ; Add delay before results
2004+ inc end_delay
2005+ lda end_delay
2006+ cmp #END_DELAY_FRAMES
2007+ bcc not_ended
2008+
2009+ ; Show results
2010+ jsr show_results
2011+ lda #STATE_RESULTS
2012+ sta game_state
2013+
2014+not_ended:
25282015 rts
25292016
25302017 ; ----------------------------------------------------------------------------
...
28252312 bne flash_t3h_loop
28262313 lda #1
28272314 sta COLRAM + (TRACK3_ROW * 40)
2315+ rts
2316+
2317+; ----------------------------------------------------------------------------
2318+; Show Results Screen
2319+; ----------------------------------------------------------------------------
2320+
2321+show_results:
2322+ ; Clear screen
2323+ ldx #0
2324+ lda #CHAR_SPACE
2325+clear_results:
2326+ sta SCREEN,x
2327+ sta SCREEN+$100,x
2328+ sta SCREEN+$200,x
2329+ sta SCREEN+$2E8,x
2330+ inx
2331+ bne clear_results
2332+
2333+ ; Draw "SONG COMPLETE!"
2334+ ldx #0
2335+draw_complete:
2336+ lda complete_text,x
2337+ beq draw_results_score
2338+ sta SCREEN + (5 * 40) + 13,x
2339+ lda #5
2340+ sta COLRAM + (5 * 40) + 13,x
2341+ inx
2342+ jmp draw_complete
2343+
2344+draw_results_score:
2345+ ; Draw score label
2346+ ldx #0
2347+draw_rs_label:
2348+ lda results_score_label,x
2349+ beq draw_rs_value
2350+ sta SCREEN + (9 * 40) + 12,x
2351+ lda #7
2352+ sta COLRAM + (9 * 40) + 12,x
2353+ inx
2354+ jmp draw_rs_label
2355+
2356+draw_rs_value:
2357+ ; Draw score value
2358+ lda score_lo
2359+ sta work_lo
2360+ lda score_hi
2361+ sta work_hi
2362+
2363+ ldx #0
2364+rs_div_10000:
2365+ lda work_lo
2366+ sec
2367+ sbc #<10000
2368+ tay
2369+ lda work_hi
2370+ sbc #>10000
2371+ bcc rs_done_10000
2372+ sta work_hi
2373+ sty work_lo
2374+ inx
2375+ jmp rs_div_10000
2376+rs_done_10000:
2377+ txa
2378+ ora #$30
2379+ sta SCREEN + (9 * 40) + 23
2380+
2381+ ldx #0
2382+rs_div_1000:
2383+ lda work_lo
2384+ sec
2385+ sbc #<1000
2386+ tay
2387+ lda work_hi
2388+ sbc #>1000
2389+ bcc rs_done_1000
2390+ sta work_hi
2391+ sty work_lo
2392+ inx
2393+ jmp rs_div_1000
2394+rs_done_1000:
2395+ txa
2396+ ora #$30
2397+ sta SCREEN + (9 * 40) + 24
2398+
2399+ ldx #0
2400+rs_div_100:
2401+ lda work_lo
2402+ sec
2403+ sbc #100
2404+ bcc rs_done_100
2405+ sta work_lo
2406+ inx
2407+ jmp rs_div_100
2408+rs_done_100:
2409+ txa
2410+ ora #$30
2411+ sta SCREEN + (9 * 40) + 25
2412+
2413+ ldx #0
2414+rs_div_10:
2415+ lda work_lo
2416+ sec
2417+ sbc #10
2418+ bcc rs_done_10
2419+ sta work_lo
2420+ inx
2421+ jmp rs_div_10
2422+rs_done_10:
2423+ txa
2424+ ora #$30
2425+ sta SCREEN + (9 * 40) + 26
2426+
2427+ lda work_lo
2428+ ora #$30
2429+ sta SCREEN + (9 * 40) + 27
2430+
2431+ lda #7
2432+ sta COLRAM + (9 * 40) + 23
2433+ sta COLRAM + (9 * 40) + 24
2434+ sta COLRAM + (9 * 40) + 25
2435+ sta COLRAM + (9 * 40) + 26
2436+ sta COLRAM + (9 * 40) + 27
2437+
2438+ ; Draw perfect count
2439+ ldx #0
2440+draw_perfect_label:
2441+ lda perfect_label,x
2442+ beq draw_perfect_value
2443+ sta SCREEN + (11 * 40) + 12,x
2444+ lda #1
2445+ sta COLRAM + (11 * 40) + 12,x
2446+ inx
2447+ jmp draw_perfect_label
2448+
2449+draw_perfect_value:
2450+ lda perfect_count
2451+ ldx #0
2452+pv_div_10:
2453+ cmp #10
2454+ bcc pv_done_10
2455+ sec
2456+ sbc #10
2457+ inx
2458+ jmp pv_div_10
2459+pv_done_10:
2460+ pha
2461+ txa
2462+ ora #$30
2463+ sta SCREEN + (11 * 40) + 23
2464+ pla
2465+ ora #$30
2466+ sta SCREEN + (11 * 40) + 24
2467+ lda #1
2468+ sta COLRAM + (11 * 40) + 23
2469+ sta COLRAM + (11 * 40) + 24
2470+
2471+ ; Draw good count
2472+ ldx #0
2473+draw_good_label:
2474+ lda good_label,x
2475+ beq draw_good_value
2476+ sta SCREEN + (12 * 40) + 12,x
2477+ lda #7
2478+ sta COLRAM + (12 * 40) + 12,x
2479+ inx
2480+ jmp draw_good_label
2481+
2482+draw_good_value:
2483+ lda good_count
2484+ ldx #0
2485+gv_div_10:
2486+ cmp #10
2487+ bcc gv_done_10
2488+ sec
2489+ sbc #10
2490+ inx
2491+ jmp gv_div_10
2492+gv_done_10:
2493+ pha
2494+ txa
2495+ ora #$30
2496+ sta SCREEN + (12 * 40) + 23
2497+ pla
2498+ ora #$30
2499+ sta SCREEN + (12 * 40) + 24
2500+ lda #7
2501+ sta COLRAM + (12 * 40) + 23
2502+ sta COLRAM + (12 * 40) + 24
2503+
2504+ ; Draw miss count
2505+ ldx #0
2506+draw_miss_label_r:
2507+ lda miss_label_r,x
2508+ beq draw_miss_value
2509+ sta SCREEN + (13 * 40) + 12,x
2510+ lda #2
2511+ sta COLRAM + (13 * 40) + 12,x
2512+ inx
2513+ jmp draw_miss_label_r
2514+
2515+draw_miss_value:
2516+ lda miss_count
2517+ ldx #0
2518+mv_div_10:
2519+ cmp #10
2520+ bcc mv_done_10
2521+ sec
2522+ sbc #10
2523+ inx
2524+ jmp mv_div_10
2525+mv_done_10:
2526+ pha
2527+ txa
2528+ ora #$30
2529+ sta SCREEN + (13 * 40) + 23
2530+ pla
2531+ ora #$30
2532+ sta SCREEN + (13 * 40) + 24
2533+ lda #2
2534+ sta COLRAM + (13 * 40) + 23
2535+ sta COLRAM + (13 * 40) + 24
2536+
2537+ ; Draw max combo
2538+ ldx #0
2539+draw_maxc_label:
2540+ lda maxcombo_label,x
2541+ beq draw_maxc_value
2542+ sta SCREEN + (15 * 40) + 12,x
2543+ lda #COMBO_COL
2544+ sta COLRAM + (15 * 40) + 12,x
2545+ inx
2546+ jmp draw_maxc_label
2547+
2548+draw_maxc_value:
2549+ lda max_combo
2550+ ldx #0
2551+mc_div_100:
2552+ cmp #100
2553+ bcc mc_done_100
2554+ sec
2555+ sbc #100
2556+ inx
2557+ jmp mc_div_100
2558+mc_done_100:
2559+ pha
2560+ txa
2561+ ora #$30
2562+ sta SCREEN + (15 * 40) + 23
2563+
2564+ pla
2565+ ldx #0
2566+mc_div_10:
2567+ cmp #10
2568+ bcc mc_done_10
2569+ sec
2570+ sbc #10
2571+ inx
2572+ jmp mc_div_10
2573+mc_done_10:
2574+ pha
2575+ txa
2576+ ora #$30
2577+ sta SCREEN + (15 * 40) + 24
2578+ pla
2579+ ora #$30
2580+ sta SCREEN + (15 * 40) + 25
2581+ lda #COMBO_COL
2582+ sta COLRAM + (15 * 40) + 23
2583+ sta COLRAM + (15 * 40) + 24
2584+ sta COLRAM + (15 * 40) + 25
2585+
2586+ ; Draw "PRESS FIRE"
2587+ ldx #0
2588+draw_return:
2589+ lda return_text,x
2590+ beq results_done
2591+ sta SCREEN + (20 * 40) + 10,x
2592+ lda #11
2593+ sta COLRAM + (20 * 40) + 10,x
2594+ inx
2595+ jmp draw_return
2596+
2597+results_done:
2598+ rts
2599+
2600+complete_text:
2601+ !scr "song complete!"
2602+ !byte 0
2603+
2604+results_score_label:
2605+ !scr "final score:"
2606+ !byte 0
2607+
2608+perfect_label:
2609+ !scr "perfect:"
2610+ !byte 0
2611+
2612+good_label:
2613+ !scr "good:"
2614+ !byte 0
2615+
2616+miss_label_r:
2617+ !scr "misses:"
2618+ !byte 0
2619+
2620+maxcombo_label:
2621+ !scr "max combo:"
2622+ !byte 0
2623+
2624+return_text:
2625+ !scr "press fire to continue"
2626+ !byte 0
2627+
2628+; ----------------------------------------------------------------------------
2629+; Show Game Over Screen
2630+; ----------------------------------------------------------------------------
2631+
2632+show_gameover:
2633+ ; Clear screen
2634+ ldx #0
2635+ lda #CHAR_SPACE
2636+clear_gameover:
2637+ sta SCREEN,x
2638+ sta SCREEN+$100,x
2639+ sta SCREEN+$200,x
2640+ sta SCREEN+$2E8,x
2641+ inx
2642+ bne clear_gameover
2643+
2644+ ; Draw "GAME OVER"
2645+ ldx #0
2646+draw_gameover_text:
2647+ lda gameover_text,x
2648+ beq draw_gameover_score
2649+ sta SCREEN + (8 * 40) + 15,x
2650+ lda #2
2651+ sta COLRAM + (8 * 40) + 15,x
2652+ inx
2653+ jmp draw_gameover_text
2654+
2655+draw_gameover_score:
2656+ ; Draw score
2657+ ldx #0
2658+draw_go_score_label:
2659+ lda gameover_score,x
2660+ beq draw_go_score_value
2661+ sta SCREEN + (12 * 40) + 12,x
2662+ lda #7
2663+ sta COLRAM + (12 * 40) + 12,x
2664+ inx
2665+ jmp draw_go_score_label
2666+
2667+draw_go_score_value:
2668+ lda score_lo
2669+ sta work_lo
2670+ lda score_hi
2671+ sta work_hi
2672+
2673+ ldx #0
2674+go_div_10000:
2675+ lda work_lo
2676+ sec
2677+ sbc #<10000
2678+ tay
2679+ lda work_hi
2680+ sbc #>10000
2681+ bcc go_done_10000
2682+ sta work_hi
2683+ sty work_lo
2684+ inx
2685+ jmp go_div_10000
2686+go_done_10000:
2687+ txa
2688+ ora #$30
2689+ sta SCREEN + (12 * 40) + 23
2690+
2691+ ldx #0
2692+go_div_1000:
2693+ lda work_lo
2694+ sec
2695+ sbc #<1000
2696+ tay
2697+ lda work_hi
2698+ sbc #>1000
2699+ bcc go_done_1000
2700+ sta work_hi
2701+ sty work_lo
2702+ inx
2703+ jmp go_div_1000
2704+go_done_1000:
2705+ txa
2706+ ora #$30
2707+ sta SCREEN + (12 * 40) + 24
2708+
2709+ ldx #0
2710+go_div_100:
2711+ lda work_lo
2712+ sec
2713+ sbc #100
2714+ bcc go_done_100
2715+ sta work_lo
2716+ inx
2717+ jmp go_div_100
2718+go_done_100:
2719+ txa
2720+ ora #$30
2721+ sta SCREEN + (12 * 40) + 25
2722+
2723+ ldx #0
2724+go_div_10:
2725+ lda work_lo
2726+ sec
2727+ sbc #10
2728+ bcc go_done_10
2729+ sta work_lo
2730+ inx
2731+ jmp go_div_10
2732+go_done_10:
2733+ txa
2734+ ora #$30
2735+ sta SCREEN + (12 * 40) + 26
2736+
2737+ lda work_lo
2738+ ora #$30
2739+ sta SCREEN + (12 * 40) + 27
2740+
2741+ lda #7
2742+ sta COLRAM + (12 * 40) + 23
2743+ sta COLRAM + (12 * 40) + 24
2744+ sta COLRAM + (12 * 40) + 25
2745+ sta COLRAM + (12 * 40) + 26
2746+ sta COLRAM + (12 * 40) + 27
2747+
2748+ ; Draw retry message
2749+ ldx #0
2750+draw_retry:
2751+ lda retry_text,x
2752+ beq gameover_done
2753+ sta SCREEN + (18 * 40) + 10,x
2754+ lda #11
2755+ sta COLRAM + (18 * 40) + 10,x
2756+ inx
2757+ jmp draw_retry
2758+
2759+gameover_done:
28282760 rts
2761+
2762+gameover_text:
2763+ !scr "game over"
2764+ !byte 0
2765+
2766+gameover_score:
2767+ !scr "your score:"
2768+ !byte 0
2769+
2770+retry_text:
2771+ !scr "press fire to continue"
2772+ !byte 0
28292773
28302774 ; ============================================================================
2831-; SONG DATA - First Song (Balanced for Phase 1)
2775+; SONG DATA - First Song: "First Steps"
28322776 ; ============================================================================
28332777 ; Format: beat, track (1-3), SID frequency high byte
2834-; Note spacing provides learnable pattern with variety
28352778 ; ============================================================================
28362779
28372780 song_data:
...
29092852 max_combo: !byte 0
29102853
29112854 ; ============================================================================
2912-; END OF SID SYMPHONY - PHASE 1 COMPLETE
2855+; END OF SID SYMPHONY - UNIT 17
29132856 ; ============================================================================
29142857