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

Title Screen

A proper title screen. Press fire to start. Complete game flow.

20% of SID Symphony

First impressions matter. This unit adds a proper title screen.

The game now starts with “SID SYMPHONY” displayed prominently on a clean black screen. “PRESS FIRE TO START” prompts the player. After completing a song or failing, pressing fire returns to the title. The complete game loop is now in place: Title → Playing → Results → Title.

Run It

Assemble and run:

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

Unit 13 Screenshot

Press fire on a joystick (port 2) or space on the keyboard to start. Play through the song, see your results, then fire returns you to the title.

Title screen — press fire to start

Four Game States

The game now has four distinct states:

; ============================================================================
; GAME STATES - Four distinct states for complete game flow
; ============================================================================

STATE_TITLE   = 0               ; Title screen - waiting to start
STATE_PLAYING = 1               ; Main gameplay
STATE_RESULTS = 2               ; Song completed successfully
STATE_GAMEOVER = 3              ; Health depleted - game over

game_state:   !byte 0           ; Current state

; ----------------------------------------------------------------------------
; Main Loop - State Dispatcher
; ----------------------------------------------------------------------------

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

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

do_title:
            jsr update_title
            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

Each frame, the main loop checks game_state and calls the appropriate update routine. This pattern - a variable controlling which code runs - scales to any number of states.

The State Machine Pattern

The state machine is one of the most useful patterns in game development:

┌───────────┐      fire        ┌──────────┐
│   TITLE   │ ────────────────▶│ PLAYING  │
└───────────┘                  └──────────┘
      ▲                              │
      │                              │
    fire                     song ends / health = 0
      │                              │
      │                              ▼
┌───────────┐                 ┌──────────┐
│  RESULTS  │◀────────────────│ GAMEOVER │
└───────────┘   song complete └──────────┘

Each state handles its own logic and decides when to transition to another state.

The Title Screen

The title screen displays the game name and instructions:

; ----------------------------------------------------------------------------
; 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 game title
            ldx #0
draw_title_text:
            lda title_big,x
            beq draw_title_done
            sta SCREEN + (8 * 40) + 14,x      ; Row 8, centered
            lda #TITLE_COL                     ; White
            sta COLRAM + (8 * 40) + 14,x
            inx
            jmp draw_title_text
draw_title_done:

            ; Draw subtitle
            ldx #0
draw_subtitle:
            lda subtitle_text,x
            beq draw_subtitle_done
            sta SCREEN + (10 * 40) + 13,x     ; Row 10
            lda #SUBTITLE_COL                  ; Grey
            sta COLRAM + (10 * 40) + 13,x
            inx
            jmp draw_subtitle
draw_subtitle_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     ; Row 20
            lda #7                             ; Yellow - draws attention
            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

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

Three elements: the title in white (draws the eye), subtitle in grey (provides context), and “PRESS FIRE TO START” in yellow (calls to action).

Waiting for Fire

The title state waits for the fire button:

; ----------------------------------------------------------------------------
; Update Title State - Wait for fire button to start
; ----------------------------------------------------------------------------

update_title:
            ; Check joystick port 2 fire button
            lda CIA1_PRA
            and #$10            ; Bit 4 = fire button
            beq fire_pressed    ; Low = pressed

            ; Also accept space bar as alternative
            lda #$7F            ; Select keyboard row 7
            sta CIA1_PRA
            lda CIA1_PRB
            and #$10            ; Space bar
            beq fire_pressed

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

fire_pressed:
            lda #$FF
            sta CIA1_PRA        ; Reset CIA

            ; Start the game!
            jsr init_game       ; Initialize all game systems
            lda #STATE_PLAYING
            sta game_state      ; Transition to playing state
            rts

; ----------------------------------------------------------------------------
; Update Results - Return to title on fire
; ----------------------------------------------------------------------------

update_results:
            lda CIA1_PRA
            and #$10
            beq results_fire

            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

            ; Return to title - complete game loop
            jsr show_title
            lda #STATE_TITLE
            sta game_state
            rts

We check both joystick port 2 (bit 4 of CIA1_PRA) and space bar as alternatives. When fire is detected, we initialise the game and transition to PLAYING state.

Returning to Title

After results or game over, pressing fire returns to the title instead of restarting immediately:

update_results:
            ; Check for fire
            lda CIA1_PRA
            and #$10
            beq results_fire
            ; ... space bar check ...
            rts

results_fire:
            jsr show_title      ; Draw title screen
            lda #STATE_TITLE
            sta game_state      ; Return to title
            rts

This creates a proper game loop. Players see the title between every play session.

Clean Initialisation

When transitioning from title to playing, init_game resets everything:

init_game:
            jsr init_screen     ; Clear and draw tracks
            jsr init_notes      ; Clear note arrays
            jsr init_score      ; Reset score to 0
            jsr init_health     ; Reset health to starting value
            jsr init_combo      ; Reset combo to 0
            jsr init_song       ; Reset song position
            rts

Each play session starts fresh. Previous scores, health, or combos don’t carry over.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 13: Title Screen
; ============================================================================
; The game now starts with a proper title screen. Press fire to play.
; Complete game flow: Title -> Playing -> Results -> Title.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
;           Fire = Start game from title screen
; ============================================================================

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

; SID Voice Settings
VOICE1_WAVE = $21
VOICE2_WAVE = $41
VOICE3_WAVE = $11

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

VOICE_AD    = $09
VOICE_SR    = $00
PULSE_WIDTH = $08

; Miss sound settings
MISS_FREQ   = $08
MISS_WAVE   = $81
MISS_AD     = $00
MISS_SR     = $90

; Visual Settings
BORDER_COL  = 0
BG_COL      = 0

TRACK1_NOTE_COL = 10
TRACK2_NOTE_COL = 13
TRACK3_NOTE_COL = 14

TRACK_LINE_COL = 11
HIT_ZONE_COL = 7

HIT_COL     = 1
PERFECT_COL = 1
GOOD_COL    = 7
MISS_COL    = 2

HEALTH_COL  = 5
PROGRESS_COL = 3
COMBO_COL   = 13

; Title screen colours
TITLE_COL   = 1                 ; White for title
SUBTITLE_COL = 11               ; Grey for subtitle

; ============================================================================
; SCORING SETTINGS
; ============================================================================

PERFECT_SCORE = 100
GOOD_SCORE    = 50

; ============================================================================
; COMBO SETTINGS
; ============================================================================

COMBO_TIER_2  = 10
COMBO_TIER_3  = 25
COMBO_TIER_4  = 50

; ============================================================================
; HEALTH SETTINGS
; ============================================================================

HEALTH_MAX    = 64
HEALTH_START  = 32
HEALTH_PERFECT = 4
HEALTH_GOOD   = 2
HEALTH_MISS   = 8

; ============================================================================
; HIT DETECTION SETTINGS
; ============================================================================

