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

Scoring and Feedback

Points for hits. Visual feedback for success. The game rewards skill.

9% of SID Symphony

Skill deserves reward. Now hits earn points, and perfect timing earns more.

This unit adds scoring and visual feedback. Hit a note in the centre of the hit zone for 100 points. Hit near the edges for 50 points. The border flashes white for perfect hits, yellow for good hits. Players will chase perfects.

Run It

Assemble and run:

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

Unit 6 Screenshot

The score displays at the top left. Watch it climb as you hit notes. Notice the border flash colour - white for perfect, yellow for good.

Scoring — hit notes for points, displayed at the top left

Hit Quality

Not all hits are equal. The hit zone spans columns 2-5, but position matters:

ColumnQualityPoints
3-4Perfect100
2, 5Good50

The centre of the hit zone rewards precision:

; ----------------------------------------------------------------------------
; Check Hit - Find note and determine hit quality
; ----------------------------------------------------------------------------
; Input: key_pressed = track number (1-3)
; Output: Carry set if hit, hit_quality set (1=good, 2=perfect)
;         Carry clear if no 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

            ; HIT! Determine quality based on position
            ; Centre columns (3-4) = perfect, edges (2, 5) = good
            cmp #HIT_ZONE_CENTRE
            bcc hit_good            ; Column 2 = good
            cmp #HIT_ZONE_CENTRE+2
            bcs hit_good            ; Column 5 = good

            ; Perfect hit (columns 3-4)
            lda #2
            sta hit_quality
            jmp hit_found

hit_good:
            ; Good hit (columns 2 or 5)
            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

The comparison logic is simple: if the note’s column is 3 or 4, it’s perfect. Otherwise it’s good. The hit_quality variable stores the result for the scoring routine.

16-Bit Score

A single byte can only count to 255. We need two bytes for scores up to 65,535:

score_lo:   !byte 0             ; Low byte (0-255)
score_hi:   !byte 0             ; High byte (0-255)
; Combined: score_hi * 256 + score_lo

Adding to a 16-bit number requires handling the carry:

; ----------------------------------------------------------------------------
; Award Points - Add score based on hit quality
; ----------------------------------------------------------------------------

award_points:
            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit - add 50 points
            lda score_lo
            clc
            adc #GOOD_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            ; Yellow border flash for good
            lda #GOOD_COL
            sta BORDER
            lda #4              ; Flash for 4 frames
            sta border_flash

            jmp award_done

award_perfect:
            ; Perfect hit - add 100 points
            lda score_lo
            clc
            adc #PERFECT_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            ; White border flash for perfect
            lda #PERFECT_COL
            sta BORDER
            lda #6              ; Flash for 6 frames
            sta border_flash

award_done:
            jsr display_score
            rts

When adding to score_lo, if the result exceeds 255, the carry flag is set. Adding that carry to score_hi handles the overflow automatically.

Displaying the Score

Converting a 16-bit binary number to decimal for display requires division. We use repeated subtraction - not elegant, but clear:

; ----------------------------------------------------------------------------
; Display Score - Convert 16-bit score to decimal and show
; ----------------------------------------------------------------------------

display_score:
            ; Convert score to 5-digit decimal display
            ; Uses simple repeated subtraction method

            ; Copy score to working area
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ; Ten-thousands digit
            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            ; Convert to screen code
            sta SCREEN + 8

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

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

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

            ; Ones digit
            lda work_lo
            ora #$30
            sta SCREEN + 12

            ; Set score digit colours
            lda #7              ; Yellow for score
            sta COLRAM + 8
            sta COLRAM + 9
            sta COLRAM + 10
            sta COLRAM + 11
            sta COLRAM + 12

            rts

; Working variables for division
work_lo:    !byte 0
work_hi:    !byte 0

For each digit, we subtract the place value (10000, 1000, 100, 10) until we can’t subtract any more. The count becomes that digit.

Border Flash

Visual feedback makes hits feel satisfying. The border flash uses a simple counter:

border_flash = $09              ; Frames remaining

update_border_flash:
            lda border_flash
            beq flash_done      ; Zero = no flash active
            dec border_flash
            bne flash_done      ; Still counting down
            ; Flash finished - reset border
            lda #BORDER_COL
            sta BORDER
