Compare commits
No commits in common. "main" and "gamemenu" have entirely different histories.
10 changed files with 24 additions and 414 deletions
151
CLAUDE.md
151
CLAUDE.md
|
|
@ -1,151 +0,0 @@
|
||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
This is a collaborative pair programming effort. Claude runs in a Docker environment with access to the codebase but cannot build or run the project. The user handles all builds and testing.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
Solitaire C64 is a Klondike solitaire card game for the Commodore 64, written in c65gm (a high-level language that compiles to 6502 assembly). Language documentation is available in `language_docs/`.
|
|
||||||
|
|
||||||
## Build Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./cm.sh # Build the project (outputs main.prg)
|
|
||||||
./start_in_vice.sh # Launch in VICE emulator
|
|
||||||
./exomizer_compress_prg.sh # Compress binary with Exomizer
|
|
||||||
```
|
|
||||||
|
|
||||||
Build chain: C65 source → c65gm compiler → 6502 assembly → ACME assembler → main.prg
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- [c65gm compiler](https://git.techserio.com/mattiashz/c65gm)
|
|
||||||
- ACME 6502 assembler
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Enable test modes by uncommenting `#DEFINE TEST_GAMES 1` in `cardgame.c65`. Test functions in `cardtests.c65` and `joysticktests.c65` can be called from main() for debugging specific scenarios.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Module Organization
|
|
||||||
|
|
||||||
**Core Game Logic:**
|
|
||||||
- `cardgame.c65` - Main entry point, initialization, RNG seeding
|
|
||||||
- `gameloop.c65` - Main loop, input processing, coordinate-to-pile mapping
|
|
||||||
- `gamestate.c65` - Global state (draw mode, selected piles, interaction flags)
|
|
||||||
- `cardmoves.c65` - Move validation and execution
|
|
||||||
- `carddeck.c65` - Fisher-Yates shuffle, dealing
|
|
||||||
- `cardrender.c65` - Card and pile rendering
|
|
||||||
|
|
||||||
**Input System:**
|
|
||||||
- `joystick.c65` - CIA Port 2 joystick driver
|
|
||||||
- `mouse.c65` - 1351 mouse driver with smoothing
|
|
||||||
- `keyboard.c65` - Non-interfering keyboard scan
|
|
||||||
- `pointer.c65` - VIC-II sprite cursor control
|
|
||||||
|
|
||||||
**Data & Constants:**
|
|
||||||
- `piles.c65` - Pile data structures (13 piles: stock, waste, 4 foundations, 7 tableaus)
|
|
||||||
- `pileconsts.c65` - Pile ID constants
|
|
||||||
- `cardconsts.c65` - Card suits, ranks, flags
|
|
||||||
|
|
||||||
### Game Data Model
|
|
||||||
|
|
||||||
- 52 cards represented as values 0-51 (0-12 Hearts, 13-25 Diamonds, 26-38 Spades, 39-51 Clubs)
|
|
||||||
- Face-down cards have high bit set ($80)
|
|
||||||
- Each pile: 1 count byte + card slots (tableaus: 53 bytes, foundations: 14 bytes)
|
|
||||||
- PILE_END marker ($FF) indicates empty pile
|
|
||||||
|
|
||||||
### Memory Layout
|
|
||||||
|
|
||||||
- `$0400` - Screen memory
|
|
||||||
- `$2000` - Custom character set
|
|
||||||
- `$3000` - Code start
|
|
||||||
- `$C000` - Screen backup (menu)
|
|
||||||
- `$D000` - VIC-II registers
|
|
||||||
- `$DC00` - CIA ports (joystick/keyboard)
|
|
||||||
|
|
||||||
### Custom Character Set (ECM Mode)
|
|
||||||
|
|
||||||
The game uses a 64-character ECM (Extended Color Mode) font stored at `$2000`. This is NOT a standard ASCII font - it contains only card-specific graphics:
|
|
||||||
|
|
||||||
- **Char 0**: Solid filled block (`$FF` bytes)
|
|
||||||
- **Chars 1-12**: Rank characters (2-10, J, Q, K) - note: Ace uses char 13
|
|
||||||
- **Char 13**: Ace character
|
|
||||||
- **Chars 14-15**: Suit symbols (spades, clubs in one color set)
|
|
||||||
- **Chars 19 ($13)**: Empty/blank character (`$00` bytes)
|
|
||||||
- **Chars $15-$39**: Suit graphics (hearts, diamonds, spades, clubs as 3x3 grids)
|
|
||||||
- **Chars $50-$51**: Suit symbols (hearts, diamonds)
|
|
||||||
- **Various**: Card borders, corners, card back pattern pieces
|
|
||||||
|
|
||||||
Character data is in `charpad_cards/`. The map PNG shows the visual layout. ECM mode uses 2 bits from color RAM to select between 4 color sets, effectively giving 64 unique characters × 4 color variations.
|
|
||||||
|
|
||||||
Since there are no alphabet characters, any text display (like "YOU WIN!") must be constructed from available shapes (solid blocks, empty spaces, card graphics).
|
|
||||||
|
|
||||||
## c65gm Language Notes
|
|
||||||
|
|
||||||
See `language_docs/` for full reference. Key points:
|
|
||||||
|
|
||||||
**Arithmetic expressions have two contexts:**
|
|
||||||
|
|
||||||
*Compile-time* (no spaces) - evaluated by compiler, supports `+ - * /` and logic operators:
|
|
||||||
```c65
|
|
||||||
value = 5+6*2 // Computed at compile time, supports * /
|
|
||||||
offset = 40*5+SCREEN // Fifth row (40 cols * 5 rows) + screen base address
|
|
||||||
// NOTE: SCREEN+40*5 would mean (SCREEN+40)*5 due to left-to-right eval!
|
|
||||||
```
|
|
||||||
|
|
||||||
*Runtime* (with spaces) - generates 6502 code, only `+ -` and logic operators:
|
|
||||||
```c65
|
|
||||||
result = a + b // Runtime addition
|
|
||||||
result = count - 1 // Runtime subtraction
|
|
||||||
// No runtime multiplication - split complex expressions into multiple statements
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical: No operator precedence** in either context. Evaluation is strictly left to right:
|
|
||||||
```c65
|
|
||||||
result = 2+3*4 // = 20, NOT 14 (evaluates as (2+3)*4)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Types:**
|
|
||||||
- `BYTE` (8-bit), `WORD` (16-bit)
|
|
||||||
- `BYTE CONST` / `WORD CONST` for constants (preferred over `#DEFINE`)
|
|
||||||
- Memory-mapped: `BYTE borderColor @ $D020`
|
|
||||||
|
|
||||||
**Control flow:** `IF`/`ENDIF`, `WHILE`/`WEND`, `FOR`/`NEXT`, `SWITCH`/`CASE`/`ENDSWITCH`, `BREAK`
|
|
||||||
|
|
||||||
**Functions:**
|
|
||||||
```c65
|
|
||||||
FUNC add(in:a, in:b, out:result)
|
|
||||||
result = a + b
|
|
||||||
FEND
|
|
||||||
```
|
|
||||||
Parameter modifiers: `in:` (read-only, default), `out:` (write-only), `io:` (read-write, both in and out).
|
|
||||||
Call with `@<label>` to pass an address: `myFunc(@dataTable)` sends the address of `dataTable` to that WORD parameter.
|
|
||||||
|
|
||||||
**Memory access:**
|
|
||||||
```c65
|
|
||||||
value = PEEK $D020 // Read byte
|
|
||||||
POKE $D020 WITH 0 // Write byte
|
|
||||||
value = PEEK screenPtr[index] // Indexed access (pointer must be WORD in zero page)
|
|
||||||
POINTER screenPtr TO $0400 // Set pointer
|
|
||||||
```
|
|
||||||
Also `PEEKW`/`POKEW` for 16-bit values.
|
|
||||||
|
|
||||||
**Pointers:** Not special like in C - just WORD variables. Whether it's a "pointer" depends on usage. Normal WORD variables work fine to hold addresses. Only use zero-page (`@ $xx`) for pointers that need indexed PEEK/POKE access.
|
|
||||||
|
|
||||||
**Inline assembly:** Use `ASM`...`ENDASM`. Reference local variables with `|varname|` syntax.
|
|
||||||
|
|
||||||
**Include guards pattern:**
|
|
||||||
```c65
|
|
||||||
#IFNDEF __MY_LIBRARY
|
|
||||||
#DEFINE __MY_LIBRARY = 1
|
|
||||||
GOTO lib_skip
|
|
||||||
// ... library code ...
|
|
||||||
LABEL lib_skip
|
|
||||||
#IFEND
|
|
||||||
```
|
|
||||||
|
|
||||||
**Number formats:** Decimal `123`, hex `$FF`, binary `%11110000`
|
|
||||||
|
|
@ -32,7 +32,6 @@ ENDASM
|
||||||
#IFDEF TEST_GAMES
|
#IFDEF TEST_GAMES
|
||||||
#INCLUDE "testgames.c65"
|
#INCLUDE "testgames.c65"
|
||||||
#IFEND
|
#IFEND
|
||||||
#INCLUDE "winscreen.c65"
|
|
||||||
#INCLUDE "gameloop.c65"
|
#INCLUDE "gameloop.c65"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,9 @@ FUNC card_display_init
|
||||||
sprite_color2 = color_black
|
sprite_color2 = color_black
|
||||||
|
|
||||||
// Initially hide all card display sprites (1-5)
|
// Initially hide all card display sprites (1-5)
|
||||||
sprite_enable = sprite_enable & %11000001 // Keep sprite 0 (pointer), clear 1-5
|
temp = sprite_enable
|
||||||
|
temp = temp & %11000001 // Keep sprite 0 (pointer), clear 1-5
|
||||||
|
sprite_enable = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -915,8 +917,12 @@ FEND
|
||||||
// Hide all card display sprites (when no card selected)
|
// Hide all card display sprites (when no card selected)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
FUNC card_display_hide
|
FUNC card_display_hide
|
||||||
|
BYTE temp
|
||||||
|
|
||||||
// Disable sprites 1-5, keep sprite 0 (pointer) and sprite 6 (menu hint)
|
// Disable sprites 1-5, keep sprite 0 (pointer) and sprite 6 (menu hint)
|
||||||
sprite_enable = sprite_enable & %11000001
|
temp = sprite_enable
|
||||||
|
temp = temp & %11000001
|
||||||
|
sprite_enable = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -952,11 +958,8 @@ FUNC menu_hint_show
|
||||||
sprite_x_msb = sprite_x_msb | %01000000
|
sprite_x_msb = sprite_x_msb | %01000000
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Set color to black for contrast against white background
|
// Set color to blue (6)
|
||||||
//sprite_color6 = color_black
|
sprite_color6 = 6
|
||||||
//sprite_color6 = color_grey
|
|
||||||
//sprite_color6 = color_light_grey
|
|
||||||
sprite_color6 = color_white // White is invisible on white background.
|
|
||||||
|
|
||||||
// Enable sprite 6
|
// Enable sprite 6
|
||||||
sprite_enable = sprite_enable | %01000000
|
sprite_enable = sprite_enable | %01000000
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
exomizer sfx basic main.prg -o siders_solitaire.prg
|
exomizer sfx basic main.prg -o main2.prg
|
||||||
|
|
|
||||||
12
gameloop.c65
12
gameloop.c65
|
|
@ -840,7 +840,6 @@ FUNC restart_game
|
||||||
game_selected_pile = PILE_ID_NONE
|
game_selected_pile = PILE_ID_NONE
|
||||||
game_selected_card_count = 0
|
game_selected_card_count = 0
|
||||||
game_prev_button_state = 0
|
game_prev_button_state = 0
|
||||||
game_win_shown = 0
|
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -902,8 +901,8 @@ FUNC game_loop
|
||||||
#IFDEF TEST_GAMES
|
#IFDEF TEST_GAMES
|
||||||
// Test game options (comment/uncomment one):
|
// Test game options (comment/uncomment one):
|
||||||
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
||||||
setup_test_game_one_move_to_win() // 1 move from victory
|
//setup_test_game_one_move_to_win() // 1 move from victory
|
||||||
//setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
||||||
#IFEND
|
#IFEND
|
||||||
|
|
||||||
BYTE menu_key
|
BYTE menu_key
|
||||||
|
|
@ -1023,11 +1022,10 @@ FUNC game_loop
|
||||||
// Check win condition
|
// Check win condition
|
||||||
check_win_condition(is_won)
|
check_win_condition(is_won)
|
||||||
IF is_won
|
IF is_won
|
||||||
IF game_win_shown = 0
|
// Flash border or show message
|
||||||
show_win_screen()
|
|
||||||
game_win_shown = 1
|
|
||||||
ENDIF
|
|
||||||
POKE $d020 , color_green
|
POKE $d020 , color_green
|
||||||
|
// Could add "YOU WIN" message here
|
||||||
|
// For now, just keep running to allow admiring the win
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Small delay to avoid reading mouse too fast
|
// Small delay to avoid reading mouse too fast
|
||||||
|
|
|
||||||
72
gamemenu.c65
72
gamemenu.c65
|
|
@ -144,32 +144,6 @@ FUNC menu_update_found_to_tab
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// FUNC menu_update_mouse_sensitivity
|
|
||||||
// Update only the mouse sensitivity display (row 12) - no screen clear
|
|
||||||
// ============================================================================
|
|
||||||
FUNC menu_update_mouse_sensitivity
|
|
||||||
WORD screen_pos @ $e8
|
|
||||||
WORD str_ptr @ $ea
|
|
||||||
|
|
||||||
// Clear the value area (cols 28-31)
|
|
||||||
screen_pos = 12*40+$0400+27
|
|
||||||
POKE screen_pos[0] , 32 // space
|
|
||||||
POKE screen_pos[1] , 32
|
|
||||||
POKE screen_pos[2] , 32
|
|
||||||
POKE screen_pos[3] , 32
|
|
||||||
|
|
||||||
// Mouse sensitivity value
|
|
||||||
screen_pos = 12*40+$0400+27
|
|
||||||
IF game_mouse_high_sensitivity == 0
|
|
||||||
POINTER str_ptr -> str_low
|
|
||||||
ELSE
|
|
||||||
POINTER str_ptr -> str_high
|
|
||||||
ENDIF
|
|
||||||
menu_print_string(screen_pos, str_ptr)
|
|
||||||
FEND
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// FUNC menu_render
|
// FUNC menu_render
|
||||||
// Render the full menu screen with current settings
|
// Render the full menu screen with current settings
|
||||||
|
|
@ -194,15 +168,9 @@ FUNC menu_render
|
||||||
// Foundation to Tableau value
|
// Foundation to Tableau value
|
||||||
menu_update_found_to_tab()
|
menu_update_found_to_tab()
|
||||||
|
|
||||||
// Mouse sensitivity option (row 12)
|
// Instructions for resume/restart (row 12+)
|
||||||
menu_print_string(12*40+$0400+4, @str_mouse_sensitivity)
|
menu_print_string(12*40+$0400+4, @str_inst_f7)
|
||||||
|
menu_print_string(15*40+$0400+4, @str_inst_runstop)
|
||||||
// Mouse sensitivity value
|
|
||||||
menu_update_mouse_sensitivity()
|
|
||||||
|
|
||||||
// Instructions for resume/restart (row 15+)
|
|
||||||
menu_print_string(15*40+$0400+4, @str_inst_f7)
|
|
||||||
menu_print_string(18*40+$0400+4, @str_inst_runstop)
|
|
||||||
|
|
||||||
// Controls info (row 20)
|
// Controls info (row 20)
|
||||||
menu_print_string(20*40+$0400+2, @str_controls)
|
menu_print_string(20*40+$0400+2, @str_controls)
|
||||||
|
|
@ -211,7 +179,7 @@ FUNC menu_render
|
||||||
menu_print_string(23*40+$0400+1, @str_license)
|
menu_print_string(23*40+$0400+1, @str_license)
|
||||||
|
|
||||||
// Set border color to menu color (purple)
|
// Set border color to menu color (purple)
|
||||||
POKE $d020 , color_grey
|
POKE $d020 , 4
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -258,18 +226,12 @@ FUNC menu_show(out:{BYTE action})
|
||||||
WEND
|
WEND
|
||||||
|
|
||||||
// Menu loop
|
// Menu loop
|
||||||
#PRAGMA _P_USE_LONG_JUMP 1
|
|
||||||
WHILE 1
|
WHILE 1
|
||||||
#PRAGMA _P_USE_LONG_JUMP 0
|
|
||||||
key_scan(key)
|
key_scan(key)
|
||||||
|
|
||||||
// Only process on key press (transition from NONE to key)
|
// Only process on key press (transition from NONE to key)
|
||||||
#PRAGMA _P_USE_LONG_JUMP 1
|
|
||||||
IF key != KEY_NONE
|
IF key != KEY_NONE
|
||||||
#PRAGMA _P_USE_LONG_JUMP 0
|
|
||||||
#PRAGMA _P_USE_LONG_JUMP 1
|
|
||||||
IF prev_key == KEY_NONE
|
IF prev_key == KEY_NONE
|
||||||
#PRAGMA _P_USE_LONG_JUMP 0
|
|
||||||
SWITCH key
|
SWITCH key
|
||||||
CASE KEY_F1
|
CASE KEY_F1
|
||||||
// Toggle draw mode
|
// Toggle draw mode
|
||||||
|
|
@ -289,15 +251,6 @@ FUNC menu_show(out:{BYTE action})
|
||||||
ENDIF
|
ENDIF
|
||||||
menu_update_found_to_tab()
|
menu_update_found_to_tab()
|
||||||
|
|
||||||
CASE KEY_F5
|
|
||||||
// Toggle mouse sensitivity
|
|
||||||
IF game_mouse_high_sensitivity == 0
|
|
||||||
game_mouse_high_sensitivity = 1
|
|
||||||
ELSE
|
|
||||||
game_mouse_high_sensitivity = 0
|
|
||||||
ENDIF
|
|
||||||
menu_update_mouse_sensitivity()
|
|
||||||
|
|
||||||
CASE KEY_F7
|
CASE KEY_F7
|
||||||
// Resume game
|
// Resume game
|
||||||
action = MENU_ACTION_RESUME
|
action = MENU_ACTION_RESUME
|
||||||
|
|
@ -354,7 +307,7 @@ FEND
|
||||||
// Menu strings (null-terminated) - Using !scr for screen codes
|
// Menu strings (null-terminated) - Using !scr for screen codes
|
||||||
LABEL str_title
|
LABEL str_title
|
||||||
ASM
|
ASM
|
||||||
!scr "Siders Solitaire Menu", 0
|
!scr "Klondike Solitaire Menu", 0
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
LABEL str_draw_mode
|
LABEL str_draw_mode
|
||||||
|
|
@ -367,11 +320,6 @@ ASM
|
||||||
!scr "F3: Foundation>Tableau:", 0
|
!scr "F3: Foundation>Tableau:", 0
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
LABEL str_mouse_sensitivity
|
|
||||||
ASM
|
|
||||||
!scr "F5: Mouse sensitivity:", 0
|
|
||||||
ENDASM
|
|
||||||
|
|
||||||
LABEL str_on
|
LABEL str_on
|
||||||
ASM
|
ASM
|
||||||
!scr "On", 0
|
!scr "On", 0
|
||||||
|
|
@ -382,16 +330,6 @@ ASM
|
||||||
!scr "Off", 0
|
!scr "Off", 0
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
LABEL str_high
|
|
||||||
ASM
|
|
||||||
!scr "High", 0
|
|
||||||
ENDASM
|
|
||||||
|
|
||||||
LABEL str_low
|
|
||||||
ASM
|
|
||||||
!scr "Low", 0
|
|
||||||
ENDASM
|
|
||||||
|
|
||||||
LABEL str_inst_f1
|
LABEL str_inst_f1
|
||||||
ASM
|
ASM
|
||||||
!scr "F1: Toggle draw mode (1/3)", 0
|
!scr "F1: Toggle draw mode (1/3)", 0
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,12 @@ GOTO __skip_lib_gamestate
|
||||||
// Game configuration
|
// Game configuration
|
||||||
BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw
|
BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw
|
||||||
BYTE game_allow_found_to_tab = 0 // Allow foundation to tableau moves: 0=disallow, 1=allow
|
BYTE game_allow_found_to_tab = 0 // Allow foundation to tableau moves: 0=disallow, 1=allow
|
||||||
BYTE game_mouse_high_sensitivity = 1 // Mouse sensitivity: 1=high (default), 0=low (filters small movements)
|
|
||||||
|
|
||||||
// Game interaction state
|
// Game interaction state
|
||||||
BYTE game_selected_pile // Currently selected pile ID (PILE_ID_NONE if none)
|
BYTE game_selected_pile // Currently selected pile ID (PILE_ID_NONE if none)
|
||||||
BYTE game_selected_card_count // Number of cards selected from tableau (1+ for tableau stacks)
|
BYTE game_selected_card_count // Number of cards selected from tableau (1+ for tableau stacks)
|
||||||
BYTE game_prev_button_state // Previous mouse button state for click detection
|
BYTE game_prev_button_state // Previous mouse button state for click detection
|
||||||
|
|
||||||
// Win state
|
|
||||||
BYTE game_win_shown = 0 // 1 if win screen has been displayed
|
|
||||||
|
|
||||||
|
|
||||||
LABEL __skip_lib_gamestate
|
LABEL __skip_lib_gamestate
|
||||||
|
|
||||||
|
|
|
||||||
17
keyboard.c65
17
keyboard.c65
|
|
@ -39,10 +39,9 @@ WORD CONST CIA_DDRB = $DC03 // Port B Data Direction Register
|
||||||
BYTE CONST KEY_NONE = 0
|
BYTE CONST KEY_NONE = 0
|
||||||
BYTE CONST KEY_F1 = 1
|
BYTE CONST KEY_F1 = 1
|
||||||
BYTE CONST KEY_F3 = 2
|
BYTE CONST KEY_F3 = 2
|
||||||
BYTE CONST KEY_F5 = 3
|
BYTE CONST KEY_F7 = 3
|
||||||
BYTE CONST KEY_F7 = 4
|
BYTE CONST KEY_RUNSTOP = 4
|
||||||
BYTE CONST KEY_RUNSTOP = 5
|
BYTE CONST KEY_RETURN = 5
|
||||||
BYTE CONST KEY_RETURN = 6
|
|
||||||
|
|
||||||
// C64 Keycode constants (for internal use)
|
// C64 Keycode constants (for internal use)
|
||||||
// Based on: https://www.c64-wiki.com/wiki/Keyboard
|
// Based on: https://www.c64-wiki.com/wiki/Keyboard
|
||||||
|
|
@ -50,7 +49,6 @@ BYTE CONST KEYCODE_RETURN = $01
|
||||||
BYTE CONST KEYCODE_F7F8 = $03
|
BYTE CONST KEYCODE_F7F8 = $03
|
||||||
BYTE CONST KEYCODE_F1F2 = $04
|
BYTE CONST KEYCODE_F1F2 = $04
|
||||||
BYTE CONST KEYCODE_F3F4 = $05
|
BYTE CONST KEYCODE_F3F4 = $05
|
||||||
BYTE CONST KEYCODE_F5F6 = $06
|
|
||||||
BYTE CONST KEYCODE_RUNSTOP = $3F
|
BYTE CONST KEYCODE_RUNSTOP = $3F
|
||||||
|
|
||||||
// Row and column lookup tables for keyboard scanning
|
// Row and column lookup tables for keyboard scanning
|
||||||
|
|
@ -67,7 +65,6 @@ ENDASM
|
||||||
// Key history for debouncing (one byte per tracked key)
|
// Key history for debouncing (one byte per tracked key)
|
||||||
BYTE key_history_f1
|
BYTE key_history_f1
|
||||||
BYTE key_history_f3
|
BYTE key_history_f3
|
||||||
BYTE key_history_f5
|
|
||||||
BYTE key_history_f7
|
BYTE key_history_f7
|
||||||
BYTE key_history_return
|
BYTE key_history_return
|
||||||
BYTE key_history_runstop
|
BYTE key_history_runstop
|
||||||
|
|
@ -162,14 +159,6 @@ FUNC key_scan(out:{BYTE key_pressed})
|
||||||
ENDIF
|
ENDIF
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Check F5
|
|
||||||
IF key_pressed == KEY_NONE
|
|
||||||
key_check_raw(KEYCODE_F5F6, key_history_f5, check_result)
|
|
||||||
IF check_result > 0
|
|
||||||
key_pressed = KEY_F5
|
|
||||||
ENDIF
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
// Check F7
|
// Check F7
|
||||||
IF key_pressed == KEY_NONE
|
IF key_pressed == KEY_NONE
|
||||||
key_check_raw(KEYCODE_F7F8, key_history_f7, check_result)
|
key_check_raw(KEYCODE_F7F8, key_history_f7, check_result)
|
||||||
|
|
|
||||||
23
pointer.c65
23
pointer.c65
|
|
@ -102,8 +102,6 @@ FUNC pointer_init({BYTE x_pos} {BYTE y_pos} {BYTE color} {BYTE sprite_data})
|
||||||
// Set sprite data pointer
|
// Set sprite data pointer
|
||||||
sprite_pointer0 = sprite_data
|
sprite_pointer0 = sprite_data
|
||||||
|
|
||||||
POKE $D01B , 0
|
|
||||||
|
|
||||||
// Enable sprite 0
|
// Enable sprite 0
|
||||||
sprite_enable = 1
|
sprite_enable = 1
|
||||||
FEND
|
FEND
|
||||||
|
|
@ -266,7 +264,6 @@ FEND
|
||||||
// Update pointer based on mouse delta movements
|
// Update pointer based on mouse delta movements
|
||||||
// Expects mouse_delta_x and mouse_delta_y to be set (from mouse.c65 library)
|
// Expects mouse_delta_x and mouse_delta_y to be set (from mouse.c65 library)
|
||||||
// Mouse deltas are signed 8-bit values
|
// Mouse deltas are signed 8-bit values
|
||||||
// If game_mouse_high_sensitivity = 0, filters out ±1 pixel movements (deadzone)
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
FUNC pointer_update_mouse({BYTE delta_x} {BYTE delta_y})
|
FUNC pointer_update_mouse({BYTE delta_x} {BYTE delta_y})
|
||||||
BYTE sign_x
|
BYTE sign_x
|
||||||
|
|
@ -274,26 +271,6 @@ FUNC pointer_update_mouse({BYTE delta_x} {BYTE delta_y})
|
||||||
BYTE abs_x
|
BYTE abs_x
|
||||||
BYTE abs_y
|
BYTE abs_y
|
||||||
|
|
||||||
BYTE CONST LOW_SENSE_FILTER = 2
|
|
||||||
|
|
||||||
// Apply deadzone filter if low sensitivity mode
|
|
||||||
IF game_mouse_high_sensitivity == 0
|
|
||||||
// Filter ±1 pixel movements (1 = +1, 255 = -1 in signed byte)
|
|
||||||
IF delta_x <= LOW_SENSE_FILTER
|
|
||||||
delta_x = 0
|
|
||||||
ENDIF
|
|
||||||
IF delta_x >= 255-LOW_SENSE_FILTER
|
|
||||||
delta_x = 0
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
IF delta_y <= LOW_SENSE_FILTER
|
|
||||||
delta_y = 0
|
|
||||||
ENDIF
|
|
||||||
IF delta_y >= 255-LOW_SENSE_FILTER
|
|
||||||
delta_y = 0
|
|
||||||
ENDIF
|
|
||||||
ENDIF
|
|
||||||
|
|
||||||
// Handle X movement
|
// Handle X movement
|
||||||
sign_x = delta_x & %10000000
|
sign_x = delta_x & %10000000
|
||||||
IF sign_x
|
IF sign_x
|
||||||
|
|
|
||||||
139
winscreen.c65
139
winscreen.c65
|
|
@ -1,139 +0,0 @@
|
||||||
|
|
||||||
#IFNDEF __lib_winscreen
|
|
||||||
#DEFINE __lib_winscreen 1
|
|
||||||
|
|
||||||
GOTO __skip_lib_winscreen
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// WIN SCREEN
|
|
||||||
// Displays "YOU WIN!" celebration when game is won
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
BYTE CONST WIN_LOGO_CHAR = $50 // Hearts suit character
|
|
||||||
BYTE CONST WIN_CLEAR_CHAR = 0 // Solid block (shows color RAM color)
|
|
||||||
|
|
||||||
// Logo dimensions and position
|
|
||||||
// Logo is 26 chars wide x 5 chars tall, centered on 40x25 screen
|
|
||||||
BYTE CONST WIN_LOGO_WIDTH = 26
|
|
||||||
BYTE CONST WIN_LOGO_HEIGHT = 5
|
|
||||||
BYTE CONST WIN_LOGO_START_COL = 7 // (40-26)/2 = 7
|
|
||||||
BYTE CONST WIN_LOGO_START_ROW = 10 // (25-5)/2 = 10
|
|
||||||
|
|
||||||
// Clear area with 1 char padding on sides
|
|
||||||
BYTE CONST WIN_CLEAR_WIDTH = 28
|
|
||||||
BYTE CONST WIN_CLEAR_START_COL = 6
|
|
||||||
|
|
||||||
// Screen base
|
|
||||||
WORD CONST SCREEN_BASE = $0400
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Logo offset data
|
|
||||||
// Each byte is an offset from logo top-left where hearts char goes
|
|
||||||
// Arranged visually to show letter shapes in source
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
LABEL win_logo_offsets
|
|
||||||
ASM
|
|
||||||
; "YOU WIN!" - 5 rows x 26 cols
|
|
||||||
; Offsets where hearts char ($50) is placed
|
|
||||||
;
|
|
||||||
; Y O U W I N !
|
|
||||||
;
|
|
||||||
; Row 0:
|
|
||||||
; █ █ ███ █ █ █ █ █ █ █ █
|
|
||||||
!8 0,2, 4,5,6, 8,10, 13,17, 19, 21,23, 25
|
|
||||||
;
|
|
||||||
; Row 1:
|
|
||||||
; █ █ █ █ █ █ █ █ ███ █
|
|
||||||
!8 41, 44,46, 48,50, 53,57, 59, 61,62,63,65
|
|
||||||
;
|
|
||||||
; Row 2:
|
|
||||||
; █ █ █ █ █ █ █ █ █ █ █ █
|
|
||||||
!8 81, 84,86, 88,90, 93,95,97, 99, 101,103,105
|
|
||||||
;
|
|
||||||
; Row 3:
|
|
||||||
; █ █ █ █ █ █ █ █ █ █ █
|
|
||||||
!8 121, 124,126,128,130, 133,135,137,139,141,143
|
|
||||||
;
|
|
||||||
; Row 4:
|
|
||||||
; █ ███ ███ █ █ █ █ █ █
|
|
||||||
!8 161, 164,165,166,168,169,170,174,176,179,181,183,185
|
|
||||||
;
|
|
||||||
; Terminator
|
|
||||||
!8 255
|
|
||||||
ENDASM
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// FUNC clear_win_area
|
|
||||||
// Clears a rectangular area in the center of screen for the logo
|
|
||||||
// Fills with WIN_CLEAR_CHAR (solid block showing white)
|
|
||||||
// ============================================================================
|
|
||||||
FUNC clear_win_area
|
|
||||||
WORD screen_ptr @ $a0
|
|
||||||
BYTE row
|
|
||||||
BYTE col
|
|
||||||
WORD row_start
|
|
||||||
|
|
||||||
// Calculate starting position: row 10, col 6
|
|
||||||
// Offset = 10 * 40 + 6 = 406
|
|
||||||
row_start = SCREEN_BASE + 406
|
|
||||||
|
|
||||||
FOR row = 0 TO WIN_LOGO_HEIGHT-1
|
|
||||||
POINTER screen_ptr -> row_start
|
|
||||||
FOR col = 0 TO WIN_CLEAR_WIDTH-1
|
|
||||||
POKE screen_ptr[col] , WIN_CLEAR_CHAR
|
|
||||||
NEXT
|
|
||||||
// Move to next row (add 40)
|
|
||||||
row_start = row_start + 40
|
|
||||||
NEXT
|
|
||||||
FEND
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// FUNC draw_win_logo
|
|
||||||
// Draws "YOU WIN!" using hearts characters at the calculated offsets
|
|
||||||
// Must call clear_win_area first
|
|
||||||
// ============================================================================
|
|
||||||
FUNC draw_win_logo
|
|
||||||
WORD screen_ptr @ $a0
|
|
||||||
WORD offset_ptr @ $a2
|
|
||||||
WORD logo_base
|
|
||||||
BYTE offset
|
|
||||||
|
|
||||||
// Logo base position: row 10, col 7
|
|
||||||
// Offset = 10 * 40 + 7 = 407
|
|
||||||
logo_base = SCREEN_BASE + 407
|
|
||||||
|
|
||||||
POINTER offset_ptr -> win_logo_offsets
|
|
||||||
BYTE index
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
// Loop through all offsets until terminator (255)
|
|
||||||
offset = PEEK offset_ptr[index]
|
|
||||||
WHILE offset != 255
|
|
||||||
// Calculate screen address: logo_base + offset
|
|
||||||
screen_ptr = logo_base + offset
|
|
||||||
|
|
||||||
// Poke hearts character
|
|
||||||
POKE screen_ptr[0] , WIN_LOGO_CHAR
|
|
||||||
|
|
||||||
// Next offset
|
|
||||||
index = index + 1
|
|
||||||
offset = PEEK offset_ptr[index]
|
|
||||||
WEND
|
|
||||||
FEND
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// FUNC show_win_screen
|
|
||||||
// Main entry point - clears area and draws the win logo
|
|
||||||
// ============================================================================
|
|
||||||
FUNC show_win_screen
|
|
||||||
clear_win_area()
|
|
||||||
draw_win_logo()
|
|
||||||
FEND
|
|
||||||
|
|
||||||
|
|
||||||
LABEL __skip_lib_winscreen
|
|
||||||
|
|
||||||
#IFEND
|
|
||||||
Loading…
Reference in a new issue