Difficulty Levels
Adding Easy/Normal/Hard difficulty selection. Making the game accessible to all skill levels.
Not everyone has the same reflexes. A game that’s too hard frustrates beginners; too easy bores experts.
This unit adds Easy/Normal/Hard difficulty selection. Difficulty affects timing windows (how precisely you must hit notes) and health loss (how much you’re punished for misses). The same songs become accessible to newcomers while still challenging veterans.
Run It
Assemble and run:
acme -f cbm -o symphony.prg symphony.asm

Press fire on the title screen, then use left/right on the joystick to change difficulty. The difficulty name changes colour: green for Easy, yellow for Normal, red for Hard.
What Difficulty Changes
Each difficulty level adjusts two parameters:
| Aspect | Easy | Normal | Hard |
|---|---|---|---|
| Perfect window | ±2 columns | ±1 column | Exact column |
| Good window | ±4 columns | ±2 columns | ±1 column |
| Health loss on miss | 4 | 8 | 12 |
On Easy, you have a generous window to hit notes. On Hard, you need pixel-perfect timing.
Difficulty Constants
We define the parameters for each difficulty as constants:
; Difficulty levels
DIFF_EASY = 0
DIFF_NORMAL = 1
DIFF_HARD = 2
; Timing windows (columns from perfect)
PERFECT_WINDOW_EASY = 2
PERFECT_WINDOW_NORMAL = 1
PERFECT_WINDOW_HARD = 0
GOOD_WINDOW_EASY = 4
GOOD_WINDOW_NORMAL = 2
GOOD_WINDOW_HARD = 1
; Health loss amounts
HEALTH_MISS_EASY = 4
HEALTH_MISS_NORMAL = 8
HEALTH_MISS_HARD = 12
The timing windows measure distance from the perfect hit column. On Easy, being 2 columns away still counts as perfect. On Hard, you must hit the exact column.
Runtime Variables
Three new zero-page variables store the active difficulty settings:
difficulty = $15 ; 0=Easy, 1=Normal, 2=Hard
perfect_window = $16 ; Current perfect timing window
good_window = $17 ; Current good timing window
health_miss_amt = $18 ; Health loss on miss
These are set when the player starts a song, based on their difficulty selection.
Setting Difficulty Parameters
When the player confirms their selection, we configure the timing variables:
set_difficulty_params:
lda difficulty
cmp #DIFF_EASY
beq set_diff_easy
cmp #DIFF_NORMAL
beq set_diff_normal
jmp set_diff_hard
set_diff_easy:
lda #PERFECT_WINDOW_EASY
sta perfect_window
lda #GOOD_WINDOW_EASY
sta good_window
lda #HEALTH_MISS_EASY
sta health_miss_amt
rts
set_diff_normal:
lda #PERFECT_WINDOW_NORMAL
sta perfect_window
lda #GOOD_WINDOW_NORMAL
sta good_window
lda #HEALTH_MISS_NORMAL
sta health_miss_amt
rts
set_diff_hard:
lda #PERFECT_WINDOW_HARD
sta perfect_window
lda #GOOD_WINDOW_HARD
sta good_window
lda #HEALTH_MISS_HARD
sta health_miss_amt
rts
This pattern—branch to difficulty-specific code—is clearer than calculating values dynamically.
Modified Hit Detection
The check_hit routine now uses the difficulty-adjusted windows:
check_hit:
; ... find a note on the pressed track ...
; Calculate distance from perfect column
lda note_col,x
sec
sbc #PERFECT_COL_POS
bcs dist_positive
; Negative - make positive
eor #$FF
clc
adc #1
dist_positive:
; A = absolute distance
; Check if within perfect window
cmp perfect_window ; Use variable, not constant!
bcc hit_is_perfect
beq hit_is_perfect
; Check if within good window
cmp good_window ; Use variable, not constant!
bcc hit_is_good
beq hit_is_good
; ...
The key change: we compare against variables (perfect_window, good_window) rather than constants. This single change makes difficulty work.
Modified Health Loss
When the player misses a note, we use the difficulty-adjusted penalty:
handle_miss:
jsr play_miss_sound
jsr break_combo
; Decrease health (difficulty-adjusted)
lda health_miss_amt ; 4/8/12 depending on difficulty
jsr decrease_health
; ...
On Easy, misses sting less. On Hard, three misses and you’re nearly dead.
Menu Controls
Left and right on the joystick cycle through difficulty levels:
menu_check_input:
; ... up/down for song selection ...
; Check joystick left (difficulty)
lda CIA1_PRA
and #$04 ; Bit 2 = left
beq menu_left_pressed
; Check joystick right (difficulty)
lda CIA1_PRA
and #$08 ; Bit 3 = right
beq menu_right_pressed
; ...
menu_left_pressed:
lda difficulty
beq menu_set_delay ; Already at Easy
dec difficulty
jsr play_menu_move
jsr draw_current_difficulty
jmp menu_set_delay
menu_right_pressed:
lda difficulty
cmp #DIFF_HARD
bcs menu_set_delay ; Already at Hard
inc difficulty
jsr play_menu_move
jsr draw_current_difficulty
jmp menu_set_delay
Bounds checking prevents cycling past Easy or Hard.
Visual Feedback
Difficulty names have distinct colours to reinforce the choice:
EASY_COL = 5 ; Green - calm, safe
NORMAL_COL = 7 ; Yellow - caution
HARD_COL = 2 ; Red - danger
draw_current_difficulty:
lda difficulty
cmp #DIFF_EASY
beq draw_diff_easy
cmp #DIFF_NORMAL
beq draw_diff_normal
jmp draw_diff_hard
draw_diff_easy:
ldx #0
draw_easy_text:
lda easy_text,x
beq draw_diff_done
sta SCREEN + (15 * 40) + 24,x
lda #EASY_COL ; Green
sta COLRAM + (15 * 40) + 24,x
inx
jmp draw_easy_text
; ...
The colour association (green=easy, yellow=normal, red=hard) is universal—players understand it instantly.
Gameplay HUD
During gameplay, the current difficulty shows in the top-left:
draw_difficulty_hud:
lda difficulty
cmp #DIFF_EASY
beq draw_hud_easy
cmp #DIFF_NORMAL
beq draw_hud_normal
jmp draw_hud_hard
draw_hud_easy:
ldx #0
draw_hud_e:
lda easy_text,x
beq draw_song_name_done
sta SCREEN + (COMBO_ROW * 40),x
lda #EASY_COL
sta COLRAM + (COMBO_ROW * 40),x
; ...
Players always know what difficulty they’re playing—no confusion about why they’re struggling (or breezing through).
Design Philosophy
Good difficulty systems follow these principles:
- Same content, different challenge - Easy players see the same songs, just with more forgiveness
- No punishment for choosing Easy - Don’t lock content or mock players
- Clear communication - Players know what each level means
- Balanced defaults - Normal should work for most players
Our implementation respects all four. Every song is available at every difficulty, and the visual feedback (colour-coded names, persistent HUD indicator) keeps players informed.
The Complete Code
; ============================================================================
; SID SYMPHONY - Unit 19: Difficulty Levels
; ============================================================================
; Adding Easy/Normal/Hard difficulty selection. Difficulty affects timing
; windows and health loss, making the game accessible to all skill levels.
;
; New concepts: Difficulty parameters, timing window adjustment
;
; Controls: Z = Track 1, X = Track 2, C = Track 3
; Up/Down = Song selection, Left/Right = Difficulty
; Fire/Space = Start game
; ============================================================================
; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================
; Screenshot mode - set to 1 to skip title and show menu immediately
; Used for automated screenshot capture. Override with: acme -DSCREENSHOT_MODE=1
!ifndef SCREENSHOT_MODE { SCREENSHOT_MODE = 0 }
; SID Voice Settings (for note playback)
VOICE1_WAVE = $21 ; Sawtooth for track 1
VOICE2_WAVE = $41 ; Pulse for track 2
VOICE3_WAVE = $11 ; Triangle for track 3
VOICE1_FREQ = $1C
VOICE2_FREQ = $0E
VOICE3_FREQ = $07
VOICE_AD = $09 ; Attack=0, Decay=9
VOICE_SR = $00 ; Sustain=0, Release=0
PULSE_WIDTH = $08
; Perfect hit sound - bright and satisfying
PERFECT_SFX_FREQ = $30 ; Higher pitch
PERFECT_SFX_WAVE = $21 ; Sawtooth (bright)
PERFECT_SFX_AD = $08 ; Fast attack, medium decay
PERFECT_SFX_SR = $00 ; No sustain
; Good hit sound - positive but lesser
GOOD_SFX_FREQ = $20 ; Lower pitch than perfect
GOOD_SFX_WAVE = $11 ; Triangle (softer)
GOOD_SFX_AD = $0A ; Slightly slower decay
GOOD_SFX_SR = $00 ; No sustain
; Miss sound settings - harsh buzz
MISS_FREQ = $08
MISS_WAVE = $81 ; Noise
MISS_AD = $00 ; Instant attack
MISS_SR = $A0 ; Quick sustain, fast release
; Menu sound settings
MENU_SELECT_FREQ = $18 ; Menu click pitch
MENU_SELECT_WAVE = $41 ; Pulse for click
MENU_SELECT_AD = $00 ; Instant
MENU_SELECT_SR = $80 ; Very short
MENU_MOVE_FREQ = $10 ; Lower pitch for cursor move
MENU_MOVE_WAVE = $11 ; Triangle (soft)
MENU_MOVE_AD = $00 ; Instant
MENU_MOVE_SR = $40 ; Very short
; Visual Settings
BORDER_COL = 0
BG_COL = 0
TRACK1_NOTE_COL = 10 ; Light red for high track
TRACK2_NOTE_COL = 13 ; Light green for mid track
TRACK3_NOTE_COL = 14 ; Light blue for low track
TRACK_LINE_COL = 11 ; Grey for track lines
HIT_ZONE_COL = 7 ; Yellow for hit zone
HIT_COL = 1
PERFECT_COL = 1 ; White flash for perfect
GOOD_COL = 7 ; Yellow flash for good
MISS_COL = 2 ; Red flash for miss
HEALTH_COL = 5 ; Green for health bar
PROGRESS_COL = 3 ; Cyan for progress bar
COMBO_COL = 13 ; Light green for combo
; Title screen colours
TITLE_COL = 1 ; White for title
SUBTITLE_COL = 11 ; Grey for subtitle
MENU_COL = 7 ; Yellow for menu items
CURSOR_COL = 1 ; White for cursor
EASY_COL = 5 ; Green for Easy
NORMAL_COL = 7 ; Yellow for Normal
HARD_COL = 2 ; Red for Hard
; ============================================================================
; SCORING SETTINGS (BALANCED)
; ============================================================================
PERFECT_SCORE = 100 ; Perfect hit value
GOOD_SCORE = 50 ; Good hit value
; ============================================================================
; COMBO SETTINGS (BALANCED)
; ============================================================================
COMBO_TIER_2 = 10 ; 2x multiplier at 10 hits
COMBO_TIER_3 = 25 ; 3x multiplier at 25 hits
COMBO_TIER_4 = 50 ; 4x multiplier at 50 hits
; ============================================================================
; HEALTH SETTINGS (BASE VALUES - MODIFIED BY DIFFICULTY)
; ============================================================================
HEALTH_MAX = 64 ; Maximum health
HEALTH_START = 32 ; Start at half health
HEALTH_PERFECT = 4 ; Gain 4 for perfect
HEALTH_GOOD = 2 ; Gain 2 for good
; Health loss per difficulty
HEALTH_MISS_EASY = 4 ; Forgiving
HEALTH_MISS_NORMAL = 8 ; Balanced
HEALTH_MISS_HARD = 12 ; Punishing
; ============================================================================
; HIT DETECTION SETTINGS (BASE - MODIFIED BY DIFFICULTY)
; ============================================================================
; Hit zone column range (where notes can be hit)
HIT_ZONE_START = 2 ; Leftmost hittable column
HIT_ZONE_END = 6 ; Rightmost hittable column
; Perfect timing column (centre of hit zone)
PERFECT_COL_POS = 4
; Timing windows per difficulty (columns from perfect)
; Easy: Perfect ±2, Good ±4
; Normal: Perfect ±1, Good ±2
; Hard: Perfect ±0, Good ±1
PERFECT_WINDOW_EASY = 2
PERFECT_WINDOW_NORMAL = 1
PERFECT_WINDOW_HARD = 0
GOOD_WINDOW_EASY = 4
GOOD_WINDOW_NORMAL = 2
GOOD_WINDOW_HARD = 1
; ============================================================================
; DIFFICULTY SETTINGS
; ============================================================================
DIFF_EASY = 0
DIFF_NORMAL = 1
DIFF_HARD = 2
; ============================================================================
; SONG SETTINGS
; ============================================================================
PROGRESS_WIDTH = 16 ; Progress bar width
; Per-song tempo (frames per beat)
TEMPO_SONG1 = 25 ; 120 BPM
TEMPO_SONG2 = 23 ; 130 BPM
; Per-song length (beats)
LENGTH_SONG1 = 64
LENGTH_SONG2 = 64
; ============================================================================
; MENU SETTINGS
; ============================================================================
NUM_SONGS = 2
KEY_DELAY = 10 ; Frames between key repeats
; ============================================================================
; GAME STATES
; ============================================================================
STATE_TITLE = 0 ; Title screen
STATE_MENU = 1 ; Song selection menu
STATE_PLAYING = 2 ; Gameplay
STATE_RESULTS = 3 ; Success results
STATE_GAMEOVER = 4 ; Failure game over
; ============================================================================
; 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 and joystick
CIA1_PRA = $DC00
CIA1_PRB = $DC01
; Track positions (balanced spacing)
TRACK1_ROW = 8 ; High track
TRACK2_ROW = 12 ; Mid track
TRACK3_ROW = 16 ; Low track
; HUD positions
HEALTH_ROW = 23 ; Health bar row
PROGRESS_ROW = 24 ; Progress bar row
; Combo display position
COMBO_ROW = 2 ; Combo display row
; Hit zone
HIT_ZONE_COLUMN = 3 ; Where notes are hit (visual)
; Custom character codes
CHAR_NOTE = 128
CHAR_TRACK = 129
CHAR_HITZONE = 130
CHAR_SPACE = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132
CHAR_CURSOR = 62 ; > character for menu cursor
CHAR_ARROW_L = 60 ; < for difficulty
CHAR_ARROW_R = 62 ; > for difficulty
; Note settings
MAX_NOTES = 8 ; Maximum simultaneous notes
NOTE_SPAWN_COL = 37 ; Where notes appear
; Timing
END_DELAY_FRAMES = 75 ; 1.5 seconds after song ends
; Zero page
ZP_PTR = $FB
ZP_PTR_HI = $FC
; Variables
frame_count = $02
beat_count = $03
song_pos = $04
song_pos_hi = $05
temp_track = $06
key_pressed = $07
hit_quality = $08
border_flash = $09
miss_track = $0A
game_state = $0B
hit_note_freq = $0C
song_beat = $0D
song_ended = $0E
end_delay = $0F
cursor_pos = $10 ; Menu cursor position
key_delay_count = $11 ; Key repeat delay counter
selected_song = $12 ; Which song to play
frames_per_beat = $13 ; Current song's tempo
song_length = $14 ; Current song's length
difficulty = $15 ; 0=Easy, 1=Normal, 2=Hard (NEW!)
perfect_window = $16 ; Current perfect timing window (NEW!)
good_window = $17 ; Current good timing window (NEW!)
health_miss_amt = $18 ; Health loss on miss (NEW!)
; ----------------------------------------------------------------------------
; 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_sid
; Start with Normal difficulty
lda #DIFF_NORMAL
sta difficulty
!if SCREENSHOT_MODE = 1 {
; Screenshot mode: skip title, go to menu
jsr show_menu
lda #STATE_MENU
sta game_state
} else {
; Normal mode: show title screen
jsr show_title
lda #STATE_TITLE
sta game_state
}
main_loop:
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
lda game_state
cmp #STATE_TITLE
beq do_title
cmp #STATE_MENU
beq do_menu
cmp #STATE_PLAYING
beq do_playing
cmp #STATE_RESULTS
beq do_results
jmp do_gameover
do_title:
jsr update_title
jmp main_loop
do_menu:
jsr update_menu
jmp main_loop
do_playing:
jsr update_playing
jmp main_loop
do_results:
jsr update_results
jmp main_loop
do_gameover:
jsr update_gameover
jmp main_loop
; ----------------------------------------------------------------------------
; Show Title Screen
; ----------------------------------------------------------------------------
show_title:
; Clear screen
lda #BORDER_COL
sta BORDER
lda #BG_COL
sta BGCOL
ldx #0
lda #CHAR_SPACE
clear_title:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_title
; Draw big title "SID SYMPHONY"
ldx #0
draw_title_text:
lda title_big,x
beq draw_title_done
sta SCREEN + (8 * 40) + 14,x
lda #TITLE_COL
sta COLRAM + (8 * 40) + 14,x
inx
jmp draw_title_text
draw_title_done:
; Draw subtitle "A RHYTHM GAME"
ldx #0
draw_subtitle:
lda subtitle_text,x
beq draw_subtitle_done
sta SCREEN + (10 * 40) + 13,x
lda #SUBTITLE_COL
sta COLRAM + (10 * 40) + 13,x
inx
jmp draw_subtitle
draw_subtitle_done:
; Draw controls
ldx #0
draw_controls:
lda controls_text,x
beq draw_controls_done
sta SCREEN + (14 * 40) + 11,x
lda #11
sta COLRAM + (14 * 40) + 11,x
inx
jmp draw_controls
draw_controls_done:
; Draw track info
ldx #0
draw_track_info:
lda track_info,x
beq draw_track_done
sta SCREEN + (16 * 40) + 9,x
lda #11
sta COLRAM + (16 * 40) + 9,x
inx
jmp draw_track_info
draw_track_done:
; Draw "PRESS FIRE TO START"
ldx #0
draw_press_fire:
lda press_fire_text,x
beq draw_press_done
sta SCREEN + (20 * 40) + 10,x
lda #7 ; Yellow
sta COLRAM + (20 * 40) + 10,x
inx
jmp draw_press_fire
draw_press_done:
rts
title_big:
!scr "sid symphony"
!byte 0
subtitle_text:
!scr "a rhythm game"
!byte 0
controls_text:
!scr "controls: z / x / c"
!byte 0
track_info:
!scr "hit notes as they reach"
!byte 0
press_fire_text:
!scr "press fire to start"
!byte 0
; ----------------------------------------------------------------------------
; Update Title State
; ----------------------------------------------------------------------------
update_title:
; Check for fire button (joystick port 2)
lda CIA1_PRA
and #$10 ; Bit 4 = fire
beq title_fire_pressed
; Also check space bar as alternative
lda #$7F
sta CIA1_PRA
lda CIA1_PRB
and #$10 ; Space
beq title_fire_pressed
; No input - stay on title
lda #$FF
sta CIA1_PRA
rts
title_fire_pressed:
lda #$FF
sta CIA1_PRA
; Play menu select sound
jsr play_menu_select
; Go to song selection menu
jsr show_menu
lda #STATE_MENU
sta game_state
rts
; ----------------------------------------------------------------------------
; Show Song Selection Menu
; ----------------------------------------------------------------------------
show_menu:
; Clear screen
ldx #0
lda #CHAR_SPACE
clear_menu:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_menu
; Initialise cursor
lda #0
sta cursor_pos
sta key_delay_count
; Draw menu title
ldx #0
draw_menu_title:
lda menu_title,x
beq draw_menu_title_done
sta SCREEN + (3 * 40) + 12,x
lda #TITLE_COL
sta COLRAM + (3 * 40) + 12,x
inx
jmp draw_menu_title
draw_menu_title_done:
; Draw song list
jsr draw_song_list
; Draw difficulty section
jsr draw_difficulty
; Draw instructions
ldx #0
draw_menu_instr:
lda menu_instructions,x
beq draw_menu_instr_done
sta SCREEN + (21 * 40) + 2,x
lda #SUBTITLE_COL
sta COLRAM + (21 * 40) + 2,x
inx
jmp draw_menu_instr
draw_menu_instr_done:
rts
menu_title:
!scr "select a song"
!byte 0
menu_instructions:
!scr "up/down:song left/right:difficulty"
!byte 0
; ----------------------------------------------------------------------------
; Draw Song List
; ----------------------------------------------------------------------------
draw_song_list:
; Song 1: "First Steps" (120 BPM)
ldx #0
draw_song1:
lda song1_name,x
beq draw_song1_done
sta SCREEN + (8 * 40) + 14,x
lda #MENU_COL
sta COLRAM + (8 * 40) + 14,x
inx
jmp draw_song1
draw_song1_done:
; Song 1 tempo info
ldx #0
draw_tempo1:
lda song1_tempo_text,x
beq draw_tempo1_done
sta SCREEN + (8 * 40) + 27,x
lda #SUBTITLE_COL
sta COLRAM + (8 * 40) + 27,x
inx
jmp draw_tempo1
draw_tempo1_done:
; Song 2: "Upbeat Groove" (130 BPM)
ldx #0
draw_song2:
lda song2_name,x
beq draw_song2_done
sta SCREEN + (10 * 40) + 14,x
lda #MENU_COL
sta COLRAM + (10 * 40) + 14,x
inx
jmp draw_song2
draw_song2_done:
; Song 2 tempo info
ldx #0
draw_tempo2:
lda song2_tempo_text,x
beq draw_tempo2_done
sta SCREEN + (10 * 40) + 27,x
lda #SUBTITLE_COL
sta COLRAM + (10 * 40) + 27,x
inx
jmp draw_tempo2
draw_tempo2_done:
; Draw cursor at current position
jsr draw_cursor
rts
; Song names and info
song1_name:
!scr "first steps"
!byte 0
song1_tempo_text:
!scr "(120 bpm)"
!byte 0
song2_name:
!scr "upbeat groove"
!byte 0
song2_tempo_text:
!scr "(130 bpm)"
!byte 0
; ----------------------------------------------------------------------------
; Draw Difficulty Selection
; ----------------------------------------------------------------------------
draw_difficulty:
; Draw "DIFFICULTY:" label
ldx #0
draw_diff_label:
lda diff_label,x
beq draw_diff_label_done
sta SCREEN + (15 * 40) + 10,x
lda #TITLE_COL
sta COLRAM + (15 * 40) + 10,x
inx
jmp draw_diff_label
draw_diff_label_done:
; Draw arrows
lda #CHAR_ARROW_L
sta SCREEN + (15 * 40) + 22
lda #SUBTITLE_COL
sta COLRAM + (15 * 40) + 22
lda #CHAR_ARROW_R
sta SCREEN + (15 * 40) + 30
lda #SUBTITLE_COL
sta COLRAM + (15 * 40) + 30
; Draw current difficulty
jsr draw_current_difficulty
rts
diff_label:
!scr "difficulty:"
!byte 0
; ----------------------------------------------------------------------------
; Draw Current Difficulty Name
; ----------------------------------------------------------------------------
draw_current_difficulty:
; Clear difficulty name area
lda #CHAR_SPACE
sta SCREEN + (15 * 40) + 24
sta SCREEN + (15 * 40) + 25
sta SCREEN + (15 * 40) + 26
sta SCREEN + (15 * 40) + 27
sta SCREEN + (15 * 40) + 28
lda difficulty
cmp #DIFF_EASY
beq draw_diff_easy
cmp #DIFF_NORMAL
beq draw_diff_normal
jmp draw_diff_hard
draw_diff_easy:
ldx #0
draw_easy_text:
lda easy_text,x
beq draw_diff_done
sta SCREEN + (15 * 40) + 24,x
lda #EASY_COL
sta COLRAM + (15 * 40) + 24,x
inx
jmp draw_easy_text
draw_diff_normal:
ldx #0
draw_normal_text:
lda normal_text,x
beq draw_diff_done
sta SCREEN + (15 * 40) + 24,x
lda #NORMAL_COL
sta COLRAM + (15 * 40) + 24,x
inx
jmp draw_normal_text
draw_diff_hard:
ldx #0
draw_hard_text:
lda hard_text,x
beq draw_diff_done
sta SCREEN + (15 * 40) + 24,x
lda #HARD_COL
sta COLRAM + (15 * 40) + 24,x
inx
jmp draw_hard_text
draw_diff_done:
rts
easy_text:
!scr "easy"
!byte 0
normal_text:
!scr "normal"
!byte 0
hard_text:
!scr "hard"
!byte 0
; ----------------------------------------------------------------------------
; Draw Menu Cursor
; ----------------------------------------------------------------------------
draw_cursor:
; Clear all cursor positions
lda #CHAR_SPACE
sta SCREEN + (8 * 40) + 12
sta SCREEN + (10 * 40) + 12
; Draw cursor at current position
lda cursor_pos
beq cursor_song1
jmp cursor_song2
cursor_song1:
lda #CHAR_CURSOR
sta SCREEN + (8 * 40) + 12
lda #CURSOR_COL
sta COLRAM + (8 * 40) + 12
rts
cursor_song2:
lda #CHAR_CURSOR
sta SCREEN + (10 * 40) + 12
lda #CURSOR_COL
sta COLRAM + (10 * 40) + 12
rts
; ----------------------------------------------------------------------------
; Update Menu State
; ----------------------------------------------------------------------------
update_menu:
; Handle key delay
lda key_delay_count
beq menu_check_input
dec key_delay_count
jmp menu_check_fire
menu_check_input:
; Check joystick up
lda CIA1_PRA
and #$01 ; Bit 0 = up
beq menu_up_pressed
; Check joystick down
lda CIA1_PRA
and #$02 ; Bit 1 = down
beq menu_down_pressed
; Check joystick left (difficulty)
lda CIA1_PRA
and #$04 ; Bit 2 = left
beq menu_left_pressed
; Check joystick right (difficulty)
lda CIA1_PRA
and #$08 ; Bit 3 = right
beq menu_right_pressed
jmp menu_check_fire
menu_up_pressed:
lda cursor_pos
beq menu_set_delay ; Already at top
dec cursor_pos
jsr play_menu_move
jsr draw_cursor
jmp menu_set_delay
menu_down_pressed:
lda cursor_pos
cmp #NUM_SONGS-1
bcs menu_set_delay ; Already at bottom
inc cursor_pos
jsr play_menu_move
jsr draw_cursor
jmp menu_set_delay
menu_left_pressed:
lda difficulty
beq menu_set_delay ; Already at Easy
dec difficulty
jsr play_menu_move
jsr draw_current_difficulty
jmp menu_set_delay
menu_right_pressed:
lda difficulty
cmp #DIFF_HARD
bcs menu_set_delay ; Already at Hard
inc difficulty
jsr play_menu_move
jsr draw_current_difficulty
jmp menu_set_delay
menu_set_delay:
lda #KEY_DELAY
sta key_delay_count
menu_check_fire:
; Check for fire button
lda CIA1_PRA
and #$10
beq menu_fire_pressed
; Check space bar
lda #$7F
sta CIA1_PRA
lda CIA1_PRB
and #$10
beq menu_fire_pressed
lda #$FF
sta CIA1_PRA
rts
menu_fire_pressed:
lda #$FF
sta CIA1_PRA
; Play select sound
jsr play_menu_select
; Store selected song
lda cursor_pos
sta selected_song
; Set difficulty parameters
jsr set_difficulty_params
; Transition to game
jsr transition_to_game
rts
; ----------------------------------------------------------------------------
; Set Difficulty Parameters
; ----------------------------------------------------------------------------
set_difficulty_params:
lda difficulty
cmp #DIFF_EASY
beq set_diff_easy
cmp #DIFF_NORMAL
beq set_diff_normal
jmp set_diff_hard
set_diff_easy:
lda #PERFECT_WINDOW_EASY
sta perfect_window
lda #GOOD_WINDOW_EASY
sta good_window
lda #HEALTH_MISS_EASY
sta health_miss_amt
rts
set_diff_normal:
lda #PERFECT_WINDOW_NORMAL
sta perfect_window
lda #GOOD_WINDOW_NORMAL
sta good_window
lda #HEALTH_MISS_NORMAL
sta health_miss_amt
rts
set_diff_hard:
lda #PERFECT_WINDOW_HARD
sta perfect_window
lda #GOOD_WINDOW_HARD
sta good_window
lda #HEALTH_MISS_HARD
sta health_miss_amt
rts
; ----------------------------------------------------------------------------
; Transition to Game
; ----------------------------------------------------------------------------
transition_to_game:
; Set up tempo and song pointer based on selection
lda selected_song
beq load_song1
jmp load_song2
load_song1:
lda #TEMPO_SONG1
sta frames_per_beat
lda #LENGTH_SONG1
sta song_length
lda #<song1_data
sta song_data_ptr
lda #>song1_data
sta song_data_ptr+1
jmp start_game
load_song2:
lda #TEMPO_SONG2
sta frames_per_beat
lda #LENGTH_SONG2
sta song_length
lda #<song2_data
sta song_data_ptr
lda #>song2_data
sta song_data_ptr+1
start_game:
; Initialise game
jsr init_game
lda #STATE_PLAYING
sta game_state
rts
; Song data pointers
song_data_ptr:
!word 0
; ----------------------------------------------------------------------------
; Play Menu Move Sound
; ----------------------------------------------------------------------------
play_menu_move:
lda #0
sta SID_V3_FREQ_LO
lda #MENU_MOVE_FREQ
sta SID_V3_FREQ_HI
lda #$08
sta SID_V3_PWHI
lda #MENU_MOVE_AD
sta SID_V3_AD
lda #MENU_MOVE_SR
sta SID_V3_SR
lda #MENU_MOVE_WAVE
ora #$01 ; Gate on
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Play Menu Select Sound
; ----------------------------------------------------------------------------
play_menu_select:
lda #0
sta SID_V3_FREQ_LO
lda #MENU_SELECT_FREQ
sta SID_V3_FREQ_HI
lda #$08
sta SID_V3_PWHI
lda #MENU_SELECT_AD
sta SID_V3_AD
lda #MENU_SELECT_SR
sta SID_V3_SR
lda #MENU_SELECT_WAVE
ora #$01 ; Gate on
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Initialize Game
; ----------------------------------------------------------------------------
init_game:
jsr init_screen
jsr init_notes
jsr init_score
jsr init_health
jsr init_combo
jsr init_song
rts
; ----------------------------------------------------------------------------
; Initialize Combo
; ----------------------------------------------------------------------------
init_combo:
lda #0
sta combo
sta max_combo
jsr display_combo
rts
; ----------------------------------------------------------------------------
; Update Playing State
; ----------------------------------------------------------------------------
update_playing:
inc frame_count
lda frame_count
cmp frames_per_beat
bcc no_new_beat
lda #0
sta frame_count
jsr advance_song
jsr check_spawn_note
no_new_beat:
jsr update_notes
jsr reset_track_colours
jsr update_border_flash
jsr check_keys
jsr check_song_end
rts
; ----------------------------------------------------------------------------
; Update Results State
; ----------------------------------------------------------------------------
update_results:
; Check for fire to return to menu
lda CIA1_PRA
and #$10
beq results_fire
; Also check space bar
lda #$7F
sta CIA1_PRA
lda CIA1_PRB
and #$10
beq results_fire
lda #$FF
sta CIA1_PRA
rts
results_fire:
lda #$FF
sta CIA1_PRA
jsr play_menu_select
; Return to menu
jsr show_menu
lda #STATE_MENU
sta game_state
rts
; ----------------------------------------------------------------------------
; Update Game Over State
; ----------------------------------------------------------------------------
update_gameover:
; Check for fire to return to menu
lda CIA1_PRA
and #$10
beq gameover_fire
; Also check space bar
lda #$7F
sta CIA1_PRA
lda CIA1_PRB
and #$10
beq gameover_fire
lda #$FF
sta CIA1_PRA
rts
gameover_fire:
lda #$FF
sta CIA1_PRA
jsr play_menu_select
; Return to menu
jsr show_menu
lda #STATE_MENU
sta game_state
rts
; ----------------------------------------------------------------------------
; Initialize Screen (Gameplay)
; ----------------------------------------------------------------------------
init_screen:
; Enable custom charset
lda #$1C ; Character ROM at $3000
sta CHARPTR
; Set colours
lda #BORDER_COL
sta BORDER
lda #BG_COL
sta BGCOL
; Clear screen
ldx #0
lda #CHAR_SPACE
clear_screen:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_screen
; Draw tracks
ldx #0
draw_tracks:
lda #CHAR_TRACK
sta SCREEN + (TRACK1_ROW * 40),x
sta SCREEN + (TRACK2_ROW * 40),x
sta SCREEN + (TRACK3_ROW * 40),x
lda #TRACK_LINE_COL
sta COLRAM + (TRACK1_ROW * 40),x
sta COLRAM + (TRACK2_ROW * 40),x
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne draw_tracks
; Draw hit zones
lda #CHAR_HITZONE
sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
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
; Draw track labels
lda #$31 ; "1"
sta SCREEN + (TRACK1_ROW * 40)
lda #$32 ; "2"
sta SCREEN + (TRACK2_ROW * 40)
lda #$33 ; "3"
sta SCREEN + (TRACK3_ROW * 40)
lda #1
sta COLRAM + (TRACK1_ROW * 40)
sta COLRAM + (TRACK2_ROW * 40)
sta COLRAM + (TRACK3_ROW * 40)
; Draw HUD labels
ldx #0
draw_score_label:
lda score_label,x
beq draw_miss_label_start
sta SCREEN,x
lda #7
sta COLRAM,x
inx
jmp draw_score_label
draw_miss_label_start:
ldx #0
draw_miss_label:
lda miss_label,x
beq draw_combo_label_start
sta SCREEN + 14,x
lda #2
sta COLRAM + 14,x
inx
jmp draw_miss_label
draw_combo_label_start:
ldx #0
draw_combo_label:
lda combo_label,x
beq draw_health_label_start
sta SCREEN + (COMBO_ROW * 40) + 27,x
lda #COMBO_COL
sta COLRAM + (COMBO_ROW * 40) + 27,x
inx
jmp draw_combo_label
draw_health_label_start:
ldx #0
draw_health_label:
lda health_label,x
beq draw_progress_label_start
sta SCREEN + (HEALTH_ROW * 40),x
lda #HEALTH_COL
sta COLRAM + (HEALTH_ROW * 40),x
inx
jmp draw_health_label
draw_progress_label_start:
ldx #0
draw_progress_label:
lda progress_label,x
beq labels_done
sta SCREEN + (PROGRESS_ROW * 40),x
lda #PROGRESS_COL
sta COLRAM + (PROGRESS_ROW * 40),x
inx
jmp draw_progress_label
labels_done:
; Draw song name based on selection
lda selected_song
beq draw_name_song1
jmp draw_name_song2
draw_name_song1:
ldx #0
draw_sn1:
lda song1_name,x
beq draw_difficulty_hud
sta SCREEN + 28,x
lda #11
sta COLRAM + 28,x
inx
jmp draw_sn1
draw_name_song2:
ldx #0
draw_sn2:
lda song2_name,x
beq draw_difficulty_hud
sta SCREEN + 26,x
lda #11
sta COLRAM + 26,x
inx
jmp draw_sn2
draw_difficulty_hud:
; Show difficulty on gameplay HUD
lda difficulty
cmp #DIFF_EASY
beq draw_hud_easy
cmp #DIFF_NORMAL
beq draw_hud_normal
jmp draw_hud_hard
draw_hud_easy:
ldx #0
draw_hud_e:
lda easy_text,x
beq draw_song_name_done
sta SCREEN + (COMBO_ROW * 40),x
lda #EASY_COL
sta COLRAM + (COMBO_ROW * 40),x
inx
jmp draw_hud_e
draw_hud_normal:
ldx #0
draw_hud_n:
lda normal_text,x
beq draw_song_name_done
sta SCREEN + (COMBO_ROW * 40),x
lda #NORMAL_COL
sta COLRAM + (COMBO_ROW * 40),x
inx
jmp draw_hud_n
draw_hud_hard:
ldx #0
draw_hud_h:
lda hard_text,x
beq draw_song_name_done
sta SCREEN + (COMBO_ROW * 40),x
lda #HARD_COL
sta COLRAM + (COMBO_ROW * 40),x
inx
jmp draw_hud_h
draw_song_name_done:
rts
score_label:
!scr "score:"
!byte 0
miss_label:
!scr "miss:"
!byte 0
combo_label:
!scr "combo:"
!byte 0
health_label:
!scr "health:"
!byte 0
progress_label:
!scr "song:"
!byte 0
; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------
init_notes:
ldx #0
lda #0
clear_notes:
sta note_track,x
sta note_col,x
sta note_freq,x
inx
cpx #MAX_NOTES
bne clear_notes
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 Health
; ----------------------------------------------------------------------------
init_health:
lda #HEALTH_START
sta health
jsr display_health
rts
; ----------------------------------------------------------------------------
; Initialize Song
; ----------------------------------------------------------------------------
init_song:
lda #0
sta frame_count
sta beat_count
sta song_beat
sta song_ended
; Point to selected song data
lda song_data_ptr
sta song_pos
lda song_data_ptr+1
sta song_pos_hi
jsr display_progress
rts
; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------
init_sid:
; Clear SID
ldx #$18
lda #0
clear_sid:
sta SID,x
dex
bpl clear_sid
; Set volume
lda #$0F
sta SID_VOLUME
; Set pulse widths
lda #PULSE_WIDTH
sta SID_V1_PWHI
sta SID_V2_PWHI
sta SID_V3_PWHI
; Set ADSR for all voices
lda #VOICE_AD
sta SID_V1_AD
sta SID_V2_AD
sta SID_V3_AD
lda #VOICE_SR
sta SID_V1_SR
sta SID_V2_SR
sta SID_V3_SR
rts
; ----------------------------------------------------------------------------
; Copy Custom Charset
; ----------------------------------------------------------------------------
copy_charset:
; Copy default ROM charset to RAM first
sei
lda #$33
sta $01
ldx #0
copy_rom_chars:
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_rom_chars
lda #$37
sta $01
cli
; Now add custom characters
; Character 128 - Note (solid circle)
lda #%00111100
sta CHARSET + (128 * 8) + 0
lda #%01111110
sta CHARSET + (128 * 8) + 1
lda #%11111111
sta CHARSET + (128 * 8) + 2
lda #%11111111
sta CHARSET + (128 * 8) + 3
lda #%11111111
sta CHARSET + (128 * 8) + 4
lda #%11111111
sta CHARSET + (128 * 8) + 5
lda #%01111110
sta CHARSET + (128 * 8) + 6
lda #%00111100
sta CHARSET + (128 * 8) + 7
; Character 129 - Track line (dashes)
lda #%00000000
sta CHARSET + (129 * 8) + 0
lda #%00000000
sta CHARSET + (129 * 8) + 1
lda #%00000000
sta CHARSET + (129 * 8) + 2
lda #%11001100
sta CHARSET + (129 * 8) + 3
lda #%11001100
sta CHARSET + (129 * 8) + 4
lda #%00000000
sta CHARSET + (129 * 8) + 5
lda #%00000000
sta CHARSET + (129 * 8) + 6
lda #%00000000
sta CHARSET + (129 * 8) + 7
; Character 130 - Hit zone (vertical bars)
lda #%10000001
sta CHARSET + (130 * 8) + 0
lda #%10000001
sta CHARSET + (130 * 8) + 1
lda #%10000001
sta CHARSET + (130 * 8) + 2
lda #%10000001
sta CHARSET + (130 * 8) + 3
lda #%10000001
sta CHARSET + (130 * 8) + 4
lda #%10000001
sta CHARSET + (130 * 8) + 5
lda #%10000001
sta CHARSET + (130 * 8) + 6
lda #%10000001
sta CHARSET + (130 * 8) + 7
; Character 131 - Bar full (solid block)
lda #%11111111
sta CHARSET + (131 * 8) + 0
sta CHARSET + (131 * 8) + 1
sta CHARSET + (131 * 8) + 2
sta CHARSET + (131 * 8) + 3
sta CHARSET + (131 * 8) + 4
sta CHARSET + (131 * 8) + 5
sta CHARSET + (131 * 8) + 6
sta CHARSET + (131 * 8) + 7
; Character 132 - Bar empty (hollow block)
lda #%11111111
sta CHARSET + (132 * 8) + 0
lda #%10000001
sta CHARSET + (132 * 8) + 1
sta CHARSET + (132 * 8) + 2
sta CHARSET + (132 * 8) + 3
sta CHARSET + (132 * 8) + 4
sta CHARSET + (132 * 8) + 5
sta CHARSET + (132 * 8) + 6
lda #%11111111
sta CHARSET + (132 * 8) + 7
rts
; ----------------------------------------------------------------------------
; Advance Song
; ----------------------------------------------------------------------------
advance_song:
inc song_beat
jsr display_progress
rts
; ----------------------------------------------------------------------------
; Check Spawn Note
; ----------------------------------------------------------------------------
check_spawn_note:
; Check if song has ended
lda song_ended
bne spawn_done
; Get pointer to current position in song
lda song_pos
sta ZP_PTR
lda song_pos_hi
sta ZP_PTR_HI
; Read beat number
ldy #0
lda (ZP_PTR),y
; Check for end marker
cmp #$FF
beq song_end_marker
; Check if this beat matches
cmp song_beat
bne spawn_done
; Spawn this note
iny
lda (ZP_PTR),y ; Track
sta temp_track
iny
lda (ZP_PTR),y ; Frequency
; Find free note slot
ldx #0
find_slot:
lda note_track,x
beq found_slot
inx
cpx #MAX_NOTES
bne find_slot
jmp advance_song_ptr ; No free slot, skip note
found_slot:
lda temp_track
sta note_track,x
lda #NOTE_SPAWN_COL
sta note_col,x
ldy #2
lda (ZP_PTR),y
sta note_freq,x
jsr draw_note
advance_song_ptr:
; Move to next note in song
lda song_pos
clc
adc #3
sta song_pos
lda song_pos_hi
adc #0
sta song_pos_hi
; Check if there are more notes on this beat
jmp check_spawn_note
song_end_marker:
lda #1
sta song_ended
spawn_done:
rts
; ----------------------------------------------------------------------------
; Update Notes
; ----------------------------------------------------------------------------
update_notes:
ldx #0
update_notes_loop:
lda note_track,x
beq update_next_note
; Erase at old position
jsr erase_note
; Move note left
dec note_col,x
; Check if past hit zone (missed)
lda note_col,x
cmp #HIT_ZONE_START
bcs note_still_active
; Note was missed
lda note_track,x
sta miss_track
lda #0
sta note_track,x
jsr handle_miss
jmp update_next_note
note_still_active:
jsr draw_note
update_next_note:
inx
cpx #MAX_NOTES
bne update_notes_loop
rts
; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------
draw_note:
lda note_track,x
cmp #1
beq draw_note_t1
cmp #2
beq draw_note_t2
jmp draw_note_t3
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
lda #CHAR_NOTE
ldy #0
sta (ZP_PTR),y
; Set colour
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
lda #CHAR_NOTE
ldy #0
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
lda #CHAR_NOTE
ldy #0
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
jmp erase_note_t3
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
lda #CHAR_TRACK
ldy #0
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
lda #CHAR_TRACK
ldy #0
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
lda #CHAR_TRACK
ldy #0
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
; ----------------------------------------------------------------------------
; Handle Miss
; ----------------------------------------------------------------------------
handle_miss:
jsr play_miss_sound
jsr break_combo
; Decrease health (using difficulty-adjusted amount)
lda health_miss_amt
jsr decrease_health
; Flash border red
lda #MISS_COL
sta BORDER
lda #8
sta border_flash
; Flash the track where miss happened
lda miss_track
cmp #1
beq flash_miss_t1
cmp #2
beq flash_miss_t2
jmp flash_miss_t3
flash_miss_t1:
ldx #0
lda #MISS_COL
flash_m1_loop:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne flash_m1_loop
rts
flash_miss_t2:
ldx #0
lda #MISS_COL
flash_m2_loop:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne flash_m2_loop
rts
flash_miss_t3:
ldx #0
lda #MISS_COL
flash_m3_loop:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne flash_m3_loop
rts
; ----------------------------------------------------------------------------
; Play Miss Sound
; ----------------------------------------------------------------------------
play_miss_sound:
inc miss_count
jsr display_misses
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
; ----------------------------------------------------------------------------
; Play Perfect Sound
; ----------------------------------------------------------------------------
play_perfect_sound:
lda #0
sta SID_V3_FREQ_LO
lda #PERFECT_SFX_FREQ
sta SID_V3_FREQ_HI
lda #PERFECT_SFX_AD
sta SID_V3_AD
lda #PERFECT_SFX_SR
sta SID_V3_SR
lda #PERFECT_SFX_WAVE
ora #$01
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Play Good Sound
; ----------------------------------------------------------------------------
play_good_sound:
lda #0
sta SID_V3_FREQ_LO
lda #GOOD_SFX_FREQ
sta SID_V3_FREQ_HI
lda #GOOD_SFX_AD
sta SID_V3_AD
lda #GOOD_SFX_SR
sta SID_V3_SR
lda #GOOD_SFX_WAVE
ora #$01
sta SID_V3_CTRL
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
; ----------------------------------------------------------------------------
; Decrease Health
; ----------------------------------------------------------------------------
decrease_health:
sta temp_track
lda health
sec
sbc temp_track
bcs health_not_zero
lda #0
health_not_zero:
sta health
jsr display_health
; Check for game over
lda health
bne no_game_over
jsr show_gameover
lda #STATE_GAMEOVER
sta game_state
no_game_over:
rts
; ----------------------------------------------------------------------------
; Display Health Bar
; ----------------------------------------------------------------------------
display_health:
; Calculate filled blocks (health / 4 = blocks, max 16)
lda health
lsr
lsr
sta temp_track
ldx #0
draw_health_bar:
cpx temp_track
bcs draw_empty_health
lda #CHAR_BAR_FULL
jmp store_health_char
draw_empty_health:
lda #CHAR_BAR_EMPTY
store_health_char:
sta SCREEN + (HEALTH_ROW * 40) + 8,x
lda #HEALTH_COL
sta COLRAM + (HEALTH_ROW * 40) + 8,x
inx
cpx #16
bne draw_health_bar
rts
; ----------------------------------------------------------------------------
; Display Progress Bar
; ----------------------------------------------------------------------------
display_progress:
; Calculate progress (song_beat / 4)
lda song_beat
lsr
lsr
sta temp_track
ldx #0
draw_progress_bar:
cpx temp_track
bcs draw_empty_progress
lda #CHAR_BAR_FULL
jmp store_progress_char
draw_empty_progress:
lda #CHAR_BAR_EMPTY
store_progress_char:
sta SCREEN + (PROGRESS_ROW * 40) + 8,x
lda #PROGRESS_COL
sta COLRAM + (PROGRESS_ROW * 40) + 8,x
inx
cpx #PROGRESS_WIDTH
bne draw_progress_bar
rts
; ----------------------------------------------------------------------------
; Combo System
; ----------------------------------------------------------------------------
increment_combo:
inc combo
lda combo
cmp max_combo
bcc combo_no_max
sta max_combo
combo_no_max:
jsr display_combo
rts
break_combo:
lda #0
sta combo
jsr display_combo
rts
display_combo:
; Display combo count
lda combo
ldx #0
combo_div_100:
cmp #100
bcc combo_done_100
sec
sbc #100
inx
jmp combo_div_100
combo_done_100:
pha
txa
ora #$30
sta SCREEN + (COMBO_ROW * 40) + 34
pla
ldx #0
combo_div_10:
cmp #10
bcc combo_done_10
sec
sbc #10
inx
jmp combo_div_10
combo_done_10:
pha
txa
ora #$30
sta SCREEN + (COMBO_ROW * 40) + 35
pla
ora #$30
sta SCREEN + (COMBO_ROW * 40) + 36
; Colour based on multiplier
jsr get_multiplier
cmp #1
beq combo_col_1x
cmp #2
beq combo_col_2x
cmp #3
beq combo_col_3x
jmp combo_col_4x
combo_col_1x:
lda #11 ; Grey
jmp set_combo_col
combo_col_2x:
lda #7 ; Yellow
jmp set_combo_col
combo_col_3x:
lda #5 ; Green
jmp set_combo_col
combo_col_4x:
lda #1 ; White
set_combo_col:
sta COLRAM + (COMBO_ROW * 40) + 34
sta COLRAM + (COMBO_ROW * 40) + 35
sta COLRAM + (COMBO_ROW * 40) + 36
rts
get_multiplier:
lda combo
cmp #COMBO_TIER_4
bcs mult_4x
cmp #COMBO_TIER_3
bcs mult_3x
cmp #COMBO_TIER_2
bcs mult_2x
lda #1
rts
mult_2x:
lda #2
rts
mult_3x:
lda #3
rts
mult_4x:
lda #4
rts
; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------
reset_track_colours:
ldx #1 ; Start at column 1 (skip label)
reset_col_loop:
lda #TRACK_LINE_COL
sta COLRAM + (TRACK1_ROW * 40),x
sta COLRAM + (TRACK2_ROW * 40),x
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne reset_col_loop
; Restore hit zone colours
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
; Redraw notes with correct colours
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 (with difficulty-adjusted timing windows)
; ----------------------------------------------------------------------------
check_hit:
ldx #0
check_hit_loop:
lda note_track,x
beq check_hit_next
cmp key_pressed
bne check_hit_next
; Check if note is in hittable range
lda note_col,x
cmp #HIT_ZONE_START
bcc check_hit_next
cmp #HIT_ZONE_END+1
bcs check_hit_next
; Note is hittable - determine quality based on distance from perfect
lda note_freq,x
sta hit_note_freq
; Calculate distance from perfect column
lda note_col,x
sec
sbc #PERFECT_COL_POS
bcs dist_positive
; Negative distance - make it positive
eor #$FF
clc
adc #1
dist_positive:
; A now contains absolute distance from perfect
; Check if within perfect window
cmp perfect_window
bcc hit_is_perfect
beq hit_is_perfect
; Check if within good window
cmp good_window
bcc hit_is_good
beq hit_is_good
; Outside good window but in hit zone - still counts as good
jmp hit_is_good
hit_is_perfect:
lda #2
sta hit_quality
jmp hit_found
hit_is_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
; ----------------------------------------------------------------------------
; Check Song End
; ----------------------------------------------------------------------------
check_song_end:
lda song_ended
beq not_ended
; Check if all notes cleared
ldx #0
check_notes_clear:
lda note_track,x
bne not_ended
inx
cpx #MAX_NOTES
bne check_notes_clear
; Add delay before results
inc end_delay
lda end_delay
cmp #END_DELAY_FRAMES
bcc not_ended
; Show results
jsr show_results
lda #STATE_RESULTS
sta game_state
not_ended:
rts
; ----------------------------------------------------------------------------
; Award Points
; ----------------------------------------------------------------------------
award_points:
jsr increment_combo
lda hit_quality
cmp #2
beq award_perfect
; Good hit
lda #GOOD_SCORE
jsr apply_multiplier
jsr add_score
inc good_count
; Play good sound effect
jsr play_good_sound
lda #GOOD_COL
sta BORDER
lda #4
sta border_flash
lda #HEALTH_GOOD
jsr increase_health
jmp award_done
award_perfect:
; Perfect hit
lda #PERFECT_SCORE
jsr apply_multiplier
jsr add_score
inc perfect_count
; Play perfect sound effect
jsr play_perfect_sound
lda #PERFECT_COL
sta BORDER
lda #6
sta border_flash
lda #HEALTH_PERFECT
jsr increase_health
award_done:
jsr display_score
rts
; ----------------------------------------------------------------------------
; Apply Multiplier
; ----------------------------------------------------------------------------
apply_multiplier:
sta base_score
jsr get_multiplier
sta current_mult
lda base_score
sta score_add_lo
lda #0
sta score_add_hi
lda current_mult
cmp #1
beq mult_done_apply
dec current_mult
mult_add_loop:
lda score_add_lo
clc
adc base_score
sta score_add_lo
lda score_add_hi
adc #0
sta score_add_hi
dec current_mult
bne mult_add_loop
mult_done_apply:
rts
base_score: !byte 0
current_mult: !byte 0
score_add_lo: !byte 0
score_add_hi: !byte 0
; ----------------------------------------------------------------------------
; Add Score
; ----------------------------------------------------------------------------
add_score:
lda score_lo
clc
adc score_add_lo
sta score_lo
lda score_hi
adc score_add_hi
sta score_hi
rts
; ----------------------------------------------------------------------------
; Display Score
; ----------------------------------------------------------------------------
display_score:
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc done_10000
sta work_hi
sty work_lo
inx
jmp div_10000
done_10000:
txa
ora #$30
sta SCREEN + 8
ldx #0
div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc done_1000
sta work_hi
sty work_lo
inx
jmp div_1000
done_1000:
txa
ora #$30
sta SCREEN + 9
ldx #0
div_100:
lda work_lo
sec
sbc #100
bcc done_100
sta work_lo
inx
jmp div_100
done_100:
txa
ora #$30
sta SCREEN + 10
ldx #0
div_10:
lda work_lo
sec
sbc #10
bcc done_10
sta work_lo
inx
jmp div_10
done_10:
txa
ora #$30
sta SCREEN + 11
lda work_lo
ora #$30
sta SCREEN + 12
lda #7
sta COLRAM + 8
sta COLRAM + 9
sta COLRAM + 10
sta COLRAM + 11
sta COLRAM + 12
rts
; ----------------------------------------------------------------------------
; Display Misses
; ----------------------------------------------------------------------------
display_misses:
lda miss_count
ldx #0
miss_div_10:
cmp #10
bcc miss_done_10
sec
sbc #10
inx
jmp miss_div_10
miss_done_10:
pha
txa
ora #$30
sta SCREEN + 21
pla
ora #$30
sta SCREEN + 22
lda #2
sta COLRAM + 21
sta COLRAM + 22
rts
work_lo: !byte 0
work_hi: !byte 0
; ----------------------------------------------------------------------------
; Play Voices (for note playback)
; ----------------------------------------------------------------------------
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
; ----------------------------------------------------------------------------
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
; ----------------------------------------------------------------------------
; Show Results Screen
; ----------------------------------------------------------------------------
show_results:
; Clear screen
ldx #0
lda #CHAR_SPACE
clear_results:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_results
; Draw "SONG COMPLETE!"
ldx #0
draw_complete:
lda complete_text,x
beq draw_results_score
sta SCREEN + (5 * 40) + 13,x
lda #5
sta COLRAM + (5 * 40) + 13,x
inx
jmp draw_complete
draw_results_score:
; Draw score label
ldx #0
draw_rs_label:
lda results_score_label,x
beq draw_rs_value
sta SCREEN + (9 * 40) + 12,x
lda #7
sta COLRAM + (9 * 40) + 12,x
inx
jmp draw_rs_label
draw_rs_value:
; Draw score value
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
rs_div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc rs_done_10000
sta work_hi
sty work_lo
inx
jmp rs_div_10000
rs_done_10000:
txa
ora #$30
sta SCREEN + (9 * 40) + 23
ldx #0
rs_div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc rs_done_1000
sta work_hi
sty work_lo
inx
jmp rs_div_1000
rs_done_1000:
txa
ora #$30
sta SCREEN + (9 * 40) + 24
ldx #0
rs_div_100:
lda work_lo
sec
sbc #100
bcc rs_done_100
sta work_lo
inx
jmp rs_div_100
rs_done_100:
txa
ora #$30
sta SCREEN + (9 * 40) + 25
ldx #0
rs_div_10:
lda work_lo
sec
sbc #10
bcc rs_done_10
sta work_lo
inx
jmp rs_div_10
rs_done_10:
txa
ora #$30
sta SCREEN + (9 * 40) + 26
lda work_lo
ora #$30
sta SCREEN + (9 * 40) + 27
lda #7
sta COLRAM + (9 * 40) + 23
sta COLRAM + (9 * 40) + 24
sta COLRAM + (9 * 40) + 25
sta COLRAM + (9 * 40) + 26
sta COLRAM + (9 * 40) + 27
; Draw perfect count
ldx #0
draw_perfect_label:
lda perfect_label,x
beq draw_perfect_value
sta SCREEN + (11 * 40) + 12,x
lda #1
sta COLRAM + (11 * 40) + 12,x
inx
jmp draw_perfect_label
draw_perfect_value:
lda perfect_count
ldx #0
pv_div_10:
cmp #10
bcc pv_done_10
sec
sbc #10
inx
jmp pv_div_10
pv_done_10:
pha
txa
ora #$30
sta SCREEN + (11 * 40) + 23
pla
ora #$30
sta SCREEN + (11 * 40) + 24
lda #1
sta COLRAM + (11 * 40) + 23
sta COLRAM + (11 * 40) + 24
; Draw good count
ldx #0
draw_good_label:
lda good_label,x
beq draw_good_value
sta SCREEN + (12 * 40) + 12,x
lda #7
sta COLRAM + (12 * 40) + 12,x
inx
jmp draw_good_label
draw_good_value:
lda good_count
ldx #0
gv_div_10:
cmp #10
bcc gv_done_10
sec
sbc #10
inx
jmp gv_div_10
gv_done_10:
pha
txa
ora #$30
sta SCREEN + (12 * 40) + 23
pla
ora #$30
sta SCREEN + (12 * 40) + 24
lda #7
sta COLRAM + (12 * 40) + 23
sta COLRAM + (12 * 40) + 24
; Draw miss count
ldx #0
draw_miss_label_r:
lda miss_label_r,x
beq draw_miss_value
sta SCREEN + (13 * 40) + 12,x
lda #2
sta COLRAM + (13 * 40) + 12,x
inx
jmp draw_miss_label_r
draw_miss_value:
lda miss_count
ldx #0
mv_div_10:
cmp #10
bcc mv_done_10
sec
sbc #10
inx
jmp mv_div_10
mv_done_10:
pha
txa
ora #$30
sta SCREEN + (13 * 40) + 23
pla
ora #$30
sta SCREEN + (13 * 40) + 24
lda #2
sta COLRAM + (13 * 40) + 23
sta COLRAM + (13 * 40) + 24
; Draw max combo
ldx #0
draw_maxc_label:
lda maxcombo_label,x
beq draw_maxc_value
sta SCREEN + (15 * 40) + 12,x
lda #COMBO_COL
sta COLRAM + (15 * 40) + 12,x
inx
jmp draw_maxc_label
draw_maxc_value:
lda max_combo
ldx #0
mc_div_100:
cmp #100
bcc mc_done_100
sec
sbc #100
inx
jmp mc_div_100
mc_done_100:
pha
txa
ora #$30
sta SCREEN + (15 * 40) + 23
pla
ldx #0
mc_div_10:
cmp #10
bcc mc_done_10
sec
sbc #10
inx
jmp mc_div_10
mc_done_10:
pha
txa
ora #$30
sta SCREEN + (15 * 40) + 24
pla
ora #$30
sta SCREEN + (15 * 40) + 25
lda #COMBO_COL
sta COLRAM + (15 * 40) + 23
sta COLRAM + (15 * 40) + 24
sta COLRAM + (15 * 40) + 25
; Draw "PRESS FIRE"
ldx #0
draw_return:
lda return_text,x
beq results_done
sta SCREEN + (20 * 40) + 10,x
lda #11
sta COLRAM + (20 * 40) + 10,x
inx
jmp draw_return
results_done:
rts
complete_text:
!scr "song complete!"
!byte 0
results_score_label:
!scr "final score:"
!byte 0
perfect_label:
!scr "perfect:"
!byte 0
good_label:
!scr "good:"
!byte 0
miss_label_r:
!scr "misses:"
!byte 0
maxcombo_label:
!scr "max combo:"
!byte 0
return_text:
!scr "press fire to continue"
!byte 0
; ----------------------------------------------------------------------------
; Show Game Over Screen
; ----------------------------------------------------------------------------
show_gameover:
; Clear screen
ldx #0
lda #CHAR_SPACE
clear_gameover:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clear_gameover
; Draw "GAME OVER"
ldx #0
draw_gameover_text:
lda gameover_text,x
beq draw_gameover_score
sta SCREEN + (8 * 40) + 15,x
lda #2
sta COLRAM + (8 * 40) + 15,x
inx
jmp draw_gameover_text
draw_gameover_score:
; Draw score
ldx #0
draw_go_score_label:
lda gameover_score,x
beq draw_go_score_value
sta SCREEN + (12 * 40) + 12,x
lda #7
sta COLRAM + (12 * 40) + 12,x
inx
jmp draw_go_score_label
draw_go_score_value:
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
go_div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc go_done_10000
sta work_hi
sty work_lo
inx
jmp go_div_10000
go_done_10000:
txa
ora #$30
sta SCREEN + (12 * 40) + 23
ldx #0
go_div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc go_done_1000
sta work_hi
sty work_lo
inx
jmp go_div_1000
go_done_1000:
txa
ora #$30
sta SCREEN + (12 * 40) + 24
ldx #0
go_div_100:
lda work_lo
sec
sbc #100
bcc go_done_100
sta work_lo
inx
jmp go_div_100
go_done_100:
txa
ora #$30
sta SCREEN + (12 * 40) + 25
ldx #0
go_div_10:
lda work_lo
sec
sbc #10
bcc go_done_10
sta work_lo
inx
jmp go_div_10
go_done_10:
txa
ora #$30
sta SCREEN + (12 * 40) + 26
lda work_lo
ora #$30
sta SCREEN + (12 * 40) + 27
lda #7
sta COLRAM + (12 * 40) + 23
sta COLRAM + (12 * 40) + 24
sta COLRAM + (12 * 40) + 25
sta COLRAM + (12 * 40) + 26
sta COLRAM + (12 * 40) + 27
; Draw retry message
ldx #0
draw_retry:
lda retry_text,x
beq gameover_done
sta SCREEN + (18 * 40) + 10,x
lda #11
sta COLRAM + (18 * 40) + 10,x
inx
jmp draw_retry
gameover_done:
rts
gameover_text:
!scr "game over"
!byte 0
gameover_score:
!scr "your score:"
!byte 0
retry_text:
!scr "press fire to continue"
!byte 0
; ============================================================================
; SONG 1 DATA - "First Steps" (120 BPM)
; ============================================================================
song1_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
; ============================================================================
; SONG 2 DATA - "Upbeat Groove" (130 BPM)
; ============================================================================
song2_data:
!byte 0, 1, $47
!byte 1, 2, $35
!byte 3, 3, $16
!byte 4, 1, $4F
!byte 5, 2, $2C
!byte 7, 1, $47
!byte 8, 3, $11
!byte 10, 2, $35
!byte 11, 1, $58
!byte 13, 2, $2C
!byte 14, 3, $1A
!byte 15, 1, $47
!byte 16, 1, $4F
!byte 17, 2, $35
!byte 18, 3, $16
!byte 19, 1, $58
!byte 20, 2, $2C
!byte 22, 1, $6A
!byte 23, 2, $35
!byte 24, 3, $11
!byte 25, 1, $58
!byte 26, 2, $47
!byte 28, 3, $1A
!byte 30, 1, $47
!byte 31, 2, $2C
!byte 32, 1, $4F
!byte 33, 3, $16
!byte 34, 2, $35
!byte 35, 1, $58
!byte 36, 3, $11
!byte 37, 1, $6A
!byte 38, 2, $47
!byte 39, 1, $58
!byte 40, 3, $1A
!byte 41, 2, $35
!byte 42, 1, $4F
!byte 43, 2, $2C
!byte 44, 1, $47
!byte 45, 3, $16
!byte 46, 2, $35
!byte 47, 1, $58
!byte 48, 3, $11
!byte 49, 2, $47
!byte 51, 1, $4F
!byte 52, 3, $1A
!byte 54, 2, $35
!byte 55, 1, $47
!byte 56, 3, $11
!byte 58, 2, $2C
!byte 59, 1, $58
!byte 60, 3, $16
!byte 61, 2, $35
!byte 62, 1, $47
!byte $FF
; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------
note_track:
!fill MAX_NOTES, 0
note_col:
!fill MAX_NOTES, 0
note_freq:
!fill MAX_NOTES, 0
; ----------------------------------------------------------------------------
; Game Variables
; ----------------------------------------------------------------------------
score_lo: !byte 0
score_hi: !byte 0
miss_count: !byte 0
perfect_count: !byte 0
good_count: !byte 0
health: !byte 0
combo: !byte 0
max_combo: !byte 0
; ============================================================================
; END OF SID SYMPHONY - UNIT 19
; ============================================================================
What’s Coming
With difficulty selection complete, we can refine the experience:
- Unit 20: Different note patterns per difficulty
- Unit 21: Song 3 with even faster tempo
- Unit 22: Enhanced combo visuals
What You’ve Learnt
- Difficulty parameters - Constants for each level, variables at runtime
- Timing window adjustment - Comparing against variables instead of constants
- Penalty scaling - Health loss proportional to difficulty
- Visual feedback - Colour coding for instant recognition
- Menu expansion - Adding a second selection axis (left/right)
The game now welcomes everyone.
What Changed
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; SID SYMPHONY - Unit 18: Song 2 - Composing for SID | |
| 2 | + | ; SID SYMPHONY - Unit 19: Difficulty Levels | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; Adding a second song with different musical character. Song 2 is faster, | |
| 5 | - | ; in a major key, and has a bouncy feel compared to Song 1's steady pace. | |
| 4 | + | ; Adding Easy/Normal/Hard difficulty selection. Difficulty affects timing | |
| 5 | + | ; windows and health loss, making the game accessible to all skill levels. | |
| 6 | 6 | ; | |
| 7 | - | ; New concepts: Multiple songs, per-song tempo, musical composition | |
| 7 | + | ; New concepts: Difficulty parameters, timing window adjustment | |
| 8 | 8 | ; | |
| 9 | 9 | ; Controls: Z = Track 1, X = Track 2, C = Track 3 | |
| 10 | - | ; Up/Down = Menu navigation | |
| 11 | - | ; Fire/Space = Select | |
| 10 | + | ; Up/Down = Song selection, Left/Right = Difficulty | |
| 11 | + | ; Fire/Space = Start game | |
| 12 | 12 | ; ============================================================================ | |
| 13 | 13 | | |
| 14 | 14 | ; ============================================================================ | |
| ... | |||
| 86 | 86 | SUBTITLE_COL = 11 ; Grey for subtitle | |
| 87 | 87 | MENU_COL = 7 ; Yellow for menu items | |
| 88 | 88 | CURSOR_COL = 1 ; White for cursor | |
| 89 | + | EASY_COL = 5 ; Green for Easy | |
| 90 | + | NORMAL_COL = 7 ; Yellow for Normal | |
| 91 | + | HARD_COL = 2 ; Red for Hard | |
| 89 | 92 | | |
| 90 | 93 | ; ============================================================================ | |
| 91 | 94 | ; SCORING SETTINGS (BALANCED) | |
| ... | |||
| 103 | 106 | COMBO_TIER_4 = 50 ; 4x multiplier at 50 hits | |
| 104 | 107 | | |
| 105 | 108 | ; ============================================================================ | |
| 106 | - | ; HEALTH SETTINGS (BALANCED FOR FAIRNESS) | |
| 109 | + | ; HEALTH SETTINGS (BASE VALUES - MODIFIED BY DIFFICULTY) | |
| 107 | 110 | ; ============================================================================ | |
| 108 | 111 | | |
| 109 | 112 | HEALTH_MAX = 64 ; Maximum health | |
| 110 | 113 | HEALTH_START = 32 ; Start at half health | |
| 111 | 114 | HEALTH_PERFECT = 4 ; Gain 4 for perfect | |
| 112 | 115 | HEALTH_GOOD = 2 ; Gain 2 for good | |
| 113 | - | HEALTH_MISS = 8 ; Lose 8 for miss (forgiving) | |
| 116 | + | | |
| 117 | + | ; Health loss per difficulty | |
| 118 | + | HEALTH_MISS_EASY = 4 ; Forgiving | |
| 119 | + | HEALTH_MISS_NORMAL = 8 ; Balanced | |
| 120 | + | HEALTH_MISS_HARD = 12 ; Punishing | |
| 114 | 121 | | |
| 115 | 122 | ; ============================================================================ | |
| 116 | - | ; HIT DETECTION SETTINGS (BALANCED TIMING WINDOWS) | |
| 123 | + | ; HIT DETECTION SETTINGS (BASE - MODIFIED BY DIFFICULTY) | |
| 117 | 124 | ; ============================================================================ | |
| 118 | 125 | | |
| 119 | - | HIT_ZONE_MIN = 2 ; Earliest hit column | |
| 120 | - | HIT_ZONE_MAX = 5 ; Latest hit column | |
| 121 | - | HIT_ZONE_CENTRE = 3 ; Perfect timing column | |
| 126 | + | ; Hit zone column range (where notes can be hit) | |
| 127 | + | HIT_ZONE_START = 2 ; Leftmost hittable column | |
| 128 | + | HIT_ZONE_END = 6 ; Rightmost hittable column | |
| 129 | + | | |
| 130 | + | ; Perfect timing column (centre of hit zone) | |
| 131 | + | PERFECT_COL_POS = 4 | |
| 132 | + | | |
| 133 | + | ; Timing windows per difficulty (columns from perfect) | |
| 134 | + | ; Easy: Perfect ±2, Good ±4 | |
| 135 | + | ; Normal: Perfect ±1, Good ±2 | |
| 136 | + | ; Hard: Perfect ±0, Good ±1 | |
| 137 | + | | |
| 138 | + | PERFECT_WINDOW_EASY = 2 | |
| 139 | + | PERFECT_WINDOW_NORMAL = 1 | |
| 140 | + | PERFECT_WINDOW_HARD = 0 | |
| 141 | + | | |
| 142 | + | GOOD_WINDOW_EASY = 4 | |
| 143 | + | GOOD_WINDOW_NORMAL = 2 | |
| 144 | + | GOOD_WINDOW_HARD = 1 | |
| 145 | + | | |
| 146 | + | ; ============================================================================ | |
| 147 | + | ; DIFFICULTY SETTINGS | |
| 148 | + | ; ============================================================================ | |
| 149 | + | | |
| 150 | + | DIFF_EASY = 0 | |
| 151 | + | DIFF_NORMAL = 1 | |
| 152 | + | DIFF_HARD = 2 | |
| 122 | 153 | | |
| 123 | 154 | ; ============================================================================ | |
| 124 | 155 | ; SONG SETTINGS | |
| ... | |||
| 127 | 158 | PROGRESS_WIDTH = 16 ; Progress bar width | |
| 128 | 159 | | |
| 129 | 160 | ; Per-song tempo (frames per beat) | |
| 130 | - | TEMPO_SONG1 = 25 ; 120 BPM (50fps / 25 = 2 beats/sec) | |
| 131 | - | TEMPO_SONG2 = 23 ; 130 BPM (50fps / 23 = ~2.17 beats/sec) | |
| 161 | + | TEMPO_SONG1 = 25 ; 120 BPM | |
| 162 | + | TEMPO_SONG2 = 23 ; 130 BPM | |
| 132 | 163 | | |
| 133 | 164 | ; Per-song length (beats) | |
| 134 | - | LENGTH_SONG1 = 64 ; ~32 seconds at 120 BPM | |
| 135 | - | LENGTH_SONG2 = 64 ; ~29 seconds at 130 BPM | |
| 165 | + | LENGTH_SONG1 = 64 | |
| 166 | + | LENGTH_SONG2 = 64 | |
| 136 | 167 | | |
| 137 | 168 | ; ============================================================================ | |
| 138 | 169 | ; MENU SETTINGS | |
| 139 | 170 | ; ============================================================================ | |
| 140 | 171 | | |
| 141 | - | NUM_SONGS = 2 ; Now we have 2 songs! | |
| 172 | + | NUM_SONGS = 2 | |
| 142 | 173 | KEY_DELAY = 10 ; Frames between key repeats | |
| 143 | 174 | | |
| 144 | 175 | ; ============================================================================ | |
| ... | |||
| 205 | 236 | COMBO_ROW = 2 ; Combo display row | |
| 206 | 237 | | |
| 207 | 238 | ; Hit zone | |
| 208 | - | HIT_ZONE_COLUMN = 3 ; Where notes are hit | |
| 239 | + | HIT_ZONE_COLUMN = 3 ; Where notes are hit (visual) | |
| 209 | 240 | | |
| 210 | 241 | ; Custom character codes | |
| 211 | 242 | CHAR_NOTE = 128 | |
| ... | |||
| 215 | 246 | CHAR_BAR_FULL = 131 | |
| 216 | 247 | CHAR_BAR_EMPTY = 132 | |
| 217 | 248 | CHAR_CURSOR = 62 ; > character for menu cursor | |
| 249 | + | CHAR_ARROW_L = 60 ; < for difficulty | |
| 250 | + | CHAR_ARROW_R = 62 ; > for difficulty | |
| 218 | 251 | | |
| 219 | 252 | ; Note settings | |
| 220 | 253 | MAX_NOTES = 8 ; Maximum simultaneous notes | |
| ... | |||
| 245 | 278 | cursor_pos = $10 ; Menu cursor position | |
| 246 | 279 | key_delay_count = $11 ; Key repeat delay counter | |
| 247 | 280 | selected_song = $12 ; Which song to play | |
| 248 | - | frames_per_beat = $13 ; Current song's tempo (NEW!) | |
| 249 | - | song_length = $14 ; Current song's length (NEW!) | |
| 281 | + | frames_per_beat = $13 ; Current song's tempo | |
| 282 | + | song_length = $14 ; Current song's length | |
| 283 | + | difficulty = $15 ; 0=Easy, 1=Normal, 2=Hard (NEW!) | |
| 284 | + | perfect_window = $16 ; Current perfect timing window (NEW!) | |
| 285 | + | good_window = $17 ; Current good timing window (NEW!) | |
| 286 | + | health_miss_amt = $18 ; Health loss on miss (NEW!) | |
| 250 | 287 | | |
| 251 | 288 | ; ---------------------------------------------------------------------------- | |
| 252 | 289 | ; BASIC Stub | |
| ... | |||
| 270 | 307 | start: | |
| 271 | 308 | jsr copy_charset | |
| 272 | 309 | jsr init_sid | |
| 310 | + | | |
| 311 | + | ; Start with Normal difficulty | |
| 312 | + | lda #DIFF_NORMAL | |
| 313 | + | sta difficulty | |
| 273 | 314 | | |
| 274 | 315 | !if SCREENSHOT_MODE = 1 { | |
| 275 | 316 | ; Screenshot mode: skip title, go to menu | |
| ... | |||
| 484 | 525 | draw_menu_title: | |
| 485 | 526 | lda menu_title,x | |
| 486 | 527 | beq draw_menu_title_done | |
| 487 | - | sta SCREEN + (4 * 40) + 12,x | |
| 528 | + | sta SCREEN + (3 * 40) + 12,x | |
| 488 | 529 | lda #TITLE_COL | |
| 489 | - | sta COLRAM + (4 * 40) + 12,x | |
| 530 | + | sta COLRAM + (3 * 40) + 12,x | |
| 490 | 531 | inx | |
| 491 | 532 | jmp draw_menu_title | |
| 492 | 533 | draw_menu_title_done: | |
| 534 | + | | |
| 535 | + | ; Draw song list | |
| 536 | + | jsr draw_song_list | |
| 537 | + | | |
| 538 | + | ; Draw difficulty section | |
| 539 | + | jsr draw_difficulty | |
| 493 | 540 | | |
| 494 | 541 | ; Draw instructions | |
| 495 | 542 | ldx #0 | |
| 496 | 543 | draw_menu_instr: | |
| 497 | 544 | lda menu_instructions,x | |
| 498 | 545 | beq draw_menu_instr_done | |
| 499 | - | sta SCREEN + (22 * 40) + 4,x | |
| 546 | + | sta SCREEN + (21 * 40) + 2,x | |
| 500 | 547 | lda #SUBTITLE_COL | |
| 501 | - | sta COLRAM + (22 * 40) + 4,x | |
| 548 | + | sta COLRAM + (21 * 40) + 2,x | |
| 502 | 549 | inx | |
| 503 | 550 | jmp draw_menu_instr | |
| 504 | 551 | draw_menu_instr_done: | |
| 505 | - | | |
| 506 | - | ; Draw song list | |
| 507 | - | jsr draw_song_list | |
| 508 | 552 | | |
| 509 | 553 | rts | |
| 510 | 554 | | |
| ... | |||
| 513 | 557 | !byte 0 | |
| 514 | 558 | | |
| 515 | 559 | menu_instructions: | |
| 516 | - | !scr "up/down to select, fire to play" | |
| 560 | + | !scr "up/down:song left/right:difficulty" | |
| 517 | 561 | !byte 0 | |
| 518 | 562 | | |
| 519 | 563 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 526 | 570 | draw_song1: | |
| 527 | 571 | lda song1_name,x | |
| 528 | 572 | beq draw_song1_done | |
| 529 | - | sta SCREEN + (10 * 40) + 14,x | |
| 573 | + | sta SCREEN + (8 * 40) + 14,x | |
| 530 | 574 | lda #MENU_COL | |
| 531 | - | sta COLRAM + (10 * 40) + 14,x | |
| 575 | + | sta COLRAM + (8 * 40) + 14,x | |
| 532 | 576 | inx | |
| 533 | 577 | jmp draw_song1 | |
| 534 | 578 | draw_song1_done: | |
| ... | |||
| 538 | 582 | draw_tempo1: | |
| 539 | 583 | lda song1_tempo_text,x | |
| 540 | 584 | beq draw_tempo1_done | |
| 541 | - | sta SCREEN + (10 * 40) + 27,x | |
| 585 | + | sta SCREEN + (8 * 40) + 27,x | |
| 542 | 586 | lda #SUBTITLE_COL | |
| 543 | - | sta COLRAM + (10 * 40) + 27,x | |
| 587 | + | sta COLRAM + (8 * 40) + 27,x | |
| 544 | 588 | inx | |
| 545 | 589 | jmp draw_tempo1 | |
| 546 | 590 | draw_tempo1_done: | |
| ... | |||
| 550 | 594 | draw_song2: | |
| 551 | 595 | lda song2_name,x | |
| 552 | 596 | beq draw_song2_done | |
| 553 | - | sta SCREEN + (12 * 40) + 14,x | |
| 597 | + | sta SCREEN + (10 * 40) + 14,x | |
| 554 | 598 | lda #MENU_COL | |
| 555 | - | sta COLRAM + (12 * 40) + 14,x | |
| 599 | + | sta COLRAM + (10 * 40) + 14,x | |
| 556 | 600 | inx | |
| 557 | 601 | jmp draw_song2 | |
| 558 | 602 | draw_song2_done: | |
| ... | |||
| 562 | 606 | draw_tempo2: | |
| 563 | 607 | lda song2_tempo_text,x | |
| 564 | 608 | beq draw_tempo2_done | |
| 565 | - | sta SCREEN + (12 * 40) + 27,x | |
| 609 | + | sta SCREEN + (10 * 40) + 27,x | |
| 566 | 610 | lda #SUBTITLE_COL | |
| 567 | - | sta COLRAM + (12 * 40) + 27,x | |
| 611 | + | sta COLRAM + (10 * 40) + 27,x | |
| 568 | 612 | inx | |
| 569 | 613 | jmp draw_tempo2 | |
| 570 | 614 | draw_tempo2_done: | |
| ... | |||
| 588 | 632 | | |
| 589 | 633 | song2_tempo_text: | |
| 590 | 634 | !scr "(130 bpm)" | |
| 635 | + | !byte 0 | |
| 636 | + | | |
| 637 | + | ; ---------------------------------------------------------------------------- | |
| 638 | + | ; Draw Difficulty Selection | |
| 639 | + | ; ---------------------------------------------------------------------------- | |
| 640 | + | | |
| 641 | + | draw_difficulty: | |
| 642 | + | ; Draw "DIFFICULTY:" label | |
| 643 | + | ldx #0 | |
| 644 | + | draw_diff_label: | |
| 645 | + | lda diff_label,x | |
| 646 | + | beq draw_diff_label_done | |
| 647 | + | sta SCREEN + (15 * 40) + 10,x | |
| 648 | + | lda #TITLE_COL | |
| 649 | + | sta COLRAM + (15 * 40) + 10,x | |
| 650 | + | inx | |
| 651 | + | jmp draw_diff_label | |
| 652 | + | draw_diff_label_done: | |
| 653 | + | | |
| 654 | + | ; Draw arrows | |
| 655 | + | lda #CHAR_ARROW_L | |
| 656 | + | sta SCREEN + (15 * 40) + 22 | |
| 657 | + | lda #SUBTITLE_COL | |
| 658 | + | sta COLRAM + (15 * 40) + 22 | |
| 659 | + | | |
| 660 | + | lda #CHAR_ARROW_R | |
| 661 | + | sta SCREEN + (15 * 40) + 30 | |
| 662 | + | lda #SUBTITLE_COL | |
| 663 | + | sta COLRAM + (15 * 40) + 30 | |
| 664 | + | | |
| 665 | + | ; Draw current difficulty | |
| 666 | + | jsr draw_current_difficulty | |
| 667 | + | | |
| 668 | + | rts | |
| 669 | + | | |
| 670 | + | diff_label: | |
| 671 | + | !scr "difficulty:" | |
| 672 | + | !byte 0 | |
| 673 | + | | |
| 674 | + | ; ---------------------------------------------------------------------------- | |
| 675 | + | ; Draw Current Difficulty Name | |
| 676 | + | ; ---------------------------------------------------------------------------- | |
| 677 | + | | |
| 678 | + | draw_current_difficulty: | |
| 679 | + | ; Clear difficulty name area | |
| 680 | + | lda #CHAR_SPACE | |
| 681 | + | sta SCREEN + (15 * 40) + 24 | |
| 682 | + | sta SCREEN + (15 * 40) + 25 | |
| 683 | + | sta SCREEN + (15 * 40) + 26 | |
| 684 | + | sta SCREEN + (15 * 40) + 27 | |
| 685 | + | sta SCREEN + (15 * 40) + 28 | |
| 686 | + | | |
| 687 | + | lda difficulty | |
| 688 | + | cmp #DIFF_EASY | |
| 689 | + | beq draw_diff_easy | |
| 690 | + | cmp #DIFF_NORMAL | |
| 691 | + | beq draw_diff_normal | |
| 692 | + | jmp draw_diff_hard | |
| 693 | + | | |
| 694 | + | draw_diff_easy: | |
| 695 | + | ldx #0 | |
| 696 | + | draw_easy_text: | |
| 697 | + | lda easy_text,x | |
| 698 | + | beq draw_diff_done | |
| 699 | + | sta SCREEN + (15 * 40) + 24,x | |
| 700 | + | lda #EASY_COL | |
| 701 | + | sta COLRAM + (15 * 40) + 24,x | |
| 702 | + | inx | |
| 703 | + | jmp draw_easy_text | |
| 704 | + | | |
| 705 | + | draw_diff_normal: | |
| 706 | + | ldx #0 | |
| 707 | + | draw_normal_text: | |
| 708 | + | lda normal_text,x | |
| 709 | + | beq draw_diff_done | |
| 710 | + | sta SCREEN + (15 * 40) + 24,x | |
| 711 | + | lda #NORMAL_COL | |
| 712 | + | sta COLRAM + (15 * 40) + 24,x | |
| 713 | + | inx | |
| 714 | + | jmp draw_normal_text | |
| 715 | + | | |
| 716 | + | draw_diff_hard: | |
| 717 | + | ldx #0 | |
| 718 | + | draw_hard_text: | |
| 719 | + | lda hard_text,x | |
| 720 | + | beq draw_diff_done | |
| 721 | + | sta SCREEN + (15 * 40) + 24,x | |
| 722 | + | lda #HARD_COL | |
| 723 | + | sta COLRAM + (15 * 40) + 24,x | |
| 724 | + | inx | |
| 725 | + | jmp draw_hard_text | |
| 726 | + | | |
| 727 | + | draw_diff_done: | |
| 728 | + | rts | |
| 729 | + | | |
| 730 | + | easy_text: | |
| 731 | + | !scr "easy" | |
| 732 | + | !byte 0 | |
| 733 | + | | |
| 734 | + | normal_text: | |
| 735 | + | !scr "normal" | |
| 736 | + | !byte 0 | |
| 737 | + | | |
| 738 | + | hard_text: | |
| 739 | + | !scr "hard" | |
| 591 | 740 | !byte 0 | |
| 592 | 741 | | |
| 593 | 742 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 597 | 746 | draw_cursor: | |
| 598 | 747 | ; Clear all cursor positions | |
| 599 | 748 | lda #CHAR_SPACE | |
| 749 | + | sta SCREEN + (8 * 40) + 12 | |
| 600 | 750 | sta SCREEN + (10 * 40) + 12 | |
| 601 | - | sta SCREEN + (12 * 40) + 12 | |
| 602 | 751 | | |
| 603 | 752 | ; Draw cursor at current position | |
| 604 | 753 | lda cursor_pos | |
| ... | |||
| 607 | 756 | | |
| 608 | 757 | cursor_song1: | |
| 609 | 758 | lda #CHAR_CURSOR | |
| 610 | - | sta SCREEN + (10 * 40) + 12 | |
| 759 | + | sta SCREEN + (8 * 40) + 12 | |
| 611 | 760 | lda #CURSOR_COL | |
| 612 | - | sta COLRAM + (10 * 40) + 12 | |
| 761 | + | sta COLRAM + (8 * 40) + 12 | |
| 613 | 762 | rts | |
| 614 | 763 | | |
| 615 | 764 | cursor_song2: | |
| 616 | 765 | lda #CHAR_CURSOR | |
| 617 | - | sta SCREEN + (12 * 40) + 12 | |
| 766 | + | sta SCREEN + (10 * 40) + 12 | |
| 618 | 767 | lda #CURSOR_COL | |
| 619 | - | sta COLRAM + (12 * 40) + 12 | |
| 768 | + | sta COLRAM + (10 * 40) + 12 | |
| 620 | 769 | rts | |
| 621 | 770 | | |
| 622 | 771 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 640 | 789 | lda CIA1_PRA | |
| 641 | 790 | and #$02 ; Bit 1 = down | |
| 642 | 791 | beq menu_down_pressed | |
| 792 | + | | |
| 793 | + | ; Check joystick left (difficulty) | |
| 794 | + | lda CIA1_PRA | |
| 795 | + | and #$04 ; Bit 2 = left | |
| 796 | + | beq menu_left_pressed | |
| 797 | + | | |
| 798 | + | ; Check joystick right (difficulty) | |
| 799 | + | lda CIA1_PRA | |
| 800 | + | and #$08 ; Bit 3 = right | |
| 801 | + | beq menu_right_pressed | |
| 643 | 802 | | |
| 644 | 803 | jmp menu_check_fire | |
| 645 | 804 | | |
| ... | |||
| 658 | 817 | inc cursor_pos | |
| 659 | 818 | jsr play_menu_move | |
| 660 | 819 | jsr draw_cursor | |
| 820 | + | jmp menu_set_delay | |
| 821 | + | | |
| 822 | + | menu_left_pressed: | |
| 823 | + | lda difficulty | |
| 824 | + | beq menu_set_delay ; Already at Easy | |
| 825 | + | dec difficulty | |
| 826 | + | jsr play_menu_move | |
| 827 | + | jsr draw_current_difficulty | |
| 828 | + | jmp menu_set_delay | |
| 829 | + | | |
| 830 | + | menu_right_pressed: | |
| 831 | + | lda difficulty | |
| 832 | + | cmp #DIFF_HARD | |
| 833 | + | bcs menu_set_delay ; Already at Hard | |
| 834 | + | inc difficulty | |
| 835 | + | jsr play_menu_move | |
| 836 | + | jsr draw_current_difficulty | |
| 661 | 837 | jmp menu_set_delay | |
| 662 | 838 | | |
| 663 | 839 | menu_set_delay: | |
| ... | |||
| 691 | 867 | ; Store selected song | |
| 692 | 868 | lda cursor_pos | |
| 693 | 869 | sta selected_song | |
| 870 | + | | |
| 871 | + | ; Set difficulty parameters | |
| 872 | + | jsr set_difficulty_params | |
| 694 | 873 | | |
| 695 | 874 | ; Transition to game | |
| 696 | 875 | jsr transition_to_game | |
| 876 | + | rts | |
| 877 | + | | |
| 878 | + | ; ---------------------------------------------------------------------------- | |
| 879 | + | ; Set Difficulty Parameters | |
| 880 | + | ; ---------------------------------------------------------------------------- | |
| 881 | + | | |
| 882 | + | set_difficulty_params: | |
| 883 | + | lda difficulty | |
| 884 | + | cmp #DIFF_EASY | |
| 885 | + | beq set_diff_easy | |
| 886 | + | cmp #DIFF_NORMAL | |
| 887 | + | beq set_diff_normal | |
| 888 | + | jmp set_diff_hard | |
| 889 | + | | |
| 890 | + | set_diff_easy: | |
| 891 | + | lda #PERFECT_WINDOW_EASY | |
| 892 | + | sta perfect_window | |
| 893 | + | lda #GOOD_WINDOW_EASY | |
| 894 | + | sta good_window | |
| 895 | + | lda #HEALTH_MISS_EASY | |
| 896 | + | sta health_miss_amt | |
| 897 | + | rts | |
| 898 | + | | |
| 899 | + | set_diff_normal: | |
| 900 | + | lda #PERFECT_WINDOW_NORMAL | |
| 901 | + | sta perfect_window | |
| 902 | + | lda #GOOD_WINDOW_NORMAL | |
| 903 | + | sta good_window | |
| 904 | + | lda #HEALTH_MISS_NORMAL | |
| 905 | + | sta health_miss_amt | |
| 906 | + | rts | |
| 907 | + | | |
| 908 | + | set_diff_hard: | |
| 909 | + | lda #PERFECT_WINDOW_HARD | |
| 910 | + | sta perfect_window | |
| 911 | + | lda #GOOD_WINDOW_HARD | |
| 912 | + | sta good_window | |
| 913 | + | lda #HEALTH_MISS_HARD | |
| 914 | + | sta health_miss_amt | |
| 697 | 915 | rts | |
| 698 | 916 | | |
| 699 | 917 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 707 | 925 | jmp load_song2 | |
| 708 | 926 | | |
| 709 | 927 | load_song1: | |
| 710 | - | ; Song 1: First Steps (120 BPM, minor key, steady) | |
| 711 | 928 | lda #TEMPO_SONG1 | |
| 712 | 929 | sta frames_per_beat | |
| 713 | 930 | lda #LENGTH_SONG1 | |
| ... | |||
| 719 | 936 | jmp start_game | |
| 720 | 937 | | |
| 721 | 938 | load_song2: | |
| 722 | - | ; Song 2: Upbeat Groove (130 BPM, major key, bouncy) | |
| 723 | 939 | lda #TEMPO_SONG2 | |
| 724 | 940 | sta frames_per_beat | |
| 725 | 941 | lda #LENGTH_SONG2 | |
| ... | |||
| 781 | 997 | rts | |
| 782 | 998 | | |
| 783 | 999 | ; ---------------------------------------------------------------------------- | |
| 784 | - | ; Initialize Game (called when starting from menu) | |
| 1000 | + | ; Initialize Game | |
| 785 | 1001 | ; ---------------------------------------------------------------------------- | |
| 786 | 1002 | | |
| 787 | 1003 | init_game: | |
| ... | |||
| 812 | 1028 | update_playing: | |
| 813 | 1029 | inc frame_count | |
| 814 | 1030 | lda frame_count | |
| 815 | - | cmp frames_per_beat ; Use per-song tempo! | |
| 1031 | + | cmp frames_per_beat | |
| 816 | 1032 | bcc no_new_beat | |
| 817 | 1033 | | |
| 818 | 1034 | lda #0 | |
| ... | |||
| 1023 | 1239 | ldx #0 | |
| 1024 | 1240 | draw_sn1: | |
| 1025 | 1241 | lda song1_name,x | |
| 1026 | - | beq draw_song_name_done | |
| 1242 | + | beq draw_difficulty_hud | |
| 1027 | 1243 | sta SCREEN + 28,x | |
| 1028 | 1244 | lda #11 | |
| 1029 | 1245 | sta COLRAM + 28,x | |
| ... | |||
| 1034 | 1250 | ldx #0 | |
| 1035 | 1251 | draw_sn2: | |
| 1036 | 1252 | lda song2_name,x | |
| 1037 | - | beq draw_song_name_done | |
| 1253 | + | beq draw_difficulty_hud | |
| 1038 | 1254 | sta SCREEN + 26,x | |
| 1039 | 1255 | lda #11 | |
| 1040 | 1256 | sta COLRAM + 26,x | |
| 1041 | 1257 | inx | |
| 1042 | 1258 | jmp draw_sn2 | |
| 1259 | + | | |
| 1260 | + | draw_difficulty_hud: | |
| 1261 | + | ; Show difficulty on gameplay HUD | |
| 1262 | + | lda difficulty | |
| 1263 | + | cmp #DIFF_EASY | |
| 1264 | + | beq draw_hud_easy | |
| 1265 | + | cmp #DIFF_NORMAL | |
| 1266 | + | beq draw_hud_normal | |
| 1267 | + | jmp draw_hud_hard | |
| 1268 | + | | |
| 1269 | + | draw_hud_easy: | |
| 1270 | + | ldx #0 | |
| 1271 | + | draw_hud_e: | |
| 1272 | + | lda easy_text,x | |
| 1273 | + | beq draw_song_name_done | |
| 1274 | + | sta SCREEN + (COMBO_ROW * 40),x | |
| 1275 | + | lda #EASY_COL | |
| 1276 | + | sta COLRAM + (COMBO_ROW * 40),x | |
| 1277 | + | inx | |
| 1278 | + | jmp draw_hud_e | |
| 1279 | + | | |
| 1280 | + | draw_hud_normal: | |
| 1281 | + | ldx #0 | |
| 1282 | + | draw_hud_n: | |
| 1283 | + | lda normal_text,x | |
| 1284 | + | beq draw_song_name_done | |
| 1285 | + | sta SCREEN + (COMBO_ROW * 40),x | |
| 1286 | + | lda #NORMAL_COL | |
| 1287 | + | sta COLRAM + (COMBO_ROW * 40),x | |
| 1288 | + | inx | |
| 1289 | + | jmp draw_hud_n | |
| 1290 | + | | |
| 1291 | + | draw_hud_hard: | |
| 1292 | + | ldx #0 | |
| 1293 | + | draw_hud_h: | |
| 1294 | + | lda hard_text,x | |
| 1295 | + | beq draw_song_name_done | |
| 1296 | + | sta SCREEN + (COMBO_ROW * 40),x | |
| 1297 | + | lda #HARD_COL | |
| 1298 | + | sta COLRAM + (COMBO_ROW * 40),x | |
| 1299 | + | inx | |
| 1300 | + | jmp draw_hud_h | |
| 1043 | 1301 | | |
| 1044 | 1302 | draw_song_name_done: | |
| 1045 | 1303 | rts | |
| ... | |||
| 1374 | 1632 | | |
| 1375 | 1633 | ; Check if past hit zone (missed) | |
| 1376 | 1634 | lda note_col,x | |
| 1377 | - | cmp #HIT_ZONE_MIN | |
| 1635 | + | cmp #HIT_ZONE_START | |
| 1378 | 1636 | bcs note_still_active | |
| 1379 | 1637 | | |
| 1380 | 1638 | ; Note was missed | |
| ... | |||
| 1571 | 1829 | jsr play_miss_sound | |
| 1572 | 1830 | jsr break_combo | |
| 1573 | 1831 | | |
| 1574 | - | ; Decrease health | |
| 1575 | - | lda #HEALTH_MISS | |
| 1832 | + | ; Decrease health (using difficulty-adjusted amount) | |
| 1833 | + | lda health_miss_amt | |
| 1576 | 1834 | jsr decrease_health | |
| 1577 | 1835 | | |
| 1578 | 1836 | ; Flash border red | |
| ... | |||
| 1977 | 2235 | rts | |
| 1978 | 2236 | | |
| 1979 | 2237 | ; ---------------------------------------------------------------------------- | |
| 1980 | - | ; Check Hit | |
| 2238 | + | ; Check Hit (with difficulty-adjusted timing windows) | |
| 1981 | 2239 | ; ---------------------------------------------------------------------------- | |
| 1982 | 2240 | | |
| 1983 | 2241 | check_hit: | |
| ... | |||
| 1990 | 2248 | cmp key_pressed | |
| 1991 | 2249 | bne check_hit_next | |
| 1992 | 2250 | | |
| 2251 | + | ; Check if note is in hittable range | |
| 1993 | 2252 | lda note_col,x | |
| 1994 | - | cmp #HIT_ZONE_MIN | |
| 2253 | + | cmp #HIT_ZONE_START | |
| 1995 | 2254 | bcc check_hit_next | |
| 1996 | - | cmp #HIT_ZONE_MAX+1 | |
| 2255 | + | cmp #HIT_ZONE_END+1 | |
| 1997 | 2256 | bcs check_hit_next | |
| 1998 | 2257 | | |
| 2258 | + | ; Note is hittable - determine quality based on distance from perfect | |
| 1999 | 2259 | lda note_freq,x | |
| 2000 | 2260 | sta hit_note_freq | |
| 2001 | 2261 | | |
| 2262 | + | ; Calculate distance from perfect column | |
| 2002 | 2263 | lda note_col,x | |
| 2003 | - | cmp #HIT_ZONE_CENTRE | |
| 2004 | - | bcc hit_good | |
| 2005 | - | cmp #HIT_ZONE_CENTRE+2 | |
| 2006 | - | bcs hit_good | |
| 2264 | + | sec | |
| 2265 | + | sbc #PERFECT_COL_POS | |
| 2266 | + | bcs dist_positive | |
| 2267 | + | ; Negative distance - make it positive | |
| 2268 | + | eor #$FF | |
| 2269 | + | clc | |
| 2270 | + | adc #1 | |
| 2271 | + | dist_positive: | |
| 2272 | + | ; A now contains absolute distance from perfect | |
| 2273 | + | | |
| 2274 | + | ; Check if within perfect window | |
| 2275 | + | cmp perfect_window | |
| 2276 | + | bcc hit_is_perfect | |
| 2277 | + | beq hit_is_perfect | |
| 2278 | + | | |
| 2279 | + | ; Check if within good window | |
| 2280 | + | cmp good_window | |
| 2281 | + | bcc hit_is_good | |
| 2282 | + | beq hit_is_good | |
| 2283 | + | | |
| 2284 | + | ; Outside good window but in hit zone - still counts as good | |
| 2285 | + | jmp hit_is_good | |
| 2007 | 2286 | | |
| 2287 | + | hit_is_perfect: | |
| 2008 | 2288 | lda #2 | |
| 2009 | 2289 | sta hit_quality | |
| 2010 | 2290 | jmp hit_found | |
| 2011 | 2291 | | |
| 2012 | - | hit_good: | |
| 2292 | + | hit_is_good: | |
| 2013 | 2293 | lda #1 | |
| 2014 | 2294 | sta hit_quality | |
| 2015 | 2295 | | |
| ... | |||
| 2818 | 3098 | !scr "press fire to continue" | |
| 2819 | 3099 | !byte 0 | |
| 2820 | 3100 | | |
| 2821 | - | ; ============================================================================ | |
| 2822 | - | ; SONG 1 DATA - "First Steps" (120 BPM, minor key, steady) | |
| 2823 | 3101 | ; ============================================================================ | |
| 2824 | - | ; Format: beat, track (1-3), SID frequency high byte | |
| 2825 | - | ; A gentle introduction - steady rhythm, predictable patterns | |
| 3102 | + | ; SONG 1 DATA - "First Steps" (120 BPM) | |
| 2826 | 3103 | ; ============================================================================ | |
| 2827 | 3104 | | |
| 2828 | 3105 | song1_data: | |
| 2829 | - | ; Opening - gentle introduction | |
| 2830 | - | !byte 0, 1, $47 ; Beat 0: Track 1, high note | |
| 2831 | - | !byte 2, 2, $2C ; Beat 2: Track 2, mid note | |
| 2832 | - | !byte 4, 3, $11 ; Beat 4: Track 3, low note | |
| 2833 | - | | |
| 2834 | - | ; Build - add more notes | |
| 3106 | + | !byte 0, 1, $47 | |
| 3107 | + | !byte 2, 2, $2C | |
| 3108 | + | !byte 4, 3, $11 | |
| 2835 | 3109 | !byte 8, 1, $3B | |
| 2836 | 3110 | !byte 10, 2, $27 | |
| 2837 | 3111 | !byte 12, 3, $13 | |
| 2838 | - | | |
| 2839 | - | ; Complexity - overlapping patterns | |
| 2840 | 3112 | !byte 16, 1, $35 | |
| 2841 | 3113 | !byte 17, 2, $2C | |
| 2842 | 3114 | !byte 18, 1, $3B | |
| 2843 | 3115 | !byte 20, 3, $16 | |
| 2844 | - | | |
| 2845 | - | ; Theme repeats | |
| 2846 | 3116 | !byte 24, 1, $47 | |
| 2847 | 3117 | !byte 26, 2, $35 | |
| 2848 | 3118 | !byte 28, 3, $11 | |
| 2849 | - | | |
| 2850 | - | ; Variation | |
| 2851 | 3119 | !byte 32, 2, $2F | |
| 2852 | 3120 | !byte 34, 1, $4F | |
| 2853 | 3121 | !byte 36, 3, $17 | |
| 2854 | - | | |
| 2855 | - | ; Building intensity | |
| 2856 | 3122 | !byte 40, 1, $58 | |
| 2857 | 3123 | !byte 42, 2, $2C | |
| 2858 | 3124 | !byte 44, 3, $11 | |
| 2859 | 3125 | !byte 46, 2, $27 | |
| 2860 | - | | |
| 2861 | - | ; Climax section | |
| 2862 | 3126 | !byte 48, 1, $6A | |
| 2863 | 3127 | !byte 49, 2, $35 | |
| 2864 | 3128 | !byte 50, 1, $58 | |
| 2865 | 3129 | !byte 52, 3, $1A | |
| 2866 | 3130 | !byte 54, 2, $2F | |
| 2867 | - | | |
| 2868 | - | ; Resolution | |
| 2869 | 3131 | !byte 56, 1, $47 | |
| 2870 | 3132 | !byte 58, 2, $2C | |
| 2871 | 3133 | !byte 60, 3, $11 | |
| 2872 | 3134 | !byte 62, 1, $35 | |
| 2873 | - | | |
| 2874 | - | !byte $FF ; End marker | |
| 3135 | + | !byte $FF | |
| 2875 | 3136 | | |
| 2876 | - | ; ============================================================================ | |
| 2877 | - | ; SONG 2 DATA - "Upbeat Groove" (130 BPM, major key, bouncy) | |
| 2878 | 3137 | ; ============================================================================ | |
| 2879 | - | ; Format: beat, track (1-3), SID frequency high byte | |
| 2880 | - | ; Brighter and bouncier than Song 1 - more syncopation, higher notes | |
| 2881 | - | ; Uses C major scale for brighter feel | |
| 3138 | + | ; SONG 2 DATA - "Upbeat Groove" (130 BPM) | |
| 2882 | 3139 | ; ============================================================================ | |
| 2883 | 3140 | | |
| 2884 | 3141 | song2_data: | |
| 2885 | - | ; Opening - bouncy start | |
| 2886 | - | !byte 0, 1, $47 ; C5 - bright start | |
| 2887 | - | !byte 1, 2, $35 ; G4 - quick response | |
| 2888 | - | !byte 3, 3, $16 ; E3 - bass hit | |
| 2889 | - | | |
| 2890 | - | ; Bouncy phrase 1 | |
| 2891 | - | !byte 4, 1, $4F ; D5 | |
| 2892 | - | !byte 5, 2, $2C ; E4 | |
| 2893 | - | !byte 7, 1, $47 ; C5 | |
| 2894 | - | !byte 8, 3, $11 ; C3 bass | |
| 2895 | - | | |
| 2896 | - | ; Syncopated section | |
| 2897 | - | !byte 10, 2, $35 ; G4 | |
| 2898 | - | !byte 11, 1, $58 ; E5 | |
| 2899 | - | !byte 13, 2, $2C ; E4 | |
| 2900 | - | !byte 14, 3, $1A ; G3 bass | |
| 2901 | - | !byte 15, 1, $47 ; C5 | |
| 2902 | - | | |
| 2903 | - | ; Build energy | |
| 2904 | - | !byte 16, 1, $4F ; D5 | |
| 2905 | - | !byte 17, 2, $35 ; G4 | |
| 2906 | - | !byte 18, 3, $16 ; E3 | |
| 2907 | - | !byte 19, 1, $58 ; E5 | |
| 2908 | - | !byte 20, 2, $2C ; E4 | |
| 2909 | - | | |
| 2910 | - | ; High energy phrase | |
| 2911 | - | !byte 22, 1, $6A ; G5 - high! | |
| 2912 | - | !byte 23, 2, $35 ; G4 | |
| 2913 | - | !byte 24, 3, $11 ; C3 | |
| 2914 | - | !byte 25, 1, $58 ; E5 | |
| 2915 | - | !byte 26, 2, $47 ; C5 on mid track | |
| 2916 | - | !byte 28, 3, $1A ; G3 | |
| 2917 | - | | |
| 2918 | - | ; Bouncy interlude | |
| 2919 | - | !byte 30, 1, $47 ; C5 | |
| 2920 | - | !byte 31, 2, $2C ; E4 | |
| 2921 | - | !byte 32, 1, $4F ; D5 | |
| 2922 | - | !byte 33, 3, $16 ; E3 | |
| 2923 | - | !byte 34, 2, $35 ; G4 | |
| 2924 | - | !byte 35, 1, $58 ; E5 | |
| 2925 | - | | |
| 2926 | - | ; Peak section - more notes! | |
| 2927 | - | !byte 36, 3, $11 ; C3 | |
| 2928 | - | !byte 37, 1, $6A ; G5 | |
| 2929 | - | !byte 38, 2, $47 ; C5 | |
| 2930 | - | !byte 39, 1, $58 ; E5 | |
| 2931 | - | !byte 40, 3, $1A ; G3 | |
| 2932 | - | !byte 41, 2, $35 ; G4 | |
| 2933 | - | !byte 42, 1, $4F ; D5 | |
| 2934 | - | !byte 43, 2, $2C ; E4 | |
| 2935 | - | | |
| 2936 | - | ; Continued intensity | |
| 2937 | - | !byte 44, 1, $47 ; C5 | |
| 2938 | - | !byte 45, 3, $16 ; E3 | |
| 2939 | - | !byte 46, 2, $35 ; G4 | |
| 2940 | - | !byte 47, 1, $58 ; E5 | |
| 2941 | - | !byte 48, 3, $11 ; C3 | |
| 2942 | - | !byte 49, 2, $47 ; C5 | |
| 2943 | - | | |
| 2944 | - | ; Wind down | |
| 2945 | - | !byte 51, 1, $4F ; D5 | |
| 2946 | - | !byte 52, 3, $1A ; G3 | |
| 2947 | - | !byte 54, 2, $35 ; G4 | |
| 2948 | - | !byte 55, 1, $47 ; C5 | |
| 2949 | - | | |
| 2950 | - | ; Final phrase | |
| 2951 | - | !byte 56, 3, $11 ; C3 | |
| 2952 | - | !byte 58, 2, $2C ; E4 | |
| 2953 | - | !byte 59, 1, $58 ; E5 | |
| 2954 | - | !byte 60, 3, $16 ; E3 | |
| 2955 | - | !byte 61, 2, $35 ; G4 | |
| 2956 | - | !byte 62, 1, $47 ; C5 - end on root | |
| 2957 | - | | |
| 2958 | - | !byte $FF ; End marker | |
| 3142 | + | !byte 0, 1, $47 | |
| 3143 | + | !byte 1, 2, $35 | |
| 3144 | + | !byte 3, 3, $16 | |
| 3145 | + | !byte 4, 1, $4F | |
| 3146 | + | !byte 5, 2, $2C | |
| 3147 | + | !byte 7, 1, $47 | |
| 3148 | + | !byte 8, 3, $11 | |
| 3149 | + | !byte 10, 2, $35 | |
| 3150 | + | !byte 11, 1, $58 | |
| 3151 | + | !byte 13, 2, $2C | |
| 3152 | + | !byte 14, 3, $1A | |
| 3153 | + | !byte 15, 1, $47 | |
| 3154 | + | !byte 16, 1, $4F | |
| 3155 | + | !byte 17, 2, $35 | |
| 3156 | + | !byte 18, 3, $16 | |
| 3157 | + | !byte 19, 1, $58 | |
| 3158 | + | !byte 20, 2, $2C | |
| 3159 | + | !byte 22, 1, $6A | |
| 3160 | + | !byte 23, 2, $35 | |
| 3161 | + | !byte 24, 3, $11 | |
| 3162 | + | !byte 25, 1, $58 | |
| 3163 | + | !byte 26, 2, $47 | |
| 3164 | + | !byte 28, 3, $1A | |
| 3165 | + | !byte 30, 1, $47 | |
| 3166 | + | !byte 31, 2, $2C | |
| 3167 | + | !byte 32, 1, $4F | |
| 3168 | + | !byte 33, 3, $16 | |
| 3169 | + | !byte 34, 2, $35 | |
| 3170 | + | !byte 35, 1, $58 | |
| 3171 | + | !byte 36, 3, $11 | |
| 3172 | + | !byte 37, 1, $6A | |
| 3173 | + | !byte 38, 2, $47 | |
| 3174 | + | !byte 39, 1, $58 | |
| 3175 | + | !byte 40, 3, $1A | |
| 3176 | + | !byte 41, 2, $35 | |
| 3177 | + | !byte 42, 1, $4F | |
| 3178 | + | !byte 43, 2, $2C | |
| 3179 | + | !byte 44, 1, $47 | |
| 3180 | + | !byte 45, 3, $16 | |
| 3181 | + | !byte 46, 2, $35 | |
| 3182 | + | !byte 47, 1, $58 | |
| 3183 | + | !byte 48, 3, $11 | |
| 3184 | + | !byte 49, 2, $47 | |
| 3185 | + | !byte 51, 1, $4F | |
| 3186 | + | !byte 52, 3, $1A | |
| 3187 | + | !byte 54, 2, $35 | |
| 3188 | + | !byte 55, 1, $47 | |
| 3189 | + | !byte 56, 3, $11 | |
| 3190 | + | !byte 58, 2, $2C | |
| 3191 | + | !byte 59, 1, $58 | |
| 3192 | + | !byte 60, 3, $16 | |
| 3193 | + | !byte 61, 2, $35 | |
| 3194 | + | !byte 62, 1, $47 | |
| 3195 | + | !byte $FF | |
| 2959 | 3196 | | |
| 2960 | 3197 | ; ---------------------------------------------------------------------------- | |
| 2961 | 3198 | ; Note Arrays | |
| ... | |||
| 2984 | 3221 | max_combo: !byte 0 | |
| 2985 | 3222 | | |
| 2986 | 3223 | ; ============================================================================ | |
| 2987 | - | ; END OF SID SYMPHONY - UNIT 18 | |
| 3224 | + | ; END OF SID SYMPHONY - UNIT 19 | |
| 2988 | 3225 | ; ============================================================================ | |
| 2989 | 3226 | |