5.7 KiB
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
./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
- 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 seedinggameloop.c65- Main loop, input processing, coordinate-to-pile mappinggamestate.c65- Global state (draw mode, selected piles, interaction flags)cardmoves.c65- Move validation and executioncarddeck.c65- Fisher-Yates shuffle, dealingcardrender.c65- Card and pile rendering
Input System:
joystick.c65- CIA Port 2 joystick drivermouse.c65- 1351 mouse driver with smoothingkeyboard.c65- Non-interfering keyboard scanpointer.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 constantscardconsts.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 (
$FFbytes) - 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 (
$00bytes) - 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:
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:
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:
result = 2+3*4 // = 20, NOT 14 (evaluates as (2+3)*4)
Types:
BYTE(8-bit),WORD(16-bit)BYTE CONST/WORD CONSTfor constants (preferred over#DEFINE)- Memory-mapped:
BYTE borderColor @ $D020
Control flow: IF/ENDIF, WHILE/WEND, FOR/NEXT, SWITCH/CASE/ENDSWITCH, BREAK
Functions:
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:
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:
#IFNDEF __MY_LIBRARY
#DEFINE __MY_LIBRARY = 1
GOTO lib_skip
// ... library code ...
LABEL lib_skip
#IFEND
Number formats: Decimal 123, hex $FF, binary %11110000