Scoring and Feedback
Points for hits. Visual feedback for success. The game rewards skill.
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

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.
Hit Quality
Not all hits are equal. The hit zone spans columns 2-5, but position matters:
| Column | Quality | Points |
|---|---|---|
| 3-4 | Perfect | 100 |
| 2, 5 | Good | 50 |
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
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; SID SYMPHONY - Unit 5: Hit Detection | |
| 2 | + | ; SID SYMPHONY - Unit 6: Scoring and Feedback | |
| 3 | 3 | ; ============================================================================ | |
| 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. | |
| 6 | 6 | ; | |
| 7 | 7 | ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low) | |
| 8 | 8 | ; ============================================================================ | |
| ... | |||
| 40 | 40 | FLASH3_COL = 6 ; Blue | |
| 41 | 41 | | |
| 42 | 42 | 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 | |
| 43 | 52 | | |
| 44 | 53 | ; ============================================================================ | |
| 45 | 54 | ; HIT DETECTION SETTINGS | |
| 46 | 55 | ; ============================================================================ | |
| 47 | 56 | | |
| 48 | - | ; Hit zone boundaries (column positions) | |
| 49 | 57 | HIT_ZONE_MIN = 2 ; Left edge of hit zone | |
| 50 | 58 | 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) | |
| 52 | 60 | | |
| 53 | 61 | ; ============================================================================ | |
| 54 | 62 | ; MEMORY MAP | |
| ... | |||
| 100 | 108 | HIT_ZONE_COLUMN = 3 | |
| 101 | 109 | | |
| 102 | 110 | ; 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 | |
| 107 | 115 | | |
| 108 | 116 | ; Note settings | |
| 109 | 117 | MAX_NOTES = 8 | |
| ... | |||
| 122 | 130 | song_pos = $04 | |
| 123 | 131 | song_pos_hi = $05 | |
| 124 | 132 | 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 | |
| 126 | 136 | | |
| 127 | 137 | ; ---------------------------------------------------------------------------- | |
| 128 | 138 | ; BASIC Stub | |
| ... | |||
| 144 | 154 | * = $0810 | |
| 145 | 155 | | |
| 146 | 156 | start: | |
| 147 | - | jsr copy_charset ; Copy ROM charset and add custom chars | |
| 157 | + | jsr copy_charset | |
| 148 | 158 | jsr init_screen | |
| 149 | 159 | jsr init_sid | |
| 150 | 160 | jsr init_notes | |
| 161 | + | jsr init_score | |
| 151 | 162 | | |
| 152 | 163 | lda #<song_data | |
| 153 | 164 | sta song_pos | |
| ... | |||
| 157 | 168 | lda #0 | |
| 158 | 169 | sta frame_count | |
| 159 | 170 | sta beat_count | |
| 171 | + | sta border_flash | |
| 160 | 172 | | |
| 161 | 173 | main_loop: | |
| 162 | 174 | lda #$FF | |
| ... | |||
| 177 | 189 | no_new_beat: | |
| 178 | 190 | jsr update_notes | |
| 179 | 191 | jsr reset_track_colours | |
| 192 | + | jsr update_border_flash | |
| 180 | 193 | jsr check_keys | |
| 181 | 194 | | |
| 182 | 195 | jmp main_loop | |
| ... | |||
| 284 | 297 | sta CHARSET + (CHAR_HITZONE * 8) + 6 | |
| 285 | 298 | lda #%01100110 | |
| 286 | 299 | sta CHARSET + (CHAR_HITZONE * 8) + 7 | |
| 300 | + | | |
| 301 | + | rts | |
| 302 | + | | |
| 303 | + | ; ---------------------------------------------------------------------------- | |
| 304 | + | ; Initialize Score | |
| 305 | + | ; ---------------------------------------------------------------------------- | |
| 287 | 306 | | |
| 307 | + | init_score: | |
| 308 | + | lda #0 | |
| 309 | + | sta score_lo | |
| 310 | + | sta score_hi | |
| 311 | + | jsr display_score | |
| 288 | 312 | rts | |
| 289 | 313 | | |
| 290 | 314 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 489 | 513 | rts | |
| 490 | 514 | | |
| 491 | 515 | ; ---------------------------------------------------------------------------- | |
| 492 | - | ; Erase Note - Restores track line character | |
| 516 | + | ; Erase Note | |
| 493 | 517 | ; ---------------------------------------------------------------------------- | |
| 494 | 518 | | |
| 495 | 519 | erase_note: | |
| ... | |||
| 684 | 708 | ; ---------------------------------------------------------------------------- | |
| 685 | 709 | | |
| 686 | 710 | 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 | |
| 687 | 724 | ldx #0 | |
| 688 | 725 | draw_title: | |
| 689 | 726 | lda title_text,x | |
| 690 | 727 | beq draw_title_done | |
| 691 | - | sta SCREEN + 13,x | |
| 728 | + | sta SCREEN + 27,x | |
| 692 | 729 | lda #1 | |
| 693 | - | sta COLRAM + 13,x | |
| 730 | + | sta COLRAM + 27,x | |
| 694 | 731 | inx | |
| 695 | 732 | bne draw_title | |
| 696 | 733 | draw_title_done: | |
| 697 | 734 | | |
| 735 | + | ; Track labels | |
| 698 | 736 | lda #$1A ; Z | |
| 699 | 737 | sta SCREEN + (TRACK1_ROW * 40) | |
| 700 | 738 | lda #TRACK1_NOTE_COL | |
| ... | |||
| 709 | 747 | sta SCREEN + (TRACK3_ROW * 40) | |
| 710 | 748 | lda #TRACK3_NOTE_COL | |
| 711 | 749 | 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: | |
| 723 | 750 | | |
| 724 | 751 | rts | |
| 725 | 752 | | |
| 726 | - | title_text: | |
| 727 | - | !scr "sid symphony" | |
| 753 | + | score_label: | |
| 754 | + | !scr "score:" | |
| 728 | 755 | !byte 0 | |
| 729 | 756 | | |
| 730 | - | instr_text: | |
| 731 | - | !scr "hit detection active" | |
| 757 | + | title_text: | |
| 758 | + | !scr "sid symphony" | |
| 732 | 759 | !byte 0 | |
| 733 | 760 | | |
| 734 | 761 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 841 | 868 | rts | |
| 842 | 869 | | |
| 843 | 870 | ; ---------------------------------------------------------------------------- | |
| 844 | - | ; Check Keys - Now with hit detection | |
| 871 | + | ; Update Border Flash | |
| 845 | 872 | ; ---------------------------------------------------------------------------- | |
| 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 | + | ; ---------------------------------------------------------------------------- | |
| 848 | 889 | | |
| 849 | 890 | check_keys: | |
| 850 | 891 | ; Check Z key (track 1) | |
| ... | |||
| 854 | 895 | and #$10 | |
| 855 | 896 | bne check_x_key | |
| 856 | 897 | | |
| 857 | - | ; Z pressed - check for hit on track 1 | |
| 858 | 898 | lda #1 | |
| 859 | 899 | sta key_pressed | |
| 860 | 900 | jsr check_hit | |
| 861 | - | bcc check_x_key ; No hit - don't play sound | |
| 901 | + | bcc check_x_key | |
| 862 | 902 | jsr play_voice1 | |
| 863 | 903 | jsr flash_track1_hit | |
| 904 | + | jsr award_points | |
| 864 | 905 | | |
| 865 | 906 | check_x_key: | |
| 866 | 907 | ; Check X key (track 2) | |
| ... | |||
| 870 | 911 | and #$80 | |
| 871 | 912 | bne check_c_key | |
| 872 | 913 | | |
| 873 | - | ; X pressed - check for hit on track 2 | |
| 874 | 914 | lda #2 | |
| 875 | 915 | sta key_pressed | |
| 876 | 916 | jsr check_hit | |
| 877 | - | bcc check_c_key ; No hit - don't play sound | |
| 917 | + | bcc check_c_key | |
| 878 | 918 | jsr play_voice2 | |
| 879 | 919 | jsr flash_track2_hit | |
| 920 | + | jsr award_points | |
| 880 | 921 | | |
| 881 | 922 | check_c_key: | |
| 882 | 923 | ; Check C key (track 3) | |
| ... | |||
| 886 | 927 | and #$10 | |
| 887 | 928 | bne check_keys_done | |
| 888 | 929 | | |
| 889 | - | ; C pressed - check for hit on track 3 | |
| 890 | 930 | lda #3 | |
| 891 | 931 | sta key_pressed | |
| 892 | 932 | jsr check_hit | |
| 893 | - | bcc check_keys_done ; No hit - don't play sound | |
| 933 | + | bcc check_keys_done | |
| 894 | 934 | jsr play_voice3 | |
| 895 | 935 | jsr flash_track3_hit | |
| 936 | + | jsr award_points | |
| 896 | 937 | | |
| 897 | 938 | check_keys_done: | |
| 898 | 939 | lda #$FF | |
| ... | |||
| 900 | 941 | rts | |
| 901 | 942 | | |
| 902 | 943 | ; ---------------------------------------------------------------------------- | |
| 903 | - | ; Check Hit - Find a note in the hit zone for the pressed track | |
| 944 | + | ; Check Hit - Find note and determine hit quality | |
| 904 | 945 | ; ---------------------------------------------------------------------------- | |
| 905 | 946 | ; 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) | |
| 907 | 948 | ; Carry clear if no hit | |
| 908 | - | ; Side effect: Removes hit note from play | |
| 909 | 949 | | |
| 910 | 950 | check_hit: | |
| 911 | 951 | ldx #0 | |
| 912 | 952 | | |
| 913 | 953 | check_hit_loop: | |
| 914 | - | ; Check if this note slot is active | |
| 915 | 954 | lda note_track,x | |
| 916 | - | beq check_hit_next ; Empty slot - skip | |
| 955 | + | beq check_hit_next | |
| 917 | 956 | | |
| 918 | - | ; Check if note is on the pressed track | |
| 919 | 957 | cmp key_pressed | |
| 920 | - | bne check_hit_next ; Wrong track - skip | |
| 958 | + | bne check_hit_next | |
| 921 | 959 | | |
| 922 | - | ; Check if note is in the hit zone | |
| 923 | 960 | lda note_col,x | |
| 924 | 961 | cmp #HIT_ZONE_MIN | |
| 925 | - | bcc check_hit_next ; Too far left - skip | |
| 962 | + | bcc check_hit_next | |
| 926 | 963 | cmp #HIT_ZONE_MAX+1 | |
| 927 | - | bcs check_hit_next ; Too far right - skip | |
| 964 | + | bcs check_hit_next | |
| 928 | 965 | | |
| 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 | |
| 932 | 972 | | |
| 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 | |
| 934 | 985 | lda #0 | |
| 935 | 986 | sta note_track,x | |
| 936 | - | | |
| 937 | - | ; Return with carry set (hit found) | |
| 938 | 987 | sec | |
| 939 | 988 | rts | |
| 940 | 989 | | |
| ... | |||
| 943 | 992 | cpx #MAX_NOTES | |
| 944 | 993 | bne check_hit_loop | |
| 945 | 994 | | |
| 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 | |
| 947 | 1029 | 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 | + | | |
| 948 | 1141 | rts | |
| 1142 | + | | |
| 1143 | + | ; Working variables for division | |
| 1144 | + | work_lo: !byte 0 | |
| 1145 | + | work_hi: !byte 0 | |
| 949 | 1146 | | |
| 950 | 1147 | ; ---------------------------------------------------------------------------- | |
| 951 | 1148 | ; Play Voices | |
| ... | |||
| 970 | 1167 | rts | |
| 971 | 1168 | | |
| 972 | 1169 | ; ---------------------------------------------------------------------------- | |
| 973 | - | ; Flash Tracks on Hit - White flash for successful hits | |
| 1170 | + | ; Flash Tracks on Hit | |
| 974 | 1171 | ; ---------------------------------------------------------------------------- | |
| 975 | 1172 | | |
| 976 | 1173 | flash_track1_hit: | |
| 977 | 1174 | ldx #0 | |
| 978 | - | lda #HIT_COL ; White for hits | |
| 1175 | + | lda #HIT_COL | |
| 979 | 1176 | flash_t1h_loop: | |
| 980 | 1177 | sta COLRAM + (TRACK1_ROW * 40),x | |
| 981 | 1178 | inx | |
| ... | |||
| 1047 | 1244 | | |
| 1048 | 1245 | note_col: | |
| 1049 | 1246 | !fill MAX_NOTES, 0 | |
| 1247 | + | | |
| 1248 | + | ; ---------------------------------------------------------------------------- | |
| 1249 | + | ; Score Variables | |
| 1250 | + | ; ---------------------------------------------------------------------------- | |
| 1251 | + | | |
| 1252 | + | score_lo: !byte 0 | |
| 1253 | + | score_hi: !byte 0 | |
| 1050 | 1254 | |