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

Combo System

Track consecutive hits. Multiply your score. Break the combo on miss.

19% of SID Symphony

Consistency pays off. This unit adds a combo system that rewards consecutive hits.

Hit notes in succession and your combo climbs. Higher combos unlock score multipliers: 2x at 10 hits, 3x at 25, 4x at 50. Miss a single note and the combo breaks, resetting to zero. The risk/reward creates tension - that 49-hit combo could become 4x on the next hit, or crash to nothing.

Run It

Assemble and run:

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

Unit 12 Screenshot

The “COMBO: 000 1x” display shows your current combo count and multiplier. Build a combo and watch both the number and colour change as you reach new tiers.

Combo system — consecutive hits multiply your score

The Combo System

The combo tracks consecutive hits without misses:

; ----------------------------------------------------------------------------
; Combo System - Track consecutive hits and calculate multiplier
; ----------------------------------------------------------------------------

; Combo settings
COMBO_TIER_2  = 10              ; 2x multiplier at 10 combo
COMBO_TIER_3  = 25              ; 3x multiplier at 25 combo
COMBO_TIER_4  = 50              ; 4x multiplier at 50 combo

; Variables
combo:        !byte 0           ; Current consecutive hits
max_combo:    !byte 0           ; Best combo this song

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

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

; ----------------------------------------------------------------------------
; Get Multiplier - Returns 1-4 based on current combo
; ----------------------------------------------------------------------------

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

            ; Default: 1x
            lda #1
            rts

mult_2x:
            lda #2
            rts

mult_3x:
            lda #3
            rts

mult_4x:
            lda #4
            rts

; ----------------------------------------------------------------------------
; Increment Combo - Called on every successful hit
; ----------------------------------------------------------------------------

increment_combo:
            inc combo

            ; Update max combo if this is the best
            lda combo
            cmp max_combo
            bcc combo_not_max
            sta max_combo
combo_not_max:

            jsr display_combo
            rts

; ----------------------------------------------------------------------------
; Break Combo - Called on every miss
; ----------------------------------------------------------------------------

break_combo:
            lda combo
            beq combo_already_zero

            ; Reset combo to zero
            lda #0
            sta combo
            jsr display_combo

combo_already_zero:
            rts

Two variables do the work: combo for current consecutive hits, max_combo for the best this song. The multiplier calculation uses simple comparisons - check if combo exceeds each threshold, return the appropriate tier.

Multiplier Tiers

The tiers reward sustained accuracy:

ComboMultiplierWhy
0-91xBaseline - no bonus yet
10-242xGetting warmed up
25-493xIn the zone
50+4xMaximum - perfect consistency

A perfect hit normally scores 100 points. At 4x multiplier, that same hit scores 400. Over a full song, the difference between 1x and 4x is massive.

Applying the Multiplier

Multiplying on the 6502 requires repeated addition:

; ----------------------------------------------------------------------------
; Apply Multiplier - Multiply base score by current combo multiplier
; ----------------------------------------------------------------------------

; Input: A = base score (e.g., 100 for perfect, 50 for good)
; Output: score_add_lo/hi = base score * multiplier

apply_multiplier:
            sta base_score
            jsr get_multiplier
            sta current_mult

            ; Start with base score
            lda base_score
            sta score_add_lo
            lda #0
            sta score_add_hi

            ; If multiplier is 1, we're done
            lda current_mult
            cmp #1
            beq mult_done_apply

            ; Multiply by adding base_score (mult-1) more times
            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

; ----------------------------------------------------------------------------
; Usage in award_points
; ----------------------------------------------------------------------------

award_points:
            ; Increment combo first (combo builds BEFORE score)
            jsr increment_combo

            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit: 50 * multiplier
            lda #GOOD_SCORE             ; 50
            jsr apply_multiplier        ; Returns in score_add_lo/hi
            jsr add_score
            ; ...

award_perfect:
            ; Perfect hit: 100 * multiplier
            lda #PERFECT_SCORE          ; 100
            jsr apply_multiplier        ; Returns in score_add_lo/hi
            jsr add_score
            ; ...

The routine stores the base score, gets the multiplier, then adds the base score that many times. For 3x, it adds the base twice (starting with one copy already in place). The 16-bit result handles scores up to 65535.

Combo Display

The display uses colour to communicate multiplier tier:

