diff --git a/README.md b/README.md index 862781b..92f83f4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # C64 Program Collection -Forty small, polished programs for the Commodore 64, written in stock Commodore BASIC V2 and 6502 machine code. The collection mixes arcade action, puzzles, simulations, mathematics, physics, creative tools, music, utilities, and visual effects. Every entry is self-contained and runs on an ordinary PAL or NTSC C64 without a cartridge or BASIC extension. +Forty-one small, polished programs for the Commodore 64, written in stock Commodore BASIC V2 and 6502 machine code. The collection mixes arcade action, puzzles, simulations, mathematics, physics, creative tools, music, utilities, and visual effects. Every entry is self-contained and runs on an ordinary PAL or NTSC C64 without a cartridge or BASIC extension. Browse and run the collection at [c64.p20.dev](https://c64.p20.dev). @@ -48,6 +48,7 @@ Browse and run the collection at [c64.p20.dev](https://c64.p20.dev). | Orbit Lab | 2026-10-22 | Physics | BASIC | `1`-`3`, `T`, `R`, Space, `Q` | Explore how velocity and gravity shape a simple two-dimensional orbit. | [BAS](orbit-lab/orbit-lab.bas) | [Run](https://c64.p20.dev/program/orbit-lab) | | Haunted Night | 2026-10-23 | Demo | Machine Code | `1`-`3`, `L`, `C`, `M`, Space, `Q` | Visit a moonlit haunted manor alive with ghosts, bats, lightning, and a sinister three-voice SID score. | [ASM](haunted-night/haunted-night.asm) | [Run](https://c64.p20.dev/program/haunted-night) | | Happy Birthday, Beca! | 2026-10-23 | Demo | Machine Code | `F`, `C`, `M`, Space, `Q` | Celebrate Beca's fifth birthday with cake, balloons, confetti, purrs, and a cheerful three-voice SID serenade. | [ASM](happy-birthday-beca/happy-birthday-beca.asm) | [Run](https://c64.p20.dev/program/happy-birthday-beca) | +| TV Fireplace | 2026-10-24 | Demo | Machine Code | `1`-`3`, `F`, `C`, `S`, Space, `Q` | Settle beside a classic brick fireplace with dancing flames, glowing embers, rising sparks, and crackling SID sound. | [ASM](tv-fireplace/tv-fireplace.asm) | [Run](https://c64.p20.dev/program/tv-fireplace) | ## Running the programs diff --git a/programs.json b/programs.json index ae04e27..70ec897 100644 --- a/programs.json +++ b/programs.json @@ -238,5 +238,11 @@ "description": "Celebrate Beca's fifth birthday with cake, balloons, confetti, purrs, and a cheerful three-voice SID serenade.", "controls": [{"keys":["F"],"description":"Replay the birthday song"},{"keys":["C"],"description":"Launch a fresh confetti burst"},{"keys":["M"],"description":"Mute or restore the SID music"},{"keys":["SPACE"],"description":"Pause or resume the celebration"},{"keys":["Q"],"description":"Return to BASIC"}], "path": "happy-birthday-beca/happy-birthday-beca.prg", "source": "happy-birthday-beca/happy-birthday-beca.asm" + }, + { + "title": "TV Fireplace", "category": "Demo", "language": "machine-code", "releaseDate": "2026-10-24", + "description": "Settle beside a classic brick fireplace with dancing flames, glowing embers, rising sparks, and crackling SID sound.", + "controls": [{"keys":["1","2","3"],"description":"Select slow, medium, or lively flames"},{"keys":["F"],"description":"Stoke the fire"},{"keys":["C"],"description":"Change the fireplace palette"},{"keys":["S"],"description":"Mute or restore the crackling sound"},{"keys":["SPACE"],"description":"Pause or resume the fire"},{"keys":["Q"],"description":"Return to BASIC"}], + "path": "tv-fireplace/tv-fireplace.prg", "source": "tv-fireplace/tv-fireplace.asm" } ] diff --git a/screenshots.json b/screenshots.json index 5de0751..be22a2d 100644 --- a/screenshots.json +++ b/screenshots.json @@ -84,5 +84,11 @@ { "key": "C", "waitAfterMs": 400 } ], "captureAfterMs": 250 + }, + "tv-fireplace": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 5000 } + ], + "captureAfterMs": 500 } } diff --git a/scripts/build-machine-code.sh b/scripts/build-machine-code.sh index d2cc196..444992e 100644 --- a/scripts/build-machine-code.sh +++ b/scripts/build-machine-code.sh @@ -16,7 +16,8 @@ for source in \ neon-rift/neon-rift.asm \ infinite-road/infinite-road.asm \ haunted-night/haunted-night.asm \ - happy-birthday-beca/happy-birthday-beca.asm + happy-birthday-beca/happy-birthday-beca.asm \ + tv-fireplace/tv-fireplace.asm do output="${source%.asm}.prg" echo "Assembling $source -> $output" diff --git a/tv-fireplace/screenshot.png b/tv-fireplace/screenshot.png new file mode 100644 index 0000000..33b2ece Binary files /dev/null and b/tv-fireplace/screenshot.png differ diff --git a/tv-fireplace/tv-fireplace.asm b/tv-fireplace/tv-fireplace.asm new file mode 100644 index 0000000..d4d783d --- /dev/null +++ b/tv-fireplace/tv-fireplace.asm @@ -0,0 +1,648 @@ +!cpu 6510 + +; TV FIREPLACE +; A warm PETSCII fireplace with animated flames, embers and SID crackles. +; Self-starting BASIC line: 10 SYS4096 +* = $0801 +!word basic_end +!word 10 +!byte $9e +!text "4096" +!byte 0 +basic_end: !word 0 + +* = $1000 +SCREEN = $0400 +COLOR = $d800 +GETIN = $ffe4 +CHROUT = $ffd2 + +start: + jsr save_state + lda #0 + sta $d020 + sta $d021 + jsr show_splash + jsr initialize_demo + +main_loop: + jsr read_controls + lda quit_flag + beq wait_frame + jmp exit_demo +wait_frame: + lda $a2 + cmp last_jiffy + beq main_loop + sta last_jiffy + lda paused + bne main_loop + inc frame_divider + lda frame_divider + cmp animation_speed + bcc sound_only + lda #0 + sta frame_divider + jsr update_fire +sound_only: + lda sound_muted + bne main_loop + jsr update_sound + jmp main_loop + +save_state: + lda $d020 + sta old_border + lda $d021 + sta old_background + lda $d011 + sta old_d011 + lda $d016 + sta old_d016 + lda $d018 + sta old_d018 + ldx #0 +save_sid: + lda $d400,x + sta old_sid,x + inx + cpx #25 + bne save_sid + rts + +initialize_demo: + lda #0 + sta paused + sta sound_muted + sta quit_flag + sta frame_divider + sta flame_phase + sta palette_index + sta stoke_timer + sta crackle_timer + lda #2 + sta animation_speed + lda #$6d + sta random_seed + lda $a2 + sta last_jiffy + lda #0 + sta $d020 + sta $d021 + lda old_d011 + and #$9f + ora #$10 + sta $d011 + lda old_d016 + and #$e8 + ora #8 + sta $d016 + lda old_d018 + and #$f1 + ora #$14 + sta $d018 + lda #147 + jsr CHROUT + jsr clear_screen + jsr draw_fireplace + jsr initialize_sid + jsr update_fire + rts + +clear_screen: + ldx #0 +clear_loop: + lda #32 + sta SCREEN,x + sta SCREEN+256,x + sta SCREEN+512,x + sta SCREEN+768,x + lda #0 + sta COLOR,x + sta COLOR+256,x + sta COLOR+512,x + sta COLOR+768,x + inx + bne clear_loop + rts + +draw_fireplace: + ; Broad wooden mantel on rows 3 and 4. + ldx #0 +mantel_loop: + lda #160 + sta SCREEN+124,x + sta SCREEN+164,x + lda #9 + sta COLOR+124,x + lda #8 + sta COLOR+164,x + inx + cpx #32 + bne mantel_loop + + ; Brick columns frame the firebox. + lda #5 + sta draw_row +brick_row_loop: + ldx draw_row + lda screen_row_lo,x + sta $fb + lda screen_row_hi,x + sta $fc + lda color_row_lo,x + sta $fd + lda color_row_hi,x + sta $fe + ldy #5 +left_bricks: + tya + eor draw_row + and #1 + tax + lda brick_chars,x + sta ($fb),y + lda palette_index + asl + tax + tya + and #1 + beq left_color + inx +left_color: + lda brick_colors,x + sta ($fd),y + iny + cpy #11 + bne left_bricks + ldy #29 +right_bricks: + tya + eor draw_row + and #1 + tax + lda brick_chars,x + sta ($fb),y + lda palette_index + asl + tax + tya + and #1 + beq right_color + inx +right_color: + lda brick_colors,x + sta ($fd),y + iny + cpy #35 + bne right_bricks + inc draw_row + lda draw_row + cmp #20 + bne brick_row_loop + + ; Stone hearth and warm wooden logs. + ldx #0 +hearth_loop: + lda #160 + sta SCREEN+804,x + sta SCREEN+844,x + lda #11 + sta COLOR+804,x + lda #12 + sta COLOR+844,x + inx + cpx #32 + bne hearth_loop + ldx #0 +logs_loop: + lda log_chars,x + sta SCREEN+771,x + lda log_colors,x + sta COLOR+771,x + inx + cpx #18 + bne logs_loop + rts + +update_fire: + inc flame_phase + lda stoke_timer + beq build_heights + dec stoke_timer +build_heights: + ldx #0 +height_loop: + txa + clc + adc flame_phase + and #15 + tay + lda flame_wave,y + sta work_value + jsr random_byte + and #1 + clc + adc work_value + ldy stoke_timer + beq store_height + cmp #0 + beq store_height + clc + adc #2 + cmp #10 + bcc store_height + lda #9 +store_height: + sta flame_heights,x + inx + cpx #16 + bne height_loop + + lda #0 + sta flame_level +level_loop: + ldx flame_level + lda flame_row_lo,x + sta $fb + lda flame_row_hi,x + sta $fc + lda flame_color_lo,x + sta $fd + lda flame_color_hi,x + sta $fe + ldx #0 +cell_loop: + lda flame_heights,x + cmp flame_level + beq empty_cell + bcc empty_cell + stx flame_column + jsr random_byte + and #3 + tay + ldx flame_level + cpx #3 + bcc choose_body_char + lda top_chars,y + bne write_flame_char +choose_body_char: + lda body_chars,y +write_flame_char: + sta temp_char + ldx flame_column + txa + clc + adc #12 + tay + sta work_column + lda temp_char +store_flame: + sta ($fb),y + ldx palette_index + lda palette_offsets,x + clc + adc flame_level + tax + lda flame_colors,x + sta ($fd),y + ldx flame_column + jmp next_cell +empty_cell: + txa + clc + adc #12 + tay + lda #32 + sta ($fb),y + lda #0 + sta ($fd),y +next_cell: + inx + cpx #16 + bne cell_loop + inc flame_level + lda flame_level + cmp #9 + beq levels_done + jmp level_loop +levels_done: + jsr draw_sparks + jsr draw_embers + rts + +draw_sparks: + ; Clear the two rows above the flames. + ldx #0 +clear_sparks: + lda #32 + sta SCREEN+332,x + sta SCREEN+372,x + lda #0 + sta COLOR+332,x + sta COLOR+372,x + inx + cpx #16 + bne clear_sparks + ldx #0 +spark_loop: + jsr random_byte + and #15 + tay + lda #46 + sta SCREEN+332,y + lda spark_colors,x + sta COLOR+332,y + jsr random_byte + and #15 + tay + lda #42 + sta SCREEN+372,y + lda spark_colors+1,x + sta COLOR+372,y + inx + cpx #3 + bne spark_loop + rts + +draw_embers: + ldx #0 +ember_loop: + jsr random_byte + and #3 + tay + lda ember_chars,y + sta SCREEN+732,x + lda ember_colors,y + sta COLOR+732,x + inx + cpx #16 + bne ember_loop + rts + +random_byte: + lda random_seed + asl + bcc random_no_xor + eor #$1d +random_no_xor: + adc #$17 + sta random_seed + rts + +initialize_sid: + ldx #0 +clear_sid: + lda #0 + sta $d400,x + inx + cpx #25 + bne clear_sid + lda #$40 + sta $d405 + lda #$58 + sta $d406 + lda #$28 + sta $d400 + lda #$03 + sta $d401 + lda #$11 + sta $d404 + lda #$0f + sta $d418 + rts + +update_sound: + lda crackle_timer + beq maybe_crackle + dec crackle_timer + bne sound_done + lda #$80 + sta $d412 + rts +maybe_crackle: + jsr random_byte + and #31 + bne sound_done + lda random_seed + sta $d40e + and #7 + ora #2 + sta $d40f + lda #$08 + sta $d413 + lda #$28 + sta $d414 + lda #$81 + sta $d412 + lda #2 + sta crackle_timer +sound_done: + rts + +read_controls: + jsr GETIN + beq controls_done + cmp #81 + bne check_one + lda #1 + sta quit_flag + rts +check_one: + cmp #49 + bne check_two + lda #4 + sta animation_speed + rts +check_two: + cmp #50 + bne check_three + lda #2 + sta animation_speed + rts +check_three: + cmp #51 + bne check_stoke + lda #1 + sta animation_speed + rts +check_stoke: + cmp #70 + bne check_palette + lda #90 + sta stoke_timer + rts +check_palette: + cmp #67 + bne check_sound + inc palette_index + lda palette_index + cmp #3 + bcc redraw_bricks + lda #0 + sta palette_index +redraw_bricks: + jsr draw_fireplace + rts +check_sound: + cmp #83 + bne check_pause + lda sound_muted + eor #1 + sta sound_muted + beq restore_sound + lda #0 + sta $d418 + rts +restore_sound: + jsr initialize_sid + rts +check_pause: + cmp #32 + bne controls_done + lda paused + eor #1 + sta paused +controls_done: + rts + +show_splash: + lda #147 + jsr CHROUT + lda #splash_text + sta $fc + ldy #0 +print_splash: + lda ($fb),y + beq wait_for_space + jsr CHROUT + iny + bne print_splash + inc $fc + jmp print_splash +wait_for_space: + jsr GETIN + cmp #32 + bne wait_for_space + rts + +exit_demo: + ldx #0 +restore_sid: + lda old_sid,x + sta $d400,x + inx + cpx #25 + bne restore_sid + lda old_d011 + sta $d011 + lda old_d016 + sta $d016 + lda old_d018 + sta $d018 + lda old_border + sta $d020 + lda old_background + sta $d021 + lda #147 + jsr CHROUT + rts + +splash_text: +!byte 13,13,13,154 +!text " TV FIREPLACE" +!byte 13,13,5 +!text " A WARM HEARTH OF FLAME AND EMBERS" +!byte 13,13,13,153 +!text " CONTROLS" +!byte 13,13,158 +!text " 1-3" +!byte 5 +!text " SPEED " +!byte 158 +!text "F" +!byte 5 +!text " STOKE " +!byte 158 +!text "C" +!byte 5 +!text " PALETTE" +!byte 13,158 +!text " SPACE" +!byte 5 +!text " PAUSE " +!byte 158 +!text "S" +!byte 5 +!text " SOUND " +!byte 158 +!text "Q" +!byte 5 +!text " QUIT" +!byte 13,13,13,5 +!text " HTTPS://C64.P20.DEV" +!byte 13,13,152 +!text " RELEASED 24 OCTOBER 2026" +!byte 13,13,13,158 +!text " PRESS SPACE TO START" +!byte 0 + +brick_chars: !byte 160,102 +brick_colors: !byte 9,8,2,9,12,15 +palette_offsets: !byte 0,9,18 +flame_colors: +!byte 7,7,7,8,8,8,2,2,2 +!byte 1,7,7,7,8,8,10,2,2 +!byte 7,7,3,3,14,14,6,6,6 +body_chars: !byte 160,160,102,160 +top_chars: !byte 102,46,42,102 +flame_wave: !byte 0,2,4,1,3,0,5,2,0,4,1,3,5,0,2,4 +spark_colors: !byte 7,8,2,7 +ember_chars: !byte 46,42,102,160 +ember_colors: !byte 2,8,9,7 +log_chars: !byte 109,109,109,110,110,110,109,109,110,110,109,109,110,110,109,109,110,110 +log_colors: !byte 9,9,8,9,8,9,9,8,9,8,9,9,8,9,8,9,9,8 + +screen_row_lo: +!for row,0,24 { !byte <(SCREEN + row*40) } +screen_row_hi: +!for row,0,24 { !byte >(SCREEN + row*40) } +color_row_lo: +!for row,0,24 { !byte <(COLOR + row*40) } +color_row_hi: +!for row,0,24 { !byte >(COLOR + row*40) } +flame_row_lo: +!byte <(SCREEN+720),<(SCREEN+680),<(SCREEN+640),<(SCREEN+600),<(SCREEN+560),<(SCREEN+520),<(SCREEN+480),<(SCREEN+440),<(SCREEN+400) +flame_row_hi: +!byte >(SCREEN+720),>(SCREEN+680),>(SCREEN+640),>(SCREEN+600),>(SCREEN+560),>(SCREEN+520),>(SCREEN+480),>(SCREEN+440),>(SCREEN+400) +flame_color_lo: +!byte <(COLOR+720),<(COLOR+680),<(COLOR+640),<(COLOR+600),<(COLOR+560),<(COLOR+520),<(COLOR+480),<(COLOR+440),<(COLOR+400) +flame_color_hi: +!byte >(COLOR+720),>(COLOR+680),>(COLOR+640),>(COLOR+600),>(COLOR+560),>(COLOR+520),>(COLOR+480),>(COLOR+440),>(COLOR+400) + +old_border: !byte 0 +old_background: !byte 0 +old_d011: !byte 0 +old_d016: !byte 0 +old_d018: !byte 0 +old_sid: !fill 25,0 +paused: !byte 0 +sound_muted: !byte 0 +quit_flag: !byte 0 +frame_divider: !byte 0 +animation_speed: !byte 0 +flame_phase: !byte 0 +palette_index: !byte 0 +stoke_timer: !byte 0 +crackle_timer: !byte 0 +random_seed: !byte 0 +last_jiffy: !byte 0 +draw_row: !byte 0 +flame_level: !byte 0 +flame_column: !byte 0 +flame_heights: !fill 16,0 +work_value: !byte 0 +work_column: !byte 0 +temp_char: !byte 0 diff --git a/tv-fireplace/tv-fireplace.prg b/tv-fireplace/tv-fireplace.prg new file mode 100644 index 0000000..a37124e Binary files /dev/null and b/tv-fireplace/tv-fireplace.prg differ