Song End and Results
Detect song completion. Display results with score breakdown.
A game needs an ending. This unit adds results and the complete game loop.
When the song finishes, the screen clears to show final results: score, perfects, goods, misses, and accuracy percentage. Press any key to play again. The game now has a proper beginning and end.
Run It
Assemble and run:
acme -f cbm -o symphony.prg symphony.asm

Play through the entire song. When it ends, you’ll see the results screen with “SONG COMPLETE!” and your statistics. Press any key to restart.
Game States
The game now has three distinct states:
; ============================================================================
; GAME STATES
; ============================================================================
STATE_PLAYING = 0
STATE_RESULTS = 1
STATE_GAMEOVER = 2
game_state: !byte 0
; ----------------------------------------------------------------------------
; Main Loop - State Machine
; ----------------------------------------------------------------------------
main_loop:
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
lda game_state
cmp #STATE_PLAYING
beq do_playing
cmp #STATE_RESULTS
beq do_results
jmp do_gameover
do_playing:
jsr update_playing
jmp main_loop
do_results:
jsr update_results
jmp main_loop
do_gameover:
jsr update_gameover
jmp main_loop
The main loop checks game_state and calls the appropriate update routine. This pattern - a state variable controlling which code runs - scales well as games grow more complex.
Detecting Song End
The song ends when all notes have been processed and cleared from the screen:
; ----------------------------------------------------------------------------
; Check Song End
; ----------------------------------------------------------------------------
check_song_end:
; First check if song data exhausted
lda song_ended
beq song_not_ended
; Song data done - wait for all notes to clear
ldx #0
check_notes_clear:
lda note_track,x
bne notes_still_active
inx
cpx #MAX_NOTES
bne check_notes_clear
; All notes cleared - count down delay
dec end_delay
bne song_not_ended
; Delay done - show results
jsr show_results
lda #STATE_RESULTS
sta game_state
notes_still_active:
song_not_ended:
rts
The logic is:
- Check if song data is exhausted (
song_endedflag) - Wait for all active notes to scroll off or be hit
- Wait a brief delay (for dramatic effect)
- Transition to results state
Tracking Statistics
We now track perfects and goods separately:
award_points:
lda hit_quality
cmp #2
beq award_perfect
; Good hit
inc good_count ; Track goods separately
; ... add score ...
award_perfect:
inc perfect_count ; Track perfects separately
; ... add score ...
These counters appear on the results screen.
Calculating Accuracy
Accuracy shows what percentage of notes the player hit:
; ----------------------------------------------------------------------------
; Calculate Accuracy - (perfects + goods) * 100 / total_notes
; ----------------------------------------------------------------------------
calculate_accuracy:
; Total hits = perfects + goods
lda perfect_count
clc
adc good_count
sta total_hits
; Total notes = hits + misses
clc
adc miss_count
sta total_notes
; Avoid division by zero
beq accuracy_zero
; Multiply hits by 100
lda total_hits
sta dividend_lo
lda #0
sta dividend_hi
ldx #100
mult_loop:
dex
beq mult_done
lda dividend_lo
clc
adc total_hits
sta dividend_lo
lda dividend_hi
adc #0
sta dividend_hi
jmp mult_loop
mult_done:
; Divide by total_notes
lda #0
sta accuracy
div_loop:
lda dividend_lo
sec
sbc total_notes
tay
lda dividend_hi
sbc #0
bcc div_done
sta dividend_hi
sty dividend_lo
inc accuracy
jmp div_loop
div_done:
rts
accuracy_zero:
lda #0
sta accuracy
rts
The formula: (perfects + goods) × 100 ÷ total_notes. We multiply first, then divide, to maintain precision. The 6502 has no division instruction, so we use repeated subtraction.
The Results Screen
The results screen displays:
SONG COMPLETE!
FINAL SCORE: 02800
PERFECTS: 15
GOODS: 10
MISSES: 05
ACCURACY: 083%
PRESS ANY KEY
Each statistic uses colour to communicate meaning: white for perfects, yellow for goods, red for misses, green for accuracy. The border turns green for success.
Press Any Key
Waiting for any key is simple:
update_results:
lda #$00
sta CIA1_PRA ; Select all keyboard rows
lda CIA1_PRB
cmp #$FF ; $FF = no keys pressed
beq results_wait
; Key pressed - restart
jsr init_game
results_wait:
lda #$FF
sta CIA1_PRA
rts
Setting CIA1_PRA to $00 selects all rows. Any key press makes CIA1_PRB read something other than $FF.
The Complete Code
; ============================================================================
; SID SYMPHONY - Unit 11: Song End and Results
; ============================================================================
; Detect song completion. Display results with score breakdown. The game
; has a proper ending now - victory or defeat, then replay.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================
; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================
; SID Voice Settings
VOICE1_WAVE = $21
VOICE2_WAVE = $41
VOICE3_WAVE = $11
VOICE1_FREQ = $1C
VOICE2_FREQ = $0E
VOICE3_FREQ = $07
VOICE_AD = $09
VOICE_SR = $00
PULSE_WIDTH = $08
; Miss sound settings
MISS_FREQ = $08
MISS_WAVE = $81
MISS_AD = $00
MISS_SR = $90
; Visual Settings
BORDER_COL = 0
BG_COL = 0
TRACK1_NOTE_COL = 10
TRACK2_NOTE_COL = 13
TRACK3_NOTE_COL = 14
TRACK_LINE_COL = 11
HIT_ZONE_COL = 7
HIT_COL = 1
PERFECT_COL = 1
GOOD_COL = 7
MISS_COL = 2
HEALTH_COL = 5
PROGRESS_COL = 3
; ============================================================================
; SCORING SETTINGS
; ============================================================================
PERFECT_SCORE = 100
GOOD_SCORE = 50
; ============================================================================
; HEALTH SETTINGS
; ============================================================================
HEALTH_MAX = 64
HEALTH_START = 32
HEALTH_PERFECT = 4
HEALTH_GOOD = 2
HEALTH_MISS = 8
; ============================================================================
; HIT DETECTION SETTINGS
; ============================================================================
HIT_ZONE_MIN = 2
HIT_ZONE_MAX = 5
HIT_ZONE_CENTRE = 3
; ============================================================================
; SONG SETTINGS
; ============================================================================
SONG_LENGTH = 64
PROGRESS_WIDTH = 16
; ============================================================================
; GAME STATES
; ============================================================================
STATE_PLAYING = 0
STATE_RESULTS = 1
STATE_GAMEOVER = 2
; ============================================================================
; MEMORY MAP
; ============================================================================
SCREEN = $0400
COLRAM = $D800
BORDER = $D020
BGCOL = $D021
CHARPTR = $D018
CHARSET = $3000
; SID registers
SID = $D400
SID_V1_FREQ_LO = $D400
SID_V1_FREQ_HI = $D401
SID_V1_PWHI = $D403
SID_V1_CTRL = $D404
SID_V1_AD = $D405
SID_V1_SR = $D406
SID_V2_FREQ_LO = $D407
SID_V2_FREQ_HI = $D408
SID_V2_PWHI = $D40A
SID_V2_CTRL = $D40B
SID_V2_AD = $D40C
SID_V2_SR = $D40D
SID_V3_FREQ_LO = $D40E
SID_V3_FREQ_HI = $D40F
SID_V3_PWHI = $D411
SID_V3_CTRL = $D412
SID_V3_AD = $D413
SID_V3_SR = $D414
SID_VOLUME = $D418
; CIA keyboard
CIA1_PRA = $DC00
CIA1_PRB = $DC01
; Track positions
TRACK1_ROW = 8
TRACK2_ROW = 12
TRACK3_ROW = 16
; HUD positions
HEALTH_ROW = 23
PROGRESS_ROW = 24
; Hit zone
HIT_ZONE_COLUMN = 3
; Custom character codes
CHAR_NOTE = 128
CHAR_TRACK = 129
CHAR_HITZONE = 130
CHAR_SPACE = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132
; Note settings
MAX_NOTES = 8
NOTE_SPAWN_COL = 37
; Timing
FRAMES_PER_BEAT = 25
END_DELAY_FRAMES = 75 ; Wait after last note before results
; Zero page
ZP_PTR = $FB
ZP_PTR_HI = $FC
; Variables
frame_count = $02
beat_count = $03
song_pos = $04
song_pos_hi = $05
temp_track = $06
key_pressed = $07
hit_quality = $08
border_flash = $09
miss_track = $0A
game_state = $0B
hit_note_freq = $0C
song_beat = $0D
song_ended = $0E ; Flag: song data exhausted
end_delay = $0F ; Countdown after last note
; ----------------------------------------------------------------------------
; BASIC Stub
; ----------------------------------------------------------------------------
* = $0801
!byte $0C, $08
!byte $0A, $00
!byte $9E
!text "2064"
!byte $00
!byte $00, $00
; ----------------------------------------------------------------------------
; Main Program
; ----------------------------------------------------------------------------
* = $0810
start:
jsr copy_charset
jsr init_game
main_loop:
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
lda game_state
cmp #STATE_PLAYING
beq do_playing
cmp #STATE_RESULTS
beq do_results
jmp do_gameover
do_playing:
jsr update_playing
jmp main_loop
do_results:
jsr update_results
jmp main_loop
do_gameover:
jsr update_gameover
jmp main_loop
; ----------------------------------------------------------------------------
; Initialize Game
; ----------------------------------------------------------------------------
init_game:
jsr init_screen
jsr init_sid
jsr init_notes
jsr init_score
jsr init_health
jsr init_song
lda #STATE_PLAYING
sta game_state
rts
; ----------------------------------------------------------------------------
; Update Playing State
; ----------------------------------------------------------------------------
update_playing:
inc frame_count
lda frame_count
cmp #FRAMES_PER_BEAT
bcc no_new_beat
lda #0
sta frame_count
jsr advance_song
jsr check_spawn_note
no_new_beat:
jsr update_notes
jsr reset_track_colours
jsr update_border_flash
jsr check_keys
; Check for song end
jsr check_song_end
rts
; ----------------------------------------------------------------------------
; Update Results State
; ----------------------------------------------------------------------------
update_results:
; Wait for any key to restart
lda #$00
sta CIA1_PRA
lda CIA1_PRB
cmp #$FF
beq results_wait
; Key pressed - restart game
jsr init_game
results_wait:
lda #$FF
sta CIA1_PRA
rts
; ----------------------------------------------------------------------------
; Update Game Over State
; ----------------------------------------------------------------------------
update_gameover:
; Wait for any key to restart
lda #$00
sta CIA1_PRA
lda CIA1_PRB
cmp #$FF
beq gameover_wait
; Key pressed - restart game
jsr init_game
gameover_wait:
lda #$FF
sta CIA1_PRA
rts
; ----------------------------------------------------------------------------
; Check Song End
; ----------------------------------------------------------------------------
check_song_end:
; First check if song data exhausted
lda song_ended
beq song_not_ended
; Song data done - wait for all notes to clear
ldx #0
check_notes_clear:
lda note_track,x
bne notes_still_active
inx
cpx #MAX_NOTES
bne check_notes_clear
; All notes cleared - count down delay
dec end_delay
bne song_not_ended
; Delay done - show results
jsr show_results
lda #STATE_RESULTS
sta game_state
notes_still_active:
song_not_ended:
rts
; ----------------------------------------------------------------------------
; Initialize Song
; ----------------------------------------------------------------------------
init_song:
lda #<song_data
sta song_pos
lda #>song_data
sta song_pos_hi
lda #0
sta frame_count
sta beat_count
sta song_beat
sta border_flash
sta song_ended
lda #END_DELAY_FRAMES
sta end_delay
jsr display_progress
rts
; ----------------------------------------------------------------------------
; Advance Song
; ----------------------------------------------------------------------------
advance_song:
lda song_ended
bne advance_done
inc beat_count
inc song_beat
lda song_beat
cmp #SONG_LENGTH
bcc song_continues
; Song reached end
lda #1
sta song_ended
song_continues:
jsr display_progress
advance_done:
rts
; ----------------------------------------------------------------------------
; Display Progress
; ----------------------------------------------------------------------------
display_progress:
lda song_beat
lsr
lsr
sta temp_progress
ldx #0
lda temp_progress
beq draw_empty_progress
draw_full_progress:
lda #CHAR_BAR_FULL
sta SCREEN + (PROGRESS_ROW * 40) + 12,x
lda #PROGRESS_COL
sta COLRAM + (PROGRESS_ROW * 40) + 12,x
inx
cpx temp_progress
bne draw_full_progress
draw_empty_progress:
cpx #PROGRESS_WIDTH
beq progress_done
lda #CHAR_BAR_EMPTY
sta SCREEN + (PROGRESS_ROW * 40) + 12,x
lda #11
sta COLRAM + (PROGRESS_ROW * 40) + 12,x
inx
jmp draw_empty_progress
progress_done:
rts
temp_progress: !byte 0
; ----------------------------------------------------------------------------
; Show Results Screen
; ----------------------------------------------------------------------------
show_results:
; Clear screen
ldx #0
lda #CHAR_SPACE
clear_for_results:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_for_results
; Title
ldx #0
draw_results_title:
lda results_title,x
beq draw_results_title_done
sta SCREEN + (4 * 40) + 14,x
lda #1
sta COLRAM + (4 * 40) + 14,x
inx
jmp draw_results_title
draw_results_title_done:
; Score label and value
ldx #0
draw_final_score_label:
lda final_score_label,x
beq draw_final_score_done
sta SCREEN + (8 * 40) + 10,x
lda #7
sta COLRAM + (8 * 40) + 10,x
inx
jmp draw_final_score_label
draw_final_score_done:
jsr display_final_score
; Perfects
ldx #0
draw_perfects_label:
lda perfects_label,x
beq draw_perfects_done
sta SCREEN + (10 * 40) + 10,x
lda #1
sta COLRAM + (10 * 40) + 10,x
inx
jmp draw_perfects_label
draw_perfects_done:
lda perfect_count
jsr display_stat_at_10
; Goods
ldx #0
draw_goods_label:
lda goods_label,x
beq draw_goods_done
sta SCREEN + (11 * 40) + 10,x
lda #7
sta COLRAM + (11 * 40) + 10,x
inx
jmp draw_goods_label
draw_goods_done:
lda good_count
jsr display_stat_at_11
; Misses
ldx #0
draw_misses_label:
lda misses_label,x
beq draw_misses_done
sta SCREEN + (12 * 40) + 10,x
lda #2
sta COLRAM + (12 * 40) + 10,x
inx
jmp draw_misses_label
draw_misses_done:
lda miss_count
jsr display_stat_at_12
; Accuracy (simplified: just show hit percentage)
ldx #0
draw_accuracy_label:
lda accuracy_label,x
beq draw_accuracy_done
sta SCREEN + (14 * 40) + 10,x
lda #5
sta COLRAM + (14 * 40) + 10,x
inx
jmp draw_accuracy_label
draw_accuracy_done:
jsr calculate_accuracy
jsr display_accuracy
; Press any key message
ldx #0
draw_press_key:
lda press_key_text,x
beq draw_press_key_done
sta SCREEN + (18 * 40) + 10,x
lda #11
sta COLRAM + (18 * 40) + 10,x
inx
jmp draw_press_key
draw_press_key_done:
; Green border for victory
lda #5
sta BORDER
rts
results_title:
!scr "song complete!"
!byte 0
final_score_label:
!scr "final score:"
!byte 0
perfects_label:
!scr "perfects:"
!byte 0
goods_label:
!scr "goods:"
!byte 0
misses_label:
!scr "misses:"
!byte 0
accuracy_label:
!scr "accuracy:"
!byte 0
press_key_text:
!scr "press any key"
!byte 0
; ----------------------------------------------------------------------------
; Display Final Score (at row 8)
; ----------------------------------------------------------------------------
display_final_score:
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
fs_div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc fs_done_10000
sta work_hi
sty work_lo
inx
jmp fs_div_10000
fs_done_10000:
txa
ora #$30
sta SCREEN + (8 * 40) + 23
ldx #0
fs_div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc fs_done_1000
sta work_hi
sty work_lo
inx
jmp fs_div_1000
fs_done_1000:
txa
ora #$30
sta SCREEN + (8 * 40) + 24
ldx #0
fs_div_100:
lda work_lo
sec
sbc #100
bcc fs_done_100
sta work_lo
inx
jmp fs_div_100
fs_done_100:
txa
ora #$30
sta SCREEN + (8 * 40) + 25
ldx #0
fs_div_10:
lda work_lo
sec
sbc #10
bcc fs_done_10
sta work_lo
inx
jmp fs_div_10
fs_done_10:
txa
ora #$30
sta SCREEN + (8 * 40) + 26
lda work_lo
ora #$30
sta SCREEN + (8 * 40) + 27
lda #7
sta COLRAM + (8 * 40) + 23
sta COLRAM + (8 * 40) + 24
sta COLRAM + (8 * 40) + 25
sta COLRAM + (8 * 40) + 26
sta COLRAM + (8 * 40) + 27
rts
; ----------------------------------------------------------------------------
; Display Stat (2-digit) at various rows
; ----------------------------------------------------------------------------
display_stat_at_10:
ldx #0
stat10_div:
cmp #10
bcc stat10_done
sec
sbc #10
inx
jmp stat10_div
stat10_done:
pha
txa
ora #$30
sta SCREEN + (10 * 40) + 23
pla
ora #$30
sta SCREEN + (10 * 40) + 24
lda #1
sta COLRAM + (10 * 40) + 23
sta COLRAM + (10 * 40) + 24
rts
display_stat_at_11:
ldx #0
stat11_div:
cmp #10
bcc stat11_done
sec
sbc #10
inx
jmp stat11_div
stat11_done:
pha
txa
ora #$30
sta SCREEN + (11 * 40) + 23
pla
ora #$30
sta SCREEN + (11 * 40) + 24
lda #7
sta COLRAM + (11 * 40) + 23
sta COLRAM + (11 * 40) + 24
rts
display_stat_at_12:
ldx #0
stat12_div:
cmp #10
bcc stat12_done
sec
sbc #10
inx
jmp stat12_div
stat12_done:
pha
txa
ora #$30
sta SCREEN + (12 * 40) + 23
pla
ora #$30
sta SCREEN + (12 * 40) + 24
lda #2
sta COLRAM + (12 * 40) + 23
sta COLRAM + (12 * 40) + 24
rts
; ----------------------------------------------------------------------------
; Calculate Accuracy - (perfects + goods) * 100 / total_notes
; ----------------------------------------------------------------------------
calculate_accuracy:
; Total hits = perfects + goods
lda perfect_count
clc
adc good_count
sta total_hits
; Total notes = hits + misses
clc
adc miss_count
sta total_notes
; Avoid division by zero
beq accuracy_zero
; Simple percentage: (hits * 100) / total
; We'll approximate with (hits * 128) / total then scale
; Or simpler: multiply hits by 100, divide by total
lda total_hits
sta dividend_lo
lda #0
sta dividend_hi
; Multiply by 100
ldx #100
mult_loop:
dex
beq mult_done
lda dividend_lo
clc
adc total_hits
sta dividend_lo
lda dividend_hi
adc #0
sta dividend_hi
jmp mult_loop
mult_done:
; Divide by total_notes
lda #0
sta accuracy
div_loop:
lda dividend_lo
sec
sbc total_notes
tay
lda dividend_hi
sbc #0
bcc div_done
sta dividend_hi
sty dividend_lo
inc accuracy
jmp div_loop
div_done:
rts
accuracy_zero:
lda #0
sta accuracy
rts
total_hits: !byte 0
total_notes: !byte 0
dividend_lo: !byte 0
dividend_hi: !byte 0
accuracy: !byte 0
; ----------------------------------------------------------------------------
; Display Accuracy (3 digits + %)
; ----------------------------------------------------------------------------
display_accuracy:
lda accuracy
; Hundreds
ldx #0
acc_div_100:
cmp #100
bcc acc_done_100
sec
sbc #100
inx
jmp acc_div_100
acc_done_100:
pha
txa
ora #$30
sta SCREEN + (14 * 40) + 23
pla
; Tens
ldx #0
acc_div_10:
cmp #10
bcc acc_done_10
sec
sbc #10
inx
jmp acc_div_10
acc_done_10:
pha
txa
ora #$30
sta SCREEN + (14 * 40) + 24
pla
; Ones
ora #$30
sta SCREEN + (14 * 40) + 25
; Percent sign
lda #$25 ; %
sta SCREEN + (14 * 40) + 26
lda #5
sta COLRAM + (14 * 40) + 23
sta COLRAM + (14 * 40) + 24
sta COLRAM + (14 * 40) + 25
sta COLRAM + (14 * 40) + 26
rts
; ----------------------------------------------------------------------------
; Copy Character Set from ROM to RAM
; ----------------------------------------------------------------------------
copy_charset:
sei
lda $01
pha
and #$FB
sta $01
ldx #0
copy_loop:
lda $D000,x
sta CHARSET,x
lda $D100,x
sta CHARSET+$100,x
lda $D200,x
sta CHARSET+$200,x
lda $D300,x
sta CHARSET+$300,x
lda $D400,x
sta CHARSET+$400,x
lda $D500,x
sta CHARSET+$500,x
lda $D600,x
sta CHARSET+$600,x
lda $D700,x
sta CHARSET+$700,x
inx
bne copy_loop
pla
sta $01
cli
jsr define_custom_chars
lda #$1C
sta CHARPTR
rts
; ----------------------------------------------------------------------------
; Define Custom Characters
; ----------------------------------------------------------------------------
define_custom_chars:
lda #%00000110
sta CHARSET + (CHAR_NOTE * 8) + 0
lda #%00011110
sta CHARSET + (CHAR_NOTE * 8) + 1
lda #%01111110
sta CHARSET + (CHAR_NOTE * 8) + 2
lda #%11111110
sta CHARSET + (CHAR_NOTE * 8) + 3
lda #%11111110
sta CHARSET + (CHAR_NOTE * 8) + 4
lda #%01111110
sta CHARSET + (CHAR_NOTE * 8) + 5
lda #%00011110
sta CHARSET + (CHAR_NOTE * 8) + 6
lda #%00000110
sta CHARSET + (CHAR_NOTE * 8) + 7
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 0
sta CHARSET + (CHAR_TRACK * 8) + 1
sta CHARSET + (CHAR_TRACK * 8) + 2
lda #%11111111
sta CHARSET + (CHAR_TRACK * 8) + 3
sta CHARSET + (CHAR_TRACK * 8) + 4
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 5
sta CHARSET + (CHAR_TRACK * 8) + 6
sta CHARSET + (CHAR_TRACK * 8) + 7
lda #%01100110
sta CHARSET + (CHAR_HITZONE * 8) + 0
sta CHARSET + (CHAR_HITZONE * 8) + 1
sta CHARSET + (CHAR_HITZONE * 8) + 2
sta CHARSET + (CHAR_HITZONE * 8) + 3
sta CHARSET + (CHAR_HITZONE * 8) + 4
sta CHARSET + (CHAR_HITZONE * 8) + 5
sta CHARSET + (CHAR_HITZONE * 8) + 6
sta CHARSET + (CHAR_HITZONE * 8) + 7
lda #%11111111
sta CHARSET + (CHAR_BAR_FULL * 8) + 0
sta CHARSET + (CHAR_BAR_FULL * 8) + 1
sta CHARSET + (CHAR_BAR_FULL * 8) + 2
sta CHARSET + (CHAR_BAR_FULL * 8) + 3
sta CHARSET + (CHAR_BAR_FULL * 8) + 4
sta CHARSET + (CHAR_BAR_FULL * 8) + 5
sta CHARSET + (CHAR_BAR_FULL * 8) + 6
sta CHARSET + (CHAR_BAR_FULL * 8) + 7
lda #%11111111
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 0
lda #%10000001
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 1
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 2
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 3
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 4
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 5
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 6
lda #%11111111
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 7
rts
; ----------------------------------------------------------------------------
; Initialize Health
; ----------------------------------------------------------------------------
init_health:
lda #HEALTH_START
sta health
jsr display_health
rts
; ----------------------------------------------------------------------------
; Initialize Score
; ----------------------------------------------------------------------------
init_score:
lda #0
sta score_lo
sta score_hi
sta miss_count
sta perfect_count
sta good_count
jsr display_score
jsr display_misses
rts
; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------
init_notes:
ldx #MAX_NOTES-1
lda #0
init_notes_loop:
sta note_track,x
sta note_col,x
sta note_freq,x
dex
bpl init_notes_loop
rts
; ----------------------------------------------------------------------------
; Check Spawn Note
; ----------------------------------------------------------------------------
check_spawn_note:
lda song_ended
bne spawn_done_early
ldy #0
spawn_check_loop:
lda (song_pos),y
cmp #$FF
beq spawn_song_end
cmp beat_count
beq spawn_match
bcs spawn_done_early
jmp spawn_advance
spawn_match:
iny
lda (song_pos),y
sta temp_track
iny
lda (song_pos),y
pha
lda temp_track
jsr spawn_note_with_freq
pla
dey
dey
spawn_advance:
lda song_pos
clc
adc #3
sta song_pos
lda song_pos_hi
adc #0
sta song_pos_hi
jmp spawn_check_loop
spawn_song_end:
lda #1
sta song_ended
spawn_done_early:
rts
; ----------------------------------------------------------------------------
; Spawn Note With Frequency
; ----------------------------------------------------------------------------
spawn_note_with_freq:
sta temp_track
ldx #0
spawn_find_slot:
lda note_track,x
beq spawn_found_slot
inx
cpx #MAX_NOTES
bne spawn_find_slot
rts
spawn_found_slot:
lda temp_track
sta note_track,x
lda #NOTE_SPAWN_COL
sta note_col,x
tsx
lda $0103,x
sta note_freq,x
jsr draw_note
rts
; ----------------------------------------------------------------------------
; Update Notes
; ----------------------------------------------------------------------------
update_notes:
ldx #0
update_loop:
lda note_track,x
beq update_next
jsr erase_note
dec note_col,x
lda note_col,x
cmp #1
bcc update_miss
jsr draw_note
jmp update_next
update_miss:
lda note_track,x
sta miss_track
lda #0
sta note_track,x
jsr handle_miss
update_next:
inx
cpx #MAX_NOTES
bne update_loop
rts
; ----------------------------------------------------------------------------
; Handle Miss
; ----------------------------------------------------------------------------
handle_miss:
inc miss_count
jsr play_miss_sound
lda #MISS_COL
sta BORDER
lda #8
sta border_flash
jsr display_misses
jsr decrease_health
rts
; ----------------------------------------------------------------------------
; Decrease Health
; ----------------------------------------------------------------------------
decrease_health:
lda health
sec
sbc #HEALTH_MISS
bcc health_zero
sta health
jsr display_health
jsr check_game_over
rts
health_zero:
lda #0
sta health
jsr display_health
jsr check_game_over
rts
; ----------------------------------------------------------------------------
; Increase Health
; ----------------------------------------------------------------------------
increase_health:
clc
adc health
cmp #HEALTH_MAX
bcc health_ok
lda #HEALTH_MAX
health_ok:
sta health
jsr display_health
rts
; ----------------------------------------------------------------------------
; Check Game Over
; ----------------------------------------------------------------------------
check_game_over:
lda health
bne not_game_over
lda #STATE_GAMEOVER
sta game_state
jsr show_game_over
not_game_over:
rts
; ----------------------------------------------------------------------------
; Show Game Over
; ----------------------------------------------------------------------------
show_game_over:
ldx #0
game_over_loop:
lda game_over_text,x
beq game_over_done
sta SCREEN + (12 * 40) + 15,x
lda #2
sta COLRAM + (12 * 40) + 15,x
inx
jmp game_over_loop
game_over_done:
lda #2
sta BORDER
rts
game_over_text:
!scr "game over"
!byte 0
; ----------------------------------------------------------------------------
; Display Health
; ----------------------------------------------------------------------------
display_health:
lda health
lsr
lsr
lsr
sta temp_health
ldx #0
lda temp_health
beq draw_empty_bars
draw_full_bars:
lda #CHAR_BAR_FULL
sta SCREEN + (HEALTH_ROW * 40) + 12,x
lda #HEALTH_COL
sta COLRAM + (HEALTH_ROW * 40) + 12,x
inx
cpx temp_health
bne draw_full_bars
draw_empty_bars:
cpx #8
beq health_done
lda #CHAR_BAR_EMPTY
sta SCREEN + (HEALTH_ROW * 40) + 12,x
lda #11
sta COLRAM + (HEALTH_ROW * 40) + 12,x
inx
jmp draw_empty_bars
health_done:
rts
temp_health: !byte 0
; ----------------------------------------------------------------------------
; Play Miss Sound
; ----------------------------------------------------------------------------
play_miss_sound:
lda #0
sta SID_V3_FREQ_LO
lda #MISS_FREQ
sta SID_V3_FREQ_HI
lda #MISS_AD
sta SID_V3_AD
lda #MISS_SR
sta SID_V3_SR
lda #MISS_WAVE
ora #$01
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------
draw_note:
lda note_track,x
cmp #1
beq draw_note_t1
cmp #2
beq draw_note_t2
cmp #3
beq draw_note_t3
rts
draw_note_t1:
lda note_col,x
clc
adc #<(SCREEN + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_NOTE
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK1_NOTE_COL
sta (ZP_PTR),y
rts
draw_note_t2:
lda note_col,x
clc
adc #<(SCREEN + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_NOTE
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK2_NOTE_COL
sta (ZP_PTR),y
rts
draw_note_t3:
lda note_col,x
clc
adc #<(SCREEN + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_NOTE
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK3_NOTE_COL
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Erase Note
; ----------------------------------------------------------------------------
erase_note:
lda note_track,x
cmp #1
beq erase_note_t1
cmp #2
beq erase_note_t2
cmp #3
beq erase_note_t3
rts
erase_note_t1:
lda note_col,x
clc
adc #<(SCREEN + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_TRACK
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK_LINE_COL
sta (ZP_PTR),y
rts
erase_note_t2:
lda note_col,x
clc
adc #<(SCREEN + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_TRACK
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK_LINE_COL
sta (ZP_PTR),y
rts
erase_note_t3:
lda note_col,x
clc
adc #<(SCREEN + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #CHAR_TRACK
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #TRACK_LINE_COL
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Initialize Screen
; ----------------------------------------------------------------------------
init_screen:
lda #BORDER_COL
sta BORDER
lda #BG_COL
sta BGCOL
ldx #0
lda #CHAR_SPACE
clr_screen:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clr_screen
ldx #0
lda #TRACK_LINE_COL
clr_colour:
sta COLRAM,x
sta COLRAM+$100,x
sta COLRAM+$200,x
sta COLRAM+$2E8,x
inx
bne clr_colour
jsr draw_tracks
jsr draw_hit_zones
jsr draw_labels
rts
; ----------------------------------------------------------------------------
; Draw Tracks
; ----------------------------------------------------------------------------
draw_tracks:
lda #CHAR_TRACK
ldx #0
draw_t1:
sta SCREEN + (TRACK1_ROW * 40),x
inx
cpx #38
bne draw_t1
ldx #0
draw_t2:
sta SCREEN + (TRACK2_ROW * 40),x
inx
cpx #38
bne draw_t2
ldx #0
draw_t3:
sta SCREEN + (TRACK3_ROW * 40),x
inx
cpx #38
bne draw_t3
rts
; ----------------------------------------------------------------------------
; Draw Hit Zones
; ----------------------------------------------------------------------------
draw_hit_zones:
lda #CHAR_HITZONE
sta SCREEN + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
lda #HIT_ZONE_COL
sta COLRAM + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
rts
; ----------------------------------------------------------------------------
; Draw Labels
; ----------------------------------------------------------------------------
draw_labels:
ldx #0
draw_score_label:
lda score_label,x
beq draw_score_label_done
sta SCREEN + 1,x
lda #1
sta COLRAM + 1,x
inx
bne draw_score_label
draw_score_label_done:
ldx #0
draw_miss_label:
lda miss_label,x
beq draw_miss_label_done
sta SCREEN + 15,x
lda #2
sta COLRAM + 15,x
inx
bne draw_miss_label
draw_miss_label_done:
ldx #0
draw_title:
lda title_text,x
beq draw_title_done
sta SCREEN + 27,x
lda #1
sta COLRAM + 27,x
inx
bne draw_title
draw_title_done:
ldx #0
draw_health_label:
lda health_label,x
beq draw_health_label_done
sta SCREEN + (HEALTH_ROW * 40) + 4,x
lda #5
sta COLRAM + (HEALTH_ROW * 40) + 4,x
inx
bne draw_health_label
draw_health_label_done:
ldx #0
draw_progress_label:
lda progress_label,x
beq draw_progress_label_done
sta SCREEN + (PROGRESS_ROW * 40) + 4,x
lda #3
sta COLRAM + (PROGRESS_ROW * 40) + 4,x
inx
bne draw_progress_label
draw_progress_label_done:
lda #$1A
sta SCREEN + (TRACK1_ROW * 40)
lda #TRACK1_NOTE_COL
sta COLRAM + (TRACK1_ROW * 40)
lda #$18
sta SCREEN + (TRACK2_ROW * 40)
lda #TRACK2_NOTE_COL
sta COLRAM + (TRACK2_ROW * 40)
lda #$03
sta SCREEN + (TRACK3_ROW * 40)
lda #TRACK3_NOTE_COL
sta COLRAM + (TRACK3_ROW * 40)
rts
score_label:
!scr "score:"
!byte 0
miss_label:
!scr "miss:"
!byte 0
title_text:
!scr "sid symphony"
!byte 0
health_label:
!scr "health:"
!byte 0
progress_label:
!scr "song:"
!byte 0
; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------
init_sid:
ldx #$18
lda #0
clear_sid:
sta SID,x
dex
bpl clear_sid
lda #$0F
sta SID_VOLUME
lda #$00
sta SID_V1_FREQ_LO
lda #VOICE1_FREQ
sta SID_V1_FREQ_HI
lda #PULSE_WIDTH
sta SID_V1_PWHI
lda #VOICE_AD
sta SID_V1_AD
lda #VOICE_SR
sta SID_V1_SR
lda #$00
sta SID_V2_FREQ_LO
lda #VOICE2_FREQ
sta SID_V2_FREQ_HI
lda #PULSE_WIDTH
sta SID_V2_PWHI
lda #VOICE_AD
sta SID_V2_AD
lda #VOICE_SR
sta SID_V2_SR
lda #$00
sta SID_V3_FREQ_LO
lda #VOICE3_FREQ
sta SID_V3_FREQ_HI
lda #PULSE_WIDTH
sta SID_V3_PWHI
lda #VOICE_AD
sta SID_V3_AD
lda #VOICE_SR
sta SID_V3_SR
rts
; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------
reset_track_colours:
ldx #0
lda #TRACK_LINE_COL
reset_t1:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne reset_t1
ldx #0
reset_t2:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne reset_t2
ldx #0
reset_t3:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne reset_t3
lda #TRACK1_NOTE_COL
sta COLRAM + (TRACK1_ROW * 40)
lda #TRACK2_NOTE_COL
sta COLRAM + (TRACK2_ROW * 40)
lda #TRACK3_NOTE_COL
sta COLRAM + (TRACK3_ROW * 40)
lda #HIT_ZONE_COL
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
jsr redraw_all_notes
rts
; ----------------------------------------------------------------------------
; Redraw All Notes
; ----------------------------------------------------------------------------
redraw_all_notes:
ldx #0
redraw_loop:
lda note_track,x
beq redraw_next
jsr draw_note
redraw_next:
inx
cpx #MAX_NOTES
bne redraw_loop
rts
; ----------------------------------------------------------------------------
; Update Border Flash
; ----------------------------------------------------------------------------
update_border_flash:
lda border_flash
beq flash_done
dec border_flash
bne flash_done
lda #BORDER_COL
sta BORDER
flash_done:
rts
; ----------------------------------------------------------------------------
; Check Keys
; ----------------------------------------------------------------------------
check_keys:
lda #$FD
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_x_key
lda #1
sta key_pressed
jsr check_hit
bcc check_x_key
jsr play_voice1_note
jsr flash_track1_hit
jsr award_points
check_x_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$80
bne check_c_key
lda #2
sta key_pressed
jsr check_hit
bcc check_c_key
jsr play_voice2_note
jsr flash_track2_hit
jsr award_points
check_c_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_keys_done
lda #3
sta key_pressed
jsr check_hit
bcc check_keys_done
jsr play_voice3_note
jsr flash_track3_hit
jsr award_points
check_keys_done:
lda #$FF
sta CIA1_PRA
rts
; ----------------------------------------------------------------------------
; Check Hit
; ----------------------------------------------------------------------------
check_hit:
ldx #0
check_hit_loop:
lda note_track,x
beq check_hit_next
cmp key_pressed
bne check_hit_next
lda note_col,x
cmp #HIT_ZONE_MIN
bcc check_hit_next
cmp #HIT_ZONE_MAX+1
bcs check_hit_next
lda note_freq,x
sta hit_note_freq
lda note_col,x
cmp #HIT_ZONE_CENTRE
bcc hit_good
cmp #HIT_ZONE_CENTRE+2
bcs hit_good
lda #2
sta hit_quality
jmp hit_found
hit_good:
lda #1
sta hit_quality
hit_found:
jsr erase_note
lda #0
sta note_track,x
sec
rts
check_hit_next:
inx
cpx #MAX_NOTES
bne check_hit_loop
lda #0
sta hit_quality
clc
rts
; ----------------------------------------------------------------------------
; Award Points - Now tracks perfects and goods separately
; ----------------------------------------------------------------------------
award_points:
lda hit_quality
cmp #2
beq award_perfect
; Good hit
inc good_count
lda score_lo
clc
adc #GOOD_SCORE
sta score_lo
lda score_hi
adc #0
sta score_hi
lda #GOOD_COL
sta BORDER
lda #4
sta border_flash
lda #HEALTH_GOOD
jsr increase_health
jmp award_done
award_perfect:
; Perfect hit
inc perfect_count
lda score_lo
clc
adc #PERFECT_SCORE
sta score_lo
lda score_hi
adc #0
sta score_hi
lda #PERFECT_COL
sta BORDER
lda #6
sta border_flash
lda #HEALTH_PERFECT
jsr increase_health
award_done:
jsr display_score
rts
; ----------------------------------------------------------------------------
; Display Score
; ----------------------------------------------------------------------------
display_score:
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc done_10000
sta work_hi
sty work_lo
inx
jmp div_10000
done_10000:
txa
ora #$30
sta SCREEN + 8
ldx #0
div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc done_1000
sta work_hi
sty work_lo
inx
jmp div_1000
done_1000:
txa
ora #$30
sta SCREEN + 9
ldx #0
div_100:
lda work_lo
sec
sbc #100
bcc done_100
sta work_lo
inx
jmp div_100
done_100:
txa
ora #$30
sta SCREEN + 10
ldx #0
div_10:
lda work_lo
sec
sbc #10
bcc done_10
sta work_lo
inx
jmp div_10
done_10:
txa
ora #$30
sta SCREEN + 11
lda work_lo
ora #$30
sta SCREEN + 12
lda #7
sta COLRAM + 8
sta COLRAM + 9
sta COLRAM + 10
sta COLRAM + 11
sta COLRAM + 12
rts
; ----------------------------------------------------------------------------
; Display Misses
; ----------------------------------------------------------------------------
display_misses:
lda miss_count
ldx #0
miss_div_10:
cmp #10
bcc miss_done_10
sec
sbc #10
inx
jmp miss_div_10
miss_done_10:
pha
txa
ora #$30
sta SCREEN + 21
pla
ora #$30
sta SCREEN + 22
lda #2
sta COLRAM + 21
sta COLRAM + 22
rts
work_lo: !byte 0
work_hi: !byte 0
; ----------------------------------------------------------------------------
; Play Voices With Note Frequency
; ----------------------------------------------------------------------------
play_voice1_note:
lda #0
sta SID_V1_FREQ_LO
lda hit_note_freq
sta SID_V1_FREQ_HI
lda #VOICE1_WAVE
ora #$01
sta SID_V1_CTRL
rts
play_voice2_note:
lda #0
sta SID_V2_FREQ_LO
lda hit_note_freq
sta SID_V2_FREQ_HI
lda #VOICE2_WAVE
ora #$01
sta SID_V2_CTRL
rts
play_voice3_note:
lda #0
sta SID_V3_FREQ_LO
lda hit_note_freq
sta SID_V3_FREQ_HI
lda #VOICE3_WAVE
ora #$01
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Flash Tracks on Hit
; ----------------------------------------------------------------------------
flash_track1_hit:
ldx #0
lda #HIT_COL
flash_t1h_loop:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne flash_t1h_loop
lda #1
sta COLRAM + (TRACK1_ROW * 40)
rts
flash_track2_hit:
ldx #0
lda #HIT_COL
flash_t2h_loop:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne flash_t2h_loop
lda #1
sta COLRAM + (TRACK2_ROW * 40)
rts
flash_track3_hit:
ldx #0
lda #HIT_COL
flash_t3h_loop:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne flash_t3h_loop
lda #1
sta COLRAM + (TRACK3_ROW * 40)
rts
; ============================================================================
; SONG DATA
; ============================================================================
song_data:
!byte 0, 1, $47
!byte 2, 2, $2C
!byte 4, 3, $11
!byte 8, 1, $3B
!byte 10, 2, $27
!byte 12, 3, $13
!byte 16, 1, $35
!byte 17, 2, $2C
!byte 18, 1, $3B
!byte 20, 3, $16
!byte 24, 1, $47
!byte 26, 2, $35
!byte 28, 3, $11
!byte 32, 2, $2F
!byte 34, 1, $4F
!byte 36, 3, $17
!byte 40, 1, $58
!byte 42, 2, $2C
!byte 44, 3, $11
!byte 46, 2, $27
!byte 48, 1, $6A
!byte 49, 2, $35
!byte 50, 1, $58
!byte 52, 3, $1A
!byte 54, 2, $2F
!byte 56, 1, $47
!byte 58, 2, $2C
!byte 60, 3, $11
!byte 62, 1, $35
!byte $FF
; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------
note_track:
!fill MAX_NOTES, 0
note_col:
!fill MAX_NOTES, 0
note_freq:
!fill MAX_NOTES, 0
; ----------------------------------------------------------------------------
; Game Variables
; ----------------------------------------------------------------------------
score_lo: !byte 0
score_hi: !byte 0
miss_count: !byte 0
perfect_count: !byte 0
good_count: !byte 0
health: !byte 0
Try This: Grade Letters
Add letter grades based on accuracy:
lda accuracy
cmp #95
bcs grade_s ; S rank
cmp #80
bcs grade_a ; A rank
cmp #60
bcs grade_b ; B rank
; ... etc
Grades give players clear targets.
Try This: High Score
Save the best score:
; Compare current to high score
lda score_hi
cmp high_score_hi
bcc not_high
bne is_high
lda score_lo
cmp high_score_lo
bcc not_high
is_high:
; New high score!
; ... copy and display ...
High scores encourage replay.
What You’ve Learnt
- State machines - Different code paths for different game states
- End detection - Waiting for all notes to resolve
- Statistics tracking - Counting perfects, goods, misses separately
- Integer percentage - Multiply then divide for accuracy
- Any-key detection - Selecting all keyboard rows at once
What’s Next
In Unit 12, we’ll add a combo system. Consecutive hits build a multiplier, rewarding sustained accuracy with bonus points.
What Changed
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; SID SYMPHONY - Unit 10: Song Playback | |
| 2 | + | ; SID SYMPHONY - Unit 11: Song End and Results | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; Song plays with proper timing. Progress bar shows how far through the song. | |
| 5 | - | ; Notes spawn at exactly the right beat. The rhythm feels right. | |
| 4 | + | ; Detect song completion. Display results with score breakdown. The game | |
| 5 | + | ; has a proper ending now - victory or defeat, then replay. | |
| 6 | 6 | ; | |
| 7 | 7 | ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low) | |
| 8 | 8 | ; ============================================================================ | |
| ... | |||
| 12 | 12 | ; ============================================================================ | |
| 13 | 13 | | |
| 14 | 14 | ; SID Voice Settings | |
| 15 | - | VOICE1_WAVE = $21 ; Sawtooth | |
| 16 | - | VOICE2_WAVE = $41 ; Pulse | |
| 17 | - | VOICE3_WAVE = $11 ; Triangle | |
| 15 | + | VOICE1_WAVE = $21 | |
| 16 | + | VOICE2_WAVE = $41 | |
| 17 | + | VOICE3_WAVE = $11 | |
| 18 | 18 | | |
| 19 | 19 | VOICE1_FREQ = $1C | |
| 20 | 20 | VOICE2_FREQ = $0E | |
| ... | |||
| 47 | 47 | MISS_COL = 2 | |
| 48 | 48 | | |
| 49 | 49 | HEALTH_COL = 5 | |
| 50 | - | PROGRESS_COL = 3 ; Cyan for progress bar | |
| 50 | + | PROGRESS_COL = 3 | |
| 51 | 51 | | |
| 52 | 52 | ; ============================================================================ | |
| 53 | 53 | ; SCORING SETTINGS | |
| ... | |||
| 78 | 78 | ; SONG SETTINGS | |
| 79 | 79 | ; ============================================================================ | |
| 80 | 80 | | |
| 81 | - | SONG_LENGTH = 64 ; Total beats in the song | |
| 82 | - | PROGRESS_WIDTH = 16 ; Width of progress bar in characters | |
| 81 | + | SONG_LENGTH = 64 | |
| 82 | + | PROGRESS_WIDTH = 16 | |
| 83 | + | | |
| 84 | + | ; ============================================================================ | |
| 85 | + | ; GAME STATES | |
| 86 | + | ; ============================================================================ | |
| 87 | + | | |
| 88 | + | STATE_PLAYING = 0 | |
| 89 | + | STATE_RESULTS = 1 | |
| 90 | + | STATE_GAMEOVER = 2 | |
| 83 | 91 | | |
| 84 | 92 | ; ============================================================================ | |
| 85 | 93 | ; MEMORY MAP | |
| ... | |||
| 148 | 156 | | |
| 149 | 157 | ; Timing | |
| 150 | 158 | FRAMES_PER_BEAT = 25 | |
| 159 | + | END_DELAY_FRAMES = 75 ; Wait after last note before results | |
| 151 | 160 | | |
| 152 | 161 | ; Zero page | |
| 153 | 162 | ZP_PTR = $FB | |
| ... | |||
| 163 | 172 | hit_quality = $08 | |
| 164 | 173 | border_flash = $09 | |
| 165 | 174 | miss_track = $0A | |
| 166 | - | game_over = $0B | |
| 175 | + | game_state = $0B | |
| 167 | 176 | hit_note_freq = $0C | |
| 168 | - | song_beat = $0D ; Current beat in song (0 to SONG_LENGTH-1) | |
| 177 | + | song_beat = $0D | |
| 178 | + | song_ended = $0E ; Flag: song data exhausted | |
| 179 | + | end_delay = $0F ; Countdown after last note | |
| 169 | 180 | | |
| 170 | 181 | ; ---------------------------------------------------------------------------- | |
| 171 | 182 | ; BASIC Stub | |
| ... | |||
| 188 | 199 | | |
| 189 | 200 | start: | |
| 190 | 201 | jsr copy_charset | |
| 202 | + | jsr init_game | |
| 203 | + | | |
| 204 | + | main_loop: | |
| 205 | + | lda #$FF | |
| 206 | + | wait_raster: | |
| 207 | + | cmp $D012 | |
| 208 | + | bne wait_raster | |
| 209 | + | | |
| 210 | + | lda game_state | |
| 211 | + | cmp #STATE_PLAYING | |
| 212 | + | beq do_playing | |
| 213 | + | cmp #STATE_RESULTS | |
| 214 | + | beq do_results | |
| 215 | + | jmp do_gameover | |
| 216 | + | | |
| 217 | + | do_playing: | |
| 218 | + | jsr update_playing | |
| 219 | + | jmp main_loop | |
| 220 | + | | |
| 221 | + | do_results: | |
| 222 | + | jsr update_results | |
| 223 | + | jmp main_loop | |
| 224 | + | | |
| 225 | + | do_gameover: | |
| 226 | + | jsr update_gameover | |
| 227 | + | jmp main_loop | |
| 228 | + | | |
| 229 | + | ; ---------------------------------------------------------------------------- | |
| 230 | + | ; Initialize Game | |
| 231 | + | ; ---------------------------------------------------------------------------- | |
| 232 | + | | |
| 233 | + | init_game: | |
| 191 | 234 | jsr init_screen | |
| 192 | 235 | jsr init_sid | |
| 193 | 236 | jsr init_notes | |
| ... | |||
| 195 | 238 | jsr init_health | |
| 196 | 239 | jsr init_song | |
| 197 | 240 | | |
| 198 | - | main_loop: | |
| 199 | - | lda #$FF | |
| 200 | - | wait_raster: | |
| 201 | - | cmp $D012 | |
| 202 | - | bne wait_raster | |
| 241 | + | lda #STATE_PLAYING | |
| 242 | + | sta game_state | |
| 203 | 243 | | |
| 204 | - | lda game_over | |
| 205 | - | bne main_loop | |
| 244 | + | rts | |
| 245 | + | | |
| 246 | + | ; ---------------------------------------------------------------------------- | |
| 247 | + | ; Update Playing State | |
| 248 | + | ; ---------------------------------------------------------------------------- | |
| 206 | 249 | | |
| 250 | + | update_playing: | |
| 207 | 251 | inc frame_count | |
| 208 | 252 | lda frame_count | |
| 209 | 253 | cmp #FRAMES_PER_BEAT | |
| ... | |||
| 220 | 264 | jsr update_border_flash | |
| 221 | 265 | jsr check_keys | |
| 222 | 266 | | |
| 223 | - | jmp main_loop | |
| 267 | + | ; Check for song end | |
| 268 | + | jsr check_song_end | |
| 269 | + | | |
| 270 | + | rts | |
| 271 | + | | |
| 272 | + | ; ---------------------------------------------------------------------------- | |
| 273 | + | ; Update Results State | |
| 274 | + | ; ---------------------------------------------------------------------------- | |
| 275 | + | | |
| 276 | + | update_results: | |
| 277 | + | ; Wait for any key to restart | |
| 278 | + | lda #$00 | |
| 279 | + | sta CIA1_PRA | |
| 280 | + | lda CIA1_PRB | |
| 281 | + | cmp #$FF | |
| 282 | + | beq results_wait | |
| 283 | + | | |
| 284 | + | ; Key pressed - restart game | |
| 285 | + | jsr init_game | |
| 286 | + | | |
| 287 | + | results_wait: | |
| 288 | + | lda #$FF | |
| 289 | + | sta CIA1_PRA | |
| 290 | + | rts | |
| 291 | + | | |
| 292 | + | ; ---------------------------------------------------------------------------- | |
| 293 | + | ; Update Game Over State | |
| 294 | + | ; ---------------------------------------------------------------------------- | |
| 295 | + | | |
| 296 | + | update_gameover: | |
| 297 | + | ; Wait for any key to restart | |
| 298 | + | lda #$00 | |
| 299 | + | sta CIA1_PRA | |
| 300 | + | lda CIA1_PRB | |
| 301 | + | cmp #$FF | |
| 302 | + | beq gameover_wait | |
| 303 | + | | |
| 304 | + | ; Key pressed - restart game | |
| 305 | + | jsr init_game | |
| 306 | + | | |
| 307 | + | gameover_wait: | |
| 308 | + | lda #$FF | |
| 309 | + | sta CIA1_PRA | |
| 310 | + | rts | |
| 311 | + | | |
| 312 | + | ; ---------------------------------------------------------------------------- | |
| 313 | + | ; Check Song End | |
| 314 | + | ; ---------------------------------------------------------------------------- | |
| 315 | + | | |
| 316 | + | check_song_end: | |
| 317 | + | ; First check if song data exhausted | |
| 318 | + | lda song_ended | |
| 319 | + | beq song_not_ended | |
| 320 | + | | |
| 321 | + | ; Song data done - wait for all notes to clear | |
| 322 | + | ldx #0 | |
| 323 | + | check_notes_clear: | |
| 324 | + | lda note_track,x | |
| 325 | + | bne notes_still_active | |
| 326 | + | inx | |
| 327 | + | cpx #MAX_NOTES | |
| 328 | + | bne check_notes_clear | |
| 329 | + | | |
| 330 | + | ; All notes cleared - count down delay | |
| 331 | + | dec end_delay | |
| 332 | + | bne song_not_ended | |
| 333 | + | | |
| 334 | + | ; Delay done - show results | |
| 335 | + | jsr show_results | |
| 336 | + | lda #STATE_RESULTS | |
| 337 | + | sta game_state | |
| 338 | + | | |
| 339 | + | notes_still_active: | |
| 340 | + | song_not_ended: | |
| 341 | + | rts | |
| 224 | 342 | | |
| 225 | 343 | ; ---------------------------------------------------------------------------- | |
| 226 | 344 | ; Initialize Song | |
| ... | |||
| 237 | 355 | sta beat_count | |
| 238 | 356 | sta song_beat | |
| 239 | 357 | sta border_flash | |
| 240 | - | sta game_over | |
| 358 | + | sta song_ended | |
| 359 | + | | |
| 360 | + | lda #END_DELAY_FRAMES | |
| 361 | + | sta end_delay | |
| 241 | 362 | | |
| 242 | 363 | jsr display_progress | |
| 243 | 364 | rts | |
| 244 | 365 | | |
| 245 | 366 | ; ---------------------------------------------------------------------------- | |
| 246 | - | ; Advance Song - Increment beat and update progress | |
| 367 | + | ; Advance Song | |
| 247 | 368 | ; ---------------------------------------------------------------------------- | |
| 248 | 369 | | |
| 249 | 370 | advance_song: | |
| 371 | + | lda song_ended | |
| 372 | + | bne advance_done | |
| 373 | + | | |
| 250 | 374 | inc beat_count | |
| 251 | 375 | inc song_beat | |
| 252 | 376 | | |
| 253 | - | ; Check if song has ended | |
| 254 | 377 | lda song_beat | |
| 255 | 378 | cmp #SONG_LENGTH | |
| 256 | 379 | bcc song_continues | |
| 257 | 380 | | |
| 258 | - | ; Song loops for now (Unit 11 will add proper end) | |
| 259 | - | lda #0 | |
| 260 | - | sta song_beat | |
| 261 | - | sta beat_count | |
| 262 | - | lda #<song_data | |
| 263 | - | sta song_pos | |
| 264 | - | lda #>song_data | |
| 265 | - | sta song_pos_hi | |
| 381 | + | ; Song reached end | |
| 382 | + | lda #1 | |
| 383 | + | sta song_ended | |
| 266 | 384 | | |
| 267 | 385 | song_continues: | |
| 268 | 386 | jsr display_progress | |
| 387 | + | | |
| 388 | + | advance_done: | |
| 269 | 389 | rts | |
| 270 | 390 | | |
| 271 | 391 | ; ---------------------------------------------------------------------------- | |
| 272 | - | ; Display Progress - Show song progress bar | |
| 392 | + | ; Display Progress | |
| 273 | 393 | ; ---------------------------------------------------------------------------- | |
| 274 | 394 | | |
| 275 | 395 | display_progress: | |
| 276 | - | ; Calculate progress: (song_beat * PROGRESS_WIDTH) / SONG_LENGTH | |
| 277 | - | ; For 64-beat song with 16-char bar: song_beat / 4 | |
| 278 | - | | |
| 279 | 396 | lda song_beat | |
| 280 | - | lsr ; Divide by 4 | |
| 397 | + | lsr | |
| 281 | 398 | lsr | |
| 282 | 399 | sta temp_progress | |
| 283 | 400 | | |
| 284 | - | ; Draw filled portion | |
| 285 | 401 | ldx #0 | |
| 286 | 402 | lda temp_progress | |
| 287 | 403 | beq draw_empty_progress | |
| ... | |||
| 300 | 416 | beq progress_done | |
| 301 | 417 | lda #CHAR_BAR_EMPTY | |
| 302 | 418 | sta SCREEN + (PROGRESS_ROW * 40) + 12,x | |
| 303 | - | lda #11 ; Dark grey | |
| 419 | + | lda #11 | |
| 304 | 420 | sta COLRAM + (PROGRESS_ROW * 40) + 12,x | |
| 305 | 421 | inx | |
| 306 | 422 | jmp draw_empty_progress | |
| ... | |||
| 309 | 425 | rts | |
| 310 | 426 | | |
| 311 | 427 | temp_progress: !byte 0 | |
| 428 | + | | |
| 429 | + | ; ---------------------------------------------------------------------------- | |
| 430 | + | ; Show Results Screen | |
| 431 | + | ; ---------------------------------------------------------------------------- | |
| 432 | + | | |
| 433 | + | show_results: | |
| 434 | + | ; Clear screen | |
| 435 | + | ldx #0 | |
| 436 | + | lda #CHAR_SPACE | |
| 437 | + | clear_for_results: | |
| 438 | + | sta SCREEN,x | |
| 439 | + | sta SCREEN+$100,x | |
| 440 | + | sta SCREEN+$200,x | |
| 441 | + | sta SCREEN+$2E8,x | |
| 442 | + | inx | |
| 443 | + | bne clear_for_results | |
| 444 | + | | |
| 445 | + | ; Title | |
| 446 | + | ldx #0 | |
| 447 | + | draw_results_title: | |
| 448 | + | lda results_title,x | |
| 449 | + | beq draw_results_title_done | |
| 450 | + | sta SCREEN + (4 * 40) + 14,x | |
| 451 | + | lda #1 | |
| 452 | + | sta COLRAM + (4 * 40) + 14,x | |
| 453 | + | inx | |
| 454 | + | jmp draw_results_title | |
| 455 | + | draw_results_title_done: | |
| 456 | + | | |
| 457 | + | ; Score label and value | |
| 458 | + | ldx #0 | |
| 459 | + | draw_final_score_label: | |
| 460 | + | lda final_score_label,x | |
| 461 | + | beq draw_final_score_done | |
| 462 | + | sta SCREEN + (8 * 40) + 10,x | |
| 463 | + | lda #7 | |
| 464 | + | sta COLRAM + (8 * 40) + 10,x | |
| 465 | + | inx | |
| 466 | + | jmp draw_final_score_label | |
| 467 | + | draw_final_score_done: | |
| 468 | + | jsr display_final_score | |
| 469 | + | | |
| 470 | + | ; Perfects | |
| 471 | + | ldx #0 | |
| 472 | + | draw_perfects_label: | |
| 473 | + | lda perfects_label,x | |
| 474 | + | beq draw_perfects_done | |
| 475 | + | sta SCREEN + (10 * 40) + 10,x | |
| 476 | + | lda #1 | |
| 477 | + | sta COLRAM + (10 * 40) + 10,x | |
| 478 | + | inx | |
| 479 | + | jmp draw_perfects_label | |
| 480 | + | draw_perfects_done: | |
| 481 | + | lda perfect_count | |
| 482 | + | jsr display_stat_at_10 | |
| 483 | + | | |
| 484 | + | ; Goods | |
| 485 | + | ldx #0 | |
| 486 | + | draw_goods_label: | |
| 487 | + | lda goods_label,x | |
| 488 | + | beq draw_goods_done | |
| 489 | + | sta SCREEN + (11 * 40) + 10,x | |
| 490 | + | lda #7 | |
| 491 | + | sta COLRAM + (11 * 40) + 10,x | |
| 492 | + | inx | |
| 493 | + | jmp draw_goods_label | |
| 494 | + | draw_goods_done: | |
| 495 | + | lda good_count | |
| 496 | + | jsr display_stat_at_11 | |
| 497 | + | | |
| 498 | + | ; Misses | |
| 499 | + | ldx #0 | |
| 500 | + | draw_misses_label: | |
| 501 | + | lda misses_label,x | |
| 502 | + | beq draw_misses_done | |
| 503 | + | sta SCREEN + (12 * 40) + 10,x | |
| 504 | + | lda #2 | |
| 505 | + | sta COLRAM + (12 * 40) + 10,x | |
| 506 | + | inx | |
| 507 | + | jmp draw_misses_label | |
| 508 | + | draw_misses_done: | |
| 509 | + | lda miss_count | |
| 510 | + | jsr display_stat_at_12 | |
| 511 | + | | |
| 512 | + | ; Accuracy (simplified: just show hit percentage) | |
| 513 | + | ldx #0 | |
| 514 | + | draw_accuracy_label: | |
| 515 | + | lda accuracy_label,x | |
| 516 | + | beq draw_accuracy_done | |
| 517 | + | sta SCREEN + (14 * 40) + 10,x | |
| 518 | + | lda #5 | |
| 519 | + | sta COLRAM + (14 * 40) + 10,x | |
| 520 | + | inx | |
| 521 | + | jmp draw_accuracy_label | |
| 522 | + | draw_accuracy_done: | |
| 523 | + | jsr calculate_accuracy | |
| 524 | + | jsr display_accuracy | |
| 525 | + | | |
| 526 | + | ; Press any key message | |
| 527 | + | ldx #0 | |
| 528 | + | draw_press_key: | |
| 529 | + | lda press_key_text,x | |
| 530 | + | beq draw_press_key_done | |
| 531 | + | sta SCREEN + (18 * 40) + 10,x | |
| 532 | + | lda #11 | |
| 533 | + | sta COLRAM + (18 * 40) + 10,x | |
| 534 | + | inx | |
| 535 | + | jmp draw_press_key | |
| 536 | + | draw_press_key_done: | |
| 537 | + | | |
| 538 | + | ; Green border for victory | |
| 539 | + | lda #5 | |
| 540 | + | sta BORDER | |
| 541 | + | | |
| 542 | + | rts | |
| 543 | + | | |
| 544 | + | results_title: | |
| 545 | + | !scr "song complete!" | |
| 546 | + | !byte 0 | |
| 547 | + | | |
| 548 | + | final_score_label: | |
| 549 | + | !scr "final score:" | |
| 550 | + | !byte 0 | |
| 551 | + | | |
| 552 | + | perfects_label: | |
| 553 | + | !scr "perfects:" | |
| 554 | + | !byte 0 | |
| 555 | + | | |
| 556 | + | goods_label: | |
| 557 | + | !scr "goods:" | |
| 558 | + | !byte 0 | |
| 559 | + | | |
| 560 | + | misses_label: | |
| 561 | + | !scr "misses:" | |
| 562 | + | !byte 0 | |
| 563 | + | | |
| 564 | + | accuracy_label: | |
| 565 | + | !scr "accuracy:" | |
| 566 | + | !byte 0 | |
| 567 | + | | |
| 568 | + | press_key_text: | |
| 569 | + | !scr "press any key" | |
| 570 | + | !byte 0 | |
| 571 | + | | |
| 572 | + | ; ---------------------------------------------------------------------------- | |
| 573 | + | ; Display Final Score (at row 8) | |
| 574 | + | ; ---------------------------------------------------------------------------- | |
| 575 | + | | |
| 576 | + | display_final_score: | |
| 577 | + | lda score_lo | |
| 578 | + | sta work_lo | |
| 579 | + | lda score_hi | |
| 580 | + | sta work_hi | |
| 581 | + | | |
| 582 | + | ldx #0 | |
| 583 | + | fs_div_10000: | |
| 584 | + | lda work_lo | |
| 585 | + | sec | |
| 586 | + | sbc #<10000 | |
| 587 | + | tay | |
| 588 | + | lda work_hi | |
| 589 | + | sbc #>10000 | |
| 590 | + | bcc fs_done_10000 | |
| 591 | + | sta work_hi | |
| 592 | + | sty work_lo | |
| 593 | + | inx | |
| 594 | + | jmp fs_div_10000 | |
| 595 | + | fs_done_10000: | |
| 596 | + | txa | |
| 597 | + | ora #$30 | |
| 598 | + | sta SCREEN + (8 * 40) + 23 | |
| 599 | + | | |
| 600 | + | ldx #0 | |
| 601 | + | fs_div_1000: | |
| 602 | + | lda work_lo | |
| 603 | + | sec | |
| 604 | + | sbc #<1000 | |
| 605 | + | tay | |
| 606 | + | lda work_hi | |
| 607 | + | sbc #>1000 | |
| 608 | + | bcc fs_done_1000 | |
| 609 | + | sta work_hi | |
| 610 | + | sty work_lo | |
| 611 | + | inx | |
| 612 | + | jmp fs_div_1000 | |
| 613 | + | fs_done_1000: | |
| 614 | + | txa | |
| 615 | + | ora #$30 | |
| 616 | + | sta SCREEN + (8 * 40) + 24 | |
| 617 | + | | |
| 618 | + | ldx #0 | |
| 619 | + | fs_div_100: | |
| 620 | + | lda work_lo | |
| 621 | + | sec | |
| 622 | + | sbc #100 | |
| 623 | + | bcc fs_done_100 | |
| 624 | + | sta work_lo | |
| 625 | + | inx | |
| 626 | + | jmp fs_div_100 | |
| 627 | + | fs_done_100: | |
| 628 | + | txa | |
| 629 | + | ora #$30 | |
| 630 | + | sta SCREEN + (8 * 40) + 25 | |
| 631 | + | | |
| 632 | + | ldx #0 | |
| 633 | + | fs_div_10: | |
| 634 | + | lda work_lo | |
| 635 | + | sec | |
| 636 | + | sbc #10 | |
| 637 | + | bcc fs_done_10 | |
| 638 | + | sta work_lo | |
| 639 | + | inx | |
| 640 | + | jmp fs_div_10 | |
| 641 | + | fs_done_10: | |
| 642 | + | txa | |
| 643 | + | ora #$30 | |
| 644 | + | sta SCREEN + (8 * 40) + 26 | |
| 645 | + | | |
| 646 | + | lda work_lo | |
| 647 | + | ora #$30 | |
| 648 | + | sta SCREEN + (8 * 40) + 27 | |
| 649 | + | | |
| 650 | + | lda #7 | |
| 651 | + | sta COLRAM + (8 * 40) + 23 | |
| 652 | + | sta COLRAM + (8 * 40) + 24 | |
| 653 | + | sta COLRAM + (8 * 40) + 25 | |
| 654 | + | sta COLRAM + (8 * 40) + 26 | |
| 655 | + | sta COLRAM + (8 * 40) + 27 | |
| 656 | + | | |
| 657 | + | rts | |
| 658 | + | | |
| 659 | + | ; ---------------------------------------------------------------------------- | |
| 660 | + | ; Display Stat (2-digit) at various rows | |
| 661 | + | ; ---------------------------------------------------------------------------- | |
| 662 | + | | |
| 663 | + | display_stat_at_10: | |
| 664 | + | ldx #0 | |
| 665 | + | stat10_div: | |
| 666 | + | cmp #10 | |
| 667 | + | bcc stat10_done | |
| 668 | + | sec | |
| 669 | + | sbc #10 | |
| 670 | + | inx | |
| 671 | + | jmp stat10_div | |
| 672 | + | stat10_done: | |
| 673 | + | pha | |
| 674 | + | txa | |
| 675 | + | ora #$30 | |
| 676 | + | sta SCREEN + (10 * 40) + 23 | |
| 677 | + | pla | |
| 678 | + | ora #$30 | |
| 679 | + | sta SCREEN + (10 * 40) + 24 | |
| 680 | + | lda #1 | |
| 681 | + | sta COLRAM + (10 * 40) + 23 | |
| 682 | + | sta COLRAM + (10 * 40) + 24 | |
| 683 | + | rts | |
| 684 | + | | |
| 685 | + | display_stat_at_11: | |
| 686 | + | ldx #0 | |
| 687 | + | stat11_div: | |
| 688 | + | cmp #10 | |
| 689 | + | bcc stat11_done | |
| 690 | + | sec | |
| 691 | + | sbc #10 | |
| 692 | + | inx | |
| 693 | + | jmp stat11_div | |
| 694 | + | stat11_done: | |
| 695 | + | pha | |
| 696 | + | txa | |
| 697 | + | ora #$30 | |
| 698 | + | sta SCREEN + (11 * 40) + 23 | |
| 699 | + | pla | |
| 700 | + | ora #$30 | |
| 701 | + | sta SCREEN + (11 * 40) + 24 | |
| 702 | + | lda #7 | |
| 703 | + | sta COLRAM + (11 * 40) + 23 | |
| 704 | + | sta COLRAM + (11 * 40) + 24 | |
| 705 | + | rts | |
| 706 | + | | |
| 707 | + | display_stat_at_12: | |
| 708 | + | ldx #0 | |
| 709 | + | stat12_div: | |
| 710 | + | cmp #10 | |
| 711 | + | bcc stat12_done | |
| 712 | + | sec | |
| 713 | + | sbc #10 | |
| 714 | + | inx | |
| 715 | + | jmp stat12_div | |
| 716 | + | stat12_done: | |
| 717 | + | pha | |
| 718 | + | txa | |
| 719 | + | ora #$30 | |
| 720 | + | sta SCREEN + (12 * 40) + 23 | |
| 721 | + | pla | |
| 722 | + | ora #$30 | |
| 723 | + | sta SCREEN + (12 * 40) + 24 | |
| 724 | + | lda #2 | |
| 725 | + | sta COLRAM + (12 * 40) + 23 | |
| 726 | + | sta COLRAM + (12 * 40) + 24 | |
| 727 | + | rts | |
| 728 | + | | |
| 729 | + | ; ---------------------------------------------------------------------------- | |
| 730 | + | ; Calculate Accuracy - (perfects + goods) * 100 / total_notes | |
| 731 | + | ; ---------------------------------------------------------------------------- | |
| 732 | + | | |
| 733 | + | calculate_accuracy: | |
| 734 | + | ; Total hits = perfects + goods | |
| 735 | + | lda perfect_count | |
| 736 | + | clc | |
| 737 | + | adc good_count | |
| 738 | + | sta total_hits | |
| 739 | + | | |
| 740 | + | ; Total notes = hits + misses | |
| 741 | + | clc | |
| 742 | + | adc miss_count | |
| 743 | + | sta total_notes | |
| 744 | + | | |
| 745 | + | ; Avoid division by zero | |
| 746 | + | beq accuracy_zero | |
| 747 | + | | |
| 748 | + | ; Simple percentage: (hits * 100) / total | |
| 749 | + | ; We'll approximate with (hits * 128) / total then scale | |
| 750 | + | ; Or simpler: multiply hits by 100, divide by total | |
| 751 | + | | |
| 752 | + | lda total_hits | |
| 753 | + | sta dividend_lo | |
| 754 | + | lda #0 | |
| 755 | + | sta dividend_hi | |
| 756 | + | | |
| 757 | + | ; Multiply by 100 | |
| 758 | + | ldx #100 | |
| 759 | + | mult_loop: | |
| 760 | + | dex | |
| 761 | + | beq mult_done | |
| 762 | + | lda dividend_lo | |
| 763 | + | clc | |
| 764 | + | adc total_hits | |
| 765 | + | sta dividend_lo | |
| 766 | + | lda dividend_hi | |
| 767 | + | adc #0 | |
| 768 | + | sta dividend_hi | |
| 769 | + | jmp mult_loop | |
| 770 | + | mult_done: | |
| 771 | + | | |
| 772 | + | ; Divide by total_notes | |
| 773 | + | lda #0 | |
| 774 | + | sta accuracy | |
| 775 | + | div_loop: | |
| 776 | + | lda dividend_lo | |
| 777 | + | sec | |
| 778 | + | sbc total_notes | |
| 779 | + | tay | |
| 780 | + | lda dividend_hi | |
| 781 | + | sbc #0 | |
| 782 | + | bcc div_done | |
| 783 | + | sta dividend_hi | |
| 784 | + | sty dividend_lo | |
| 785 | + | inc accuracy | |
| 786 | + | jmp div_loop | |
| 787 | + | div_done: | |
| 788 | + | rts | |
| 789 | + | | |
| 790 | + | accuracy_zero: | |
| 791 | + | lda #0 | |
| 792 | + | sta accuracy | |
| 793 | + | rts | |
| 794 | + | | |
| 795 | + | total_hits: !byte 0 | |
| 796 | + | total_notes: !byte 0 | |
| 797 | + | dividend_lo: !byte 0 | |
| 798 | + | dividend_hi: !byte 0 | |
| 799 | + | accuracy: !byte 0 | |
| 800 | + | | |
| 801 | + | ; ---------------------------------------------------------------------------- | |
| 802 | + | ; Display Accuracy (3 digits + %) | |
| 803 | + | ; ---------------------------------------------------------------------------- | |
| 804 | + | | |
| 805 | + | display_accuracy: | |
| 806 | + | lda accuracy | |
| 807 | + | | |
| 808 | + | ; Hundreds | |
| 809 | + | ldx #0 | |
| 810 | + | acc_div_100: | |
| 811 | + | cmp #100 | |
| 812 | + | bcc acc_done_100 | |
| 813 | + | sec | |
| 814 | + | sbc #100 | |
| 815 | + | inx | |
| 816 | + | jmp acc_div_100 | |
| 817 | + | acc_done_100: | |
| 818 | + | pha | |
| 819 | + | txa | |
| 820 | + | ora #$30 | |
| 821 | + | sta SCREEN + (14 * 40) + 23 | |
| 822 | + | pla | |
| 823 | + | | |
| 824 | + | ; Tens | |
| 825 | + | ldx #0 | |
| 826 | + | acc_div_10: | |
| 827 | + | cmp #10 | |
| 828 | + | bcc acc_done_10 | |
| 829 | + | sec | |
| 830 | + | sbc #10 | |
| 831 | + | inx | |
| 832 | + | jmp acc_div_10 | |
| 833 | + | acc_done_10: | |
| 834 | + | pha | |
| 835 | + | txa | |
| 836 | + | ora #$30 | |
| 837 | + | sta SCREEN + (14 * 40) + 24 | |
| 838 | + | pla | |
| 839 | + | | |
| 840 | + | ; Ones | |
| 841 | + | ora #$30 | |
| 842 | + | sta SCREEN + (14 * 40) + 25 | |
| 843 | + | | |
| 844 | + | ; Percent sign | |
| 845 | + | lda #$25 ; % | |
| 846 | + | sta SCREEN + (14 * 40) + 26 | |
| 847 | + | | |
| 848 | + | lda #5 | |
| 849 | + | sta COLRAM + (14 * 40) + 23 | |
| 850 | + | sta COLRAM + (14 * 40) + 24 | |
| 851 | + | sta COLRAM + (14 * 40) + 25 | |
| 852 | + | sta COLRAM + (14 * 40) + 26 | |
| 853 | + | | |
| 854 | + | rts | |
| 312 | 855 | | |
| 313 | 856 | ; ---------------------------------------------------------------------------- | |
| 314 | 857 | ; Copy Character Set from ROM to RAM | |
| ... | |||
| 442 | 985 | sta score_lo | |
| 443 | 986 | sta score_hi | |
| 444 | 987 | sta miss_count | |
| 988 | + | sta perfect_count | |
| 989 | + | sta good_count | |
| 445 | 990 | jsr display_score | |
| 446 | 991 | jsr display_misses | |
| 447 | 992 | rts | |
| ... | |||
| 466 | 1011 | ; ---------------------------------------------------------------------------- | |
| 467 | 1012 | | |
| 468 | 1013 | check_spawn_note: | |
| 1014 | + | lda song_ended | |
| 1015 | + | bne spawn_done_early | |
| 1016 | + | | |
| 469 | 1017 | ldy #0 | |
| 470 | 1018 | | |
| 471 | 1019 | spawn_check_loop: | |
| 472 | 1020 | lda (song_pos),y | |
| 473 | 1021 | cmp #$FF | |
| 474 | - | beq spawn_done ; Don't auto-restart here | |
| 1022 | + | beq spawn_song_end | |
| 475 | 1023 | | |
| 476 | 1024 | cmp beat_count | |
| 477 | 1025 | beq spawn_match | |
| 478 | - | bcs spawn_done | |
| 1026 | + | bcs spawn_done_early | |
| 479 | 1027 | | |
| 480 | 1028 | jmp spawn_advance | |
| 481 | 1029 | | |
| ... | |||
| 502 | 1050 | sta song_pos_hi | |
| 503 | 1051 | jmp spawn_check_loop | |
| 504 | 1052 | | |
| 505 | - | spawn_done: | |
| 1053 | + | spawn_song_end: | |
| 1054 | + | lda #1 | |
| 1055 | + | sta song_ended | |
| 1056 | + | | |
| 1057 | + | spawn_done_early: | |
| 506 | 1058 | rts | |
| 507 | 1059 | | |
| 508 | 1060 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 631 | 1183 | lda health | |
| 632 | 1184 | bne not_game_over | |
| 633 | 1185 | | |
| 634 | - | lda #1 | |
| 635 | - | sta game_over | |
| 1186 | + | lda #STATE_GAMEOVER | |
| 1187 | + | sta game_state | |
| 636 | 1188 | jsr show_game_over | |
| 637 | 1189 | | |
| 638 | 1190 | not_game_over: | |
| ... | |||
| 1322 | 1874 | rts | |
| 1323 | 1875 | | |
| 1324 | 1876 | ; ---------------------------------------------------------------------------- | |
| 1325 | - | ; Award Points | |
| 1877 | + | ; Award Points - Now tracks perfects and goods separately | |
| 1326 | 1878 | ; ---------------------------------------------------------------------------- | |
| 1327 | 1879 | | |
| 1328 | 1880 | award_points: | |
| 1329 | 1881 | lda hit_quality | |
| 1330 | 1882 | cmp #2 | |
| 1331 | 1883 | beq award_perfect | |
| 1884 | + | | |
| 1885 | + | ; Good hit | |
| 1886 | + | inc good_count | |
| 1332 | 1887 | | |
| 1333 | 1888 | lda score_lo | |
| 1334 | 1889 | clc | |
| ... | |||
| 1349 | 1904 | jmp award_done | |
| 1350 | 1905 | | |
| 1351 | 1906 | award_perfect: | |
| 1907 | + | ; Perfect hit | |
| 1908 | + | inc perfect_count | |
| 1909 | + | | |
| 1352 | 1910 | lda score_lo | |
| 1353 | 1911 | clc | |
| 1354 | 1912 | adc #PERFECT_SCORE | |
| ... | |||
| 1568 | 2126 | ; ============================================================================ | |
| 1569 | 2127 | | |
| 1570 | 2128 | song_data: | |
| 1571 | - | ; Bar 1 | |
| 1572 | 2129 | !byte 0, 1, $47 | |
| 1573 | 2130 | !byte 2, 2, $2C | |
| 1574 | 2131 | !byte 4, 3, $11 | |
| 1575 | 2132 | | |
| 1576 | - | ; Bar 2 | |
| 1577 | 2133 | !byte 8, 1, $3B | |
| 1578 | 2134 | !byte 10, 2, $27 | |
| 1579 | 2135 | !byte 12, 3, $13 | |
| 1580 | 2136 | | |
| 1581 | - | ; Bar 3 | |
| 1582 | 2137 | !byte 16, 1, $35 | |
| 1583 | 2138 | !byte 17, 2, $2C | |
| 1584 | 2139 | !byte 18, 1, $3B | |
| 1585 | 2140 | !byte 20, 3, $16 | |
| 1586 | 2141 | | |
| 1587 | - | ; Bar 4 | |
| 1588 | 2142 | !byte 24, 1, $47 | |
| 1589 | 2143 | !byte 26, 2, $35 | |
| 1590 | 2144 | !byte 28, 3, $11 | |
| 1591 | 2145 | | |
| 1592 | - | ; Bar 5 | |
| 1593 | 2146 | !byte 32, 2, $2F | |
| 1594 | 2147 | !byte 34, 1, $4F | |
| 1595 | 2148 | !byte 36, 3, $17 | |
| 1596 | 2149 | | |
| 1597 | - | ; Bar 6 | |
| 1598 | 2150 | !byte 40, 1, $58 | |
| 1599 | 2151 | !byte 42, 2, $2C | |
| 1600 | 2152 | !byte 44, 3, $11 | |
| 1601 | 2153 | !byte 46, 2, $27 | |
| 1602 | 2154 | | |
| 1603 | - | ; Bar 7 | |
| 1604 | 2155 | !byte 48, 1, $6A | |
| 1605 | 2156 | !byte 49, 2, $35 | |
| 1606 | 2157 | !byte 50, 1, $58 | |
| 1607 | 2158 | !byte 52, 3, $1A | |
| 1608 | 2159 | !byte 54, 2, $2F | |
| 1609 | 2160 | | |
| 1610 | - | ; Bar 8 | |
| 1611 | 2161 | !byte 56, 1, $47 | |
| 1612 | 2162 | !byte 58, 2, $2C | |
| 1613 | 2163 | !byte 60, 3, $11 | |
| ... | |||
| 1632 | 2182 | ; Game Variables | |
| 1633 | 2183 | ; ---------------------------------------------------------------------------- | |
| 1634 | 2184 | | |
| 1635 | - | score_lo: !byte 0 | |
| 1636 | - | score_hi: !byte 0 | |
| 1637 | - | miss_count: !byte 0 | |
| 1638 | - | health: !byte 0 | |
| 2185 | + | score_lo: !byte 0 | |
| 2186 | + | score_hi: !byte 0 | |
| 2187 | + | miss_count: !byte 0 | |
| 2188 | + | perfect_count: !byte 0 | |
| 2189 | + | good_count: !byte 0 | |
| 2190 | + | health: !byte 0 | |
| 1639 | 2191 | |