solitaire-c64/CLAUDE.md
2026-01-18 17:30:16 +01:00

5.7 KiB
Raw Permalink Blame History

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:

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:

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 CONST for 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