HIT_ZONE_MIN = 2
HIT_ZONE_MAX = 5
HIT_ZONE_CENTRE = 3

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

SONG_LENGTH   = 64
PROGRESS_WIDTH = 16

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

STATE_TITLE   = 0               ; NEW: Title screen
STATE_PLAYING = 1
STATE_RESULTS = 2
STATE_GAMEOVER = 3

; ============================================================================
; 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
TRACK1_ROW  = 8
TRACK2_ROW  = 12
TRACK3_ROW  = 16

; HUD positions
HEALTH_ROW  = 23
PROGRESS_ROW = 24

; Combo display position
COMBO_ROW   = 2

; Hit zone
HIT_ZONE_COLUMN = 3

; Custom character codes
CHAR_NOTE   = 128
CHAR_TRACK  = 129
CHAR_HITZONE = 130
CHAR_SPACE  = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132

; Note settings
MAX_NOTES   = 8
NOTE_SPAWN_COL = 37

; Timing
FRAMES_PER_BEAT = 25
END_DELAY_FRAMES = 75

; 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

; ----------------------------------------------------------------------------
; 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
            jsr show_title

            ; Start in TITLE state
            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_PLAYING
            beq do_playing
            cmp #STATE_RESULTS
            beq do_results
            jmp do_gameover

do_title:
            jsr update_title
            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 fire_pressed

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

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

fire_pressed:
            lda #$FF
            sta CIA1_PRA

            ; Start the game!
            jsr init_game
            lda #STATE_PLAYING
            sta game_state
            rts

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

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 title
            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

            ; Return to title screen
            jsr show_title
            lda #STATE_TITLE
            sta game_state
            rts

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

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

            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

            ; Return to title screen
            jsr show_title
            lda #STATE_TITLE
            sta game_state
            rts

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

check_song_end:
            lda song_ended
            beq song_not_ended

            ldx #0
check_notes_clear:
            lda note_track,x
            bne notes_still_active
            inx
            cpx #MAX_NOTES
            bne check_notes_clear

            dec end_delay
            bne song_not_ended

            jsr show_results
            lda #STATE_RESULTS
            sta game_state

notes_still_active:
song_not_ended:
            rts

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

init_song:
            lda #<song_data
            sta song_pos
            lda #>song_data
            sta song_pos_hi

            lda #0
            sta frame_count
            sta beat_count
            sta song_beat
            sta border_flash
            sta song_ended

            lda #END_DELAY_FRAMES
            sta end_delay

            jsr display_progress
            rts

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

advance_song:
            lda song_ended
            bne advance_done

            inc beat_count
            inc song_beat

            lda song_beat
            cmp #SONG_LENGTH
            bcc song_continues

            lda #1
            sta song_ended

song_continues:
            jsr display_progress

advance_done:
            rts

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

display_progress:
            lda song_beat
            lsr
            lsr
            sta temp_progress

            ldx #0
            lda temp_progress
            beq draw_empty_progress

draw_full_progress:
            lda #CHAR_BAR_FULL
            sta SCREEN + (PROGRESS_ROW * 40) + 12,x
            lda #PROGRESS_COL
            sta COLRAM + (PROGRESS_ROW * 40) + 12,x
            inx
            cpx temp_progress
            bne draw_full_progress

draw_empty_progress:
            cpx #PROGRESS_WIDTH
            beq progress_done
            lda #CHAR_BAR_EMPTY
            sta SCREEN + (PROGRESS_ROW * 40) + 12,x
            lda #11
            sta COLRAM + (PROGRESS_ROW * 40) + 12,x
            inx
            jmp draw_empty_progress

progress_done:
            rts

temp_progress: !byte 0

; ----------------------------------------------------------------------------
; Get Multiplier
; ----------------------------------------------------------------------------

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

; ----------------------------------------------------------------------------
; Display Combo
; ----------------------------------------------------------------------------

display_combo:
            ldx #0
draw_combo_label:
            lda combo_label,x
            beq draw_combo_value
            sta SCREEN + (COMBO_ROW * 40) + 12,x
            lda #11
            sta COLRAM + (COMBO_ROW * 40) + 12,x
            inx
            jmp draw_combo_label

draw_combo_value:
            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) + 18
            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) + 19
            pla

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

            jsr get_multiplier
            cmp #4
            beq combo_col_4x
            cmp #3
            beq combo_col_3x
            cmp #2
            beq combo_col_2x

            lda #11
            jmp set_combo_col

combo_col_2x:
            lda #7
            jmp set_combo_col

combo_col_3x:
            lda #5
            jmp set_combo_col

combo_col_4x:
            lda #1

set_combo_col:
            sta COLRAM + (COMBO_ROW * 40) + 18
            sta COLRAM + (COMBO_ROW * 40) + 19
            sta COLRAM + (COMBO_ROW * 40) + 20

            jsr get_multiplier
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 22
            lda #$18
            sta SCREEN + (COMBO_ROW * 40) + 23

            jsr get_multiplier
            cmp #4
            beq mult_col_4x
            cmp #3
            beq mult_col_3x
            cmp #2
            beq mult_col_2x

            lda #11
            jmp set_mult_col

mult_col_2x:
            lda #7
            jmp set_mult_col

mult_col_3x:
            lda #5
            jmp set_mult_col

mult_col_4x:
            lda #1

set_mult_col:
            sta COLRAM + (COMBO_ROW * 40) + 22
            sta COLRAM + (COMBO_ROW * 40) + 23

            rts

combo_label:
            !scr "combo:"
            !byte 0

; ----------------------------------------------------------------------------
; Increment Combo
; ----------------------------------------------------------------------------

increment_combo:
            inc combo

            lda combo
            cmp max_combo
            bcc combo_not_max
            sta max_combo
combo_not_max:

            jsr display_combo
            rts

; ----------------------------------------------------------------------------
; Break Combo
; ----------------------------------------------------------------------------

break_combo:
            lda combo
            beq combo_already_zero

            lda #0
            sta combo
            jsr display_combo

combo_already_zero:
            rts

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

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

            ldx #0
draw_results_title:
            lda results_title,x
            beq draw_results_title_done
            sta SCREEN + (3 * 40) + 14,x
            lda #1
            sta COLRAM + (3 * 40) + 14,x
            inx
            jmp draw_results_title
draw_results_title_done:

            ldx #0
draw_final_score_label:
            lda final_score_label,x
            beq draw_final_score_done
            sta SCREEN + (6 * 40) + 10,x
            lda #7
            sta COLRAM + (6 * 40) + 10,x
            inx
            jmp draw_final_score_label
draw_final_score_done:
            jsr display_final_score

            ldx #0
draw_perfects_label:
            lda perfects_label,x
            beq draw_perfects_done
            sta SCREEN + (8 * 40) + 10,x
            lda #1
            sta COLRAM + (8 * 40) + 10,x
            inx
            jmp draw_perfects_label
draw_perfects_done:
            lda perfect_count
            jsr display_stat_at_8

            ldx #0