flash_done:
            rts

Setting border_flash to a non-zero value triggers the flash. Each frame decrements the counter. When it reaches zero, the border returns to black.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 6: Scoring and Feedback
; ============================================================================
; Score points for hitting notes. Perfect timing (centre of hit zone) awards
; more points than good timing (edges). Visual feedback rewards accuracy.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================

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

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

VOICE1_FREQ = $1C               ; High pitch
VOICE2_FREQ = $0E               ; Mid pitch
VOICE3_FREQ = $07               ; Low pitch

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

; Visual Settings
BORDER_COL  = 0                 ; Black border
BG_COL      = 0                 ; Black background

TRACK1_NOTE_COL = 10            ; Light red
TRACK2_NOTE_COL = 13            ; Light green
TRACK3_NOTE_COL = 14            ; Light blue

TRACK_LINE_COL = 11             ; Dark grey
HIT_ZONE_COL = 7                ; Yellow

FLASH1_COL  = 2                 ; Red
FLASH2_COL  = 5                 ; Green
FLASH3_COL  = 6                 ; Blue

HIT_COL     = 1                 ; White - flash on successful hit
PERFECT_COL = 1                 ; White - perfect hit border flash
GOOD_COL    = 7                 ; Yellow - good hit border flash

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

PERFECT_SCORE = 100             ; Points for perfect hit
GOOD_SCORE    = 50              ; Points for good hit

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

HIT_ZONE_MIN = 2                ; Left edge of hit zone
HIT_ZONE_MAX = 5                ; Right edge of hit zone
HIT_ZONE_CENTRE = 3             ; Perfect hit position (and 4)

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

SCREEN      = $0400             ; Screen memory
COLRAM      = $D800             ; Colour RAM
BORDER      = $D020             ; Border colour
BGCOL       = $D021             ; Background colour
CHARPTR     = $D018             ; Character memory pointer

CHARSET     = $3000             ; Custom character set location

; 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

; Hit zone
HIT_ZONE_COLUMN = 3

; Custom character codes
CHAR_NOTE   = 128
CHAR_TRACK  = 129
CHAR_HITZONE = 130
CHAR_SPACE  = 32

; Note settings
MAX_NOTES   = 8
NOTE_SPAWN_COL = 37

; Timing
FRAMES_PER_BEAT = 25

; 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               ; 0=none, 1=good, 2=perfect
border_flash = $09              ; Frames remaining for border flash

; ----------------------------------------------------------------------------
; 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_screen
            jsr init_sid
            jsr init_notes
            jsr init_score

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

            lda #0
            sta frame_count
            sta beat_count
            sta border_flash

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

            inc frame_count
            lda frame_count
            cmp #FRAMES_PER_BEAT
            bcc no_new_beat

            lda #0
            sta frame_count
            jsr check_spawn_note
            inc beat_count

no_new_beat:
            jsr update_notes
            jsr reset_track_colours
            jsr update_border_flash
            jsr check_keys

            jmp main_loop

; ----------------------------------------------------------------------------
; 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:
            ; Character 128: Note (filled chevron pointing left)
            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

            ; Character 129: Track line (centered horizontal line)
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 0
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 1
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 2
            lda #%11111111
            sta CHARSET + (CHAR_TRACK * 8) + 3
            lda #%11111111
            sta CHARSET + (CHAR_TRACK * 8) + 4
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 5
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 6
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 7

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

            rts

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

init_score:
            lda #0
            sta score_lo
            sta score_hi
            jsr display_score
            rts

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

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

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

check_spawn_note:
            ldy #0

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

            cmp beat_count
            beq spawn_match
            bcs spawn_done

            jmp spawn_advance

spawn_match:
            iny
            lda (song_pos),y
            jsr spawn_note
            dey

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

spawn_done:
            rts

spawn_restart_song:
            lda #<song_data
            sta song_pos
            lda #>song_data
            sta song_pos_hi
            lda #0
            sta beat_count
            rts

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

spawn_note:
            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
            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_deactivate

            jsr draw_note
            jmp update_next

update_deactivate:
            lda #0
            sta note_track,x

update_next:
            inx
            cpx #MAX_NOTES
            bne update_loop
            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

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

            lda #CHAR_TRACK
            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:
            ; Draw "SCORE:" label
            ldx #0