; ----------------------------------------------------------------------------
; Display Combo - Show combo count and multiplier with colour feedback
; ----------------------------------------------------------------------------

COMBO_ROW = 2                   ; Second row of screen

display_combo:
            ; Draw "COMBO:" label
            ldx #0
draw_combo_label:
            lda combo_label,x
            beq draw_combo_value
            sta SCREEN + (COMBO_ROW * 40) + 12,x
            lda #11             ; Grey for label
            sta COLRAM + (COMBO_ROW * 40) + 12,x
            inx
            jmp draw_combo_label

draw_combo_value:
            ; Display 3-digit combo value
            lda combo

            ; Hundreds digit
            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            ; Convert to screen code
            sta SCREEN + (COMBO_ROW * 40) + 18
            pla

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

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

            ; Colour based on multiplier tier
            jsr get_multiplier
            cmp #4
            beq combo_col_4x
            cmp #3
            beq combo_col_3x
            cmp #2
            beq combo_col_2x

            lda #11             ; 1x = grey (no bonus)
            jmp set_combo_col

combo_col_2x:
            lda #7              ; 2x = yellow
            jmp set_combo_col

combo_col_3x:
            lda #5              ; 3x = green
            jmp set_combo_col

combo_col_4x:
            lda #1              ; 4x = white (maximum!)

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

            ; Display multiplier (e.g., "2x")
            jsr get_multiplier
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 22
            lda #$18            ; 'x' character
            sta SCREEN + (COMBO_ROW * 40) + 23

            rts

combo_label:
            !scr "combo:"
            !byte 0

Grey means no bonus (1x). Yellow signals 2x. Green shows 3x. White indicates maximum 4x. Players instantly know their multiplier state without reading numbers.

Breaking the Combo

Missing breaks the combo dramatically:

handle_miss:
            inc miss_count
            jsr play_miss_sound
            ; ... visual feedback ...

            ; Break the combo!
            jsr break_combo

            rts

A single line - jsr break_combo - undoes potentially dozens of consecutive hits. This creates the tension that makes combos meaningful.

Max Combo on Results

The results screen now shows max combo:

        SONG COMPLETE!

        FINAL SCORE: 05200
        PERFECTS:    18
        GOODS:       08
        MISSES:      02
        MAX COMBO:   31
        ACCURACY:    092%

Max combo tells players how close they came to a perfect run. A score of 5000 with max combo 10 plays differently than 5000 with max combo 45.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 12: Combo System
; ============================================================================
; Consecutive hits build a combo. Higher combos increase the score multiplier.
; Miss a note and the combo resets to zero. Consistency is rewarded.
;
; 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
COMBO_COL   = 13                ; Yellow for combo display

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

PERFECT_SCORE = 100
GOOD_SCORE    = 50

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

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

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

; Combo display position
COMBO_ROW   = 2
COMBO_COL_POS = 17

; 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_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_combo
            jsr init_song

            lda #STATE_PLAYING
            sta game_state

            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:
            lda #$00
            sta CIA1_PRA
            lda CIA1_PRB
            cmp #$FF
            beq results_wait

            jsr init_game

results_wait:
            lda #$FF
            sta CIA1_PRA
            rts

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

update_gameover:
            lda #$00
            sta CIA1_PRA
            lda CIA1_PRB
            cmp #$FF
            beq gameover_wait

            jsr init_game

gameover_wait:
            lda #$FF
            sta CIA1_PRA
            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 - Returns multiplier (1-4) based on combo
; ----------------------------------------------------------------------------

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

            ; Default: 1x
            lda #1
            rts

mult_2x:
            lda #2
            rts

mult_3x:
            lda #3
            rts

mult_4x:
            lda #4
            rts

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

display_combo:
            ; Draw "COMBO:" label
            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:
            ; Display 3-digit combo value
            lda combo

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

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

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

            ; Colour based on multiplier tier
            jsr get_multiplier
            cmp #4
            beq combo_col_4x
            cmp #3
            beq combo_col_3x
            cmp #2
            beq combo_col_2x

            ; 1x = grey
            lda #11
            jmp set_combo_col

combo_col_2x:
            lda #7              ; Yellow
            jmp set_combo_col

combo_col_3x:
            lda #5              ; Green
            jmp set_combo_col