draw_goods_label:
            lda goods_label,x
            beq draw_goods_done
            sta SCREEN + (9 * 40) + 10,x
            lda #7
            sta COLRAM + (9 * 40) + 10,x
            inx
            jmp draw_goods_label
draw_goods_done:
            lda good_count
            jsr display_stat_at_9

            ldx #0
draw_misses_label:
            lda misses_label,x
            beq draw_misses_done
            sta SCREEN + (10 * 40) + 10,x
            lda #2
            sta COLRAM + (10 * 40) + 10,x
            inx
            jmp draw_misses_label
draw_misses_done:
            lda miss_count
            jsr display_stat_at_10

            ldx #0
draw_max_combo_label:
            lda max_combo_label,x
            beq draw_max_combo_done
            sta SCREEN + (12 * 40) + 10,x
            lda #13
            sta COLRAM + (12 * 40) + 10,x
            inx
            jmp draw_max_combo_label
draw_max_combo_done:
            lda max_combo
            jsr display_stat_at_12

            ldx #0
draw_accuracy_label:
            lda accuracy_label,x
            beq draw_accuracy_done
            sta SCREEN + (14 * 40) + 10,x
            lda #5
            sta COLRAM + (14 * 40) + 10,x
            inx
            jmp draw_accuracy_label
draw_accuracy_done:
            jsr calculate_accuracy
            jsr display_accuracy

            ldx #0
draw_press_key:
            lda press_fire_results,x
            beq draw_press_key_done
            sta SCREEN + (18 * 40) + 9,x
            lda #7
            sta COLRAM + (18 * 40) + 9,x
            inx
            jmp draw_press_key
draw_press_key_done:

            lda #5
            sta BORDER

            rts

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

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

perfects_label:
            !scr "perfects:"
            !byte 0

goods_label:
            !scr "goods:"
            !byte 0

misses_label:
            !scr "misses:"
            !byte 0

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

accuracy_label:
            !scr "accuracy:"
            !byte 0

press_fire_results:
            !scr "press fire for title"
            !byte 0

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

display_final_score:
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

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

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

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

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

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

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

            rts

; ----------------------------------------------------------------------------
; Display Stats
; ----------------------------------------------------------------------------

display_stat_at_8:
            ldx #0
stat8_div:
            cmp #10
            bcc stat8_done
            sec
            sbc #10
            inx
            jmp stat8_div