draw_score_label:
            lda score_label,x
            beq draw_score_label_done
            sta SCREEN + 1,x
            lda #1              ; White
            sta COLRAM + 1,x
            inx
            bne draw_score_label
draw_score_label_done:

            ; Draw title
            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:

            ; Track labels
            lda #$1A            ; Z
            sta SCREEN + (TRACK1_ROW * 40)
            lda #TRACK1_NOTE_COL
            sta COLRAM + (TRACK1_ROW * 40)

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

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

            rts

score_label:
            !scr "score:"
            !byte 0

title_text:
            !scr "sid symphony"
            !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
; ----------------------------------------------------------------------------
; Decrements flash counter and resets border when done

update_border_flash:
            lda border_flash
            beq flash_done
            dec border_flash
            bne flash_done
            ; Flash finished - reset border
            lda #BORDER_COL
            sta BORDER
flash_done:
            rts

; ----------------------------------------------------------------------------
; Check Keys - With hit detection and scoring
; ----------------------------------------------------------------------------

check_keys:
            ; Check Z key (track 1)
            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
            jsr flash_track1_hit
            jsr award_points

check_x_key:
            ; Check X key (track 2)
            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
            jsr flash_track2_hit
            jsr award_points

check_c_key:
            ; Check C key (track 3)
            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
            jsr flash_track3_hit
            jsr award_points

check_keys_done:
            lda #$FF
            sta CIA1_PRA
            rts

; ----------------------------------------------------------------------------
; Check Hit - Find note and determine hit quality
; ----------------------------------------------------------------------------
; Input: key_pressed = track number (1-3)
; Output: Carry set if hit, hit_quality set (1=good, 2=perfect)
;         Carry clear if no 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

            ; HIT! Determine quality based on position
            ; Centre columns (3-4) = perfect, edges (2, 5) = good
            cmp #HIT_ZONE_CENTRE
            bcc hit_good            ; Column 2 = good
            cmp #HIT_ZONE_CENTRE+2
            bcs hit_good            ; Column 5 = good

            ; Perfect hit (columns 3-4)
            lda #2
            sta hit_quality
            jmp hit_found

hit_good:
            ; Good hit (columns 2 or 5)
            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 - Add score based on hit quality
; ----------------------------------------------------------------------------

award_points:
            lda hit_quality
            cmp #2
            beq award_perfect

            ; Good hit - add 50 points
            lda score_lo
            clc
            adc #GOOD_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            ; Yellow border flash for good
            lda #GOOD_COL
            sta BORDER
            lda #4              ; Flash for 4 frames
            sta border_flash

            jmp award_done

award_perfect:
            ; Perfect hit - add 100 points
            lda score_lo
            clc
            adc #PERFECT_SCORE
            sta score_lo
            lda score_hi
            adc #0
            sta score_hi

            ; White border flash for perfect
            lda #PERFECT_COL
            sta BORDER
            lda #6              ; Flash for 6 frames
            sta border_flash

award_done:
            jsr display_score
            rts

; ----------------------------------------------------------------------------
; Display Score - Convert 16-bit score to decimal and show
; ----------------------------------------------------------------------------

display_score:
            ; Convert score to 5-digit decimal display
            ; Uses simple repeated subtraction method

            ; Copy score to working area
            lda score_lo
            sta work_lo
            lda score_hi
            sta work_hi

            ; Ten-thousands digit
            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            ; Convert to screen code
            sta SCREEN + 8

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

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

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

            ; Ones digit
            lda work_lo
            ora #$30
            sta SCREEN + 12

            ; Set score digit colours
            lda #7              ; Yellow for score
            sta COLRAM + 8
            sta COLRAM + 9
            sta COLRAM + 10
            sta COLRAM + 11
            sta COLRAM + 12

            rts

; Working variables for division
work_lo:    !byte 0
work_hi:    !byte 0

; ----------------------------------------------------------------------------
; Play Voices
; ----------------------------------------------------------------------------

play_voice1:
            lda #VOICE1_WAVE
            ora #$01
            sta SID_V1_CTRL
            rts

play_voice2:
            lda #VOICE2_WAVE
            ora #$01
            sta SID_V2_CTRL
            rts

play_voice3:
            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
            !byte 2, 2
            !byte 4, 3
            !byte 6, 1

            !byte 8, 2
            !byte 10, 3
            !byte 12, 1
            !byte 14, 2

            !byte 16, 3
            !byte 18, 1
            !byte 20, 2
            !byte 22, 3

            !byte 24, 1
            !byte 25, 2
            !byte 26, 3
            !byte 28, 1
            !byte 29, 2
            !byte 30, 3

            !byte $FF

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

note_track:
            !fill MAX_NOTES, 0

note_col:
            !fill MAX_NOTES, 0

; ----------------------------------------------------------------------------
; Score Variables
; ----------------------------------------------------------------------------

score_lo:   !byte 0
score_hi:   !byte 0

Try This: Higher Scores

Make perfect hits worth more:

PERFECT_SCORE = 200             ; Double points for perfect
GOOD_SCORE    = 25              ; Reduced for good

This widens the skill gap - precision matters more.

Try This: Longer Flash

Make the visual feedback more dramatic:

; In award_perfect:
lda #12             ; Flash for 12 frames (double)
sta border_flash

What You’ve Learnt

  • 16-bit arithmetic - Two bytes for larger numbers, carry handles overflow
  • Binary to decimal - Repeated subtraction converts for display
  • Timing feedback - Hit quality based on position within zone
  • Visual feedback - Border flash rewards successful actions

What’s Next

In Unit 7, we’ll handle missed notes. Notes that scroll past unhit will trigger negative feedback - a harsh sound and a red flash. Mistakes need to feel like mistakes.

What Changed