combo_col_4x:
            lda #1              ; White (best)

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

            ; Display multiplier
            jsr get_multiplier
            ora #$30
            sta SCREEN + (COMBO_ROW * 40) + 22
            lda #$18            ; 'x' character
            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

            ; Update max combo if needed
            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

            ; Combo was active - show break
            lda #0
            sta combo
            jsr display_combo

combo_already_zero:
            rts

; ----------------------------------------------------------------------------
; 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 + (3 * 40) + 14,x
            lda #1
            sta COLRAM + (3 * 40) + 14,x
            inx
            jmp draw_results_title
draw_results_title_done:

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

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

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

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

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

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

            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_key_text:
            !scr "press any key"
            !byte 0

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

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 Stat (2-digit) at various rows
; ----------------------------------------------------------------------------

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 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 - Now breaks combo
; ----------------------------------------------------------------------------

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

            ; Break the combo!
            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_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 uses combo multiplier
; ----------------------------------------------------------------------------

award_points:
            ; Increment combo first
            jsr increment_combo

            ; Get base score
            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit: 50 * multiplier
            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:
            ; Perfect hit: 100 * multiplier
            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 - A = base score, returns multiplied score in score_add_lo/hi
; ----------------------------------------------------------------------------

apply_multiplier:
            sta base_score
            jsr get_multiplier
            sta current_mult

            ; Start with base score
            lda base_score
            sta score_add_lo
            lda #0
            sta score_add_hi

            ; If multiplier is 1, we're done
            lda current_mult
            cmp #1
            beq mult_done_apply

            ; Multiply by adding base_score (mult-1) more times
            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 - Adds score_add_lo/hi to 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 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
combo:        !byte 0
max_combo:    !byte 0

Try This: Combo Sound

Add an ascending tone as combo builds:

play_combo_sound:
            lda combo
            lsr                 ; Divide by 2
            clc
            adc #$10            ; Base frequency
            sta SID_V1_FREQ_HI
            ; ... trigger note ...

Higher combos produce higher pitches.

Try This: Combo Milestones

Flash the screen when reaching new tiers:

increment_combo:
            inc combo
            lda combo
            cmp #COMBO_TIER_2
            beq combo_milestone
            cmp #COMBO_TIER_3
            beq combo_milestone
            cmp #COMBO_TIER_4
            beq combo_milestone
            jmp no_milestone

combo_milestone:
            ; Flash border yellow briefly
            lda #7
            sta BORDER
            lda #8
            sta border_flash
no_milestone:
            ; ... rest of routine ...

Celebrate reaching 10, 25, and 50.

What You’ve Learnt

  • Combo tracking - Increment on hit, reset on miss
  • Multiplier tiers - Thresholds that increase rewards
  • 8-bit multiplication - Repeated addition for score scaling
  • Colour feedback - Visual encoding of game state
  • Risk/reward design - Tension between safety and bonus

What’s Next

In Unit 13, we’ll add a title screen. The game will start with a proper presentation, wait for player input, then begin play.

What Changed

Unit 11 → Unit 12
+381-99
11 ; ============================================================================
2-; SID SYMPHONY - Unit 11: Song End and Results
2+; SID SYMPHONY - Unit 12: Combo System
33 ; ============================================================================
4-; Detect song completion. Display results with score breakdown. The game
5-; has a proper ending now - victory or defeat, then replay.
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.
66 ;
77 ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
88 ; ============================================================================
...
4848
4949 HEALTH_COL = 5
5050 PROGRESS_COL = 3
51+COMBO_COL = 13 ; Yellow for combo display
5152
5253 ; ============================================================================
5354 ; SCORING SETTINGS
...
5556
5657 PERFECT_SCORE = 100
5758 GOOD_SCORE = 50
59+
60+; ============================================================================
61+; COMBO SETTINGS
62+; ============================================================================
63+
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
5867
5968 ; ============================================================================
6069 ; HEALTH SETTINGS
...
138147 ; HUD positions
139148 HEALTH_ROW = 23
140149 PROGRESS_ROW = 24
150+
151+; Combo display position
152+COMBO_ROW = 2
153+COMBO_COL_POS = 17
141154
142155 ; Hit zone
143156 HIT_ZONE_COLUMN = 3
...
156169
157170 ; Timing
158171 FRAMES_PER_BEAT = 25
159-END_DELAY_FRAMES = 75 ; Wait after last note before results
172+END_DELAY_FRAMES = 75
160173
161174 ; Zero page
162175 ZP_PTR = $FB
...
175188 game_state = $0B
176189 hit_note_freq = $0C
177190 song_beat = $0D
178-song_ended = $0E ; Flag: song data exhausted
179-end_delay = $0F ; Countdown after last note
191+song_ended = $0E
192+end_delay = $0F
180193
181194 ; ----------------------------------------------------------------------------
182195 ; BASIC Stub
...
236249 jsr init_notes
237250 jsr init_score
238251 jsr init_health
252+ jsr init_combo
239253 jsr init_song
240254
241255 lda #STATE_PLAYING
242256 sta game_state
257+
258+ rts
259+
260+; ----------------------------------------------------------------------------
261+; Initialize Combo
262+; ----------------------------------------------------------------------------
243263
264+init_combo:
265+ lda #0
266+ sta combo
267+ sta max_combo
268+ jsr display_combo
244269 rts
245270
246271 ; ----------------------------------------------------------------------------
...
263288 jsr reset_track_colours
264289 jsr update_border_flash
265290 jsr check_keys
266-
267- ; Check for song end
268291 jsr check_song_end
269292
270293 rts
...
274297 ; ----------------------------------------------------------------------------
275298
276299 update_results:
277- ; Wait for any key to restart
278300 lda #$00
279301 sta CIA1_PRA
280302 lda CIA1_PRB
281303 cmp #$FF
282304 beq results_wait
283305
284- ; Key pressed - restart game
285306 jsr init_game
286307
287308 results_wait:
...
294315 ; ----------------------------------------------------------------------------
295316
296317 update_gameover:
297- ; Wait for any key to restart
298318 lda #$00
299319 sta CIA1_PRA
300320 lda CIA1_PRB
301321 cmp #$FF
302322 beq gameover_wait
303323
304- ; Key pressed - restart game
305324 jsr init_game
306325
307326 gameover_wait:
...
314333 ; ----------------------------------------------------------------------------
315334
316335 check_song_end:
317- ; First check if song data exhausted
318336 lda song_ended
319337 beq song_not_ended
320338
321- ; Song data done - wait for all notes to clear
322339 ldx #0
323340 check_notes_clear:
324341 lda note_track,x
...
327344 cpx #MAX_NOTES
328345 bne check_notes_clear
329346
330- ; All notes cleared - count down delay
331347 dec end_delay
332348 bne song_not_ended
333349
334- ; Delay done - show results
335350 jsr show_results
336351 lda #STATE_RESULTS
337352 sta game_state
...
378393 cmp #SONG_LENGTH
379394 bcc song_continues
380395
381- ; Song reached end
382396 lda #1
383397 sta song_ended
384398
...
425439 rts
426440
427441 temp_progress: !byte 0
442+
443+; ----------------------------------------------------------------------------
444+; Get Multiplier - Returns multiplier (1-4) based on combo
445+; ----------------------------------------------------------------------------
446+
447+get_multiplier:
448+ lda combo
449+ cmp #COMBO_TIER_4
450+ bcs mult_4x
451+ cmp #COMBO_TIER_3
452+ bcs mult_3x
453+ cmp #COMBO_TIER_2
454+ bcs mult_2x
455+
456+ ; Default: 1x
457+ lda #1
458+ rts
459+
460+mult_2x:
461+ lda #2
462+ rts
463+
464+mult_3x:
465+ lda #3
466+ rts
467+
468+mult_4x:
469+ lda #4
470+ rts
471+
472+; ----------------------------------------------------------------------------
473+; Display Combo
474+; ----------------------------------------------------------------------------
475+
476+display_combo:
477+ ; Draw "COMBO:" label
478+ ldx #0
479+draw_combo_label:
480+ lda combo_label,x
481+ beq draw_combo_value
482+ sta SCREEN + (COMBO_ROW * 40) + 12,x
483+ lda #11
484+ sta COLRAM + (COMBO_ROW * 40) + 12,x
485+ inx
486+ jmp draw_combo_label
487+
488+draw_combo_value:
489+ ; Display 3-digit combo value
490+ lda combo
491+
492+ ; Hundreds
493+ ldx #0
494+combo_div_100:
495+ cmp #100
496+ bcc combo_done_100
497+ sec
498+ sbc #100
499+ inx
500+ jmp combo_div_100
501+combo_done_100:
502+ pha
503+ txa
504+ ora #$30
505+ sta SCREEN + (COMBO_ROW * 40) + 18
506+ pla
507+
508+ ; Tens
509+ ldx #0
510+combo_div_10:
511+ cmp #10
512+ bcc combo_done_10
513+ sec
514+ sbc #10
515+ inx
516+ jmp combo_div_10
517+combo_done_10:
518+ pha
519+ txa
520+ ora #$30
521+ sta SCREEN + (COMBO_ROW * 40) + 19
522+ pla
523+
524+ ; Ones
525+ ora #$30
526+ sta SCREEN + (COMBO_ROW * 40) + 20
527+
528+ ; Colour based on multiplier tier
529+ jsr get_multiplier
530+ cmp #4
531+ beq combo_col_4x
532+ cmp #3
533+ beq combo_col_3x
534+ cmp #2
535+ beq combo_col_2x
536+
537+ ; 1x = grey
538+ lda #11
539+ jmp set_combo_col
540+
541+combo_col_2x:
542+ lda #7 ; Yellow
543+ jmp set_combo_col
544+
545+combo_col_3x:
546+ lda #5 ; Green
547+ jmp set_combo_col
548+
549+combo_col_4x:
550+ lda #1 ; White (best)
551+
552+set_combo_col:
553+ sta COLRAM + (COMBO_ROW * 40) + 18
554+ sta COLRAM + (COMBO_ROW * 40) + 19
555+ sta COLRAM + (COMBO_ROW * 40) + 20
556+
557+ ; Display multiplier
558+ jsr get_multiplier
559+ ora #$30
560+ sta SCREEN + (COMBO_ROW * 40) + 22
561+ lda #$18 ; 'x' character
562+ sta SCREEN + (COMBO_ROW * 40) + 23
563+
564+ jsr get_multiplier
565+ cmp #4
566+ beq mult_col_4x
567+ cmp #3
568+ beq mult_col_3x
569+ cmp #2
570+ beq mult_col_2x
571+
572+ lda #11
573+ jmp set_mult_col
574+
575+mult_col_2x:
576+ lda #7
577+ jmp set_mult_col
578+
579+mult_col_3x:
580+ lda #5
581+ jmp set_mult_col
582+
583+mult_col_4x:
584+ lda #1
585+
586+set_mult_col:
587+ sta COLRAM + (COMBO_ROW * 40) + 22
588+ sta COLRAM + (COMBO_ROW * 40) + 23
589+
590+ rts
591+
592+combo_label:
593+ !scr "combo:"
594+ !byte 0
595+
596+; ----------------------------------------------------------------------------
597+; Increment Combo
598+; ----------------------------------------------------------------------------
599+
600+increment_combo:
601+ inc combo
602+
603+ ; Update max combo if needed
604+ lda combo
605+ cmp max_combo
606+ bcc combo_not_max
607+ sta max_combo
608+combo_not_max:
609+
610+ jsr display_combo
611+ rts
612+
613+; ----------------------------------------------------------------------------
614+; Break Combo
615+; ----------------------------------------------------------------------------
616+
617+break_combo:
618+ lda combo
619+ beq combo_already_zero
620+
621+ ; Combo was active - show break
622+ lda #0
623+ sta combo
624+ jsr display_combo
625+
626+combo_already_zero:
627+ rts
428628
429629 ; ----------------------------------------------------------------------------
430630 ; Show Results Screen
...
447647 draw_results_title:
448648 lda results_title,x
449649 beq draw_results_title_done
450- sta SCREEN + (4 * 40) + 14,x
650+ sta SCREEN + (3 * 40) + 14,x
451651 lda #1
452- sta COLRAM + (4 * 40) + 14,x
652+ sta COLRAM + (3 * 40) + 14,x
453653 inx
454654 jmp draw_results_title
455655 draw_results_title_done:
456656
457- ; Score label and value
657+ ; Score
458658 ldx #0
459659 draw_final_score_label:
460660 lda final_score_label,x
461661 beq draw_final_score_done
462- sta SCREEN + (8 * 40) + 10,x
662+ sta SCREEN + (6 * 40) + 10,x
463663 lda #7
464- sta COLRAM + (8 * 40) + 10,x
664+ sta COLRAM + (6 * 40) + 10,x
465665 inx
466666 jmp draw_final_score_label
467667 draw_final_score_done:
...
472672 draw_perfects_label:
473673 lda perfects_label,x
474674 beq draw_perfects_done
475- sta SCREEN + (10 * 40) + 10,x
675+ sta SCREEN + (8 * 40) + 10,x
476676 lda #1
477- sta COLRAM + (10 * 40) + 10,x
677+ sta COLRAM + (8 * 40) + 10,x
478678 inx
479679 jmp draw_perfects_label
480680 draw_perfects_done:
481681 lda perfect_count
482- jsr display_stat_at_10
682+ jsr display_stat_at_8
483683
484684 ; Goods
485685 ldx #0
486686 draw_goods_label:
487687 lda goods_label,x
488688 beq draw_goods_done
489- sta SCREEN + (11 * 40) + 10,x
689+ sta SCREEN + (9 * 40) + 10,x
490690 lda #7
491- sta COLRAM + (11 * 40) + 10,x
691+ sta COLRAM + (9 * 40) + 10,x
492692 inx
493693 jmp draw_goods_label
494694 draw_goods_done:
495695 lda good_count
496- jsr display_stat_at_11
696+ jsr display_stat_at_9
497697
498698 ; Misses
499699 ldx #0
500700 draw_misses_label:
501701 lda misses_label,x
502702 beq draw_misses_done
503- sta SCREEN + (12 * 40) + 10,x
703+ sta SCREEN + (10 * 40) + 10,x
504704 lda #2
505- sta COLRAM + (12 * 40) + 10,x
705+ sta COLRAM + (10 * 40) + 10,x
506706 inx
507707 jmp draw_misses_label
508708 draw_misses_done:
509709 lda miss_count
710+ jsr display_stat_at_10
711+
712+ ; Max Combo
713+ ldx #0
714+draw_max_combo_label:
715+ lda max_combo_label,x
716+ beq draw_max_combo_done
717+ sta SCREEN + (12 * 40) + 10,x
718+ lda #13
719+ sta COLRAM + (12 * 40) + 10,x
720+ inx
721+ jmp draw_max_combo_label
722+draw_max_combo_done:
723+ lda max_combo
510724 jsr display_stat_at_12
511725
512- ; Accuracy (simplified: just show hit percentage)
726+ ; Accuracy
513727 ldx #0
514728 draw_accuracy_label:
515729 lda accuracy_label,x
...
523737 jsr calculate_accuracy
524738 jsr display_accuracy
525739
526- ; Press any key message
740+ ; Press any key
527741 ldx #0
528742 draw_press_key:
529743 lda press_key_text,x
...
535749 jmp draw_press_key
536750 draw_press_key_done:
537751
538- ; Green border for victory
539752 lda #5
540753 sta BORDER
541754
...
559772
560773 misses_label:
561774 !scr "misses:"
775+ !byte 0
776+
777+max_combo_label:
778+ !scr "max combo:"
562779 !byte 0
563780
564781 accuracy_label:
...
570787 !byte 0
571788
572789 ; ----------------------------------------------------------------------------
573-; Display Final Score (at row 8)
790+; Display Final Score (at row 6)
574791 ; ----------------------------------------------------------------------------
575792
576793 display_final_score:
...
595812 fs_done_10000:
596813 txa
597814 ora #$30
598- sta SCREEN + (8 * 40) + 23
815+ sta SCREEN + (6 * 40) + 23
599816
600817 ldx #0
601818 fs_div_1000:
...
613830 fs_done_1000:
614831 txa
615832 ora #$30
616- sta SCREEN + (8 * 40) + 24
833+ sta SCREEN + (6 * 40) + 24
617834
618835 ldx #0
619836 fs_div_100:
...
627844 fs_done_100:
628845 txa
629846 ora #$30
630- sta SCREEN + (8 * 40) + 25
847+ sta SCREEN + (6 * 40) + 25
631848
632849 ldx #0
633850 fs_div_10:
...
641858 fs_done_10:
642859 txa
643860 ora #$30
644- sta SCREEN + (8 * 40) + 26
861+ sta SCREEN + (6 * 40) + 26
645862
646863 lda work_lo
647864 ora #$30
648- sta SCREEN + (8 * 40) + 27
865+ sta SCREEN + (6 * 40) + 27
649866
650867 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
868+ sta COLRAM + (6 * 40) + 23
869+ sta COLRAM + (6 * 40) + 24
870+ sta COLRAM + (6 * 40) + 25
871+ sta COLRAM + (6 * 40) + 26
872+ sta COLRAM + (6 * 40) + 27
656873
657874 rts
658875
...
660877 ; Display Stat (2-digit) at various rows
661878 ; ----------------------------------------------------------------------------
662879
663-display_stat_at_10:
880+display_stat_at_8:
664881 ldx #0
665-stat10_div:
882+stat8_div:
666883 cmp #10
667- bcc stat10_done
884+ bcc stat8_done
668885 sec
669886 sbc #10
670887 inx
671- jmp stat10_div
672-stat10_done:
888+ jmp stat8_div
889+stat8_done:
673890 pha
674891 txa
675892 ora #$30
676- sta SCREEN + (10 * 40) + 23
893+ sta SCREEN + (8 * 40) + 23
677894 pla
678895 ora #$30
679- sta SCREEN + (10 * 40) + 24
896+ sta SCREEN + (8 * 40) + 24
680897 lda #1
681- sta COLRAM + (10 * 40) + 23
682- sta COLRAM + (10 * 40) + 24
898+ sta COLRAM + (8 * 40) + 23
899+ sta COLRAM + (8 * 40) + 24
683900 rts
684901
685-display_stat_at_11:
902+display_stat_at_9:
686903 ldx #0
687-stat11_div:
904+stat9_div:
688905 cmp #10
689- bcc stat11_done
906+ bcc stat9_done
690907 sec
691908 sbc #10
692909 inx
693- jmp stat11_div
694-stat11_done:
910+ jmp stat9_div
911+stat9_done:
695912 pha
696913 txa
697914 ora #$30
698- sta SCREEN + (11 * 40) + 23
915+ sta SCREEN + (9 * 40) + 23
699916 pla
700917 ora #$30
701- sta SCREEN + (11 * 40) + 24
918+ sta SCREEN + (9 * 40) + 24
702919 lda #7
703- sta COLRAM + (11 * 40) + 23
704- sta COLRAM + (11 * 40) + 24
920+ sta COLRAM + (9 * 40) + 23
921+ sta COLRAM + (9 * 40) + 24
922+ rts
923+
924+display_stat_at_10:
925+ ldx #0
926+stat10_div:
927+ cmp #10
928+ bcc stat10_done
929+ sec
930+ sbc #10
931+ inx
932+ jmp stat10_div
933+stat10_done:
934+ pha
935+ txa
936+ ora #$30
937+ sta SCREEN + (10 * 40) + 23
938+ pla
939+ ora #$30
940+ sta SCREEN + (10 * 40) + 24
941+ lda #2
942+ sta COLRAM + (10 * 40) + 23
943+ sta COLRAM + (10 * 40) + 24
705944 rts
706945
707946 display_stat_at_12:
...
721960 pla
722961 ora #$30
723962 sta SCREEN + (12 * 40) + 24
724- lda #2
963+ lda #13
725964 sta COLRAM + (12 * 40) + 23
726965 sta COLRAM + (12 * 40) + 24
727966 rts
728967
729968 ; ----------------------------------------------------------------------------
730-; Calculate Accuracy - (perfects + goods) * 100 / total_notes
969+; Calculate Accuracy
731970 ; ----------------------------------------------------------------------------
732971
733972 calculate_accuracy:
734- ; Total hits = perfects + goods
735973 lda perfect_count
736974 clc
737975 adc good_count
738976 sta total_hits
739977
740- ; Total notes = hits + misses
741978 clc
742979 adc miss_count
743980 sta total_notes
744981
745- ; Avoid division by zero
746982 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
751983
752984 lda total_hits
753985 sta dividend_lo
754986 lda #0
755987 sta dividend_hi
756988
757- ; Multiply by 100
758989 ldx #100
759990 mult_loop:
760991 dex
...
7691000 jmp mult_loop
7701001 mult_done:
7711002
772- ; Divide by total_notes
7731003 lda #0
7741004 sta accuracy
7751005 div_loop:
...
7991029 accuracy: !byte 0
8001030
8011031 ; ----------------------------------------------------------------------------
802-; Display Accuracy (3 digits + %)
1032+; Display Accuracy
8031033 ; ----------------------------------------------------------------------------
8041034
8051035 display_accuracy:
8061036 lda accuracy
8071037
808- ; Hundreds
8091038 ldx #0
8101039 acc_div_100:
8111040 cmp #100
...
8211050 sta SCREEN + (14 * 40) + 23
8221051 pla
8231052
824- ; Tens
8251053 ldx #0
8261054 acc_div_10:
8271055 cmp #10
...
8371065 sta SCREEN + (14 * 40) + 24
8381066 pla
8391067
840- ; Ones
8411068 ora #$30
8421069 sta SCREEN + (14 * 40) + 25
8431070
844- ; Percent sign
845- lda #$25 ; %
1071+ lda #$25
8461072 sta SCREEN + (14 * 40) + 26
8471073
8481074 lda #5
...
11211347 rts
11221348
11231349 ; ----------------------------------------------------------------------------
1124-; Handle Miss
1350+; Handle Miss - Now breaks combo
11251351 ; ----------------------------------------------------------------------------
11261352
11271353 handle_miss:
...
11361362
11371363 jsr display_misses
11381364 jsr decrease_health
1365+
1366+ ; Break the combo!
1367+ jsr break_combo
11391368
11401369 rts
11411370
...
18742103 rts
18752104
18762105 ; ----------------------------------------------------------------------------
1877-; Award Points - Now tracks perfects and goods separately
2106+; Award Points - Now uses combo multiplier
18782107 ; ----------------------------------------------------------------------------
18792108
18802109 award_points:
2110+ ; Increment combo first
2111+ jsr increment_combo
2112+
2113+ ; Get base score
18812114 lda hit_quality
18822115 cmp #2
18832116 beq award_perfect
18842117
1885- ; Good hit
1886- inc good_count
2118+ ; Good hit: 50 * multiplier
2119+ lda #GOOD_SCORE
2120+ jsr apply_multiplier
2121+ jsr add_score
18872122
1888- lda score_lo
1889- clc
1890- adc #GOOD_SCORE
1891- sta score_lo
1892- lda score_hi
1893- adc #0
1894- sta score_hi
2123+ inc good_count
18952124
18962125 lda #GOOD_COL
18972126 sta BORDER
...
19042133 jmp award_done
19052134
19062135 award_perfect:
1907- ; Perfect hit
1908- inc perfect_count
2136+ ; Perfect hit: 100 * multiplier
2137+ lda #PERFECT_SCORE
2138+ jsr apply_multiplier
2139+ jsr add_score
19092140
1910- lda score_lo
1911- clc
1912- adc #PERFECT_SCORE
1913- sta score_lo
1914- lda score_hi
1915- adc #0
1916- sta score_hi
2141+ inc perfect_count
19172142
19182143 lda #PERFECT_COL
19192144 sta BORDER
...
19252150
19262151 award_done:
19272152 jsr display_score
2153+ rts
2154+
2155+; ----------------------------------------------------------------------------
2156+; Apply Multiplier - A = base score, returns multiplied score in score_add_lo/hi
2157+; ----------------------------------------------------------------------------
2158+
2159+apply_multiplier:
2160+ sta base_score
2161+ jsr get_multiplier
2162+ sta current_mult
2163+
2164+ ; Start with base score
2165+ lda base_score
2166+ sta score_add_lo
2167+ lda #0
2168+ sta score_add_hi
2169+
2170+ ; If multiplier is 1, we're done
2171+ lda current_mult
2172+ cmp #1
2173+ beq mult_done_apply
2174+
2175+ ; Multiply by adding base_score (mult-1) more times
2176+ dec current_mult
2177+mult_add_loop:
2178+ lda score_add_lo
2179+ clc
2180+ adc base_score
2181+ sta score_add_lo
2182+ lda score_add_hi
2183+ adc #0
2184+ sta score_add_hi
2185+ dec current_mult
2186+ bne mult_add_loop
2187+
2188+mult_done_apply:
2189+ rts
2190+
2191+base_score: !byte 0
2192+current_mult: !byte 0
2193+score_add_lo: !byte 0
2194+score_add_hi: !byte 0
2195+
2196+; ----------------------------------------------------------------------------
2197+; Add Score - Adds score_add_lo/hi to score
2198+; ----------------------------------------------------------------------------
2199+
2200+add_score:
2201+ lda score_lo
2202+ clc
2203+ adc score_add_lo
2204+ sta score_lo
2205+ lda score_hi
2206+ adc score_add_hi
2207+ sta score_hi
19282208 rts
19292209
19302210 ; ----------------------------------------------------------------------------
...
21882468 perfect_count: !byte 0
21892469 good_count: !byte 0
21902470 health: !byte 0
2471+combo: !byte 0
2472+max_combo: !byte 0
21912473