stat8_done:
            pha
            txa
            ora #$30
            sta SCREEN + (8 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (8 * 40) + 24
            lda #1
            sta COLRAM + (8 * 40) + 23
            sta COLRAM + (8 * 40) + 24
            rts

display_stat_at_9:
            ldx #0
stat9_div:
            cmp #10
            bcc stat9_done
            sec
            sbc #10
            inx
            jmp stat9_div
stat9_done:
            pha
            txa
            ora #$30
            sta SCREEN + (9 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (9 * 40) + 24
            lda #7
            sta COLRAM + (9 * 40) + 23
            sta COLRAM + (9 * 40) + 24
            rts

display_stat_at_10:
            ldx #0
stat10_div:
            cmp #10
            bcc stat10_done
            sec
            sbc #10
            inx
            jmp stat10_div
stat10_done:
            pha
            txa
            ora #$30
            sta SCREEN + (10 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (10 * 40) + 24
            lda #2
            sta COLRAM + (10 * 40) + 23
            sta COLRAM + (10 * 40) + 24
            rts

display_stat_at_12:
            ldx #0
stat12_div:
            cmp #10
            bcc stat12_done
            sec
            sbc #10
            inx
            jmp stat12_div
stat12_done:
            pha
            txa
            ora #$30
            sta SCREEN + (12 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (12 * 40) + 24
            lda #13
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24
            rts

; ----------------------------------------------------------------------------
; Calculate Accuracy
; ----------------------------------------------------------------------------

calculate_accuracy:
            lda perfect_count
            clc
            adc good_count
            sta total_hits

            clc
            adc miss_count
            sta total_notes

            beq accuracy_zero

            lda total_hits
            sta dividend_lo
            lda #0
            sta dividend_hi

            ldx #100
mult_loop:
            dex
            beq mult_done
            lda dividend_lo
            clc
            adc total_hits
            sta dividend_lo
            lda dividend_hi
            adc #0
            sta dividend_hi
            jmp mult_loop
mult_done:

            lda #0
            sta accuracy
div_loop:
            lda dividend_lo
            sec
            sbc total_notes
            tay
            lda dividend_hi
            sbc #0
            bcc div_done
            sta dividend_hi
            sty dividend_lo
            inc accuracy
            jmp div_loop
div_done:
            rts

accuracy_zero:
            lda #0
            sta accuracy
            rts

total_hits:   !byte 0
total_notes:  !byte 0
dividend_lo:  !byte 0
dividend_hi:  !byte 0
accuracy:     !byte 0

; ----------------------------------------------------------------------------
; Display Accuracy
; ----------------------------------------------------------------------------

display_accuracy:
            lda accuracy

            ldx #0
acc_div_100:
            cmp #100
            bcc acc_done_100
            sec
            sbc #100
            inx
            jmp acc_div_100
acc_done_100:
            pha
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 23
            pla

            ldx #0
acc_div_10:
            cmp #10
            bcc acc_done_10
            sec
            sbc #10
            inx
            jmp acc_div_10
acc_done_10:
            pha
            txa
            ora #$30
            sta SCREEN + (14 * 40) + 24
            pla

            ora #$30
            sta SCREEN + (14 * 40) + 25

            lda #$25
            sta SCREEN + (14 * 40) + 26

            lda #5
            sta COLRAM + (14 * 40) + 23
            sta COLRAM + (14 * 40) + 24
            sta COLRAM + (14 * 40) + 25
            sta COLRAM + (14 * 40) + 26

            rts

; ----------------------------------------------------------------------------
; Copy Character Set
; ----------------------------------------------------------------------------

copy_charset:
            sei

            lda $01
            pha
            and #$FB
            sta $01

            ldx #0
copy_loop:
            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_loop

            pla
            sta $01

            cli

            jsr define_custom_chars

            lda #$1C
            sta CHARPTR

            rts

; ----------------------------------------------------------------------------
; Define Custom Characters
; ----------------------------------------------------------------------------

define_custom_chars:
            lda #%00000110
            sta CHARSET + (CHAR_NOTE * 8) + 0
            lda #%00011110
            sta CHARSET + (CHAR_NOTE * 8) + 1
            lda #%01111110
            sta CHARSET + (CHAR_NOTE * 8) + 2
            lda #%11111110
            sta CHARSET + (CHAR_NOTE * 8) + 3
            lda #%11111110
            sta CHARSET + (CHAR_NOTE * 8) + 4
            lda #%01111110
            sta CHARSET + (CHAR_NOTE * 8) + 5
            lda #%00011110
            sta CHARSET + (CHAR_NOTE * 8) + 6
            lda #%00000110
            sta CHARSET + (CHAR_NOTE * 8) + 7

            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 0
            sta CHARSET + (CHAR_TRACK * 8) + 1
            sta CHARSET + (CHAR_TRACK * 8) + 2
            lda #%11111111
            sta CHARSET + (CHAR_TRACK * 8) + 3
            sta CHARSET + (CHAR_TRACK * 8) + 4
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 5
            sta CHARSET + (CHAR_TRACK * 8) + 6
            sta CHARSET + (CHAR_TRACK * 8) + 7

            lda #%01100110
            sta CHARSET + (CHAR_HITZONE * 8) + 0
            sta CHARSET + (CHAR_HITZONE * 8) + 1
            sta CHARSET + (CHAR_HITZONE * 8) + 2
            sta CHARSET + (CHAR_HITZONE * 8) + 3
            sta CHARSET + (CHAR_HITZONE * 8) + 4
            sta CHARSET + (CHAR_HITZONE * 8) + 5
            sta CHARSET + (CHAR_HITZONE * 8) + 6
            sta CHARSET + (CHAR_HITZONE * 8) + 7

            lda #%11111111
            sta CHARSET + (CHAR_BAR_FULL * 8) + 0
            sta CHARSET + (CHAR_BAR_FULL * 8) + 1
            sta CHARSET + (CHAR_BAR_FULL * 8) + 2
            sta CHARSET + (CHAR_BAR_FULL * 8) + 3
            sta CHARSET + (CHAR_BAR_FULL * 8) + 4
            sta CHARSET + (CHAR_BAR_FULL * 8) + 5
            sta CHARSET + (CHAR_BAR_FULL * 8) + 6
            sta CHARSET + (CHAR_BAR_FULL * 8) + 7

            lda #%11111111
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 0
            lda #%10000001
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 1
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 2
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 3
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 4
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 5
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 6
            lda #%11111111
            sta CHARSET + (CHAR_BAR_EMPTY * 8) + 7

            rts

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

init_health:
            lda #HEALTH_START
            sta health
            jsr display_health
            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 Notes
; ----------------------------------------------------------------------------

init_notes:
            ldx #MAX_NOTES-1
            lda #0
init_notes_loop:
            sta note_track,x
            sta note_col,x
            sta note_freq,x
            dex
            bpl init_notes_loop
            rts

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

check_spawn_note:
            lda song_ended
            bne spawn_done_early

            ldy #0

spawn_check_loop:
            lda (song_pos),y
            cmp #$FF
            beq spawn_song_end

            cmp beat_count
            beq spawn_match
            bcs spawn_done_early

            jmp spawn_advance

spawn_match:
            iny
            lda (song_pos),y
            sta temp_track
            iny
            lda (song_pos),y
            pha
            lda temp_track
            jsr spawn_note_with_freq
            pla
            dey
            dey

spawn_advance:
            lda song_pos
            clc
            adc #3
            sta song_pos
            lda song_pos_hi
            adc #0
            sta song_pos_hi
            jmp spawn_check_loop

spawn_song_end:
            lda #1
            sta song_ended

spawn_done_early:
            rts

; ----------------------------------------------------------------------------
; Spawn Note With Frequency
; ----------------------------------------------------------------------------

spawn_note_with_freq:
            sta temp_track

            ldx #0
spawn_find_slot:
            lda note_track,x
            beq spawn_found_slot
            inx
            cpx #MAX_NOTES
            bne spawn_find_slot
            rts

spawn_found_slot:
            lda temp_track
            sta note_track,x
            lda #NOTE_SPAWN_COL
            sta note_col,x

            tsx
            lda $0103,x
            sta note_freq,x

            jsr draw_note
            rts

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

update_notes:
            ldx #0

update_loop:
            lda note_track,x
            beq update_next

            jsr erase_note

            dec note_col,x
            lda note_col,x
            cmp #1
            bcc update_miss

            jsr draw_note
            jmp update_next

update_miss:
            lda note_track,x
            sta miss_track
            lda #0
            sta note_track,x
            jsr handle_miss

update_next:
            inx
            cpx #MAX_NOTES
            bne update_loop
            rts

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

handle_miss:
            inc miss_count

            jsr play_miss_sound

            lda #MISS_COL
            sta BORDER
            lda #8
            sta border_flash

            jsr display_misses
            jsr decrease_health
            jsr break_combo

            rts

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

decrease_health:
            lda health
            sec
            sbc #HEALTH_MISS
            bcc health_zero
            sta health
            jsr display_health
            jsr check_game_over
            rts

health_zero:
            lda #0
            sta health
            jsr display_health
            jsr check_game_over
            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

; ----------------------------------------------------------------------------
; Check Game Over
; ----------------------------------------------------------------------------

check_game_over:
            lda health
            bne not_game_over

            lda #STATE_GAMEOVER
            sta game_state
            jsr show_game_over

not_game_over:
            rts

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

show_game_over:
            ldx #0
game_over_loop:
            lda game_over_text,x
            beq game_over_text_done
            sta SCREEN + (10 * 40) + 15,x
            lda #2
            sta COLRAM + (10 * 40) + 15,x
            inx
            jmp game_over_loop
game_over_text_done:

            ldx #0
game_over_press:
            lda press_fire_gameover,x
            beq game_over_done
            sta SCREEN + (14 * 40) + 9,x
            lda #7
            sta COLRAM + (14 * 40) + 9,x
            inx
            jmp game_over_press

game_over_done:
            lda #2
            sta BORDER
            rts

game_over_text:
            !scr "game over"
            !byte 0

press_fire_gameover:
            !scr "press fire for title"
            !byte 0

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

display_health:
            lda health
            lsr
            lsr
            lsr
            sta temp_health

            ldx #0
            lda temp_health
            beq draw_empty_bars

draw_full_bars:
            lda #CHAR_BAR_FULL
            sta SCREEN + (HEALTH_ROW * 40) + 12,x
            lda #HEALTH_COL
            sta COLRAM + (HEALTH_ROW * 40) + 12,x
            inx
            cpx temp_health
            bne draw_full_bars

draw_empty_bars:
            cpx #8
            beq health_done
            lda #CHAR_BAR_EMPTY
            sta SCREEN + (HEALTH_ROW * 40) + 12,x
            lda #11
            sta COLRAM + (HEALTH_ROW * 40) + 12,x
            inx
            jmp draw_empty_bars

health_done:
            rts

temp_health: !byte 0

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

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

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

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

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

            ldy #0
            lda #CHAR_NOTE
            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 #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

            ldy #0
            lda #CHAR_NOTE
            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

            ldy #0
            lda #CHAR_NOTE
            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
            cmp #3
            beq erase_note_t3
            rts

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

            ldy #0
            lda #CHAR_TRACK
            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

            ldy #0
            lda #CHAR_TRACK
            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

            ldy #0
            lda #CHAR_TRACK
            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

; ----------------------------------------------------------------------------
; Initialize Screen
; ----------------------------------------------------------------------------

init_screen:
            lda #BORDER_COL
            sta BORDER
            lda #BG_COL
            sta BGCOL

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

            ldx #0
            lda #TRACK_LINE_COL
clr_colour:
            sta COLRAM,x
            sta COLRAM+$100,x
            sta COLRAM+$200,x
            sta COLRAM+$2E8,x
            inx
            bne clr_colour

            jsr draw_tracks
            jsr draw_hit_zones
            jsr draw_labels

            rts

; ----------------------------------------------------------------------------
; Draw Tracks
; ----------------------------------------------------------------------------

draw_tracks:
            lda #CHAR_TRACK
            ldx #0
draw_t1:
            sta SCREEN + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne draw_t1

            ldx #0
draw_t2:
            sta SCREEN + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne draw_t2

            ldx #0
draw_t3:
            sta SCREEN + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne draw_t3

            rts

; ----------------------------------------------------------------------------
; Draw Hit Zones
; ----------------------------------------------------------------------------

draw_hit_zones:
            lda #CHAR_HITZONE

            sta SCREEN + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
            sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN

            sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN

            sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
            sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
            sta SCREEN + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN

            lda #HIT_ZONE_COL
            sta COLRAM + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
            sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN

            sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN

            sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
            sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
            sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
            sta COLRAM + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN

            rts

; ----------------------------------------------------------------------------
; Draw Labels
; ----------------------------------------------------------------------------

draw_labels:
            ldx #0
draw_score_label:
            lda score_label,x
            beq draw_score_label_done
            sta SCREEN + 1,x
            lda #1
            sta COLRAM + 1,x
            inx
            bne draw_score_label
draw_score_label_done:

            ldx #0
draw_miss_label:
            lda miss_label,x
            beq draw_miss_label_done
            sta SCREEN + 15,x
            lda #2
            sta COLRAM + 15,x
            inx
            bne draw_miss_label
draw_miss_label_done:

            ldx #0
draw_title_game:
            lda title_game,x
            beq draw_title_game_done
            sta SCREEN + 27,x
            lda #1
            sta COLRAM + 27,x
            inx
            bne draw_title_game
draw_title_game_done:

            ldx #0
draw_health_label:
            lda health_label,x
            beq draw_health_label_done
            sta SCREEN + (HEALTH_ROW * 40) + 4,x
            lda #5
            sta COLRAM + (HEALTH_ROW * 40) + 4,x
            inx
            bne draw_health_label
draw_health_label_done:

            ldx #0
draw_progress_label:
            lda progress_label,x
            beq draw_progress_label_done
            sta SCREEN + (PROGRESS_ROW * 40) + 4,x
            lda #3
            sta COLRAM + (PROGRESS_ROW * 40) + 4,x
            inx
            bne draw_progress_label
draw_progress_label_done:

            lda #$1A
            sta SCREEN + (TRACK1_ROW * 40)
            lda #TRACK1_NOTE_COL
            sta COLRAM + (TRACK1_ROW * 40)

            lda #$18
            sta SCREEN + (TRACK2_ROW * 40)
            lda #TRACK2_NOTE_COL
            sta COLRAM + (TRACK2_ROW * 40)

            lda #$03
            sta SCREEN + (TRACK3_ROW * 40)
            lda #TRACK3_NOTE_COL
            sta COLRAM + (TRACK3_ROW * 40)

            rts

score_label:
            !scr "score:"
            !byte 0

miss_label:
            !scr "miss:"
            !byte 0

title_game:
            !scr "sid symphony"
            !byte 0

health_label:
            !scr "health:"
            !byte 0

progress_label:
            !scr "song:"
            !byte 0

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

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

            lda #$0F
            sta SID_VOLUME

            lda #$00
            sta SID_V1_FREQ_LO
            lda #VOICE1_FREQ
            sta SID_V1_FREQ_HI
            lda #PULSE_WIDTH
            sta SID_V1_PWHI
            lda #VOICE_AD
            sta SID_V1_AD
            lda #VOICE_SR
            sta SID_V1_SR

            lda #$00
            sta SID_V2_FREQ_LO
            lda #VOICE2_FREQ
            sta SID_V2_FREQ_HI
            lda #PULSE_WIDTH
            sta SID_V2_PWHI
            lda #VOICE_AD
            sta SID_V2_AD
            lda #VOICE_SR
            sta SID_V2_SR

            lda #$00
            sta SID_V3_FREQ_LO
            lda #VOICE3_FREQ
            sta SID_V3_FREQ_HI
            lda #PULSE_WIDTH
            sta SID_V3_PWHI
            lda #VOICE_AD
            sta SID_V3_AD
            lda #VOICE_SR
            sta SID_V3_SR

            rts

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

reset_track_colours:
            ldx #0
            lda #TRACK_LINE_COL
reset_t1:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne reset_t1

            ldx #0
reset_t2:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne reset_t2

            ldx #0
reset_t3:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne reset_t3

            lda #TRACK1_NOTE_COL
            sta COLRAM + (TRACK1_ROW * 40)
            lda #TRACK2_NOTE_COL
            sta COLRAM + (TRACK2_ROW * 40)
            lda #TRACK3_NOTE_COL
            sta COLRAM + (TRACK3_ROW * 40)

            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

            jsr redraw_all_notes

            rts

; ----------------------------------------------------------------------------
; Redraw All Notes
; ----------------------------------------------------------------------------

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

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

award_points:
            jsr increment_combo

            lda hit_quality
            cmp #2
            beq award_perfect

            lda #GOOD_SCORE
            jsr apply_multiplier
            jsr add_score

            inc good_count

            lda #GOOD_COL
            sta BORDER
            lda #4
            sta border_flash

            lda #HEALTH_GOOD
            jsr increase_health

            jmp award_done

award_perfect:
            lda #PERFECT_SCORE
            jsr apply_multiplier
            jsr add_score

            inc perfect_count

            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
; ----------------------------------------------------------------------------

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

; ============================================================================
; SONG DATA
; ============================================================================

song_data:
            !byte 0, 1, $47
            !byte 2, 2, $2C
            !byte 4, 3, $11

            !byte 8, 1, $3B
            !byte 10, 2, $27
            !byte 12, 3, $13

            !byte 16, 1, $35
            !byte 17, 2, $2C
            !byte 18, 1, $3B
            !byte 20, 3, $16

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

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

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

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

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

            !byte $FF

; ----------------------------------------------------------------------------
; 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

Try This: Animated Title

Add a simple animation to the title screen:

update_title:
            ; Cycle "PRESS FIRE" colour
            inc title_timer
            lda title_timer
            and #$0F            ; Every 16 frames
            bne no_colour_change

            lda press_fire_col
            clc
            adc #1
            and #$0F            ; Keep in range 0-15
            sta press_fire_col
            ; Update colour RAM...

no_colour_change:
            ; Check fire...

Cycling colours draw attention.

Try This: High Score on Title

Display the best score achieved on the title screen:

            ldx #0
draw_high_score:
            lda high_score_label,x
            beq draw_high_done
            sta SCREEN + (15 * 40) + 12,x
            ; ... display high_score value ...

High scores encourage replay.

What You’ve Learnt

  • State machines - Variables controlling code paths
  • Title screen design - Clear title, instructions, call to action
  • Fire button detection - CIA port for joystick input
  • State transitions - Moving cleanly between game phases
  • Game loop completion - Title → Play → Results → Title

What’s Next

In Unit 14, we’ll improve the game over screen. Currently it’s just “GAME OVER” overlaid on gameplay. We’ll give it the same treatment as the title screen - a dedicated display with score summary.

What Changed

Unit 12 → Unit 13
+245-86
11 ; ============================================================================
2-; SID SYMPHONY - Unit 12: Combo System
2+; SID SYMPHONY - Unit 13: Title Screen
33 ; ============================================================================
4-; Consecutive hits build a combo. Higher combos increase the score multiplier.
5-; Miss a note and the combo resets to zero. Consistency is rewarded.
4+; The game now starts with a proper title screen. Press fire to play.
5+; Complete game flow: Title -> Playing -> Results -> Title.
66 ;
77 ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
8+; Fire = Start game from title screen
89 ; ============================================================================
910
1011 ; ============================================================================
...
4849
4950 HEALTH_COL = 5
5051 PROGRESS_COL = 3
51-COMBO_COL = 13 ; Yellow for combo display
52+COMBO_COL = 13
53+
54+; Title screen colours
55+TITLE_COL = 1 ; White for title
56+SUBTITLE_COL = 11 ; Grey for subtitle
5257
5358 ; ============================================================================
5459 ; SCORING SETTINGS
...
6166 ; COMBO SETTINGS
6267 ; ============================================================================
6368
64-COMBO_TIER_2 = 10 ; 2x multiplier at 10 combo
65-COMBO_TIER_3 = 25 ; 3x multiplier at 25 combo
66-COMBO_TIER_4 = 50 ; 4x multiplier at 50 combo
69+COMBO_TIER_2 = 10
70+COMBO_TIER_3 = 25
71+COMBO_TIER_4 = 50
6772
6873 ; ============================================================================
6974 ; HEALTH SETTINGS
...
9499 ; GAME STATES
95100 ; ============================================================================
96101
97-STATE_PLAYING = 0
98-STATE_RESULTS = 1
99-STATE_GAMEOVER = 2
102+STATE_TITLE = 0 ; NEW: Title screen
103+STATE_PLAYING = 1
104+STATE_RESULTS = 2
105+STATE_GAMEOVER = 3
100106
101107 ; ============================================================================
102108 ; MEMORY MAP
...
135141
136142 SID_VOLUME = $D418
137143
138-; CIA keyboard
144+; CIA keyboard and joystick
139145 CIA1_PRA = $DC00
140146 CIA1_PRB = $DC01
141147
...
150156
151157 ; Combo display position
152158 COMBO_ROW = 2
153-COMBO_COL_POS = 17
154159
155160 ; Hit zone
156161 HIT_ZONE_COLUMN = 3
...
212217
213218 start:
214219 jsr copy_charset
215- jsr init_game
220+ jsr init_sid
221+ jsr show_title
222+
223+ ; Start in TITLE state
224+ lda #STATE_TITLE
225+ sta game_state
216226
217227 main_loop:
218228 lda #$FF
...
221231 bne wait_raster
222232
223233 lda game_state
234+ cmp #STATE_TITLE
235+ beq do_title
224236 cmp #STATE_PLAYING
225237 beq do_playing
226238 cmp #STATE_RESULTS
227239 beq do_results
228240 jmp do_gameover
241+
242+do_title:
243+ jsr update_title
244+ jmp main_loop
229245
230246 do_playing:
231247 jsr update_playing
...
240256 jmp main_loop
241257
242258 ; ----------------------------------------------------------------------------
243-; Initialize Game
259+; Show Title Screen
260+; ----------------------------------------------------------------------------
261+
262+show_title:
263+ ; Clear screen
264+ lda #BORDER_COL
265+ sta BORDER
266+ lda #BG_COL
267+ sta BGCOL
268+
269+ ldx #0
270+ lda #CHAR_SPACE
271+clear_title:
272+ sta SCREEN,x
273+ sta SCREEN+$100,x
274+ sta SCREEN+$200,x
275+ sta SCREEN+$2E8,x
276+ inx
277+ bne clear_title
278+
279+ ; Draw big title "SID SYMPHONY"
280+ ldx #0
281+draw_title_text:
282+ lda title_big,x
283+ beq draw_title_done
284+ sta SCREEN + (8 * 40) + 14,x
285+ lda #TITLE_COL
286+ sta COLRAM + (8 * 40) + 14,x
287+ inx
288+ jmp draw_title_text
289+draw_title_done:
290+
291+ ; Draw subtitle "A RHYTHM GAME"
292+ ldx #0
293+draw_subtitle:
294+ lda subtitle_text,x
295+ beq draw_subtitle_done
296+ sta SCREEN + (10 * 40) + 13,x
297+ lda #SUBTITLE_COL
298+ sta COLRAM + (10 * 40) + 13,x
299+ inx
300+ jmp draw_subtitle
301+draw_subtitle_done:
302+
303+ ; Draw controls
304+ ldx #0
305+draw_controls:
306+ lda controls_text,x
307+ beq draw_controls_done
308+ sta SCREEN + (14 * 40) + 11,x
309+ lda #11
310+ sta COLRAM + (14 * 40) + 11,x
311+ inx
312+ jmp draw_controls
313+draw_controls_done:
314+
315+ ; Draw track info
316+ ldx #0
317+draw_track_info:
318+ lda track_info,x
319+ beq draw_track_done
320+ sta SCREEN + (16 * 40) + 9,x
321+ lda #11
322+ sta COLRAM + (16 * 40) + 9,x
323+ inx
324+ jmp draw_track_info
325+draw_track_done:
326+
327+ ; Draw "PRESS FIRE TO START"
328+ ldx #0
329+draw_press_fire:
330+ lda press_fire_text,x
331+ beq draw_press_done
332+ sta SCREEN + (20 * 40) + 10,x
333+ lda #7 ; Yellow
334+ sta COLRAM + (20 * 40) + 10,x
335+ inx
336+ jmp draw_press_fire
337+draw_press_done:
338+
339+ rts
340+
341+title_big:
342+ !scr "sid symphony"
343+ !byte 0
344+
345+subtitle_text:
346+ !scr "a rhythm game"
347+ !byte 0
348+
349+controls_text:
350+ !scr "controls: z / x / c"
351+ !byte 0
352+
353+track_info:
354+ !scr "hit notes as they reach"
355+ !byte 0
356+
357+press_fire_text:
358+ !scr "press fire to start"
359+ !byte 0
360+
361+; ----------------------------------------------------------------------------
362+; Update Title State
363+; ----------------------------------------------------------------------------
364+
365+update_title:
366+ ; Check for fire button (joystick port 2)
367+ lda CIA1_PRA
368+ and #$10 ; Bit 4 = fire
369+ beq fire_pressed
370+
371+ ; Also check space bar as alternative
372+ lda #$7F
373+ sta CIA1_PRA
374+ lda CIA1_PRB
375+ and #$10 ; Space
376+ beq fire_pressed
377+
378+ ; No input - stay on title
379+ lda #$FF
380+ sta CIA1_PRA
381+ rts
382+
383+fire_pressed:
384+ lda #$FF
385+ sta CIA1_PRA
386+
387+ ; Start the game!
388+ jsr init_game
389+ lda #STATE_PLAYING
390+ sta game_state
391+ rts
392+
393+; ----------------------------------------------------------------------------
394+; Initialize Game (called when starting from title)
244395 ; ----------------------------------------------------------------------------
245396
246397 init_game:
247398 jsr init_screen
248- jsr init_sid
249399 jsr init_notes
250400 jsr init_score
251401 jsr init_health
252402 jsr init_combo
253403 jsr init_song
254-
255- lda #STATE_PLAYING
256- sta game_state
257404
258405 rts
259406
...
297444 ; ----------------------------------------------------------------------------
298445
299446 update_results:
300- lda #$00
447+ ; Check for fire to return to title
448+ lda CIA1_PRA
449+ and #$10
450+ beq results_fire
451+
452+ ; Also check space bar
453+ lda #$7F
301454 sta CIA1_PRA
302455 lda CIA1_PRB
303- cmp #$FF
304- beq results_wait
456+ and #$10
457+ beq results_fire
305458
306- jsr init_game
459+ lda #$FF
460+ sta CIA1_PRA
461+ rts
307462
308-results_wait:
463+results_fire:
309464 lda #$FF
310465 sta CIA1_PRA
466+
467+ ; Return to title screen
468+ jsr show_title
469+ lda #STATE_TITLE
470+ sta game_state
311471 rts
312472
313473 ; ----------------------------------------------------------------------------
...
315475 ; ----------------------------------------------------------------------------
316476
317477 update_gameover:
318- lda #$00
478+ ; Check for fire to return to title
479+ lda CIA1_PRA
480+ and #$10
481+ beq gameover_fire
482+
483+ lda #$7F
319484 sta CIA1_PRA
320485 lda CIA1_PRB
321- cmp #$FF
322- beq gameover_wait
486+ and #$10
487+ beq gameover_fire
323488
324- jsr init_game
489+ lda #$FF
490+ sta CIA1_PRA
491+ rts
325492
326-gameover_wait:
493+gameover_fire:
327494 lda #$FF
328495 sta CIA1_PRA
496+
497+ ; Return to title screen
498+ jsr show_title
499+ lda #STATE_TITLE
500+ sta game_state
329501 rts
330502
331503 ; ----------------------------------------------------------------------------
...
441613 temp_progress: !byte 0
442614
443615 ; ----------------------------------------------------------------------------
444-; Get Multiplier - Returns multiplier (1-4) based on combo
616+; Get Multiplier
445617 ; ----------------------------------------------------------------------------
446618
447619 get_multiplier:
...
453625 cmp #COMBO_TIER_2
454626 bcs mult_2x
455627
456- ; Default: 1x
457628 lda #1
458629 rts
459630
...
474645 ; ----------------------------------------------------------------------------
475646
476647 display_combo:
477- ; Draw "COMBO:" label
478648 ldx #0
479649 draw_combo_label:
480650 lda combo_label,x
...
486656 jmp draw_combo_label
487657
488658 draw_combo_value:
489- ; Display 3-digit combo value
490659 lda combo
491660
492- ; Hundreds
493661 ldx #0
494662 combo_div_100:
495663 cmp #100
...
505673 sta SCREEN + (COMBO_ROW * 40) + 18
506674 pla
507675
508- ; Tens
509676 ldx #0
510677 combo_div_10:
511678 cmp #10
...
521688 sta SCREEN + (COMBO_ROW * 40) + 19
522689 pla
523690
524- ; Ones
525691 ora #$30
526692 sta SCREEN + (COMBO_ROW * 40) + 20
527693
528- ; Colour based on multiplier tier
529694 jsr get_multiplier
530695 cmp #4
531696 beq combo_col_4x
...
534699 cmp #2
535700 beq combo_col_2x
536701
537- ; 1x = grey
538702 lda #11
539703 jmp set_combo_col
540704
541705 combo_col_2x:
542- lda #7 ; Yellow
706+ lda #7
543707 jmp set_combo_col
544708
545709 combo_col_3x:
546- lda #5 ; Green
710+ lda #5
547711 jmp set_combo_col
548712
549713 combo_col_4x:
550- lda #1 ; White (best)
714+ lda #1
551715
552716 set_combo_col:
553717 sta COLRAM + (COMBO_ROW * 40) + 18
554718 sta COLRAM + (COMBO_ROW * 40) + 19
555719 sta COLRAM + (COMBO_ROW * 40) + 20
556720
557- ; Display multiplier
558721 jsr get_multiplier
559722 ora #$30
560723 sta SCREEN + (COMBO_ROW * 40) + 22
561- lda #$18 ; 'x' character
724+ lda #$18
562725 sta SCREEN + (COMBO_ROW * 40) + 23
563726
564727 jsr get_multiplier
...
600763 increment_combo:
601764 inc combo
602765
603- ; Update max combo if needed
604766 lda combo
605767 cmp max_combo
606768 bcc combo_not_max
...
618780 lda combo
619781 beq combo_already_zero
620782
621- ; Combo was active - show break
622783 lda #0
623784 sta combo
624785 jsr display_combo
...
631792 ; ----------------------------------------------------------------------------
632793
633794 show_results:
634- ; Clear screen
635795 ldx #0
636796 lda #CHAR_SPACE
637797 clear_for_results:
...
642802 inx
643803 bne clear_for_results
644804
645- ; Title
646805 ldx #0
647806 draw_results_title:
648807 lda results_title,x
...
654813 jmp draw_results_title
655814 draw_results_title_done:
656815
657- ; Score
658816 ldx #0
659817 draw_final_score_label:
660818 lda final_score_label,x
...
667825 draw_final_score_done:
668826 jsr display_final_score
669827
670- ; Perfects
671828 ldx #0
672829 draw_perfects_label:
673830 lda perfects_label,x
...
681838 lda perfect_count
682839 jsr display_stat_at_8
683840
684- ; Goods
685841 ldx #0
686842 draw_goods_label:
687843 lda goods_label,x
...
695851 lda good_count
696852 jsr display_stat_at_9
697853
698- ; Misses
699854 ldx #0
700855 draw_misses_label:
701856 lda misses_label,x
...
709864 lda miss_count
710865 jsr display_stat_at_10
711866
712- ; Max Combo
713867 ldx #0
714868 draw_max_combo_label:
715869 lda max_combo_label,x
...
723877 lda max_combo
724878 jsr display_stat_at_12
725879
726- ; Accuracy
727880 ldx #0
728881 draw_accuracy_label:
729882 lda accuracy_label,x
...
737890 jsr calculate_accuracy
738891 jsr display_accuracy
739892
740- ; Press any key
741893 ldx #0
742894 draw_press_key:
743- lda press_key_text,x
895+ lda press_fire_results,x
744896 beq draw_press_key_done
745- sta SCREEN + (18 * 40) + 10,x
746- lda #11
747- sta COLRAM + (18 * 40) + 10,x
897+ sta SCREEN + (18 * 40) + 9,x
898+ lda #7
899+ sta COLRAM + (18 * 40) + 9,x
748900 inx
749901 jmp draw_press_key
750902 draw_press_key_done:
...
782934 !scr "accuracy:"
783935 !byte 0
784936
785-press_key_text:
786- !scr "press any key"
937+press_fire_results:
938+ !scr "press fire for title"
787939 !byte 0
788940
789941 ; ----------------------------------------------------------------------------
790-; Display Final Score (at row 6)
942+; Display Final Score
791943 ; ----------------------------------------------------------------------------
792944
793945 display_final_score:
...
8741026 rts
8751027
8761028 ; ----------------------------------------------------------------------------
877-; Display Stat (2-digit) at various rows
1029+; Display Stats
8781030 ; ----------------------------------------------------------------------------
8791031
8801032 display_stat_at_8:
...
10801232 rts
10811233
10821234 ; ----------------------------------------------------------------------------
1083-; Copy Character Set from ROM to RAM
1235+; Copy Character Set
10841236 ; ----------------------------------------------------------------------------
10851237
10861238 copy_charset:
...
13471499 rts
13481500
13491501 ; ----------------------------------------------------------------------------
1350-; Handle Miss - Now breaks combo
1502+; Handle Miss
13511503 ; ----------------------------------------------------------------------------
13521504
13531505 handle_miss:
...
13621514
13631515 jsr display_misses
13641516 jsr decrease_health
1365-
1366- ; Break the combo!
13671517 jsr break_combo
13681518
13691519 rts
...
14271577 ldx #0
14281578 game_over_loop:
14291579 lda game_over_text,x
1430- beq game_over_done
1431- sta SCREEN + (12 * 40) + 15,x
1580+ beq game_over_text_done
1581+ sta SCREEN + (10 * 40) + 15,x
14321582 lda #2
1433- sta COLRAM + (12 * 40) + 15,x
1583+ sta COLRAM + (10 * 40) + 15,x
14341584 inx
14351585 jmp game_over_loop
1586+game_over_text_done:
1587+
1588+ ldx #0
1589+game_over_press:
1590+ lda press_fire_gameover,x
1591+ beq game_over_done
1592+ sta SCREEN + (14 * 40) + 9,x
1593+ lda #7
1594+ sta COLRAM + (14 * 40) + 9,x
1595+ inx
1596+ jmp game_over_press
1597+
14361598 game_over_done:
14371599 lda #2
14381600 sta BORDER
...
14401602
14411603 game_over_text:
14421604 !scr "game over"
1605+ !byte 0
1606+
1607+press_fire_gameover:
1608+ !scr "press fire for title"
14431609 !byte 0
14441610
14451611 ; ----------------------------------------------------------------------------
...
18021968 draw_miss_label_done:
18031969
18041970 ldx #0
1805-draw_title:
1806- lda title_text,x
1807- beq draw_title_done
1971+draw_title_game:
1972+ lda title_game,x
1973+ beq draw_title_game_done
18081974 sta SCREEN + 27,x
18091975 lda #1
18101976 sta COLRAM + 27,x
18111977 inx
1812- bne draw_title
1813-draw_title_done:
1978+ bne draw_title_game
1979+draw_title_game_done:
18141980
18151981 ldx #0
18161982 draw_health_label:
...
18592025 !scr "miss:"
18602026 !byte 0
18612027
1862-title_text:
2028+title_game:
18632029 !scr "sid symphony"
18642030 !byte 0
18652031
...
21032269 rts
21042270
21052271 ; ----------------------------------------------------------------------------
2106-; Award Points - Now uses combo multiplier
2272+; Award Points
21072273 ; ----------------------------------------------------------------------------
21082274
21092275 award_points:
2110- ; Increment combo first
21112276 jsr increment_combo
21122277
2113- ; Get base score
21142278 lda hit_quality
21152279 cmp #2
21162280 beq award_perfect
21172281
2118- ; Good hit: 50 * multiplier
21192282 lda #GOOD_SCORE
21202283 jsr apply_multiplier
21212284 jsr add_score
...
21332296 jmp award_done
21342297
21352298 award_perfect:
2136- ; Perfect hit: 100 * multiplier
21372299 lda #PERFECT_SCORE
21382300 jsr apply_multiplier
21392301 jsr add_score
...
21532315 rts
21542316
21552317 ; ----------------------------------------------------------------------------
2156-; Apply Multiplier - A = base score, returns multiplied score in score_add_lo/hi
2318+; Apply Multiplier
21572319 ; ----------------------------------------------------------------------------
21582320
21592321 apply_multiplier:
...
21612323 jsr get_multiplier
21622324 sta current_mult
21632325
2164- ; Start with base score
21652326 lda base_score
21662327 sta score_add_lo
21672328 lda #0
21682329 sta score_add_hi
21692330
2170- ; If multiplier is 1, we're done
21712331 lda current_mult
21722332 cmp #1
21732333 beq mult_done_apply
21742334
2175- ; Multiply by adding base_score (mult-1) more times
21762335 dec current_mult
21772336 mult_add_loop:
21782337 lda score_add_lo
...
21942353 score_add_hi: !byte 0
21952354
21962355 ; ----------------------------------------------------------------------------
2197-; Add Score - Adds score_add_lo/hi to score
2356+; Add Score
21982357 ; ----------------------------------------------------------------------------
21992358
22002359 add_score:
...
23282487 work_hi: !byte 0
23292488
23302489 ; ----------------------------------------------------------------------------
2331-; Play Voices With Note Frequency
2490+; Play Voices
23322491 ; ----------------------------------------------------------------------------
23332492
23342493 play_voice1_note:
...
23622521 rts
23632522
23642523 ; ----------------------------------------------------------------------------
2365-; Flash Tracks on Hit
2524+; Flash Tracks
23662525 ; ----------------------------------------------------------------------------
23672526
23682527 flash_track1_hit: