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

Song End and Results

Detect song completion. Display results with score breakdown.

17% of SID Symphony

A game needs an ending. This unit adds results and the complete game loop.

When the song finishes, the screen clears to show final results: score, perfects, goods, misses, and accuracy percentage. Press any key to play again. The game now has a proper beginning and end.

Run It

Assemble and run:

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

Unit 11 Screenshot

Play through the entire song. When it ends, you’ll see the results screen with “SONG COMPLETE!” and your statistics. Press any key to restart.

Song end — results screen shows performance statistics

Game States

The game now has three distinct states:

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

STATE_PLAYING = 0
STATE_RESULTS = 1
STATE_GAMEOVER = 2

game_state:   !byte 0

; ----------------------------------------------------------------------------
; Main Loop - State Machine
; ----------------------------------------------------------------------------

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

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

do_playing:
            jsr update_playing
            jmp main_loop

do_results:
            jsr update_results
            jmp main_loop

do_gameover:
            jsr update_gameover
            jmp main_loop

The main loop checks game_state and calls the appropriate update routine. This pattern - a state variable controlling which code runs - scales well as games grow more complex.

Detecting Song End

The song ends when all notes have been processed and cleared from the screen:

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

check_song_end:
            ; First check if song data exhausted
            lda song_ended
            beq song_not_ended

            ; Song data done - wait for all notes to clear
            ldx #0
check_notes_clear:
            lda note_track,x
            bne notes_still_active
            inx
            cpx #MAX_NOTES
            bne check_notes_clear

            ; All notes cleared - count down delay
            dec end_delay
            bne song_not_ended

            ; Delay done - show results
            jsr show_results
            lda #STATE_RESULTS
            sta game_state

notes_still_active:
song_not_ended:
            rts

The logic is:

  1. Check if song data is exhausted (song_ended flag)
  2. Wait for all active notes to scroll off or be hit
  3. Wait a brief delay (for dramatic effect)
  4. Transition to results state

Tracking Statistics

We now track perfects and goods separately:

award_points:
            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit
            inc good_count      ; Track goods separately
            ; ... add score ...

award_perfect:
            inc perfect_count   ; Track perfects separately
            ; ... add score ...

These counters appear on the results screen.

Calculating Accuracy

Accuracy shows what percentage of notes the player hit:

; ----------------------------------------------------------------------------
; Calculate Accuracy - (perfects + goods) * 100 / total_notes
; ----------------------------------------------------------------------------

calculate_accuracy:
            ; Total hits = perfects + goods
            lda perfect_count
            clc
            adc good_count
            sta total_hits

            ; Total notes = hits + misses
            clc
            adc miss_count
            sta total_notes

            ; Avoid division by zero
            beq accuracy_zero

            ; Multiply hits by 100
            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:

            ; Divide by total_notes
            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

The formula: (perfects + goods) × 100 ÷ total_notes. We multiply first, then divide, to maintain precision. The 6502 has no division instruction, so we use repeated subtraction.

The Results Screen

The results screen displays:

        SONG COMPLETE!

        FINAL SCORE: 02800
        PERFECTS:    15
        GOODS:       10
        MISSES:      05
        ACCURACY:    083%

        PRESS ANY KEY

Each statistic uses colour to communicate meaning: white for perfects, yellow for goods, red for misses, green for accuracy. The border turns green for success.

Press Any Key

Waiting for any key is simple:

update_results:
            lda #$00
            sta CIA1_PRA        ; Select all keyboard rows
            lda CIA1_PRB
            cmp #$FF            ; $FF = no keys pressed
            beq results_wait

            ; Key pressed - restart
            jsr init_game

results_wait:
            lda #$FF
            sta CIA1_PRA
            rts

Setting CIA1_PRA to $00 selects all rows. Any key press makes CIA1_PRB read something other than $FF.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 11: Song End and Results
; ============================================================================
; Detect song completion. Display results with score breakdown. The game
; has a proper ending now - victory or defeat, then replay.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================

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

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

PERFECT_SCORE = 100
GOOD_SCORE    = 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_PLAYING = 0
STATE_RESULTS = 1
STATE_GAMEOVER = 2

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

; 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           ; Wait after last note before results

; 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               ; Flag: song data exhausted
end_delay   = $0F               ; Countdown after last note

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

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

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

do_playing:
            jsr update_playing
            jmp main_loop

do_results:
            jsr update_results
            jmp main_loop

do_gameover:
            jsr update_gameover
            jmp main_loop

; ----------------------------------------------------------------------------
; Initialize Game
; ----------------------------------------------------------------------------

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

            lda #STATE_PLAYING
            sta game_state

            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

            ; Check for song end
            jsr check_song_end

            rts

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

update_results:
            ; Wait for any key to restart
            lda #$00
            sta CIA1_PRA
            lda CIA1_PRB
            cmp #$FF
            beq results_wait

            ; Key pressed - restart game
            jsr init_game

results_wait:
            lda #$FF
            sta CIA1_PRA
            rts

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

update_gameover:
            ; Wait for any key to restart
            lda #$00
            sta CIA1_PRA
            lda CIA1_PRB
            cmp #$FF
            beq gameover_wait

            ; Key pressed - restart game
            jsr init_game

gameover_wait:
            lda #$FF
            sta CIA1_PRA
            rts

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

check_song_end:
            ; First check if song data exhausted
            lda song_ended
            beq song_not_ended

            ; Song data done - wait for all notes to clear
            ldx #0
check_notes_clear:
            lda note_track,x
            bne notes_still_active
            inx
            cpx #MAX_NOTES
            bne check_notes_clear

            ; All notes cleared - count down delay
            dec end_delay
            bne song_not_ended

            ; Delay done - show results
            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

            ; Song reached end
            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

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

show_results:
            ; Clear screen
            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

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

            ; Score label and value
            ldx #0
draw_final_score_label:
            lda final_score_label,x
            beq draw_final_score_done
            sta SCREEN + (8 * 40) + 10,x
            lda #7
            sta COLRAM + (8 * 40) + 10,x
            inx
            jmp draw_final_score_label
draw_final_score_done:
            jsr display_final_score

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

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

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

            ; Accuracy (simplified: just show hit percentage)
            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

            ; Press any key message
            ldx #0
draw_press_key:
            lda press_key_text,x
            beq draw_press_key_done
            sta SCREEN + (18 * 40) + 10,x
            lda #11
            sta COLRAM + (18 * 40) + 10,x
            inx
            jmp draw_press_key
draw_press_key_done:

            ; Green border for victory
            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

accuracy_label:
            !scr "accuracy:"
            !byte 0

press_key_text:
            !scr "press any key"
            !byte 0

; ----------------------------------------------------------------------------
; Display Final Score (at row 8)
; ----------------------------------------------------------------------------

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 + (8 * 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 + (8 * 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 + (8 * 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 + (8 * 40) + 26

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

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

            rts

; ----------------------------------------------------------------------------
; Display Stat (2-digit) at various rows
; ----------------------------------------------------------------------------

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 #1
            sta COLRAM + (10 * 40) + 23
            sta COLRAM + (10 * 40) + 24
            rts

display_stat_at_11:
            ldx #0
stat11_div:
            cmp #10
            bcc stat11_done
            sec
            sbc #10
            inx
            jmp stat11_div
stat11_done:
            pha
            txa
            ora #$30
            sta SCREEN + (11 * 40) + 23
            pla
            ora #$30
            sta SCREEN + (11 * 40) + 24
            lda #7
            sta COLRAM + (11 * 40) + 23
            sta COLRAM + (11 * 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 #2
            sta COLRAM + (12 * 40) + 23
            sta COLRAM + (12 * 40) + 24
            rts

; ----------------------------------------------------------------------------
; Calculate Accuracy - (perfects + goods) * 100 / total_notes
; ----------------------------------------------------------------------------

calculate_accuracy:
            ; Total hits = perfects + goods
            lda perfect_count
            clc
            adc good_count
            sta total_hits

            ; Total notes = hits + misses
            clc
            adc miss_count
            sta total_notes

            ; Avoid division by zero
            beq accuracy_zero

            ; Simple percentage: (hits * 100) / total
            ; We'll approximate with (hits * 128) / total then scale
            ; Or simpler: multiply hits by 100, divide by total

            lda total_hits
            sta dividend_lo
            lda #0
            sta dividend_hi

            ; Multiply by 100
            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:

            ; Divide by total_notes
            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 (3 digits + %)
; ----------------------------------------------------------------------------

display_accuracy:
            lda accuracy

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

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

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

            ; Percent sign
            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 from ROM to RAM
; ----------------------------------------------------------------------------

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

            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_done
            sta SCREEN + (12 * 40) + 15,x
            lda #2
            sta COLRAM + (12 * 40) + 15,x
            inx
            jmp game_over_loop
game_over_done:
            lda #2
            sta BORDER
            rts

game_over_text:
            !scr "game over"
            !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:
            lda title_text,x
            beq draw_title_done
            sta SCREEN + 27,x
            lda #1
            sta COLRAM + 27,x
            inx
            bne draw_title
draw_title_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_text:
            !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 - Now tracks perfects and goods separately
; ----------------------------------------------------------------------------

award_points:
            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit
            inc good_count

            lda score_lo
            clc
            adc #GOOD_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            lda #GOOD_COL
            sta BORDER
            lda #4
            sta border_flash

            lda #HEALTH_GOOD
            jsr increase_health

            jmp award_done

award_perfect:
            ; Perfect hit
            inc perfect_count

            lda score_lo
            clc
            adc #PERFECT_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            lda #PERFECT_COL
            sta BORDER
            lda #6
            sta border_flash

            lda #HEALTH_PERFECT
            jsr increase_health

award_done:
            jsr display_score
            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 With Note Frequency
; ----------------------------------------------------------------------------

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

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

Try This: Grade Letters

Add letter grades based on accuracy:

            lda accuracy
            cmp #95
            bcs grade_s         ; S rank
            cmp #80
            bcs grade_a         ; A rank
            cmp #60
            bcs grade_b         ; B rank
            ; ... etc

Grades give players clear targets.

Try This: High Score

Save the best score:

            ; Compare current to high score
            lda score_hi
            cmp high_score_hi
            bcc not_high
            bne is_high
            lda score_lo
            cmp high_score_lo
            bcc not_high
is_high:
            ; New high score!
            ; ... copy and display ...

High scores encourage replay.

What You’ve Learnt

  • State machines - Different code paths for different game states
  • End detection - Waiting for all notes to resolve
  • Statistics tracking - Counting perfects, goods, misses separately
  • Integer percentage - Multiply then divide for accuracy
  • Any-key detection - Selecting all keyboard rows at once

What’s Next

In Unit 12, we’ll add a combo system. Consecutive hits build a multiplier, rewarding sustained accuracy with bonus points.

What Changed

Unit 10 → Unit 11
+607-55
11 ; ============================================================================
2-; SID SYMPHONY - Unit 10: Song Playback
2+; SID SYMPHONY - Unit 11: Song End and Results
33 ; ============================================================================
4-; Song plays with proper timing. Progress bar shows how far through the song.
5-; Notes spawn at exactly the right beat. The rhythm feels right.
4+; Detect song completion. Display results with score breakdown. The game
5+; has a proper ending now - victory or defeat, then replay.
66 ;
77 ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
88 ; ============================================================================
...
1212 ; ============================================================================
1313
1414 ; SID Voice Settings
15-VOICE1_WAVE = $21 ; Sawtooth
16-VOICE2_WAVE = $41 ; Pulse
17-VOICE3_WAVE = $11 ; Triangle
15+VOICE1_WAVE = $21
16+VOICE2_WAVE = $41
17+VOICE3_WAVE = $11
1818
1919 VOICE1_FREQ = $1C
2020 VOICE2_FREQ = $0E
...
4747 MISS_COL = 2
4848
4949 HEALTH_COL = 5
50-PROGRESS_COL = 3 ; Cyan for progress bar
50+PROGRESS_COL = 3
5151
5252 ; ============================================================================
5353 ; SCORING SETTINGS
...
7878 ; SONG SETTINGS
7979 ; ============================================================================
8080
81-SONG_LENGTH = 64 ; Total beats in the song
82-PROGRESS_WIDTH = 16 ; Width of progress bar in characters
81+SONG_LENGTH = 64
82+PROGRESS_WIDTH = 16
83+
84+; ============================================================================
85+; GAME STATES
86+; ============================================================================
87+
88+STATE_PLAYING = 0
89+STATE_RESULTS = 1
90+STATE_GAMEOVER = 2
8391
8492 ; ============================================================================
8593 ; MEMORY MAP
...
148156
149157 ; Timing
150158 FRAMES_PER_BEAT = 25
159+END_DELAY_FRAMES = 75 ; Wait after last note before results
151160
152161 ; Zero page
153162 ZP_PTR = $FB
...
163172 hit_quality = $08
164173 border_flash = $09
165174 miss_track = $0A
166-game_over = $0B
175+game_state = $0B
167176 hit_note_freq = $0C
168-song_beat = $0D ; Current beat in song (0 to SONG_LENGTH-1)
177+song_beat = $0D
178+song_ended = $0E ; Flag: song data exhausted
179+end_delay = $0F ; Countdown after last note
169180
170181 ; ----------------------------------------------------------------------------
171182 ; BASIC Stub
...
188199
189200 start:
190201 jsr copy_charset
202+ jsr init_game
203+
204+main_loop:
205+ lda #$FF
206+wait_raster:
207+ cmp $D012
208+ bne wait_raster
209+
210+ lda game_state
211+ cmp #STATE_PLAYING
212+ beq do_playing
213+ cmp #STATE_RESULTS
214+ beq do_results
215+ jmp do_gameover
216+
217+do_playing:
218+ jsr update_playing
219+ jmp main_loop
220+
221+do_results:
222+ jsr update_results
223+ jmp main_loop
224+
225+do_gameover:
226+ jsr update_gameover
227+ jmp main_loop
228+
229+; ----------------------------------------------------------------------------
230+; Initialize Game
231+; ----------------------------------------------------------------------------
232+
233+init_game:
191234 jsr init_screen
192235 jsr init_sid
193236 jsr init_notes
...
195238 jsr init_health
196239 jsr init_song
197240
198-main_loop:
199- lda #$FF
200-wait_raster:
201- cmp $D012
202- bne wait_raster
241+ lda #STATE_PLAYING
242+ sta game_state
203243
204- lda game_over
205- bne main_loop
244+ rts
245+
246+; ----------------------------------------------------------------------------
247+; Update Playing State
248+; ----------------------------------------------------------------------------
206249
250+update_playing:
207251 inc frame_count
208252 lda frame_count
209253 cmp #FRAMES_PER_BEAT
...
220264 jsr update_border_flash
221265 jsr check_keys
222266
223- jmp main_loop
267+ ; Check for song end
268+ jsr check_song_end
269+
270+ rts
271+
272+; ----------------------------------------------------------------------------
273+; Update Results State
274+; ----------------------------------------------------------------------------
275+
276+update_results:
277+ ; Wait for any key to restart
278+ lda #$00
279+ sta CIA1_PRA
280+ lda CIA1_PRB
281+ cmp #$FF
282+ beq results_wait
283+
284+ ; Key pressed - restart game
285+ jsr init_game
286+
287+results_wait:
288+ lda #$FF
289+ sta CIA1_PRA
290+ rts
291+
292+; ----------------------------------------------------------------------------
293+; Update Game Over State
294+; ----------------------------------------------------------------------------
295+
296+update_gameover:
297+ ; Wait for any key to restart
298+ lda #$00
299+ sta CIA1_PRA
300+ lda CIA1_PRB
301+ cmp #$FF
302+ beq gameover_wait
303+
304+ ; Key pressed - restart game
305+ jsr init_game
306+
307+gameover_wait:
308+ lda #$FF
309+ sta CIA1_PRA
310+ rts
311+
312+; ----------------------------------------------------------------------------
313+; Check Song End
314+; ----------------------------------------------------------------------------
315+
316+check_song_end:
317+ ; First check if song data exhausted
318+ lda song_ended
319+ beq song_not_ended
320+
321+ ; Song data done - wait for all notes to clear
322+ ldx #0
323+check_notes_clear:
324+ lda note_track,x
325+ bne notes_still_active
326+ inx
327+ cpx #MAX_NOTES
328+ bne check_notes_clear
329+
330+ ; All notes cleared - count down delay
331+ dec end_delay
332+ bne song_not_ended
333+
334+ ; Delay done - show results
335+ jsr show_results
336+ lda #STATE_RESULTS
337+ sta game_state
338+
339+notes_still_active:
340+song_not_ended:
341+ rts
224342
225343 ; ----------------------------------------------------------------------------
226344 ; Initialize Song
...
237355 sta beat_count
238356 sta song_beat
239357 sta border_flash
240- sta game_over
358+ sta song_ended
359+
360+ lda #END_DELAY_FRAMES
361+ sta end_delay
241362
242363 jsr display_progress
243364 rts
244365
245366 ; ----------------------------------------------------------------------------
246-; Advance Song - Increment beat and update progress
367+; Advance Song
247368 ; ----------------------------------------------------------------------------
248369
249370 advance_song:
371+ lda song_ended
372+ bne advance_done
373+
250374 inc beat_count
251375 inc song_beat
252376
253- ; Check if song has ended
254377 lda song_beat
255378 cmp #SONG_LENGTH
256379 bcc song_continues
257380
258- ; Song loops for now (Unit 11 will add proper end)
259- lda #0
260- sta song_beat
261- sta beat_count
262- lda #<song_data
263- sta song_pos
264- lda #>song_data
265- sta song_pos_hi
381+ ; Song reached end
382+ lda #1
383+ sta song_ended
266384
267385 song_continues:
268386 jsr display_progress
387+
388+advance_done:
269389 rts
270390
271391 ; ----------------------------------------------------------------------------
272-; Display Progress - Show song progress bar
392+; Display Progress
273393 ; ----------------------------------------------------------------------------
274394
275395 display_progress:
276- ; Calculate progress: (song_beat * PROGRESS_WIDTH) / SONG_LENGTH
277- ; For 64-beat song with 16-char bar: song_beat / 4
278-
279396 lda song_beat
280- lsr ; Divide by 4
397+ lsr
281398 lsr
282399 sta temp_progress
283400
284- ; Draw filled portion
285401 ldx #0
286402 lda temp_progress
287403 beq draw_empty_progress
...
300416 beq progress_done
301417 lda #CHAR_BAR_EMPTY
302418 sta SCREEN + (PROGRESS_ROW * 40) + 12,x
303- lda #11 ; Dark grey
419+ lda #11
304420 sta COLRAM + (PROGRESS_ROW * 40) + 12,x
305421 inx
306422 jmp draw_empty_progress
...
309425 rts
310426
311427 temp_progress: !byte 0
428+
429+; ----------------------------------------------------------------------------
430+; Show Results Screen
431+; ----------------------------------------------------------------------------
432+
433+show_results:
434+ ; Clear screen
435+ ldx #0
436+ lda #CHAR_SPACE
437+clear_for_results:
438+ sta SCREEN,x
439+ sta SCREEN+$100,x
440+ sta SCREEN+$200,x
441+ sta SCREEN+$2E8,x
442+ inx
443+ bne clear_for_results
444+
445+ ; Title
446+ ldx #0
447+draw_results_title:
448+ lda results_title,x
449+ beq draw_results_title_done
450+ sta SCREEN + (4 * 40) + 14,x
451+ lda #1
452+ sta COLRAM + (4 * 40) + 14,x
453+ inx
454+ jmp draw_results_title
455+draw_results_title_done:
456+
457+ ; Score label and value
458+ ldx #0
459+draw_final_score_label:
460+ lda final_score_label,x
461+ beq draw_final_score_done
462+ sta SCREEN + (8 * 40) + 10,x
463+ lda #7
464+ sta COLRAM + (8 * 40) + 10,x
465+ inx
466+ jmp draw_final_score_label
467+draw_final_score_done:
468+ jsr display_final_score
469+
470+ ; Perfects
471+ ldx #0
472+draw_perfects_label:
473+ lda perfects_label,x
474+ beq draw_perfects_done
475+ sta SCREEN + (10 * 40) + 10,x
476+ lda #1
477+ sta COLRAM + (10 * 40) + 10,x
478+ inx
479+ jmp draw_perfects_label
480+draw_perfects_done:
481+ lda perfect_count
482+ jsr display_stat_at_10
483+
484+ ; Goods
485+ ldx #0
486+draw_goods_label:
487+ lda goods_label,x
488+ beq draw_goods_done
489+ sta SCREEN + (11 * 40) + 10,x
490+ lda #7
491+ sta COLRAM + (11 * 40) + 10,x
492+ inx
493+ jmp draw_goods_label
494+draw_goods_done:
495+ lda good_count
496+ jsr display_stat_at_11
497+
498+ ; Misses
499+ ldx #0
500+draw_misses_label:
501+ lda misses_label,x
502+ beq draw_misses_done
503+ sta SCREEN + (12 * 40) + 10,x
504+ lda #2
505+ sta COLRAM + (12 * 40) + 10,x
506+ inx
507+ jmp draw_misses_label
508+draw_misses_done:
509+ lda miss_count
510+ jsr display_stat_at_12
511+
512+ ; Accuracy (simplified: just show hit percentage)
513+ ldx #0
514+draw_accuracy_label:
515+ lda accuracy_label,x
516+ beq draw_accuracy_done
517+ sta SCREEN + (14 * 40) + 10,x
518+ lda #5
519+ sta COLRAM + (14 * 40) + 10,x
520+ inx
521+ jmp draw_accuracy_label
522+draw_accuracy_done:
523+ jsr calculate_accuracy
524+ jsr display_accuracy
525+
526+ ; Press any key message
527+ ldx #0
528+draw_press_key:
529+ lda press_key_text,x
530+ beq draw_press_key_done
531+ sta SCREEN + (18 * 40) + 10,x
532+ lda #11
533+ sta COLRAM + (18 * 40) + 10,x
534+ inx
535+ jmp draw_press_key
536+draw_press_key_done:
537+
538+ ; Green border for victory
539+ lda #5
540+ sta BORDER
541+
542+ rts
543+
544+results_title:
545+ !scr "song complete!"
546+ !byte 0
547+
548+final_score_label:
549+ !scr "final score:"
550+ !byte 0
551+
552+perfects_label:
553+ !scr "perfects:"
554+ !byte 0
555+
556+goods_label:
557+ !scr "goods:"
558+ !byte 0
559+
560+misses_label:
561+ !scr "misses:"
562+ !byte 0
563+
564+accuracy_label:
565+ !scr "accuracy:"
566+ !byte 0
567+
568+press_key_text:
569+ !scr "press any key"
570+ !byte 0
571+
572+; ----------------------------------------------------------------------------
573+; Display Final Score (at row 8)
574+; ----------------------------------------------------------------------------
575+
576+display_final_score:
577+ lda score_lo
578+ sta work_lo
579+ lda score_hi
580+ sta work_hi
581+
582+ ldx #0
583+fs_div_10000:
584+ lda work_lo
585+ sec
586+ sbc #<10000
587+ tay
588+ lda work_hi
589+ sbc #>10000
590+ bcc fs_done_10000
591+ sta work_hi
592+ sty work_lo
593+ inx
594+ jmp fs_div_10000
595+fs_done_10000:
596+ txa
597+ ora #$30
598+ sta SCREEN + (8 * 40) + 23
599+
600+ ldx #0
601+fs_div_1000:
602+ lda work_lo
603+ sec
604+ sbc #<1000
605+ tay
606+ lda work_hi
607+ sbc #>1000
608+ bcc fs_done_1000
609+ sta work_hi
610+ sty work_lo
611+ inx
612+ jmp fs_div_1000
613+fs_done_1000:
614+ txa
615+ ora #$30
616+ sta SCREEN + (8 * 40) + 24
617+
618+ ldx #0
619+fs_div_100:
620+ lda work_lo
621+ sec
622+ sbc #100
623+ bcc fs_done_100
624+ sta work_lo
625+ inx
626+ jmp fs_div_100
627+fs_done_100:
628+ txa
629+ ora #$30
630+ sta SCREEN + (8 * 40) + 25
631+
632+ ldx #0
633+fs_div_10:
634+ lda work_lo
635+ sec
636+ sbc #10
637+ bcc fs_done_10
638+ sta work_lo
639+ inx
640+ jmp fs_div_10
641+fs_done_10:
642+ txa
643+ ora #$30
644+ sta SCREEN + (8 * 40) + 26
645+
646+ lda work_lo
647+ ora #$30
648+ sta SCREEN + (8 * 40) + 27
649+
650+ lda #7
651+ sta COLRAM + (8 * 40) + 23
652+ sta COLRAM + (8 * 40) + 24
653+ sta COLRAM + (8 * 40) + 25
654+ sta COLRAM + (8 * 40) + 26
655+ sta COLRAM + (8 * 40) + 27
656+
657+ rts
658+
659+; ----------------------------------------------------------------------------
660+; Display Stat (2-digit) at various rows
661+; ----------------------------------------------------------------------------
662+
663+display_stat_at_10:
664+ ldx #0
665+stat10_div:
666+ cmp #10
667+ bcc stat10_done
668+ sec
669+ sbc #10
670+ inx
671+ jmp stat10_div
672+stat10_done:
673+ pha
674+ txa
675+ ora #$30
676+ sta SCREEN + (10 * 40) + 23
677+ pla
678+ ora #$30
679+ sta SCREEN + (10 * 40) + 24
680+ lda #1
681+ sta COLRAM + (10 * 40) + 23
682+ sta COLRAM + (10 * 40) + 24
683+ rts
684+
685+display_stat_at_11:
686+ ldx #0
687+stat11_div:
688+ cmp #10
689+ bcc stat11_done
690+ sec
691+ sbc #10
692+ inx
693+ jmp stat11_div
694+stat11_done:
695+ pha
696+ txa
697+ ora #$30
698+ sta SCREEN + (11 * 40) + 23
699+ pla
700+ ora #$30
701+ sta SCREEN + (11 * 40) + 24
702+ lda #7
703+ sta COLRAM + (11 * 40) + 23
704+ sta COLRAM + (11 * 40) + 24
705+ rts
706+
707+display_stat_at_12:
708+ ldx #0
709+stat12_div:
710+ cmp #10
711+ bcc stat12_done
712+ sec
713+ sbc #10
714+ inx
715+ jmp stat12_div
716+stat12_done:
717+ pha
718+ txa
719+ ora #$30
720+ sta SCREEN + (12 * 40) + 23
721+ pla
722+ ora #$30
723+ sta SCREEN + (12 * 40) + 24
724+ lda #2
725+ sta COLRAM + (12 * 40) + 23
726+ sta COLRAM + (12 * 40) + 24
727+ rts
728+
729+; ----------------------------------------------------------------------------
730+; Calculate Accuracy - (perfects + goods) * 100 / total_notes
731+; ----------------------------------------------------------------------------
732+
733+calculate_accuracy:
734+ ; Total hits = perfects + goods
735+ lda perfect_count
736+ clc
737+ adc good_count
738+ sta total_hits
739+
740+ ; Total notes = hits + misses
741+ clc
742+ adc miss_count
743+ sta total_notes
744+
745+ ; Avoid division by zero
746+ beq accuracy_zero
747+
748+ ; Simple percentage: (hits * 100) / total
749+ ; We'll approximate with (hits * 128) / total then scale
750+ ; Or simpler: multiply hits by 100, divide by total
751+
752+ lda total_hits
753+ sta dividend_lo
754+ lda #0
755+ sta dividend_hi
756+
757+ ; Multiply by 100
758+ ldx #100
759+mult_loop:
760+ dex
761+ beq mult_done
762+ lda dividend_lo
763+ clc
764+ adc total_hits
765+ sta dividend_lo
766+ lda dividend_hi
767+ adc #0
768+ sta dividend_hi
769+ jmp mult_loop
770+mult_done:
771+
772+ ; Divide by total_notes
773+ lda #0
774+ sta accuracy
775+div_loop:
776+ lda dividend_lo
777+ sec
778+ sbc total_notes
779+ tay
780+ lda dividend_hi
781+ sbc #0
782+ bcc div_done
783+ sta dividend_hi
784+ sty dividend_lo
785+ inc accuracy
786+ jmp div_loop
787+div_done:
788+ rts
789+
790+accuracy_zero:
791+ lda #0
792+ sta accuracy
793+ rts
794+
795+total_hits: !byte 0
796+total_notes: !byte 0
797+dividend_lo: !byte 0
798+dividend_hi: !byte 0
799+accuracy: !byte 0
800+
801+; ----------------------------------------------------------------------------
802+; Display Accuracy (3 digits + %)
803+; ----------------------------------------------------------------------------
804+
805+display_accuracy:
806+ lda accuracy
807+
808+ ; Hundreds
809+ ldx #0
810+acc_div_100:
811+ cmp #100
812+ bcc acc_done_100
813+ sec
814+ sbc #100
815+ inx
816+ jmp acc_div_100
817+acc_done_100:
818+ pha
819+ txa
820+ ora #$30
821+ sta SCREEN + (14 * 40) + 23
822+ pla
823+
824+ ; Tens
825+ ldx #0
826+acc_div_10:
827+ cmp #10
828+ bcc acc_done_10
829+ sec
830+ sbc #10
831+ inx
832+ jmp acc_div_10
833+acc_done_10:
834+ pha
835+ txa
836+ ora #$30
837+ sta SCREEN + (14 * 40) + 24
838+ pla
839+
840+ ; Ones
841+ ora #$30
842+ sta SCREEN + (14 * 40) + 25
843+
844+ ; Percent sign
845+ lda #$25 ; %
846+ sta SCREEN + (14 * 40) + 26
847+
848+ lda #5
849+ sta COLRAM + (14 * 40) + 23
850+ sta COLRAM + (14 * 40) + 24
851+ sta COLRAM + (14 * 40) + 25
852+ sta COLRAM + (14 * 40) + 26
853+
854+ rts
312855
313856 ; ----------------------------------------------------------------------------
314857 ; Copy Character Set from ROM to RAM
...
442985 sta score_lo
443986 sta score_hi
444987 sta miss_count
988+ sta perfect_count
989+ sta good_count
445990 jsr display_score
446991 jsr display_misses
447992 rts
...
4661011 ; ----------------------------------------------------------------------------
4671012
4681013 check_spawn_note:
1014+ lda song_ended
1015+ bne spawn_done_early
1016+
4691017 ldy #0
4701018
4711019 spawn_check_loop:
4721020 lda (song_pos),y
4731021 cmp #$FF
474- beq spawn_done ; Don't auto-restart here
1022+ beq spawn_song_end
4751023
4761024 cmp beat_count
4771025 beq spawn_match
478- bcs spawn_done
1026+ bcs spawn_done_early
4791027
4801028 jmp spawn_advance
4811029
...
5021050 sta song_pos_hi
5031051 jmp spawn_check_loop
5041052
505-spawn_done:
1053+spawn_song_end:
1054+ lda #1
1055+ sta song_ended
1056+
1057+spawn_done_early:
5061058 rts
5071059
5081060 ; ----------------------------------------------------------------------------
...
6311183 lda health
6321184 bne not_game_over
6331185
634- lda #1
635- sta game_over
1186+ lda #STATE_GAMEOVER
1187+ sta game_state
6361188 jsr show_game_over
6371189
6381190 not_game_over:
...
13221874 rts
13231875
13241876 ; ----------------------------------------------------------------------------
1325-; Award Points
1877+; Award Points - Now tracks perfects and goods separately
13261878 ; ----------------------------------------------------------------------------
13271879
13281880 award_points:
13291881 lda hit_quality
13301882 cmp #2
13311883 beq award_perfect
1884+
1885+ ; Good hit
1886+ inc good_count
13321887
13331888 lda score_lo
13341889 clc
...
13491904 jmp award_done
13501905
13511906 award_perfect:
1907+ ; Perfect hit
1908+ inc perfect_count
1909+
13521910 lda score_lo
13531911 clc
13541912 adc #PERFECT_SCORE
...
15682126 ; ============================================================================
15692127
15702128 song_data:
1571- ; Bar 1
15722129 !byte 0, 1, $47
15732130 !byte 2, 2, $2C
15742131 !byte 4, 3, $11
15752132
1576- ; Bar 2
15772133 !byte 8, 1, $3B
15782134 !byte 10, 2, $27
15792135 !byte 12, 3, $13
15802136
1581- ; Bar 3
15822137 !byte 16, 1, $35
15832138 !byte 17, 2, $2C
15842139 !byte 18, 1, $3B
15852140 !byte 20, 3, $16
15862141
1587- ; Bar 4
15882142 !byte 24, 1, $47
15892143 !byte 26, 2, $35
15902144 !byte 28, 3, $11
15912145
1592- ; Bar 5
15932146 !byte 32, 2, $2F
15942147 !byte 34, 1, $4F
15952148 !byte 36, 3, $17
15962149
1597- ; Bar 6
15982150 !byte 40, 1, $58
15992151 !byte 42, 2, $2C
16002152 !byte 44, 3, $11
16012153 !byte 46, 2, $27
16022154
1603- ; Bar 7
16042155 !byte 48, 1, $6A
16052156 !byte 49, 2, $35
16062157 !byte 50, 1, $58
16072158 !byte 52, 3, $1A
16082159 !byte 54, 2, $2F
16092160
1610- ; Bar 8
16112161 !byte 56, 1, $47
16122162 !byte 58, 2, $2C
16132163 !byte 60, 3, $11
...
16322182 ; Game Variables
16332183 ; ----------------------------------------------------------------------------
16342184
1635-score_lo: !byte 0
1636-score_hi: !byte 0
1637-miss_count: !byte 0
1638-health: !byte 0
2185+score_lo: !byte 0
2186+score_hi: !byte 0
2187+miss_count: !byte 0
2188+perfect_count: !byte 0
2189+good_count: !byte 0
2190+health: !byte 0
16392191