Unit 5 → Unit 6
+261-57
11 ; ============================================================================
2-; SID SYMPHONY - Unit 5: Hit Detection
2+; SID SYMPHONY - Unit 6: Scoring and Feedback
33 ; ============================================================================
4-; Detect when keypresses match notes in the hit zone. Press the right key at
5-; the right time to hit notes and hear the SID play. Wrong timing does nothing.
4+; Score points for hitting notes. Perfect timing (centre of hit zone) awards
5+; more points than good timing (edges). Visual feedback rewards accuracy.
66 ;
77 ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
88 ; ============================================================================
...
4040 FLASH3_COL = 6 ; Blue
4141
4242 HIT_COL = 1 ; White - flash on successful hit
43+PERFECT_COL = 1 ; White - perfect hit border flash
44+GOOD_COL = 7 ; Yellow - good hit border flash
45+
46+; ============================================================================
47+; SCORING SETTINGS
48+; ============================================================================
49+
50+PERFECT_SCORE = 100 ; Points for perfect hit
51+GOOD_SCORE = 50 ; Points for good hit
4352
4453 ; ============================================================================
4554 ; HIT DETECTION SETTINGS
4655 ; ============================================================================
4756
48-; Hit zone boundaries (column positions)
4957 HIT_ZONE_MIN = 2 ; Left edge of hit zone
5058 HIT_ZONE_MAX = 5 ; Right edge of hit zone
51-HIT_ZONE_CENTRE = 3 ; Perfect hit position
59+HIT_ZONE_CENTRE = 3 ; Perfect hit position (and 4)
5260
5361 ; ============================================================================
5462 ; MEMORY MAP
...
100108 HIT_ZONE_COLUMN = 3
101109
102110 ; Custom character codes
103-CHAR_NOTE = 128 ; Filled arrow/circle for notes
104-CHAR_TRACK = 129 ; Thin horizontal line for tracks
105-CHAR_HITZONE = 130 ; Vertical bar for hit zone
106-CHAR_SPACE = 32 ; Space
111+CHAR_NOTE = 128
112+CHAR_TRACK = 129
113+CHAR_HITZONE = 130
114+CHAR_SPACE = 32
107115
108116 ; Note settings
109117 MAX_NOTES = 8
...
122130 song_pos = $04
123131 song_pos_hi = $05
124132 temp_track = $06
125-key_pressed = $07 ; Which track key was pressed (0=none)
133+key_pressed = $07
134+hit_quality = $08 ; 0=none, 1=good, 2=perfect
135+border_flash = $09 ; Frames remaining for border flash
126136
127137 ; ----------------------------------------------------------------------------
128138 ; BASIC Stub
...
144154 * = $0810
145155
146156 start:
147- jsr copy_charset ; Copy ROM charset and add custom chars
157+ jsr copy_charset
148158 jsr init_screen
149159 jsr init_sid
150160 jsr init_notes
161+ jsr init_score
151162
152163 lda #<song_data
153164 sta song_pos
...
157168 lda #0
158169 sta frame_count
159170 sta beat_count
171+ sta border_flash
160172
161173 main_loop:
162174 lda #$FF
...
177189 no_new_beat:
178190 jsr update_notes
179191 jsr reset_track_colours
192+ jsr update_border_flash
180193 jsr check_keys
181194
182195 jmp main_loop
...
284297 sta CHARSET + (CHAR_HITZONE * 8) + 6
285298 lda #%01100110
286299 sta CHARSET + (CHAR_HITZONE * 8) + 7
300+
301+ rts
302+
303+; ----------------------------------------------------------------------------
304+; Initialize Score
305+; ----------------------------------------------------------------------------
287306
307+init_score:
308+ lda #0
309+ sta score_lo
310+ sta score_hi
311+ jsr display_score
288312 rts
289313
290314 ; ----------------------------------------------------------------------------
...
489513 rts
490514
491515 ; ----------------------------------------------------------------------------
492-; Erase Note - Restores track line character
516+; Erase Note
493517 ; ----------------------------------------------------------------------------
494518
495519 erase_note:
...
684708 ; ----------------------------------------------------------------------------
685709
686710 draw_labels:
711+ ; Draw "SCORE:" label
712+ ldx #0
713+draw_score_label:
714+ lda score_label,x
715+ beq draw_score_label_done
716+ sta SCREEN + 1,x
717+ lda #1 ; White
718+ sta COLRAM + 1,x
719+ inx
720+ bne draw_score_label
721+draw_score_label_done:
722+
723+ ; Draw title
687724 ldx #0
688725 draw_title:
689726 lda title_text,x
690727 beq draw_title_done
691- sta SCREEN + 13,x
728+ sta SCREEN + 27,x
692729 lda #1
693- sta COLRAM + 13,x
730+ sta COLRAM + 27,x
694731 inx
695732 bne draw_title
696733 draw_title_done:
697734
735+ ; Track labels
698736 lda #$1A ; Z
699737 sta SCREEN + (TRACK1_ROW * 40)
700738 lda #TRACK1_NOTE_COL
...
709747 sta SCREEN + (TRACK3_ROW * 40)
710748 lda #TRACK3_NOTE_COL
711749 sta COLRAM + (TRACK3_ROW * 40)
712-
713- ldx #0
714-draw_instr:
715- lda instr_text,x
716- beq draw_instr_done
717- sta SCREEN + (23 * 40) + 6,x
718- lda #TRACK_LINE_COL
719- sta COLRAM + (23 * 40) + 6,x
720- inx
721- bne draw_instr
722-draw_instr_done:
723750
724751 rts
725752
726-title_text:
727- !scr "sid symphony"
753+score_label:
754+ !scr "score:"
728755 !byte 0
729756
730-instr_text:
731- !scr "hit detection active"
757+title_text:
758+ !scr "sid symphony"
732759 !byte 0
733760
734761 ; ----------------------------------------------------------------------------
...
841868 rts
842869
843870 ; ----------------------------------------------------------------------------
844-; Check Keys - Now with hit detection
871+; Update Border Flash
845872 ; ----------------------------------------------------------------------------
846-; Checks if a key is pressed and if there's a matching note in the hit zone.
847-; Only plays sound and removes note on a successful hit.
873+; Decrements flash counter and resets border when done
874+
875+update_border_flash:
876+ lda border_flash
877+ beq flash_done
878+ dec border_flash
879+ bne flash_done
880+ ; Flash finished - reset border
881+ lda #BORDER_COL
882+ sta BORDER
883+flash_done:
884+ rts
885+
886+; ----------------------------------------------------------------------------
887+; Check Keys - With hit detection and scoring
888+; ----------------------------------------------------------------------------
848889
849890 check_keys:
850891 ; Check Z key (track 1)
...
854895 and #$10
855896 bne check_x_key
856897
857- ; Z pressed - check for hit on track 1
858898 lda #1
859899 sta key_pressed
860900 jsr check_hit
861- bcc check_x_key ; No hit - don't play sound
901+ bcc check_x_key
862902 jsr play_voice1
863903 jsr flash_track1_hit
904+ jsr award_points
864905
865906 check_x_key:
866907 ; Check X key (track 2)
...
870911 and #$80
871912 bne check_c_key
872913
873- ; X pressed - check for hit on track 2
874914 lda #2
875915 sta key_pressed
876916 jsr check_hit
877- bcc check_c_key ; No hit - don't play sound
917+ bcc check_c_key
878918 jsr play_voice2
879919 jsr flash_track2_hit
920+ jsr award_points
880921
881922 check_c_key:
882923 ; Check C key (track 3)
...
886927 and #$10
887928 bne check_keys_done
888929
889- ; C pressed - check for hit on track 3
890930 lda #3
891931 sta key_pressed
892932 jsr check_hit
893- bcc check_keys_done ; No hit - don't play sound
933+ bcc check_keys_done
894934 jsr play_voice3
895935 jsr flash_track3_hit
936+ jsr award_points
896937
897938 check_keys_done:
898939 lda #$FF
...
900941 rts
901942
902943 ; ----------------------------------------------------------------------------
903-; Check Hit - Find a note in the hit zone for the pressed track
944+; Check Hit - Find note and determine hit quality
904945 ; ----------------------------------------------------------------------------
905946 ; Input: key_pressed = track number (1-3)
906-; Output: Carry set if hit found, X = note index
947+; Output: Carry set if hit, hit_quality set (1=good, 2=perfect)
907948 ; Carry clear if no hit
908-; Side effect: Removes hit note from play
909949
910950 check_hit:
911951 ldx #0
912952
913953 check_hit_loop:
914- ; Check if this note slot is active
915954 lda note_track,x
916- beq check_hit_next ; Empty slot - skip
955+ beq check_hit_next
917956
918- ; Check if note is on the pressed track
919957 cmp key_pressed
920- bne check_hit_next ; Wrong track - skip
958+ bne check_hit_next
921959
922- ; Check if note is in the hit zone
923960 lda note_col,x
924961 cmp #HIT_ZONE_MIN
925- bcc check_hit_next ; Too far left - skip
962+ bcc check_hit_next
926963 cmp #HIT_ZONE_MAX+1
927- bcs check_hit_next ; Too far right - skip
964+ bcs check_hit_next
928965
929- ; HIT! Note is in zone on correct track
930- ; Erase the note from screen
931- jsr erase_note
966+ ; HIT! Determine quality based on position
967+ ; Centre columns (3-4) = perfect, edges (2, 5) = good
968+ cmp #HIT_ZONE_CENTRE
969+ bcc hit_good ; Column 2 = good
970+ cmp #HIT_ZONE_CENTRE+2
971+ bcs hit_good ; Column 5 = good
932972
933- ; Deactivate the note
973+ ; Perfect hit (columns 3-4)
974+ lda #2
975+ sta hit_quality
976+ jmp hit_found
977+
978+hit_good:
979+ ; Good hit (columns 2 or 5)
980+ lda #1
981+ sta hit_quality
982+
983+hit_found:
984+ jsr erase_note
934985 lda #0
935986 sta note_track,x
936-
937- ; Return with carry set (hit found)
938987 sec
939988 rts
940989
...
943992 cpx #MAX_NOTES
944993 bne check_hit_loop
945994
946- ; No hit found - return with carry clear
995+ lda #0
996+ sta hit_quality
997+ clc
998+ rts
999+
1000+; ----------------------------------------------------------------------------
1001+; Award Points - Add score based on hit quality
1002+; ----------------------------------------------------------------------------
1003+
1004+award_points:
1005+ lda hit_quality
1006+ cmp #2
1007+ beq award_perfect
1008+
1009+ ; Good hit - add 50 points
1010+ lda score_lo
1011+ clc
1012+ adc #GOOD_SCORE
1013+ sta score_lo
1014+ lda score_hi
1015+ adc #0
1016+ sta score_hi
1017+
1018+ ; Yellow border flash for good
1019+ lda #GOOD_COL
1020+ sta BORDER
1021+ lda #4 ; Flash for 4 frames
1022+ sta border_flash
1023+
1024+ jmp award_done
1025+
1026+award_perfect:
1027+ ; Perfect hit - add 100 points
1028+ lda score_lo
9471029 clc
1030+ adc #PERFECT_SCORE
1031+ sta score_lo
1032+ lda score_hi
1033+ adc #0
1034+ sta score_hi
1035+
1036+ ; White border flash for perfect
1037+ lda #PERFECT_COL
1038+ sta BORDER
1039+ lda #6 ; Flash for 6 frames
1040+ sta border_flash
1041+
1042+award_done:
1043+ jsr display_score
1044+ rts
1045+
1046+; ----------------------------------------------------------------------------
1047+; Display Score - Convert 16-bit score to decimal and show
1048+; ----------------------------------------------------------------------------
1049+
1050+display_score:
1051+ ; Convert score to 5-digit decimal display
1052+ ; Uses simple repeated subtraction method
1053+
1054+ ; Copy score to working area
1055+ lda score_lo
1056+ sta work_lo
1057+ lda score_hi
1058+ sta work_hi
1059+
1060+ ; Ten-thousands digit
1061+ ldx #0
1062+div_10000:
1063+ lda work_lo
1064+ sec
1065+ sbc #<10000
1066+ tay
1067+ lda work_hi
1068+ sbc #>10000
1069+ bcc done_10000
1070+ sta work_hi
1071+ sty work_lo
1072+ inx
1073+ jmp div_10000
1074+done_10000:
1075+ txa
1076+ ora #$30 ; Convert to screen code
1077+ sta SCREEN + 8
1078+
1079+ ; Thousands digit
1080+ ldx #0
1081+div_1000:
1082+ lda work_lo
1083+ sec
1084+ sbc #<1000
1085+ tay
1086+ lda work_hi
1087+ sbc #>1000
1088+ bcc done_1000
1089+ sta work_hi
1090+ sty work_lo
1091+ inx
1092+ jmp div_1000
1093+done_1000:
1094+ txa
1095+ ora #$30
1096+ sta SCREEN + 9
1097+
1098+ ; Hundreds digit
1099+ ldx #0
1100+div_100:
1101+ lda work_lo
1102+ sec
1103+ sbc #100
1104+ bcc done_100
1105+ sta work_lo
1106+ inx
1107+ jmp div_100
1108+done_100:
1109+ txa
1110+ ora #$30
1111+ sta SCREEN + 10
1112+
1113+ ; Tens digit
1114+ ldx #0
1115+div_10:
1116+ lda work_lo
1117+ sec
1118+ sbc #10
1119+ bcc done_10
1120+ sta work_lo
1121+ inx
1122+ jmp div_10
1123+done_10:
1124+ txa
1125+ ora #$30
1126+ sta SCREEN + 11
1127+
1128+ ; Ones digit
1129+ lda work_lo
1130+ ora #$30
1131+ sta SCREEN + 12
1132+
1133+ ; Set score digit colours
1134+ lda #7 ; Yellow for score
1135+ sta COLRAM + 8
1136+ sta COLRAM + 9
1137+ sta COLRAM + 10
1138+ sta COLRAM + 11
1139+ sta COLRAM + 12
1140+
9481141 rts
1142+
1143+; Working variables for division
1144+work_lo: !byte 0
1145+work_hi: !byte 0
9491146
9501147 ; ----------------------------------------------------------------------------
9511148 ; Play Voices
...
9701167 rts
9711168
9721169 ; ----------------------------------------------------------------------------
973-; Flash Tracks on Hit - White flash for successful hits
1170+; Flash Tracks on Hit
9741171 ; ----------------------------------------------------------------------------
9751172
9761173 flash_track1_hit:
9771174 ldx #0
978- lda #HIT_COL ; White for hits
1175+ lda #HIT_COL
9791176 flash_t1h_loop:
9801177 sta COLRAM + (TRACK1_ROW * 40),x
9811178 inx
...
10471244
10481245 note_col:
10491246 !fill MAX_NOTES, 0
1247+
1248+; ----------------------------------------------------------------------------
1249+; Score Variables
1250+; ----------------------------------------------------------------------------
1251+
1252+score_lo: !byte 0
1253+score_hi: !byte 0
10501254