From 9c723cce1d5692bdd3d1d6631ec87fb3af95fd7a Mon Sep 17 00:00:00 2001 From: Mattias Hansson Date: Fri, 2 Jan 2026 13:21:20 +0100 Subject: [PATCH] First commit in fresh repo --- .gitignore | 60 ++ Dockerfile | 6 + README.md | 45 +- cardconsts.c65 | 31 + carddeck.c65 | 289 ++++++ cardgame.c65 | 152 +++ cardmoves.c65 | 385 +++++++ cardrender.c65 | 963 +++++++++++++++++ cardsprites.c65 | 899 ++++++++++++++++ cardtests.c65 | 878 ++++++++++++++++ charpad_cards/charpad_cards.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards2.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards3.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards4.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards5.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards6 - Chars.asm | 163 +++ charpad_cards/charpad_cards6 - Chars.bin | Bin 0 -> 2048 bytes charpad_cards/charpad_cards6.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards7 - Chars.bin | Bin 0 -> 2048 bytes charpad_cards/charpad_cards7 - Map.png | Bin 0 -> 3070 bytes charpad_cards/charpad_cards7.bin | Bin 0 -> 512 bytes charpad_cards/charpad_cards7.ctm | Bin 0 -> 4593 bytes charpad_cards/charpad_cards7_ecm.bin | Bin 0 -> 512 bytes charpad_cards/visualize_chars.sh | 1 + .../charpad_color_sprites - Project.asm | 113 ++ .../charpad_color_sprites.spd | Bin 0 -> 660 bytes .../charpad_rank_sprites - Project.asm | 143 +++ charpad_rank_sprites/charpad_rank_sprites.png | Bin 0 -> 1026 bytes charpad_rank_sprites/charpad_rank_sprites.spd | Bin 0 -> 980 bytes charpad_rank_sprites/charpad_rank_sprites.xcf | Bin 0 -> 5078 bytes claude_code_docker.sh | 4 + cm.sh | 21 + docker-compose.yml | 16 + exomizer_compress_prg.sh | 1 + gameloop.c65 | 967 ++++++++++++++++++ joystick.c65 | 72 ++ joysticktests.c65 | 272 +++++ mouse.c65 | 307 ++++++ piles.c65 | 78 ++ pointer.c65 | 371 +++++++ random.c65 | 131 +++ testgames.c65 | 249 +++++ utils.c65 | 142 +++ 43 files changed, 6757 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 cardconsts.c65 create mode 100644 carddeck.c65 create mode 100644 cardgame.c65 create mode 100644 cardmoves.c65 create mode 100644 cardrender.c65 create mode 100644 cardsprites.c65 create mode 100644 cardtests.c65 create mode 100644 charpad_cards/charpad_cards.ctm create mode 100644 charpad_cards/charpad_cards2.ctm create mode 100644 charpad_cards/charpad_cards3.ctm create mode 100644 charpad_cards/charpad_cards4.ctm create mode 100644 charpad_cards/charpad_cards5.ctm create mode 100644 charpad_cards/charpad_cards6 - Chars.asm create mode 100644 charpad_cards/charpad_cards6 - Chars.bin create mode 100644 charpad_cards/charpad_cards6.ctm create mode 100644 charpad_cards/charpad_cards7 - Chars.bin create mode 100644 charpad_cards/charpad_cards7 - Map.png create mode 100644 charpad_cards/charpad_cards7.bin create mode 100644 charpad_cards/charpad_cards7.ctm create mode 100644 charpad_cards/charpad_cards7_ecm.bin create mode 100644 charpad_cards/visualize_chars.sh create mode 100644 charpad_color_sprites/charpad_color_sprites - Project.asm create mode 100644 charpad_color_sprites/charpad_color_sprites.spd create mode 100644 charpad_rank_sprites/charpad_rank_sprites - Project.asm create mode 100644 charpad_rank_sprites/charpad_rank_sprites.png create mode 100644 charpad_rank_sprites/charpad_rank_sprites.spd create mode 100644 charpad_rank_sprites/charpad_rank_sprites.xcf create mode 100644 claude_code_docker.sh create mode 100755 cm.sh create mode 100644 docker-compose.yml create mode 100644 exomizer_compress_prg.sh create mode 100644 gameloop.c65 create mode 100644 joystick.c65 create mode 100644 joysticktests.c65 create mode 100644 mouse.c65 create mode 100644 piles.c65 create mode 100644 pointer.c65 create mode 100644 random.c65 create mode 100644 testgames.c65 create mode 100644 utils.c65 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..915d48f --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Python bytecode / caches +__pycache__/ +*.py[cod] +*$py.class +*.pyo +*.pyd +*.so + +# Virtual environments +.venv/ +venv/ +ENV/ +.music/.venv/ # if your venv lives specifically under /music +.python-version +.env +.env.* + +# Packaging / wheels / builds +build/ +dist/ +.eggs/ +*.egg-info/ +pip-wheel-metadata/ + +# Tests / coverage / benchmarks +.pytest_cache/ +.tox/ +.nox/ +.coverage* +htmlcov/ +.hypothesis/ +.benchmark/ + +# Type checkers / linters +.mypy_cache/ +.pytype/ +.pyre/ +.ruff_cache/ + +# Jupyter +.ipynb_checkpoints/ + +# Logs / databases / local data +*.log +*.sqlite3 + +# OS cruft +.DS_Store +Thumbs.db + +# IDEs / editors +.idea/ +*.iml +.vscode/ +*.s +*.sym +*.prg + +.claude/ +.npm/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..653f077 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM node:18-alpine +WORKDIR /app +RUN apk add --no-cache bash +ENV SHELL=/bin/bash +RUN npm install -g @anthropic-ai/claude-code +CMD ["claude"] diff --git a/README.md b/README.md index 065d38b..01fbd87 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ -# solitaire-c64 +# Solitaire C64 -A solitaire style game for the Commodore 64 \ No newline at end of file +A classic Klondike solitaire card game for the Commodore 64, written in c65gm. + +## Features + +- **Full Klondike Solitaire**: Stock, waste, 7 tableaus, and 4 foundation piles +- **Dual Input Support**: Play with joystick or 1351 mouse +- **Draw Modes**: Toggle between draw-1 and draw-3 gameplay +- **Custom Graphics**: Character-based card rendering using extended color mode +- **Sprite Cursor**: Visual pointer for card selection and movement +- **Smart Shuffling**: Hardware-seeded RNG for true randomness + +## Technical Details + +**Language**: c65gm (C-like language for 6502/C64) +**Memory Layout**: Code at $3000, custom charset at $2000 +**Graphics**: Extended Color Mode (ECM) with custom character set +**Input**: CIA joystick ports + 1351 proportional mouse support + +## Building + +Requires the c65gm compiler and ACME assembler: + +```bash +./cm.sh +``` + +Outputs `main.prg` ready to load on C64 or emulator. + +## Project Structure + +- `cardgame.c65` - Main entry point and initialization +- `gameloop.c65` - Game loop, interaction, and pile detection +- `cardmoves.c65` - Move validation and execution logic +- `cardrender.c65` - Card and pile rendering routines +- `carddeck.c65` - Deck shuffling and dealing +- `mouse.c65` / `joystick.c65` - Input handling +- `pointer.c65` - Sprite cursor management +- `cardsprites.c65` - Sprite data for cursor + +## License + +See LICENSE file for details. diff --git a/cardconsts.c65 b/cardconsts.c65 new file mode 100644 index 0000000..5d6b03d --- /dev/null +++ b/cardconsts.c65 @@ -0,0 +1,31 @@ + +#IFNDEF __lib_cardconsts +#DEFINE __lib_cardconsts 1 + +// Card suits +BYTE CONST CARD_SUIT_HEARTS = 0 +BYTE CONST CARD_SUIT_DIAMONDS = 1 +BYTE CONST CARD_SUIT_SPADES = 2 +BYTE CONST CARD_SUIT_CLUBS = 3 + +// Card ranks +BYTE CONST CARD_ACE = 0 +BYTE CONST CARD_2 = 1 +BYTE CONST CARD_3 = 2 +BYTE CONST CARD_4 = 3 +BYTE CONST CARD_5 = 4 +BYTE CONST CARD_6 = 5 +BYTE CONST CARD_7 = 6 +BYTE CONST CARD_8 = 7 +BYTE CONST CARD_9 = 8 +BYTE CONST CARD_10 = 9 +BYTE CONST CARD_JACK = 10 +BYTE CONST CARD_QUEEN = 11 +BYTE CONST CARD_KING = 12 + +// Card representation flags +BYTE CONST CARD_FACEDOWN = $80 +BYTE CONST CARD_MASK = $7F +BYTE CONST PILE_END = $FF + +#IFEND diff --git a/carddeck.c65 b/carddeck.c65 new file mode 100644 index 0000000..bb3794f --- /dev/null +++ b/carddeck.c65 @@ -0,0 +1,289 @@ + +#IFNDEF __lib_carddeck +#DEFINE __lib_carddeck 1 + +#INCLUDE "random.c65" + +GOTO __skip_lib_carddeck + +// Validation error codes +BYTE CONST VALIDATE_OK = 0 +BYTE CONST VALIDATE_DUPLICATE = 1 +BYTE CONST VALIDATE_MISSING = 2 +BYTE CONST VALIDATE_INVALID = 3 + +// Temporary array to track seen cards (52 bytes) +LABEL validate_seen +ASM + !fill 52, 0 +ENDASM + +// Initialize stock with all 52 cards (face down) +// Cards stored as $80-$B3 (card value OR'd with CARD_FACEDOWN) +FUNC stock_init + WORD ptr @ $fa + POINTER ptr -> pile_stock + BYTE card + + // Set count to 52 + POKE ptr[0] , 52 + + // Fill with cards 0-51, all face down ($80 = CARD_FACEDOWN) + ptr++ + FOR card = $80 TO $80+51 + POKE ptr , card + ptr++ + NEXT +FEND + +// Shuffle stock pile using Fisher-Yates algorithm +FUNC stock_shuffle + WORD ptr @ $fa + POINTER ptr -> pile_stock + BYTE i + BYTE j + BYTE temp_a + BYTE temp_b + + // Fisher-Yates: swap each card with a random card from remaining deck + i = 52 + WHILE i >= 2 + rand_max(i, j) + j++ + // Swap cards at positions i and j (1-indexed in pile) + IF i <> j + temp_a = PEEK ptr[i] + temp_b = PEEK ptr[j] + POKE ptr[i] , temp_b + POKE ptr[j] , temp_a + ENDIF + i-- + WEND +FEND + +// Deal cards from stock to a single tableau pile +// num_cards: how many cards to deal +// Top card is flipped face up +FUNC deal_to_tableau({WORD tab_ptr @ $fc} {BYTE num_cards}) + WORD stock_ptr @ $fa + POINTER stock_ptr -> pile_stock + BYTE stock_count + BYTE card + BYTE i + + // Set tableau count + POKE tab_ptr[0] , num_cards + + // Deal cards from stock to tableau + stock_count = PEEK stock_ptr[0] + FOR i = 1 TO num_cards + card = PEEK stock_ptr[stock_count] + POKE tab_ptr[i] , card + stock_count-- + NEXT + + // Update stock count + POKE stock_ptr[0] , stock_count + + // Flip top card face up (clear CARD_FACEDOWN bit) + card = PEEK tab_ptr[num_cards] + card = card & CARD_MASK + POKE tab_ptr[num_cards] , card +FEND + +// Deal cards to all 7 tableau piles (Klondike layout) +// Tab0=1, Tab1=2, Tab2=3, Tab3=4, Tab4=5, Tab5=6, Tab6=7 cards +// Top card of each tableau is face up +FUNC deal_tableaus + WORD ptr @ $fc + + POINTER ptr -> pile_tab0 + deal_to_tableau(ptr, 1) + + POINTER ptr -> pile_tab1 + deal_to_tableau(ptr, 2) + + POINTER ptr -> pile_tab2 + deal_to_tableau(ptr, 3) + + POINTER ptr -> pile_tab3 + deal_to_tableau(ptr, 4) + + POINTER ptr -> pile_tab4 + deal_to_tableau(ptr, 5) + + POINTER ptr -> pile_tab5 + deal_to_tableau(ptr, 6) + + POINTER ptr -> pile_tab6 + deal_to_tableau(ptr, 7) +FEND + +// Clear the seen array +FUNC validate_clear_seen + WORD ptr @ $fa + POINTER ptr -> validate_seen + BYTE i + FOR i = 0 TO 51 + POKE ptr[i] , 0 + NEXT +FEND + +// Check cards in a pile, mark as seen +// Returns 0 if OK, 1 if duplicate found +FUNC validate_pile({WORD pile_ptr @ $fa} out:{BYTE result}) + WORD seen_ptr @ $fc + POINTER seen_ptr -> validate_seen + + BYTE count + BYTE i + BYTE card + BYTE card_val + BYTE already_seen + + result = VALIDATE_OK + count = PEEK pile_ptr[0] + + FOR i = 1 TO count + card = PEEK pile_ptr[i] + card_val = card & CARD_MASK + + // Check if valid card (0-51) + IF card_val >= 52 + result = VALIDATE_INVALID + EXIT + ENDIF + + already_seen = PEEK seen_ptr[card_val] + IF already_seen + result = VALIDATE_DUPLICATE + EXIT + ENDIF + POKE seen_ptr[card_val] , 1 + NEXT +FEND + +// Validate entire deck across all piles +// Returns: 0=OK, 1=duplicate, 2=missing +FUNC validate_deck(out:{BYTE result}) + WORD ptr @ $fa + WORD seen_ptr @ $fc + BYTE pile_result + BYTE i + BYTE seen + + // Clear seen array + validate_clear_seen() + + result = VALIDATE_OK + + // Check stock + POINTER ptr -> pile_stock + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + // Check waste + POINTER ptr -> pile_waste + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + // Check tableau piles + POINTER ptr -> pile_tab0 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab1 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab2 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab3 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab4 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab5 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_tab6 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + // Check foundation piles + POINTER ptr -> pile_found0 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_found1 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_found2 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + POINTER ptr -> pile_found3 + validate_pile(ptr, pile_result) + IF pile_result + result = pile_result + EXIT + ENDIF + + // Now check all 52 cards were seen + POINTER seen_ptr -> validate_seen + FOR i = 0 TO 51 + seen = PEEK seen_ptr[i] + IF seen == 0 + result = VALIDATE_MISSING + EXIT + ENDIF + NEXT + +FEND + +LABEL __skip_lib_carddeck + +#IFEND diff --git a/cardgame.c65 b/cardgame.c65 new file mode 100644 index 0000000..8734b6d --- /dev/null +++ b/cardgame.c65 @@ -0,0 +1,152 @@ +#INCLUDE +#INCLUDE + +//#DEFINE MOUSE_NO_SMOOTHING 1 + +// Enable test game setups (comment out for release build) +//#DEFINE TEST_GAMES 1 + +GOTO start + +ASM + *=$3000 +ENDASM + +#INCLUDE "utils.c65" +#INCLUDE "cardconsts.c65" +#INCLUDE "piles.c65" +#INCLUDE "random.c65" +#INCLUDE "carddeck.c65" +#INCLUDE "cardrender.c65" +//#INCLUDE "cardtests.c65" +#INCLUDE "joystick.c65" +#INCLUDE "mouse.c65" +#INCLUDE "pointer.c65" +//#INCLUDE "joysticktests.c65" +#IFDEF TEST_GAMES +#INCLUDE "testgames.c65" +#IFEND +#INCLUDE "gameloop.c65" + + +FUNC main + + + ASM + sei + ENDASM + + // Initialize game state +#IFNDEF TEST_GAMES + // Normal game: random shuffle and deal + // Seed RNG with multiple entropy sources for better randomness + WORD timer_seed @ $fa + BYTE raster @ $d012 + BYTE extra_entropy + BYTE warmup + + // Combine CIA timer with raster position + timer_seed = PEEKW $DC04 + extra_entropy = raster + timer_seed = timer_seed ^ extra_entropy + rand_seed(timer_seed) + + // Warm up RNG by advancing it based on timer low byte + extra_entropy = PEEK $DC04 + FOR warmup = 0 TO extra_entropy + rand(extra_entropy) + NEXT + + stock_init() + stock_shuffle() + deal_tableaus() +#IFEND + + BYTE validation_result + validate_deck(validation_result) + IF validation_result + POKE $d020 , color_red + ENDIF + + set_vic_bank(0) // $0000 - $3fff + set_vic_screenmem(1) // $400 + set_vic_charmem(4) // $2000 + set_vic_ecm() + + + WORD charset_ptr + WORD charset_end_ptr + POINTER charset_ptr -> card_charset + POINTER charset_end_ptr -> card_charset_end + mem_copy(charset_ptr, charset_end_ptr, $2000) + //mem_copy_range(charset_ptr, $2000, 512) + + fill_mem($d800, $d800+999, color_white) // Fill color mem + POKE $d020 , color_white //color_grey + POKE $d021 , color_black + POKE $d022 , color_red + POKE $d023 , color_light_grey + POKE $d024 , color_dark_grey + + + //show_charset() + + fill_mem($0400, $0400+999, 0) + + //render_card($0400, $30, 0) // Ace of Hearts + //render_card($0400, $35, 27) // 2 of Spades + //render_card($0400, $3a, 15) // 3 of Diamonds + + + //render_all_cards_test() + //render_facedown_test() + //render_faceup_stack_test() + //render_tableaus_test() + //render_midgame_test() + //render_card_back($0400, 35) // Test card back at column 35 + //render_empty_pile($0400, 8*40+35) // Test empty pile below card back + //render_foundation_test() + //render_waste_test() + //render_all_piles_test() + + // Move tests - runs multiple tests, press key between each + //test_stock_to_waste_draw1() // Draw 1 card x3 from stock + //wait_key() + //fill_mem($0400, $0400+999, 0) + //test_waste_to_tab_valid() // Move card from waste to tableau + //wait_key() + //fill_mem($0400, $0400+999, 0) + //test_tab_to_found() // Tableau to foundation + auto-flip + //wait_key() + //fill_mem($0400, $0400+999, 0) + //test_tab_to_tab_stack() // Move stack of 3 cards + auto-flip + + // Joystick tests + //test_joystick_read() // Test joystick input reading - suit symbols + //fill_mem($0400, $0400+999, 0) + //test_pointer_sprite() // Test sprite pointer with joystick + //fill_mem($0400, $0400+999, 0) + //test_pointer_manual() // Test sprite pointer manual movement + + // Mouse test + //test_pointer_mouse() // Test sprite pointer with 1351 mouse + + // Start the game! + game_init() + game_loop() // Never returns + + +FEND + + +LABEL start + + +main() + + +LABEL card_charset +ASM + !binary "charpad_cards/charpad_cards7.bin" +ENDASM +LABEL card_charset_end diff --git a/cardmoves.c65 b/cardmoves.c65 new file mode 100644 index 0000000..99cb002 --- /dev/null +++ b/cardmoves.c65 @@ -0,0 +1,385 @@ + +#IFNDEF __lib_cardmoves +#DEFINE __lib_cardmoves 1 + +#INCLUDE "cardconsts.c65" +#INCLUDE "piles.c65" +#INCLUDE "cardrender.c65" + +GOTO __skip_lib_cardmoves + +// Card colors +BYTE CONST COLOR_RED = 0 +BYTE CONST COLOR_BLACK = 1 + +// Move result codes +BYTE CONST MOVE_OK = 1 +BYTE CONST MOVE_INVALID = 0 + +// ============================================================================ +// Helper Functions +// ============================================================================ + +// Get card color (0=red, 1=black) +// Cards 0-25 are red (Hearts, Diamonds), 26-51 are black (Spades, Clubs) +FUNC card_color({BYTE card_id} out:{BYTE color}) + IF card_id < 26 + color = COLOR_RED + ELSE + color = COLOR_BLACK + ENDIF +FEND + +// Get top card from pile (returns card with facedown bit, or PILE_END if empty) +// Uses $fa - for source pile operations +FUNC pile_top_card({WORD pile_ptr @ $fa} out:{BYTE card}) + BYTE count + count = PEEK pile_ptr[0] + IF count == 0 + card = PILE_END + ELSE + card = PEEK pile_ptr[count] + ENDIF +FEND + +// Remove top card from pile (returns the card, updates count) +// Uses $fa - for source pile operations +FUNC pile_pop({WORD pile_ptr @ $fa} out:{BYTE card}) + BYTE count + count = PEEK pile_ptr[0] + card = PEEK pile_ptr[count] + count-- + POKE pile_ptr[0] , count +FEND + +// Add card to top of pile (updates count) +// Uses $fc - for destination pile operations +FUNC pile_push({WORD pile_ptr @ $fc} {BYTE card}) + BYTE count + count = PEEK pile_ptr[0] + count++ + POKE pile_ptr[count] , card + POKE pile_ptr[0] , count +FEND + +// Flip top card face-up if it's face-down +// Uses $fa - for source pile operations +FUNC pile_flip_top({WORD pile_ptr @ $fa}) + BYTE count + BYTE card + BYTE is_facedown + count = PEEK pile_ptr[0] + IF count > 0 + card = PEEK pile_ptr[count] + is_facedown = card & CARD_FACEDOWN + IF is_facedown + card = card & CARD_MASK + POKE pile_ptr[count] , card + ENDIF + ENDIF +FEND + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// Can card go on foundation? (Ace if empty, else same suit +1 rank) +// Uses $fc - destination pile +FUNC can_place_on_foundation({BYTE card_id} {WORD found_ptr @ $fc} out:{BYTE valid}) + BYTE card_suit + BYTE card_rank + BYTE top_card + BYTE top_suit + BYTE top_rank + BYTE count + BYTE expected_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + count = PEEK found_ptr[0] + + IF count == 0 + // Empty foundation: only Ace allowed + IF card_rank == CARD_ACE + valid = 1 + ELSE + valid = 0 + ENDIF + ELSE + // Must be same suit, one rank higher + top_card = PEEK found_ptr[count] + top_card = top_card & CARD_MASK + card_id_to_suit_rank(top_card, top_suit, top_rank) + expected_rank = top_rank + 1 + valid = 0 + IF card_suit == top_suit + IF card_rank == expected_rank + valid = 1 + ENDIF + ENDIF + ENDIF +FEND + +// Can card go on tableau? (King if empty, else opposite color -1 rank) +// Uses $fc - destination pile +FUNC can_place_on_tableau({BYTE card_id} {WORD tab_ptr @ $fc} out:{BYTE valid}) + BYTE card_rank + BYTE card_col + BYTE top_card + BYTE top_rank + BYTE top_col + BYTE count + BYTE card_suit + BYTE top_suit + BYTE expected_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + card_color(card_id, card_col) + count = PEEK tab_ptr[0] + + IF count == 0 + // Empty tableau: only King allowed + IF card_rank == CARD_KING + valid = 1 + ELSE + valid = 0 + ENDIF + ELSE + // Must be opposite color, one rank lower + top_card = PEEK tab_ptr[count] + top_card = top_card & CARD_MASK + card_id_to_suit_rank(top_card, top_suit, top_rank) + card_color(top_card, top_col) + expected_rank = card_rank + 1 + valid = 0 + IF card_col <> top_col + IF expected_rank == top_rank + valid = 1 + ENDIF + ENDIF + ENDIF +FEND + +// ============================================================================ +// Move Functions +// ============================================================================ + +// Stock to Waste: Draw cards from stock to waste +// draw_count = 1 or 3 depending on game variant +// Stock is source @ $fa, Waste is destination @ $fc +FUNC move_stock_to_waste({BYTE draw_count} out:{BYTE success}) + WORD stock_ptr @ $fa + WORD waste_ptr @ $fc + POINTER stock_ptr -> pile_stock + POINTER waste_ptr -> pile_waste + BYTE stock_count + BYTE card + BYTE i + + stock_count = PEEK stock_ptr[0] + IF stock_count == 0 + success = MOVE_INVALID + EXIT + ENDIF + + // Limit draw_count to available cards + IF draw_count > stock_count + draw_count = stock_count + ENDIF + + FOR i = 1 TO draw_count + pile_pop(stock_ptr, card) + card = card & CARD_MASK // Flip face-up + pile_push(waste_ptr, card) + NEXT + + success = MOVE_OK +FEND + +// Reset Stock: Flip waste back to stock (all face-down) +// Waste is source @ $fa, Stock is destination @ $fc +FUNC move_reset_stock(out:{BYTE success}) + WORD waste_ptr @ $fa + WORD stock_ptr @ $fc + POINTER waste_ptr -> pile_waste + POINTER stock_ptr -> pile_stock + BYTE waste_count + BYTE card + BYTE i + + waste_count = PEEK waste_ptr[0] + IF waste_count == 0 + success = MOVE_INVALID + EXIT + ENDIF + + // Move all waste cards to stock (reversed, face-down) + FOR i = 1 TO waste_count + pile_pop(waste_ptr, card) + card = card | CARD_FACEDOWN + pile_push(stock_ptr, card) + NEXT + + success = MOVE_OK +FEND + +// Waste to Tableau +FUNC move_waste_to_tab({WORD tab_ptr @ $fc} out:{BYTE success}) + WORD waste_ptr @ $fa + POINTER waste_ptr -> pile_waste + BYTE card + BYTE valid + + pile_top_card(waste_ptr, card) + IF card == PILE_END + success = MOVE_INVALID + EXIT + ENDIF + + card = card & CARD_MASK + can_place_on_tableau(card, tab_ptr, valid) + IF valid == 0 + success = MOVE_INVALID + EXIT + ENDIF + + pile_pop(waste_ptr, card) + pile_push(tab_ptr, card) + success = MOVE_OK +FEND + +// Waste to Foundation +FUNC move_waste_to_found({WORD found_ptr @ $fc} out:{BYTE success}) + WORD waste_ptr @ $fa + POINTER waste_ptr -> pile_waste + BYTE card + BYTE valid + + pile_top_card(waste_ptr, card) + IF card == PILE_END + success = MOVE_INVALID + EXIT + ENDIF + + card = card & CARD_MASK + can_place_on_foundation(card, found_ptr, valid) + IF valid == 0 + success = MOVE_INVALID + EXIT + ENDIF + + pile_pop(waste_ptr, card) + pile_push(found_ptr, card) + success = MOVE_OK +FEND + +// Tableau to Foundation (top card only) +FUNC move_tab_to_found({WORD tab_ptr @ $fa} {WORD found_ptr @ $fc} out:{BYTE success}) + BYTE card + BYTE valid + BYTE is_facedown + + pile_top_card(tab_ptr, card) + IF card == PILE_END + success = MOVE_INVALID + EXIT + ENDIF + + // Must be face-up + is_facedown = card & CARD_FACEDOWN + IF is_facedown + success = MOVE_INVALID + EXIT + ENDIF + + card = card & CARD_MASK + can_place_on_foundation(card, found_ptr, valid) + IF valid == 0 + success = MOVE_INVALID + EXIT + ENDIF + + pile_pop(tab_ptr, card) + pile_push(found_ptr, card) + pile_flip_top(tab_ptr) // Reveal next card + success = MOVE_OK +FEND + +// Tableau to Tableau: Move card_count cards from src to dst +FUNC move_tab_to_tab({WORD src_ptr @ $fa} {WORD dst_ptr @ $fc} {BYTE card_count} out:{BYTE success}) + BYTE src_count + BYTE start_idx + BYTE bottom_card + BYTE valid + BYTE i + BYTE card + BYTE is_facedown + + src_count = PEEK src_ptr[0] + IF card_count > src_count + success = MOVE_INVALID + EXIT + ENDIF + IF card_count == 0 + success = MOVE_INVALID + EXIT + ENDIF + + // Find the bottom card of the stack to move + start_idx = src_count - card_count + start_idx = start_idx + 1 + bottom_card = PEEK src_ptr[start_idx] + + // Bottom card must be face-up + is_facedown = bottom_card & CARD_FACEDOWN + IF is_facedown + success = MOVE_INVALID + EXIT + ENDIF + + bottom_card = bottom_card & CARD_MASK + can_place_on_tableau(bottom_card, dst_ptr, valid) + IF valid == 0 + success = MOVE_INVALID + EXIT + ENDIF + + // Move cards (preserve order) + FOR i = start_idx TO src_count + card = PEEK src_ptr[i] + pile_push(dst_ptr, card) + NEXT + + // Update source count + src_count = start_idx - 1 + POKE src_ptr[0] , src_count + + pile_flip_top(src_ptr) // Reveal next card + success = MOVE_OK +FEND + +// Foundation to Tableau (optional rule - some variants allow this) +FUNC move_found_to_tab({WORD found_ptr @ $fa} {WORD tab_ptr @ $fc} out:{BYTE success}) + BYTE card + BYTE valid + + pile_top_card(found_ptr, card) + IF card == PILE_END + success = MOVE_INVALID + EXIT + ENDIF + + card = card & CARD_MASK + can_place_on_tableau(card, tab_ptr, valid) + IF valid == 0 + success = MOVE_INVALID + EXIT + ENDIF + + pile_pop(found_ptr, card) + pile_push(tab_ptr, card) + success = MOVE_OK +FEND + +LABEL __skip_lib_cardmoves + +#IFEND diff --git a/cardrender.c65 b/cardrender.c65 new file mode 100644 index 0000000..be9928f --- /dev/null +++ b/cardrender.c65 @@ -0,0 +1,963 @@ + +#IFNDEF __lib_cardrender +#DEFINE __lib_cardrender 1 + +#INCLUDE "cardconsts.c65" + +GOTO __skip_lib_cardrender + +LABEL card_charcode_map +ASM + !8 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 +ENDASM + +LABEL card_suit_charcode_map +ASM + !8 $50, $51, $0e, $0f +ENDASM + +LABEL suit_graphic_hearts +ASM + !8 $1d+64, $1e+64, $1f+64, $2d+64, $2e+64, $2f+64, 0+64, $3e+64, 0+64 +ENDASM + +LABEL suit_graphic_diamonds +ASM + !8 0+64, $1b+64, 0+64, $2a+64, $2b+64, $2c+64, 0+64, $3b+64, 0+64 +ENDASM + +LABEL suit_graphic_spades +ASM + !8 0, $18, $19, $27, $28, $29, $37, $38, $39 +ENDASM + +LABEL suit_graphic_clubs +ASM + !8 0, $15, 0, $24, $25, $26, 0, $35, $36 +ENDASM + +// Convert card ID (0-51) to suit and rank +// 0-12: Hearts, 13-25: Diamonds, 26-38: Spades, 39-51: Clubs +FUNC card_id_to_suit_rank({BYTE card_id} out:{BYTE card_suit} out:{BYTE card_rank}) + card_rank = card_id + card_suit = 0 + + IF card_rank >= 13 + card_rank = card_rank - 13 + card_suit = 1 + ENDIF + + IF card_rank >= 13 + card_rank = card_rank - 13 + card_suit = 2 + ENDIF + + IF card_rank >= 13 + card_rank = card_rank - 13 + card_suit = 3 + ENDIF +FEND + +// ZP usage $fa-$fd +// Render full card using card_id (0-51) +FUNC render_card({WORD screen_address} {WORD offset} {BYTE card_id}) + BYTE card_suit + BYTE card_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank charcode + WORD card_charcode_map_ptr @ $fa + POINTER card_charcode_map_ptr -> card_charcode_map + + BYTE card_rank_charcode + card_rank_charcode = PEEK card_charcode_map_ptr[card_rank] + + // Adjust color for red suit + IF card_suit < CARD_SUIT_SPADES + card_rank_charcode = card_rank_charcode + 64 //red color for hearts and diamonds + ENDIF + + // Get suit charcode + WORD card_suit_charcode_map_ptr @ $fa + POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map + + BYTE suit_charcode + suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit] + + + WORD p2 @ $fa + SWITCH card_suit + CASE CARD_SUIT_HEARTS + POINTER p2 -> suit_graphic_hearts + CASE CARD_SUIT_DIAMONDS + POINTER p2 -> suit_graphic_diamonds + CASE CARD_SUIT_SPADES + POINTER p2 -> suit_graphic_spades + CASE CARD_SUIT_CLUBS + POINTER p2 -> suit_graphic_clubs + ENDSWITCH + + WORD p @ $fc + p = screen_address + offset + + POKE p[0] , 224 // top left corner + POKE p[1] , 225 + POKE p[2] , 225 + POKE p[3] , 225 + POKE p[4] , 226 + + p = p + 40 + + POKE p[0] , 240 + POKE p[1] , card_rank_charcode + POKE p[2] , 0 + POKE p[3] , suit_charcode + POKE p[4] , 243 + + p = p + 40 + + + // Here we draw the bigger suit graphic in the middle of the card + BYTE g + POKE p[0] , 240 + g = PEEK p2[0] + POKE p[1] , g + g = PEEK p2[1] + POKE p[2] , g + g = PEEK p2[2] + POKE p[3] , g + POKE p[4] , 243 + + p = p + 40 + + POKE p[0] , 240 + g = PEEK p2[3] + POKE p[1] , g + g = PEEK p2[4] + POKE p[2] , g + g = PEEK p2[5] + POKE p[3] , g + POKE p[4] , 243 + + p = p + 40 + + POKE p[0] , 240 + g = PEEK p2[6] + POKE p[1] , g + g = PEEK p2[7] + POKE p[2] , g + g = PEEK p2[8] + POKE p[3] , g + POKE p[4] , 243 + + p = p + 40 + + POKE p[0] , 240 + POKE p[1] , suit_charcode + POKE p[2] , 0 + POKE p[3] , card_rank_charcode + POKE p[4] , 243 + + p = p + 40 + + POKE p[0] , 241 + POKE p[1] , 227 + POKE p[2] , 227 + POKE p[3] , 227 + POKE p[4] , 242 + + +FEND + +// Render face-down card stack and top border of visible card +// Returns number of rows drawn (for offset calculation) +// n=0: just draws normal top border ($e0) +// n=1-2: draws modified top border showing stacked cards +// n=3+: draws multiple rows ending with top border +FUNC render_facedown_stack({WORD screen_address} {WORD offset} {BYTE num_facedown} out:{BYTE rows_drawn}) + WORD p @ $fc + p = screen_address + offset + BYTE middle_rows + BYTE top_type + + IF num_facedown == 0 + // Normal top border + POKE p[0] , $e0 + POKE p[1] , $e1 + POKE p[2] , $e1 + POKE p[3] , $e1 + POKE p[4] , $e2 + rows_drawn = 1 + EXIT + ENDIF + + IF num_facedown == 1 + // 1 face-down: $fa row + POKE p[0] , $fa + POKE p[1] , $da + POKE p[2] , $da + POKE p[3] , $da + POKE p[4] , $fc + rows_drawn = 1 + EXIT + ENDIF + + IF num_facedown == 2 + // 2 face-down: $fd row + POKE p[0] , $fd + POKE p[1] , $dc + POKE p[2] , $dc + POKE p[3] , $dc + POKE p[4] , $ff + rows_drawn = 1 + EXIT + ENDIF + + // 3+ face-down cards + // Top row based on (num_facedown mod 3) + top_type = num_facedown + WHILE top_type >= 3 + top_type = top_type - 3 + WEND + + // Draw top row + IF top_type == 0 + POKE p[0] , $e0 + POKE p[1] , $e1 + POKE p[2] , $e1 + POKE p[3] , $e1 + POKE p[4] , $e2 + ENDIF + IF top_type == 1 + POKE p[0] , $fa + POKE p[1] , $da + POKE p[2] , $da + POKE p[3] , $da + POKE p[4] , $fc + ENDIF + IF top_type == 2 + POKE p[0] , $fd + POKE p[1] , $dc + POKE p[2] , $dc + POKE p[3] , $dc + POKE p[4] , $ff + ENDIF + p = p + 40 + rows_drawn = 1 + + // Calculate middle $d7 rows: (num_facedown - 3) / 3 + middle_rows = num_facedown - 3 + WHILE middle_rows >= 3 + middle_rows = middle_rows - 3 + // Draw $d7 row + POKE p[0] , $d7 + POKE p[1] , $dc + POKE p[2] , $dc + POKE p[3] , $dc + POKE p[4] , $f4 + p = p + 40 + rows_drawn++ + WEND + + // Draw card top row: $d7 for all n>=3 + POKE p[0] , $d7 + POKE p[1] , $dc + POKE p[2] , $dc + POKE p[3] , $dc + POKE p[4] , $f4 + rows_drawn++ +FEND + +// Draw connecting border between stacked face-up cards +// Used before each face-up card after the first +FUNC render_connecting_border({WORD screen_address} {WORD offset}) + WORD p @ $fc + p = screen_address + offset + + POKE p[0] , $d4 + POKE p[1] , $e1 + POKE p[2] , $e1 + POKE p[3] , $e1 + POKE p[4] , $d6 +FEND + +// Render just the rank/suit row (no top border) +// Used for face-up cards in a stack where top border is already drawn +FUNC render_card_body_partial({WORD screen_address} {WORD offset} {BYTE card_id}) + BYTE card_suit + BYTE card_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank charcode + WORD card_charcode_map_ptr @ $fa + POINTER card_charcode_map_ptr -> card_charcode_map + + BYTE card_rank_charcode + card_rank_charcode = PEEK card_charcode_map_ptr[card_rank] + + // Adjust color for red suit + IF card_suit < CARD_SUIT_SPADES + card_rank_charcode = card_rank_charcode + 64 + ENDIF + + // Get suit charcode + WORD card_suit_charcode_map_ptr @ $fa + POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map + + BYTE suit_charcode + suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit] + + WORD p @ $fc + p = screen_address + offset + + // Rank/suit row + POKE p[0] , $f0 + POKE p[1] , card_rank_charcode + POKE p[2] , 0 + POKE p[3] , suit_charcode + POKE p[4] , $f3 +FEND + +// Render card body without top border (rank/suit + middle + bottom) +// Used for the last face-up card in a stack +FUNC render_card_body_full({WORD screen_address} {WORD offset} {BYTE card_id}) + BYTE card_suit + BYTE card_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank charcode + WORD card_charcode_map_ptr @ $fa + POINTER card_charcode_map_ptr -> card_charcode_map + + BYTE card_rank_charcode + card_rank_charcode = PEEK card_charcode_map_ptr[card_rank] + + // Adjust color for red suit + IF card_suit < CARD_SUIT_SPADES + card_rank_charcode = card_rank_charcode + 64 + ENDIF + + // Get suit charcode + WORD card_suit_charcode_map_ptr @ $fa + POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map + + BYTE suit_charcode + suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit] + + // Get suit graphic pointer + WORD p2 @ $fa + SWITCH card_suit + CASE CARD_SUIT_HEARTS + POINTER p2 -> suit_graphic_hearts + CASE CARD_SUIT_DIAMONDS + POINTER p2 -> suit_graphic_diamonds + CASE CARD_SUIT_SPADES + POINTER p2 -> suit_graphic_spades + CASE CARD_SUIT_CLUBS + POINTER p2 -> suit_graphic_clubs + ENDSWITCH + + WORD p @ $fc + p = screen_address + offset + WORD CONST SCREEN_MEM_END = $0400+999 + + // Bounds check: skip last 2 rows if they'd overflow screen (compact mode) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + // Row 0: Rank/suit row + POKE p[0] , $f0 + POKE p[1] , card_rank_charcode + POKE p[2] , 0 + POKE p[3] , suit_charcode + POKE p[4] , $f3 + + p = p + 40 + + // Bounds check: skip last 2 rows if they'd overflow screen (compact mode) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + // Rows 1-3: Suit graphic + BYTE g + POKE p[0] , $f0 + g = PEEK p2[0] + POKE p[1] , g + g = PEEK p2[1] + POKE p[2] , g + g = PEEK p2[2] + POKE p[3] , g + POKE p[4] , $f3 + + p = p + 40 + + // Bounds check: skip last 2 rows if they'd overflow screen (compact mode) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + POKE p[0] , $f0 + g = PEEK p2[3] + POKE p[1] , g + g = PEEK p2[4] + POKE p[2] , g + g = PEEK p2[5] + POKE p[3] , g + POKE p[4] , $f3 + + p = p + 40 + + // Bounds check: skip last 2 rows if they'd overflow screen (compact mode) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + POKE p[0] , $f0 + g = PEEK p2[6] + POKE p[1] , g + g = PEEK p2[7] + POKE p[2] , g + g = PEEK p2[8] + POKE p[3] , g + POKE p[4] , $f3 + + p = p + 40 + + // Bounds check: skip last 2 rows if they'd overflow screen (compact mode) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + // Row 4: Bottom rank/suit + POKE p[0] , $f0 + POKE p[1] , suit_charcode + POKE p[2] , 0 + POKE p[3] , card_rank_charcode + POKE p[4] , $f3 + + p = p + 40 + + // Bounds check: skip bottom border if it'd overflow (K-2 case) + IF p > SCREEN_MEM_END-4 + EXIT + ENDIF + + // Row 5: Bottom border + POKE p[0] , $f1 + POKE p[1] , $e3 + POKE p[2] , $e3 + POKE p[3] , $e3 + POKE p[4] , $f2 +FEND + +// Render a complete tableau pile from pile data +// Handles face-down stack and all face-up cards +FUNC render_tableau_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe}) + BYTE count + BYTE facedown_count + BYTE faceup_count + BYTE faceup_index + BYTE card + BYTE card_id + BYTE i + BYTE rows + WORD pos + BYTE use_compact + + count = PEEK pile_ptr[0] + + // Empty pile - nothing to render + IF count == 0 + EXIT + ENDIF + + // Count face-down cards + facedown_count = 0 + BYTE is_facedown + FOR i = 1 TO count + card = PEEK pile_ptr[i] + is_facedown = card & CARD_FACEDOWN + IF is_facedown + facedown_count++ + ENDIF + NEXT + + faceup_count = count - facedown_count + + // Calculate if we need compact mode (total rows > 17) + BYTE facedown_rows + BYTE faceup_rows_normal + BYTE total_rows + BYTE temp_count + + // Calculate facedown rows + IF facedown_count == 0 + facedown_rows = 0 + ELSE + IF facedown_count <= 2 + facedown_rows = 1 + ELSE + facedown_rows = 2 + temp_count = facedown_count - 3 + WHILE temp_count >= 3 + temp_count = temp_count - 3 + facedown_rows++ + WEND + ENDIF + ENDIF + + // Calculate faceup rows (normal mode) + IF faceup_count == 0 + faceup_rows_normal = 0 + ELSE + IF faceup_count == 1 + faceup_rows_normal = 6 + ELSE + // Normal mode: 4 + 2*N rows + faceup_rows_normal = faceup_count + faceup_count + faceup_rows_normal = faceup_rows_normal + 4 + ENDIF + ENDIF + + // Check if we need compact mode (screen rows 8-24 = 17 rows available) + total_rows = facedown_rows + faceup_rows_normal + use_compact = 0 + IF total_rows > 17 + use_compact = 1 + ENDIF + + // Draw face-down stack + first face-up top border + pos = offset + render_facedown_stack(screen_address, pos, facedown_count, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + + // If no face-up cards, we're done (shouldn't happen in valid game) + IF faceup_count == 0 + EXIT + ENDIF + + // Render face-up cards + faceup_index = 0 + #PRAGMA _P_USE_LONG_JUMP 1 + FOR i = 1 TO count + #PRAGMA _P_USE_LONG_JUMP 0 + card = PEEK pile_ptr[i] + + // Skip face-down cards + is_facedown = card & CARD_FACEDOWN + IF is_facedown + // do nothing + ELSE + faceup_index++ + card_id = card & CARD_MASK + + IF faceup_index == 1 + // First face-up: top border already drawn, just body + IF faceup_count == 1 + // Only one face-up card - render full + render_card_body_full(screen_address, pos, card_id) + ELSE + // More cards follow - render partial + render_card_body_partial(screen_address, pos, card_id) + pos = pos + 40 + ENDIF + ELSE + // Subsequent face-up cards + IF use_compact == 0 + // Normal mode: draw connecting border + render_connecting_border(screen_address, pos) + pos = pos + 40 + ENDIF + + IF faceup_index == faceup_count + // Last face-up card - render full + render_card_body_full(screen_address, pos, card_id) + ELSE + // More cards follow - render partial + render_card_body_partial(screen_address, pos, card_id) + pos = pos + 40 + ENDIF + ENDIF + ENDIF + NEXT +FEND + +// Render partial card (top 2 rows only) for stacked display +// Used for face-up cards underneath the top card +FUNC render_card_partial({WORD screen_address} {WORD offset} {BYTE card_id}) + BYTE card_suit + BYTE card_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank charcode + WORD card_charcode_map_ptr @ $fa + POINTER card_charcode_map_ptr -> card_charcode_map + + BYTE card_rank_charcode + card_rank_charcode = PEEK card_charcode_map_ptr[card_rank] + + // Adjust color for red suit + IF card_suit < CARD_SUIT_SPADES + card_rank_charcode = card_rank_charcode + 64 + ENDIF + + // Get suit charcode + WORD card_suit_charcode_map_ptr @ $fa + POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map + + BYTE suit_charcode + suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit] + + WORD p @ $fc + p = screen_address + offset + + // Row 0: top border + POKE p[0] , 224 + POKE p[1] , 225 + POKE p[2] , 225 + POKE p[3] , 225 + POKE p[4] , 226 + + p = p + 40 + + // Row 1: rank and suit + POKE p[0] , 240 + POKE p[1] , card_rank_charcode + POKE p[2] , 0 + POKE p[3] , suit_charcode + POKE p[4] , 243 +FEND + +// Render back side of a card (face-down full card) +// Same border as face card, middle filled with $52 +FUNC render_card_back({WORD screen_address} {WORD offset}) + WORD p @ $fc + p = screen_address + offset + + // Row 0: top border + POKE p[0] , $e0 + POKE p[1] , $e1 + POKE p[2] , $e1 + POKE p[3] , $e1 + POKE p[4] , $e2 + + p = p + 40 + + // Row 1: side borders + $52 fill + POKE p[0] , $f0 + POKE p[1] , $52 + POKE p[2] , $52 + POKE p[3] , $52 + POKE p[4] , $f3 + + p = p + 40 + + // Row 2: side borders + $52 fill + POKE p[0] , $f0 + POKE p[1] , $52 + POKE p[2] , $52 + POKE p[3] , $52 + POKE p[4] , $f3 + + p = p + 40 + + // Row 3: side borders + $52 fill + POKE p[0] , $f0 + POKE p[1] , $52 + POKE p[2] , $52 + POKE p[3] , $52 + POKE p[4] , $f3 + + p = p + 40 + + // Row 4: side borders + $52 fill + POKE p[0] , $f0 + POKE p[1] , $52 + POKE p[2] , $52 + POKE p[3] , $52 + POKE p[4] , $f3 + + p = p + 40 + + // Row 5: side borders + $52 fill + POKE p[0] , $f0 + POKE p[1] , $52 + POKE p[2] , $52 + POKE p[3] , $52 + POKE p[4] , $f3 + + p = p + 40 + + // Row 6: bottom border + POKE p[0] , $f1 + POKE p[1] , $e3 + POKE p[2] , $e3 + POKE p[3] , $e3 + POKE p[4] , $f2 +FEND + +// Render empty pile placeholder +// Same border shape but -$80 for light pink color, interior filled with 0 +FUNC render_empty_pile({WORD screen_address} {WORD offset}) + WORD p @ $fc + p = screen_address + offset + + // Row 0: top border (pink) + POKE p[0] , $60 + POKE p[1] , $61 + POKE p[2] , $61 + POKE p[3] , $61 + POKE p[4] , $62 + + p = p + 40 + + // Row 1: side borders + empty fill + POKE p[0] , $70 + POKE p[1] , 0 + POKE p[2] , 0 + POKE p[3] , 0 + POKE p[4] , $73 + + p = p + 40 + + // Row 2: side borders + empty fill + POKE p[0] , $70 + POKE p[1] , 0 + POKE p[2] , 0 + POKE p[3] , 0 + POKE p[4] , $73 + + p = p + 40 + + // Row 3: side borders + empty fill + POKE p[0] , $70 + POKE p[1] , 0 + POKE p[2] , 0 + POKE p[3] , 0 + POKE p[4] , $73 + + p = p + 40 + + // Row 4: side borders + empty fill + POKE p[0] , $70 + POKE p[1] , 0 + POKE p[2] , 0 + POKE p[3] , 0 + POKE p[4] , $73 + + p = p + 40 + + // Row 5: side borders + empty fill + POKE p[0] , $70 + POKE p[1] , 0 + POKE p[2] , 0 + POKE p[3] , 0 + POKE p[4] , $73 + + p = p + 40 + + // Row 6: bottom border (pink) + POKE p[0] , $71 + POKE p[1] , $63 + POKE p[2] , $63 + POKE p[3] , $63 + POKE p[4] , $72 +FEND + +// Render foundation pile - shows top card or empty placeholder +FUNC render_foundation_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe}) + BYTE count + BYTE card + BYTE card_id + + count = PEEK pile_ptr[0] + + IF count == 0 + render_empty_pile(screen_address, offset) + ELSE + card = PEEK pile_ptr[count] + card_id = card & CARD_MASK + render_card(screen_address, offset, card_id) + ENDIF +FEND + +// Render stock pile - shows card back or empty placeholder +FUNC render_stock_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe}) + BYTE count + + count = PEEK pile_ptr[0] + + IF count == 0 + render_empty_pile(screen_address, offset) + ELSE + render_card_back(screen_address, offset) + ENDIF +FEND + +// Render left edge of card (2 columns) for fanned display +FUNC render_card_left_edge({WORD screen_address} {WORD offset} {BYTE card_id}) + BYTE card_suit + BYTE card_rank + + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank charcode + WORD card_charcode_map_ptr @ $fa + POINTER card_charcode_map_ptr -> card_charcode_map + + BYTE card_rank_charcode + card_rank_charcode = PEEK card_charcode_map_ptr[card_rank] + + // Adjust color for red suit + IF card_suit < CARD_SUIT_SPADES + card_rank_charcode = card_rank_charcode + 64 + ENDIF + + // Get suit charcode + WORD card_suit_charcode_map_ptr @ $fa + POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map + + BYTE suit_charcode + suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit] + + // Get suit graphic pointer + WORD p2 @ $fa + SWITCH card_suit + CASE CARD_SUIT_HEARTS + POINTER p2 -> suit_graphic_hearts + CASE CARD_SUIT_DIAMONDS + POINTER p2 -> suit_graphic_diamonds + CASE CARD_SUIT_SPADES + POINTER p2 -> suit_graphic_spades + CASE CARD_SUIT_CLUBS + POINTER p2 -> suit_graphic_clubs + ENDSWITCH + + WORD p @ $fc + p = screen_address + offset + + BYTE g + + // Row 0: top border (2 cols) + POKE p[0] , $e0 + POKE p[1] , $e1 + + p = p + 40 + + // Row 1: left border + rank + POKE p[0] , $f0 + POKE p[1] , card_rank_charcode + + p = p + 40 + + // Row 2: left border + suit graphic + POKE p[0] , $f0 + g = PEEK p2[0] + POKE p[1] , g + + p = p + 40 + + // Row 3: left border + suit graphic + POKE p[0] , $f0 + g = PEEK p2[3] + POKE p[1] , g + + p = p + 40 + + // Row 4: left border + suit graphic + POKE p[0] , $f0 + g = PEEK p2[6] + POKE p[1] , g + + p = p + 40 + + // Row 5: left border + suit + POKE p[0] , $f0 + POKE p[1] , suit_charcode + + p = p + 40 + + // Row 6: bottom border (2 cols) + POKE p[0] , $f1 + POKE p[1] , $e3 +FEND + +// Render waste pile - draw-3 style fanned display +// Shows up to 3 cards fanned horizontally (2 col offset per card) +FUNC render_waste_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe} {BYTE draw_mode}) + BYTE count + BYTE card + BYTE card_id + WORD off2 + WORD off4 + + count = PEEK pile_ptr[0] + + // Draw-1 mode: only show top card + IF draw_mode == 1 + IF count == 0 + render_empty_pile(screen_address, offset) + ELSE + card = PEEK pile_ptr[count] + card_id = card & CARD_MASK + render_card(screen_address, offset, card_id) + ENDIF + EXIT + ENDIF + + // Draw-3 mode: fan out top 3 cards + off2 = offset + 2 + off4 = offset + 4 + + SWITCH count + CASE 0 + render_empty_pile(screen_address, offset) + + CASE 1 + card = PEEK pile_ptr[1] + card_id = card & CARD_MASK + render_card(screen_address, offset, card_id) + + CASE 2 + // 2nd from top (left edge) + card = PEEK pile_ptr[1] + card_id = card & CARD_MASK + render_card_left_edge(screen_address, offset, card_id) + // Top card (full) + card = PEEK pile_ptr[2] + card_id = card & CARD_MASK + render_card(screen_address, off2, card_id) + + DEFAULT + // 3+ cards: show top 3 fanned + BYTE idx + // 3rd from top (left edge) + idx = count - 2 + card = PEEK pile_ptr[idx] + card_id = card & CARD_MASK + render_card_left_edge(screen_address, offset, card_id) + + // 2nd from top (left edge) + idx = count - 1 + card = PEEK pile_ptr[idx] + card_id = card & CARD_MASK + render_card_left_edge(screen_address, off2, card_id) + + // Top card (full) + card = PEEK pile_ptr[count] + card_id = card & CARD_MASK + render_card(screen_address, off4, card_id) + ENDSWITCH +FEND + +LABEL __skip_lib_cardrender + +#IFEND diff --git a/cardsprites.c65 b/cardsprites.c65 new file mode 100644 index 0000000..58558ea --- /dev/null +++ b/cardsprites.c65 @@ -0,0 +1,899 @@ + +#IFNDEF __lib_cardsprites +#DEFINE __lib_cardsprites 1 + +GOTO __skip_lib_cardsprites + +// ============================================================================ +// CARD RANK SPRITES (A-K) +// ============================================================================ +// Sprites for displaying card ranks in multiplexed border display +// Each sprite is 64 bytes (21 rows of 3 bytes + 1 padding byte) +// ============================================================================ + +// Sprite 0: Ace +LABEL sprite_rank_ace +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $10, $00 + !8 $00, $10, $00 + !8 $00, $38, $00 + !8 $00, $38, $00 + !8 $00, $6c, $00 + !8 $00, $6c, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $01, $ff, $00 + !8 $01, $83, $00 + !8 $03, $c7, $80 + !8 $03, $c7, $80 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 1: 2 +LABEL sprite_rank_2 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $06, $00 + !8 $00, $0e, $00 + !8 $00, $1c, $00 + !8 $00, $38, $00 + !8 $00, $70, $00 + !8 $00, $e6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $fe, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 2: 3 +LABEL sprite_rank_3 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $fe, $00 + !8 $00, $fe, $00 + !8 $00, $cc, $00 + !8 $00, $18, $00 + !8 $00, $30, $00 + !8 $00, $7c, $00 + !8 $00, $7e, $00 + !8 $00, $06, $00 + !8 $00, $06, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 3: 4 +LABEL sprite_rank_4 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $0c, $00 + !8 $00, $1c, $00 + !8 $00, $3c, $00 + !8 $00, $7c, $00 + !8 $00, $ec, $00 + !8 $01, $cc, $00 + !8 $01, $ff, $00 + !8 $01, $ff, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $1e, $00 + !8 $00, $1e, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 4: 5 +LABEL sprite_rank_5 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $fe, $00 + !8 $00, $fe, $00 + !8 $00, $c0, $00 + !8 $00, $c0, $00 + !8 $00, $fc, $00 + !8 $00, $fe, $00 + !8 $00, $06, $00 + !8 $00, $06, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 5: 6 +LABEL sprite_rank_6 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $3c, $00 + !8 $00, $7c, $00 + !8 $00, $e0, $00 + !8 $00, $c0, $00 + !8 $00, $fc, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 6: 7 +LABEL sprite_rank_7 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $fe, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $18, $00 + !8 $00, $18, $00 + !8 $00, $18, $00 + !8 $00, $30, $00 + !8 $00, $30, $00 + !8 $00, $30, $00 + !8 $00, $30, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 7: 8 +LABEL sprite_rank_8 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 8: 9 +LABEL sprite_rank_9 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7e, $00 + !8 $00, $06, $00 + !8 $00, $0e, $00 + !8 $00, $7c, $00 + !8 $00, $78, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 9: 10 +LABEL sprite_rank_10 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $01, $9e, $00 + !8 $01, $bf, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $b3, $00 + !8 $01, $bf, $00 + !8 $01, $9e, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 10: Jack +LABEL sprite_rank_jack +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $1e, $00 + !8 $00, $1e, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $0c, $00 + !8 $00, $cc, $00 + !8 $00, $cc, $00 + !8 $00, $fc, $00 + !8 $00, $78, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 11: Queen +LABEL sprite_rank_queen +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $c6, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $0e, $00 + !8 $00, $06, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 12: King +LABEL sprite_rank_king +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $01, $ef, $00 + !8 $01, $ef, $00 + !8 $00, $cc, $00 + !8 $00, $d8, $00 + !8 $00, $f0, $00 + !8 $00, $e0, $00 + !8 $00, $f0, $00 + !8 $00, $d8, $00 + !8 $00, $cc, $00 + !8 $00, $c6, $00 + !8 $01, $ef, $00 + !8 $01, $ef, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// ============================================================================ +// CREDITS SPRITES +// ============================================================================ +// Two sprites that go together horizontally to display credits +// ============================================================================ + +// Sprite 13: Credits (left part) +LABEL sprite_credits_left +ASM + !8 $ae, $ea, $ee + !8 $aa, $8a, $2a + !8 $ee, $8c, $4a + !8 $aa, $8a, $8a + !8 $aa, $ea, $ee + !8 $00, $00, $00 + !8 $0e, $4c, $ee + !8 $08, $4a, $8a + !8 $0e, $4a, $ce + !8 $02, $4a, $8c + !8 $0e, $4c, $ea + !8 $00, $00, $00 + !8 $00, $ee, $ee + !8 $00, $2a, $28 + !8 $00, $ea, $ee + !8 $00, $8a, $8a + !8 $00, $ee, $ee + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// Sprite 14: Credits (right part) +LABEL sprite_credits_right +ASM + !8 $4c, $00, $00 + !8 $4a, $00, $00 + !8 $4a, $00, $00 + !8 $4a, $00, $00 + !8 $4c, $00, $00 + !8 $00, $00, $00 + !8 $e0, $00, $00 + !8 $80, $00, $00 + !8 $e0, $00, $00 + !8 $20, $00, $00 + !8 $e0, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $fe +ENDASM + +// ============================================================================ +// SUIT COLOR SPRITES (Overlay pairs for multi-color display) +// ============================================================================ +// Each suit uses 2 sprites overlaid to allow for two screen colors +// Hearts (sprites 15-16), Diamonds (17-18), Clubs (19-20), Spades (21-22) +// ============================================================================ + +// Sprite 15: Hearts (layer 1) +LABEL sprite_suit_hearts_1 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $01, $c7, $00 + !8 $03, $ef, $80 + !8 $07, $ff, $c0 + !8 $07, $ff, $c0 + !8 $07, $ff, $c0 + !8 $03, $ff, $80 + !8 $01, $ff, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $38, $00 + !8 $00, $10, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $12 +ENDASM + +// Sprite 16: Hearts (layer 2) +LABEL sprite_suit_hearts_2 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $41, $00 + !8 $00, $20, $80 + !8 $00, $00, $80 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $0a +ENDASM + +// Sprite 17: Diamonds (layer 1) +LABEL sprite_suit_diamonds_1 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $10, $00 + !8 $00, $38, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $01, $ff, $00 + !8 $03, $ff, $80 + !8 $03, $ff, $80 + !8 $01, $ff, $00 + !8 $00, $fe, $00 + !8 $00, $7c, $00 + !8 $00, $38, $00 + !8 $00, $10, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $12 +ENDASM + +// Sprite 18: Diamonds (layer 2) +LABEL sprite_suit_diamonds_2 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $08, $00 + !8 $00, $04, $00 + !8 $00, $02, $00 + !8 $00, $01, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $0a +ENDASM + +// Sprite 19: Clubs (layer 1) +LABEL sprite_suit_clubs_1 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $38, $00 + !8 $00, $7c, $00 + !8 $00, $7c, $00 + !8 $00, $7c, $00 + !8 $03, $bb, $80 + !8 $07, $d7, $c0 + !8 $07, $ff, $c0 + !8 $07, $d7, $c0 + !8 $03, $93, $80 + !8 $00, $10, $00 + !8 $00, $38, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $10 +ENDASM + +// Sprite 20: Clubs (layer 2) +LABEL sprite_suit_clubs_2 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $38, $00 + !8 $00, $0c, $00 + !8 $00, $04, $00 + !8 $00, $04, $00 + !8 $01, $83, $80 + !8 $00, $c0, $c0 + !8 $00, $00, $40 + !8 $00, $00, $40 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $08, $00 + !8 $00, $04, $00 + ;!8 $08, $00, $00 + ;!8 $04, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $0b +ENDASM + +// Sprite 21: Spades (layer 1) +LABEL sprite_suit_spades_1 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $10, $00 + !8 $00, $38, $00 + !8 $00, $7c, $00 + !8 $00, $fe, $00 + !8 $01, $ff, $00 + !8 $03, $ff, $80 + !8 $03, $ff, $80 + !8 $03, $ff, $80 + !8 $01, $d7, $00 + !8 $00, $10, $00 + !8 $00, $38, $00 + !8 $00, $7c, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $10 +ENDASM + +// Sprite 22: Spades (layer 2) +LABEL sprite_suit_spades_2 +ASM + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $10, $00 + !8 $00, $18, $00 + !8 $00, $0c, $00 + !8 $00, $06, $00 + !8 $00, $03, $00 + !8 $00, $01, $80 + !8 $00, $00, $80 + !8 $00, $00, $80 + !8 $00, $00, $00 + !8 $00, $00, $00 + ;!8 $08, $00, $00 + !8 $00, $08, $00 + ;!8 $04, $00, $00 + !8 $00, $04, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $0b +ENDASM + +// ============================================================================ +// TEXT SPRITES - "SELECTED CARD" +// ============================================================================ +// Two sprites that display "SELECTED CARD" text +// ============================================================================ + +// Sprite 23: "SELECTED CARD" (left part) +LABEL sprite_text_selected_card_1 +ASM + !8 $77, $47, $77 + !8 $44, $44, $42 + !8 $76, $46, $42 + !8 $14, $44, $42 + !8 $77, $77, $72 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $77, $76, $00 + !8 $45, $55, $00 + !8 $47, $75, $00 + !8 $45, $65, $00 + !8 $75, $56, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00 +ENDASM + +// Sprite 24: "SELECTED CARD" (right part) +LABEL sprite_text_selected_card_2 +ASM + !8 $76, $00, $00 + !8 $45, $00, $00 + !8 $65, $00, $00 + !8 $45, $00, $00 + !8 $76, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00 +ENDASM + +// ============================================================================ +// CARD DISPLAY SPRITE MANAGEMENT +// ============================================================================ +// Functions to display selected card using sprites in upper right corner +// Uses sprites 1-5 (sprite 0 is reserved for pointer) +// ============================================================================ + +// VIC-II Sprite registers for card display (sprites 1-5) +// Note: sprite_x_msb and sprite_enable already declared in pointer.c65 +BYTE sprite_x1 @ $d002 // Sprite 1 X +BYTE sprite_y1 @ $d003 // Sprite 1 Y +BYTE sprite_x2 @ $d004 // Sprite 2 X +BYTE sprite_y2 @ $d005 // Sprite 2 Y +BYTE sprite_x3 @ $d006 // Sprite 3 X +BYTE sprite_y3 @ $d007 // Sprite 3 Y +BYTE sprite_x4 @ $d008 // Sprite 4 X +BYTE sprite_y4 @ $d009 // Sprite 4 Y +BYTE sprite_x5 @ $d00a // Sprite 5 X +BYTE sprite_y5 @ $d00b // Sprite 5 Y +BYTE sprite_color1 @ $d028 // Sprite 1 color +BYTE sprite_color2 @ $d029 // Sprite 2 color +BYTE sprite_color3 @ $d02a // Sprite 3 color +BYTE sprite_color4 @ $d02b // Sprite 4 color +BYTE sprite_color5 @ $d02c // Sprite 5 color +BYTE sprite_pointer1 @ $07f9 // Sprite 1 pointer +BYTE sprite_pointer2 @ $07fa // Sprite 2 pointer +BYTE sprite_pointer3 @ $07fb // Sprite 3 pointer +BYTE sprite_pointer4 @ $07fc // Sprite 4 pointer +BYTE sprite_pointer5 @ $07fd // Sprite 5 pointer + +// Card display sprite positions (upper right corner) +// Screen starts at (24,50), size 320x200 +// Sprites are 24 pixels wide +WORD CONST CARD_TEXT_X1 = 312 // "SELECTED CARD" left part +WORD CONST CARD_TEXT_X2 = CARD_TEXT_X1+24 // "SELECTED CARD" right part +BYTE CONST CARD_TEXT_Y = 50 // Top of screen +WORD CONST CARD_RANK_X = 312 // Rank sprite X +WORD CONST CARD_SUIT_X = CARD_RANK_X+14 // Suit sprites X (both layers) - side by side with rank +BYTE CONST CARD_RANK_Y = 64 // Below text (50 + 21 pixel sprite height) + +// Sprite data block numbers (offset from base at $2240 / 64 = 137) +BYTE CONST SPRITE_BLOCK_TEXT_LEFT = 160 // sprite_text_selected_card_1 (sprite 23) +BYTE CONST SPRITE_BLOCK_TEXT_RIGHT = 161 // sprite_text_selected_card_2 (sprite 24) +// Rank blocks: 137-149 (ace=137, 2=138, ... king=149) +// Suit blocks: hearts1=152, hearts2=153, diamonds1=154, diamonds2=155, +// clubs1=156, clubs2=157, spades1=158, spades2=159 + +// ============================================================================ +// FUNC card_display_init +// Initialize card display sprites (position and "SELECTED CARD" text) +// Call this once during game initialization +// ============================================================================ +FUNC card_display_init + BYTE temp + + // Set "SELECTED CARD" text sprites (sprites 1 & 2) + sprite_pointer1 = SPRITE_BLOCK_TEXT_LEFT + sprite_pointer2 = SPRITE_BLOCK_TEXT_RIGHT + + // Position text sprites + sprite_x1 = CARD_TEXT_X1 + sprite_y1 = CARD_TEXT_Y + sprite_x2 = CARD_TEXT_X2 + sprite_y2 = CARD_TEXT_Y + + // Set X MSB for sprites > 255 + temp = sprite_x_msb + IF CARD_TEXT_X1 > 255 + temp = temp | %00000010 // Sprite 1 X MSB + ENDIF + IF CARD_TEXT_X2 > 255 + temp = temp | %00000100 // Sprite 2 X MSB + ENDIF + sprite_x_msb = temp + + // Set text sprite colors (black) + sprite_color1 = color_black + sprite_color2 = color_black + + // Initially hide all card display sprites (1-5) + temp = sprite_enable + temp = temp & %11000001 // Keep sprite 0 (pointer), clear 1-5 + sprite_enable = temp +FEND + + +// ============================================================================ +// FUNC card_display_show +// Show selected card using sprites +// Parameters: +// card_rank: Card rank (0-12: A,2-10,J,Q,K) +// card_suit: Card suit (0-3: hearts, diamonds, clubs, spades) +// ============================================================================ +FUNC card_display_show({BYTE card_rank} {BYTE card_suit}) + BYTE rank_block + BYTE suit_block1 + BYTE suit_block2 + BYTE suit_color + BYTE rank_color + BYTE suit_color2 + + // Calculate sprite block for rank (137 = ace, 138 = 2, etc.) + rank_block = 137 + rank_block = rank_block + card_rank + + // Calculate sprite blocks for suit (2 layers each) + // Sprite order: Hearts: 152-153, Diamonds: 154-155, Clubs: 156-157, Spades: 158-159 + // Card suit order: 0=Hearts, 1=Diamonds, 2=Spades, 3=Clubs + // Need to swap spades(2) and clubs(3) + BYTE suit_index + IF card_suit == 2 + suit_index = 3 // Spades (card suit 2) -> sprite index 3 + ELSE + IF card_suit == 3 + suit_index = 2 // Clubs (card suit 3) -> sprite index 2 + ELSE + suit_index = card_suit // Hearts and Diamonds map directly + ENDIF + ENDIF + + ASM + lda |suit_index| + asl // Multiply by 2 (shift left) + clc + adc #152 + sta |suit_block1| + ENDASM + suit_block2 = suit_block1 + 1 + //suit_block2 = suit_block2 + 1 + + // Determine suit colors + // Rank and suit layer 1: red for hearts/diamonds, black for clubs/spades + // Suit layer 2: pink for red suits, dark grey for black suits + + IF card_suit < 2 + rank_color = color_red + suit_color = color_red + suit_color2 = color_pink + ELSE + rank_color = color_black + suit_color = color_black + suit_color2 = color_dark_grey + ENDIF + + // Set sprite 3: Rank + sprite_pointer3 = rank_block + sprite_x3 = CARD_RANK_X + sprite_y3 = CARD_RANK_Y + sprite_color3 = rank_color + + // Set sprites 4 & 5: Suit (2 layers at same position) + // Sprite 4 has higher priority (drawn on top), so it gets the overlay layer + // Sprite 5 has lower priority (drawn behind), so it gets the base layer + sprite_pointer4 = suit_block2 // Overlay layer (drawn on top) + sprite_pointer5 = suit_block1 // Base layer (drawn behind) + sprite_x4 = CARD_SUIT_X + sprite_y4 = CARD_RANK_Y // Same Y as rank + sprite_x5 = CARD_SUIT_X // Exactly same X/Y as sprite 4 + sprite_y5 = CARD_RANK_Y + sprite_color4 = suit_color2 // Overlay color (pink/dark grey) + sprite_color5 = suit_color // Base color (red/black) + + // Set X MSB for rank and suit sprites + IF CARD_RANK_X > 255 + sprite_x_msb = sprite_x_msb | %00111110 + ENDIF + + // Enable all card display sprites (1-5) + sprite_enable = sprite_enable | %00111110 +FEND + + +// ============================================================================ +// FUNC card_display_hide +// Hide all card display sprites (when no card selected) +// ============================================================================ +FUNC card_display_hide + BYTE temp + + // Disable sprites 1-5, keep sprite 0 (pointer) + temp = sprite_enable + temp = temp & %11000001 + sprite_enable = temp +FEND + +LABEL __skip_lib_cardsprites + +#IFEND diff --git a/cardtests.c65 b/cardtests.c65 new file mode 100644 index 0000000..6790521 --- /dev/null +++ b/cardtests.c65 @@ -0,0 +1,878 @@ + +#IFNDEF __lib_cardtests +#DEFINE __lib_cardtests 1 + +#INCLUDE "cardrender.c65" +#INCLUDE "piles.c65" +#INCLUDE "carddeck.c65" +#INCLUDE "cardmoves.c65" + +GOTO __skip_lib_cardtests + +FUNC show_charset + + WORD i + BYTE b + b = 0 + FOR i = $0400 TO $0400+999 + POKE i , b + b++ + NEXT +FEND + +// Demo: fill $c000-$c1ff with 512 random bytes +FUNC rand_demo + WORD ptr @ $fa + WORD count + BYTE r + + ptr = $c000 + FOR count = 0 TO 511 + //rand(r) + rand_max(52, r) + POKE ptr , r + ptr++ + NEXT +FEND + +FUNC render_all_cards_test + // Test render_card - render all cards by ID + BYTE i + WORD wi + wi = 0 + FOR i = 0 TO 12 + render_card($0404, wi, i) + wi = wi + 2 + NEXT + + wi = 0 + FOR i = 0+13 TO 12+13 + render_card(7*40+$0404, wi, i) + wi = wi + 2 + NEXT + + wi = 0 + FOR i = 13*2+0 TO 13*2+12 + render_card(2*7*40+$0404, wi, i) + wi = wi + 2 + NEXT + + wi = 0 + FOR i = 13*3+0 TO 13*3+12 + render_card(3*7*40+$0404, wi, i) + wi = wi + 2 + NEXT + +FEND + +// Test face-down stack rendering (1-6 cards) +FUNC render_facedown_test + BYTE rows + + // Column 0: 1 face-down + render_facedown_stack($0400, 0, 1, rows) + + // Column 1: 2 face-down + render_facedown_stack($0400, 6, 2, rows) + + // Column 2: 3 face-down + render_facedown_stack($0400, 12, 3, rows) + + // Column 3: 4 face-down + render_facedown_stack($0400, 18, 4, rows) + + // Column 4: 5 face-down + render_facedown_stack($0400, 24, 0, rows) + + // Column 5: 6 face-down + render_facedown_stack($0400, 30, 51, rows) +FEND + +// Test stacked face-up cards +FUNC render_faceup_stack_test + BYTE rows + BYTE i + WORD pos + + // Column 0: 1 face-down, 1 face-up (full card) + pos = 0 + render_facedown_stack($0400, pos, 1, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + render_card_body_full($0400, pos, 0) // Ace of Hearts + + // Column 1: 2 face-down, 2 face-up + pos = 6 + render_facedown_stack($0400, pos, 2, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + render_card_body_partial($0400, pos, 13) // Ace of Diamonds + pos = pos + 40 + render_connecting_border($0400, pos) + pos = pos + 40 + render_card_body_full($0400, pos, 26) // Ace of Spades + + // Column 2: 1 face-down, 3 face-up + pos = 12 + render_facedown_stack($0400, pos, 1, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + render_card_body_partial($0400, pos, 1) // 2 of Hearts + pos = pos + 40 + render_connecting_border($0400, pos) + pos = pos + 40 + render_card_body_partial($0400, pos, 14) // 2 of Diamonds + pos = pos + 40 + render_connecting_border($0400, pos) + pos = pos + 40 + render_card_body_full($0400, pos, 27) // 2 of Spades + + // Column 3: 0 face-down, 2 face-up (no face-down cards) + pos = 18 + render_facedown_stack($0400, pos, 0, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + render_card_body_partial($0400, pos, 39) // Ace of Clubs + pos = pos + 40 + render_connecting_border($0400, pos) + pos = pos + 40 + render_card_body_full($0400, pos, 40) // 2 of Clubs + + // Column 4: 3 face-down, 1 face-up + pos = 24 + render_facedown_stack($0400, pos, 3, rows) + FOR i = 1 TO rows + pos = pos + 40 + NEXT + render_card_body_full($0400, pos, 12) // King of Hearts + + // Column 5: Standalone card (foundation style) + render_card($0400, 30, 51) // King of Clubs +FEND + +// Test rendering actual dealt tableau piles +FUNC render_tableaus_test + WORD ptr @ $f8 + + // Render all 7 tableau piles + // Each pile is 6 chars wide (5 card + 1 space) + POINTER ptr -> pile_tab0 + render_tableau_pile($0400, 0, ptr) + + POINTER ptr -> pile_tab1 + render_tableau_pile($0400, 5, ptr) + + POINTER ptr -> pile_tab2 + render_tableau_pile($0400, 10, ptr) + + POINTER ptr -> pile_tab3 + render_tableau_pile($0400, 15, ptr) + + POINTER ptr -> pile_tab4 + render_tableau_pile($0400, 20, ptr) + + POINTER ptr -> pile_tab5 + render_tableau_pile($0400, 25, ptr) + + POINTER ptr -> pile_tab6 + render_tableau_pile($0400, 30, ptr) +FEND + +// Setup mid-game tableau piles for demo (Klondike rules) +// Card IDs: Hearts 0-12 (red), Diamonds 13-25 (red), Spades 26-38 (black), Clubs 39-51 (black) +// Rank: A=0, 2=1, 3=2, 4=3, 5=4, 6=5, 7=6, 8=7, 9=8, 10=9, J=10, Q=11, K=12 +// Klondike: alternating colors, descending rank +FUNC setup_midgame_piles + WORD ptr @ $f8 + + // Tab0: K♥(red) → Q♠(black) → J♦(red) → 10♣(black) + POINTER ptr -> pile_tab0 + POKE ptr[0] , 4 + POKE ptr[1] , 12 // King of Hearts (red) + POKE ptr[2] , 37 // Queen of Spades (black) + POKE ptr[3] , 23 // Jack of Diamonds (red) + POKE ptr[4] , 48 // 10 of Clubs (black) + + // Tab1: 1 face-down, Q♣(black) → J♥(red) → 10♠(black) + POINTER ptr -> pile_tab1 + POKE ptr[0] , 4 + POKE ptr[1] , 0|CARD_FACEDOWN + POKE ptr[2] , 50 // Queen of Clubs (black) + POKE ptr[3] , 10 // Jack of Hearts (red) + POKE ptr[4] , 35 // 10 of Spades (black) + + // Tab2: 2 face-down, 7♦(red) + POINTER ptr -> pile_tab2 + POKE ptr[0] , 3 + POKE ptr[1] , 1|CARD_FACEDOWN + POKE ptr[2] , 2|CARD_FACEDOWN + POKE ptr[3] , 19 // 7 of Diamonds (red) + + // Tab3: K♠(black) → Q♥(red) → J♣(black) → 10♦(red) → 9♠(black) + POINTER ptr -> pile_tab3 + POKE ptr[0] , 5 + POKE ptr[1] , 38 // King of Spades (black) + POKE ptr[2] , 11 // Queen of Hearts (red) + POKE ptr[3] , 49 // Jack of Clubs (black) + POKE ptr[4] , 22 // 10 of Diamonds (red) + POKE ptr[5] , 34 // 9 of Spades (black) + + // Tab4: 3 face-down, K♦(red) → Q♣(black) + POINTER ptr -> pile_tab4 + POKE ptr[0] , 5 + POKE ptr[1] , 3|CARD_FACEDOWN + POKE ptr[2] , 4|CARD_FACEDOWN + POKE ptr[3] , 5|CARD_FACEDOWN + POKE ptr[4] , 25 // King of Diamonds (red) + POKE ptr[5] , 50 // Queen of Clubs (black) + + // Tab5: 4 face-down, 5♥(red) + POINTER ptr -> pile_tab5 + POKE ptr[0] , 5 + POKE ptr[1] , 6|CARD_FACEDOWN + POKE ptr[2] , 7|CARD_FACEDOWN + POKE ptr[3] , 8|CARD_FACEDOWN + POKE ptr[4] , 9|CARD_FACEDOWN + POKE ptr[5] , 4 // 5 of Hearts (red) + + // Tab6: 5 face-down, 8♣(black) → 7♥(red) + POINTER ptr -> pile_tab6 + POKE ptr[0] , 7 + POKE ptr[1] , 10|CARD_FACEDOWN + POKE ptr[2] , 11|CARD_FACEDOWN + POKE ptr[3] , 12|CARD_FACEDOWN + POKE ptr[4] , 13|CARD_FACEDOWN + POKE ptr[5] , 14|CARD_FACEDOWN + POKE ptr[6] , 46 // 8 of Clubs (black) + POKE ptr[7] , 6 // 7 of Hearts (red) +FEND + +// Test mid-game tableau rendering +FUNC render_midgame_test + setup_midgame_piles() + render_tableaus_test() +FEND + +// Setup foundation piles in different states for testing +FUNC setup_foundation_test_piles + WORD ptr @ $f8 + + // Foundation 0: empty (Hearts) + POINTER ptr -> pile_found0 + POKE ptr[0] , 0 + + // Foundation 1: Ace only (Diamonds) + POINTER ptr -> pile_found1 + POKE ptr[0] , 1 + POKE ptr[1] , 13 // A♦ + + // Foundation 2: A-2-3 (Spades) + POINTER ptr -> pile_found2 + POKE ptr[0] , 3 + POKE ptr[1] , 26 // A♠ + POKE ptr[2] , 27 // 2♠ + POKE ptr[3] , 28 // 3♠ + + // Foundation 3: full A-K (Clubs) + POINTER ptr -> pile_found3 + POKE ptr[0] , 13 + BYTE i + BYTE card + card = 39 + ptr++ + FOR i = 0 TO 12 + POKE ptr , card + ptr++ + card++ + NEXT +FEND + +// Test foundation pile rendering +FUNC render_foundation_test + WORD ptr @ $f8 + + setup_foundation_test_piles() + + // Render 4 foundations side by side + POINTER ptr -> pile_found0 + render_foundation_pile($0400, 0, ptr) + + POINTER ptr -> pile_found1 + render_foundation_pile($0400, 6, ptr) + + POINTER ptr -> pile_found2 + render_foundation_pile($0400, 12, ptr) + + POINTER ptr -> pile_found3 + render_foundation_pile($0400, 18, ptr) +FEND + +// Test waste pile rendering with different card counts +FUNC render_waste_test + WORD ptr @ $f8 // Use $f8 to avoid conflict with $fa used by sub-functions + + // Test 0 cards + POINTER ptr -> pile_waste + POKE ptr[0] , 0 + render_waste_pile($0400, 0, ptr, 3) + + // Test 1 card + POKE ptr[0] , 1 + POKE ptr[1] , 0 // A♥ + render_waste_pile($0400, 10, ptr, 3) + + // Test 2 cards + POKE ptr[0] , 2 + POKE ptr[1] , 13 // A♦ + POKE ptr[2] , 26 // A♠ + render_waste_pile($0400, 20, ptr, 3) + + // Test 3 cards + POKE ptr[0] , 3 + POKE ptr[1] , 39 // A♣ + POKE ptr[2] , 1 // 2♥ + POKE ptr[3] , 14 // 2♦ + render_waste_pile($0400, 8*40, ptr, 3) + + // Test 5 cards (should show only top 3) + POKE ptr[0] , 5 + POKE ptr[1] , 10 // J♥ + POKE ptr[2] , 11 // Q♥ + POKE ptr[3] , 12 // K♥ + POKE ptr[4] , 25 // K♦ + POKE ptr[5] , 38 // K♠ + render_waste_pile($0400, 8*40+15, ptr, 3) +FEND + +// Test all pile renderers together - Klondike layout +FUNC render_all_piles_test + WORD ptr @ $f8 + + // Setup stock with some cards + POINTER ptr -> pile_stock + POKE ptr[0] , 10 + + // Setup waste with 3 cards + POINTER ptr -> pile_waste + POKE ptr[0] , 3 + POKE ptr[1] , 5 // 6♥ + POKE ptr[2] , 18 // 6♦ + POKE ptr[3] , 31 // 6♠ + + // Setup foundations + POINTER ptr -> pile_found0 + POKE ptr[0] , 0 // empty + + POINTER ptr -> pile_found1 + POKE ptr[0] , 1 + POKE ptr[1] , 13 // A♦ + + POINTER ptr -> pile_found2 + POKE ptr[0] , 2 + POKE ptr[1] , 26 // A♠ + POKE ptr[2] , 27 // 2♠ + + POINTER ptr -> pile_found3 + POKE ptr[0] , 3 + POKE ptr[1] , 39 // A♣ + POKE ptr[2] , 40 // 2♣ + POKE ptr[3] , 41 // 3♣ + + // Setup tableaus using midgame piles + setup_midgame_piles() + + // Row 0: Stock, Waste, gap, 4 Foundations + // Stock at col 0 + POINTER ptr -> pile_stock + render_stock_pile($0400, 0, ptr) + + // Waste at col 6 (fanned takes 9 cols) + POINTER ptr -> pile_waste + render_waste_pile($0400, 6, ptr, 3) + + // Foundations at cols 16, 21, 26, 31 + POINTER ptr -> pile_found0 + render_foundation_pile($0400, 16, ptr) + + POINTER ptr -> pile_found1 + render_foundation_pile($0400, 21, ptr) + + POINTER ptr -> pile_found2 + render_foundation_pile($0400, 26, ptr) + + POINTER ptr -> pile_found3 + render_foundation_pile($0400, 31, ptr) + + // Row 8: 7 Tableaus + POINTER ptr -> pile_tab0 + render_tableau_pile($0400, 8*40, ptr) + + POINTER ptr -> pile_tab1 + render_tableau_pile($0400, 8*40+5, ptr) + + POINTER ptr -> pile_tab2 + render_tableau_pile($0400, 8*40+10, ptr) + + POINTER ptr -> pile_tab3 + render_tableau_pile($0400, 8*40+15, ptr) + + POINTER ptr -> pile_tab4 + render_tableau_pile($0400, 8*40+20, ptr) + + POINTER ptr -> pile_tab5 + render_tableau_pile($0400, 8*40+25, ptr) + + POINTER ptr -> pile_tab6 + render_tableau_pile($0400, 8*40+30, ptr) +FEND + +// ============================================================================ +// Move Function Tests +// ============================================================================ + +// Clear all piles to known empty state +FUNC clear_all_piles + WORD ptr @ $f8 + + POINTER ptr -> pile_stock + POKE ptr[0] , 0 + + POINTER ptr -> pile_waste + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab0 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab1 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab2 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab3 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab4 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab5 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab6 + POKE ptr[0] , 0 + + POINTER ptr -> pile_found0 + POKE ptr[0] , 0 + + POINTER ptr -> pile_found1 + POKE ptr[0] , 0 + + POINTER ptr -> pile_found2 + POKE ptr[0] , 0 + + POINTER ptr -> pile_found3 + POKE ptr[0] , 0 +FEND + +// Helper: Render stock and waste side by side for move tests +FUNC render_stock_waste_test + WORD ptr @ $f8 + + POINTER ptr -> pile_stock + render_stock_pile($0400, 0, ptr) + + POINTER ptr -> pile_waste + render_waste_pile($0400, 6, ptr, 3) +FEND + +// Test move_stock_to_waste with draw-1 +FUNC test_stock_to_waste_draw1 + WORD ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup stock with 5 cards (face-down) + POINTER ptr -> pile_stock + POKE ptr[0] , 5 + POKE ptr[1] , 0|CARD_FACEDOWN // A♥ + POKE ptr[2] , 13|CARD_FACEDOWN // A♦ + POKE ptr[3] , 26|CARD_FACEDOWN // A♠ + POKE ptr[4] , 39|CARD_FACEDOWN // A♣ + POKE ptr[5] , 12|CARD_FACEDOWN // K♥ + + // Render BEFORE + render_stock_waste_test() + wait_key() + + // Draw 1 card three times + move_stock_to_waste(1, success) // K♥ to waste + move_stock_to_waste(1, success) // A♣ to waste + move_stock_to_waste(1, success) // A♠ to waste + + // Render AFTER: stock should have 2, waste should have 3 + fill_mem($0400, $0400+999, 0) + render_stock_waste_test() +FEND + +// Test move_stock_to_waste with draw-3 +FUNC test_stock_to_waste_draw3 + WORD ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup stock with 7 cards (face-down) + POINTER ptr -> pile_stock + POKE ptr[0] , 7 + POKE ptr[1] , 0|CARD_FACEDOWN // A♥ + POKE ptr[2] , 1|CARD_FACEDOWN // 2♥ + POKE ptr[3] , 2|CARD_FACEDOWN // 3♥ + POKE ptr[4] , 3|CARD_FACEDOWN // 4♥ + POKE ptr[5] , 4|CARD_FACEDOWN // 5♥ + POKE ptr[6] , 5|CARD_FACEDOWN // 6♥ + POKE ptr[7] , 6|CARD_FACEDOWN // 7♥ + + // Draw 3 cards + move_stock_to_waste(3, success) + + // Render result: stock should have 4, waste should have 3 + render_stock_waste_test() +FEND + +// Test move_reset_stock +FUNC test_reset_stock + WORD ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup waste with 4 cards (face-up) + POINTER ptr -> pile_waste + POKE ptr[0] , 4 + POKE ptr[1] , 0 // A♥ + POKE ptr[2] , 13 // A♦ + POKE ptr[3] , 26 // A♠ + POKE ptr[4] , 39 // A♣ + + // Reset stock + move_reset_stock(success) + + // Render result: stock should have 4 (face-down), waste empty + render_stock_waste_test() +FEND + +// Test move_waste_to_tab - valid move +FUNC test_waste_to_tab_valid + WORD ptr @ $f8 + WORD tab_ptr @ $f6 + BYTE success + + clear_all_piles() + + // Setup waste with 8♥ (red, rank 7) + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 7 // 8♥ + + // Setup tab0 with 9♠ (black, rank 8) - valid target for red 8 + POINTER tab_ptr -> pile_tab0 + POKE tab_ptr[0] , 1 + POKE tab_ptr[1] , 34 // 9♠ + + // Render BEFORE + render_tableau_pile($0400, 0, tab_ptr) + render_waste_pile($0400, 10, ptr, 3) + wait_key() + + // Move waste to tab + move_waste_to_tab(tab_ptr, success) + + // Render AFTER + fill_mem($0400, $0400+999, 0) + POINTER tab_ptr -> pile_tab0 + render_tableau_pile($0400, 0, tab_ptr) + POINTER ptr -> pile_waste + render_waste_pile($0400, 10, ptr, 3) +FEND + +// Test move_waste_to_tab - King to empty tableau +FUNC test_waste_to_tab_king_empty + WORD ptr @ $f8 + WORD tab_ptr @ $fc + BYTE success + + clear_all_piles() + + // Setup waste with K♠ (black, rank 12) + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 38 // K♠ + + // Tab0 is empty + POINTER tab_ptr -> pile_tab0 + + // Move King to empty tableau + move_waste_to_tab(tab_ptr, success) + + // Render tableau - should show K♠ + render_tableau_pile($0400, 0, tab_ptr) +FEND + +// Test move_waste_to_tab - invalid move (same color) +FUNC test_waste_to_tab_invalid + WORD ptr @ $f8 + WORD tab_ptr @ $fc + BYTE success + + clear_all_piles() + + // Setup waste with 8♥ (red) + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 7 // 8♥ + + // Setup tab0 with 9♦ (red) - invalid target (same color) + POINTER tab_ptr -> pile_tab0 + POKE tab_ptr[0] , 1 + POKE tab_ptr[1] , 21 // 9♦ + + // Try move - should fail + move_waste_to_tab(tab_ptr, success) + + // Render - waste should still have card, tableau unchanged + render_tableau_pile($0400, 0, tab_ptr) + POINTER ptr -> pile_waste + render_waste_pile($0400, 10, ptr, 3) +FEND + +// Test move_waste_to_found - build Ace to foundation +FUNC test_waste_to_found_ace + WORD ptr @ $f8 + WORD found_ptr @ $fc + BYTE success + + clear_all_piles() + + // Setup waste with A♥ + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 0 // A♥ + + // Foundation 0 is empty + POINTER found_ptr -> pile_found0 + + // Move Ace to foundation + move_waste_to_found(found_ptr, success) + + // Render foundation - should show A♥ + render_foundation_pile($0400, 0, found_ptr) +FEND + +// Test move_waste_to_found - build sequence +FUNC test_waste_to_found_sequence + WORD ptr @ $f8 + WORD found_ptr @ $fc + BYTE success + + clear_all_piles() + + // Setup foundation with A♠, 2♠ + POINTER found_ptr -> pile_found2 + POKE found_ptr[0] , 2 + POKE found_ptr[1] , 26 // A♠ + POKE found_ptr[2] , 27 // 2♠ + + // Setup waste with 3♠ + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 28 // 3♠ + + // Move 3♠ to foundation + move_waste_to_found(found_ptr, success) + + // Render foundation - should show 3♠ + render_foundation_pile($0400, 0, found_ptr) +FEND + +// Test move_tab_to_found +FUNC test_tab_to_found + WORD tab_ptr @ $f6 + WORD found_ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup tab0 with face-down card + A♦ + POINTER tab_ptr -> pile_tab0 + POKE tab_ptr[0] , 2 + POKE tab_ptr[1] , 5|CARD_FACEDOWN // 6♥ face-down + POKE tab_ptr[2] , 13 // A♦ face-up + + // Foundation 1 empty + POINTER found_ptr -> pile_found1 + + // Render BEFORE + render_foundation_pile($0400, 0, found_ptr) + render_tableau_pile($0400, 10, tab_ptr) + wait_key() + + // Move A♦ to foundation + move_tab_to_found(tab_ptr, found_ptr, success) + + // Render AFTER - foundation should have A♦, tableau should show flipped 6♥ + fill_mem($0400, $0400+999, 0) + POINTER found_ptr -> pile_found1 + POINTER tab_ptr -> pile_tab0 + render_foundation_pile($0400, 0, found_ptr) + render_tableau_pile($0400, 10, tab_ptr) +FEND + +// Test move_tab_to_tab - move single card +FUNC test_tab_to_tab_single + WORD src_ptr @ $f6 + WORD dst_ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup src with 8♦ (red) + POINTER src_ptr -> pile_tab0 + POKE src_ptr[0] , 1 + POKE src_ptr[1] , 20 // 8♦ + + // Setup dst with 9♠ (black) + POINTER dst_ptr -> pile_tab1 + POKE dst_ptr[0] , 1 + POKE dst_ptr[1] , 34 // 9♠ + + // Move 1 card from tab0 to tab1 + move_tab_to_tab(src_ptr, dst_ptr, 1, success) + + // Render both tableaus + render_tableau_pile($0400, 0, src_ptr) + render_tableau_pile($0400, 10, dst_ptr) +FEND + +// Test move_tab_to_tab - move stack of 3 cards +FUNC test_tab_to_tab_stack + WORD src_ptr @ $f6 + WORD dst_ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup src with: face-down, Q♠(black), J♦(red), 10♣(black) + POINTER src_ptr -> pile_tab0 + POKE src_ptr[0] , 4 + POKE src_ptr[1] , 0|CARD_FACEDOWN // hidden card + POKE src_ptr[2] , 37 // Q♠ (black) + POKE src_ptr[3] , 23 // J♦ (red) + POKE src_ptr[4] , 48 // 10♣ (black) + + // Setup dst with K♥ (red) - valid for Q♠ + POINTER dst_ptr -> pile_tab1 + POKE dst_ptr[0] , 1 + POKE dst_ptr[1] , 12 // K♥ + + // Render BEFORE state + render_tableau_pile($0400, 0, src_ptr) + render_tableau_pile($0400, 10, dst_ptr) + + // Wait for keypress + wait_key() + + // Move 3 cards (Q♠, J♦, 10♣) from tab0 to tab1 + move_tab_to_tab(src_ptr, dst_ptr, 3, success) + + // Clear screen before redraw + fill_mem($0400, $0400+999, 0) + + // Render AFTER state - src should show flipped card, dst should have 4 cards + render_tableau_pile($0400, 0, src_ptr) + render_tableau_pile($0400, 10, dst_ptr) +FEND + +// Test move_tab_to_tab - King to empty tableau +FUNC test_tab_to_tab_king_empty + WORD src_ptr @ $f6 + WORD dst_ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup src with K♣ (black) + POINTER src_ptr -> pile_tab0 + POKE src_ptr[0] , 1 + POKE src_ptr[1] , 51 // K♣ + + // dst is empty + POINTER dst_ptr -> pile_tab1 + + // Move King to empty tableau + move_tab_to_tab(src_ptr, dst_ptr, 1, success) + + // Render + render_tableau_pile($0400, 0, src_ptr) + render_tableau_pile($0400, 10, dst_ptr) +FEND + +// Test move_found_to_tab - undo move from foundation +FUNC test_found_to_tab + WORD found_ptr @ $f6 + WORD tab_ptr @ $f8 + BYTE success + + clear_all_piles() + + // Setup foundation with A♥, 2♥, 3♥ + POINTER found_ptr -> pile_found0 + POKE found_ptr[0] , 3 + POKE found_ptr[1] , 0 // A♥ + POKE found_ptr[2] , 1 // 2♥ + POKE found_ptr[3] , 2 // 3♥ + + // Setup tableau with 4♠ (black) - valid for 3♥ (red) + POINTER tab_ptr -> pile_tab0 + POKE tab_ptr[0] , 1 + POKE tab_ptr[1] , 29 // 4♠ + + // Move 3♥ from foundation to tableau + move_found_to_tab(found_ptr, tab_ptr, success) + + // Render + render_foundation_pile($0400, 0, found_ptr) + render_tableau_pile($0400, 10, tab_ptr) +FEND + +// Run all move tests sequentially (for visual inspection) +// Each test clears screen area and renders result +FUNC run_all_move_tests + BYTE dummy + + // Test 1: Stock to Waste draw-1 + test_stock_to_waste_draw1() + // Wait for keypress or delay here if needed + + // To run individual tests, call them directly: + // test_stock_to_waste_draw3() + // test_reset_stock() + // test_waste_to_tab_valid() + // test_waste_to_tab_king_empty() + // test_waste_to_tab_invalid() + // test_waste_to_found_ace() + // test_waste_to_found_sequence() + // test_tab_to_found() + // test_tab_to_tab_single() + // test_tab_to_tab_stack() + // test_tab_to_tab_king_empty() + // test_found_to_tab() +FEND + +LABEL __skip_lib_cardtests + +#IFEND diff --git a/charpad_cards/charpad_cards.ctm b/charpad_cards/charpad_cards.ctm new file mode 100644 index 0000000000000000000000000000000000000000..18671a6766c6daedb2d4ebc913e9148c0f3135b8 GIT binary patch literal 4593 zcmeHHy>HV%6n`XW=-Kmpe_6bvi3X13o` zppmpl&~hA?vOORzN(ga-AfN}1!}S(%jtJV1f?)8o)4@2xer$JH{Q0wQDChJNlbDWn zLQdTgIXiiEaz?m*KnR7^La|cY+=0Dvu~KbpZ`LZs!m0s{YNc4-dkqcLE49WBL?X^d zsat0zrAsup`0`ZPZD~c&!xW(lK7Hr@@hITCh_;;$jY`w+Hc`5LsPAH`+dDR|pqR(w zq3?Srf^Vy2S*B@PX!ksSI2LxBIZ9i$(Dj{_i_2)tkI-@KaPNe|6@2ti+`7;x^@kxY z-XGxiQ)-ozk0GeLy-ADyUR@V`UwkKgPG-%)y#3eDnYGSqG^PLg`{X=mj%Xfy%{)^T z?GOIa|Ct0)fS8A*@p|0ieGl&ob{QnO2bTShSAN<5>GyxUTG?y=`~ScFA^V#CTa4|G zxtJJSVx}2NA?6SB|9s{Q1Aj4z^jLZ<20bzX5et+$f=FkZlF5J&E1yE#6Opw~%Wwml znXfK<=j#VdM@ir8fx*vs2_CFSl$Yo*Ldp4>a20bH-!-_7HDD;rX6QM*fR|`X&UX`T z!EMfR2g-0)<9h>d;oV$(O}H1G?*Tl7M;hNJAnVa`xeC27GFAvf)#cla_B?&c+5ui`Ork zDN+4U^P+5NkeZS-EGk#sD$14yc_40;G%PCD197V;TN)JJooq_du&7)$FUqz=Nh|Oh DZ~Z`0 literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards2.ctm b/charpad_cards/charpad_cards2.ctm new file mode 100644 index 0000000000000000000000000000000000000000..c211ea90efac5b8c2f003a12dafa58b85dd0860a GIT binary patch literal 4593 zcmeHHy>HV%6n}MFxk*)d38GAqDn3GXK03998L6t0zb8bk)Ugu_Ab~OhQh^SYI3x#o z$d1I44i0q4h(m{1a+2fS*>@KoB5`6bh`ft@zxVa?`MsBWTzQf+49Mf!g}h-cubJ)l z6lf$Z60~gFp==LGixNVdAPDH8ZF9Xv>|=uV;~?n&bX|-i?8kP8#a}%8hH^o#Fp24C zC*;f-lJnD7r{{$0hlEgA%@@kmtv%Q;70Q*y?pC#2$gdjEsFVw({nyYyyU2-cWfb#d zH1K^7MeywuEz2}b3+6pOa~GFl+zyb7rmc8cpcG{ysSmnj@MAUo%ft zMf-!l^nWTr6d>jyX}lgcf8WFVf?Wnl?tw-B5G$WT+!K+t&q{Cu znwhT-eCO*2Oh!rH?1910cm*D;NR*T4C_>5kns5zs7~gf+#2PRZW;660UcgJVCFi>d zx8OEsxdSD*tMR>oxA1Nzz9!s@&i4Qw!nVfu2?%_ik*|q06koX_QBIYG%PAt&5N=vP|^zg E29v))Q~&?~ literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards3.ctm b/charpad_cards/charpad_cards3.ctm new file mode 100644 index 0000000000000000000000000000000000000000..f04da5032049113c7a7f50fb854e52e5a2875f12 GIT binary patch literal 4593 zcmeHHy>HV%6n`|t-0#h*U=hH^^JF^TDD zC*;^2l9QuXM<;~qdxTI}%~^JNV;gpgmR+iCZIo>*w`xGGWLw3Z*HA;A%I;{Fu?i z;G&skC^^j^zB@y zsNFnRmvMo{E3j7A5sfYmp literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards4.ctm b/charpad_cards/charpad_cards4.ctm new file mode 100644 index 0000000000000000000000000000000000000000..bc7262a5cf6ccbd7496555989c6fc06c438736f5 GIT binary patch literal 4593 zcmeHH&uI4yGyC4_Jjy-3q-l`Au?`7Mk6nz{ zKTx2;YQ(LXrbXH8k{Tt1Sgz~Rl4)|kM$8?8{Eq9kPi-6b5%$Msi|s#t`V;M#o?#I4 zktgKP>XD<$e&vX8zeEUy;0a~XSusz+Ek>b)u^I1T2Oq5 zr(4av_yx519%^+QG;vMH8oC~j$8~mw)9Lmlk4NabkuBg>WKoq{tYjES;-M^E+3bUmEB*T(OsR5#QbLr}DuLB{iHr$JWUQ`UX5m9y0ZN4IrX0?AU4Q;2)loH z-|Thw-V62^gn0)R^$%u#QUCGpe=u9KdyD`7(YdRC9L@hfZ2eO#U>dgoHNi;)*1dTD z7uSTMrU|6h#LBtx-hzrB^pOq3Nyk?oB`!C4Hxc)#AEw|sROdb#@SBf6Fdl_H*#V8; z@eCYT^3kr3wgn2WSA{DW!}PAgHOv7+FX0vJbMy?J!wW>=^=`mTxW!{{LkjM2bO7(6 z!qFRe3-6}VtHNDzzx$AZ2VCVVd;^oC&p_bIlzLUnA^S@9O-nx7_0jeedM!BN<1dUy zVe>biE>WjBu)@ESeebG|wt$WBRUOpxu$MpCIa}~!4tlC{=A)-NC#v3x-y5qO&7Qyc zbcw2m>KDaDL8?olsHj-=swgfBazMT+QB+i{1M*c-TomNJDy&PQsHj-gFN#~Buo?IV D>m9ZI literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards5.ctm b/charpad_cards/charpad_cards5.ctm new file mode 100644 index 0000000000000000000000000000000000000000..f3b1d113d613b3212256666ac39da914be9aee39 GIT binary patch literal 4593 zcmeHHJ8u&~5FVRE2BXLnMuN&x#KX7;LX`w+q$o;ieu{|_DN2=!3PF%dUI9|1IEg!S zg?l6v$w9h63YWPMB%7S=V`gsW*3Ub~p~JH$@60zl-|o(g_tDbhOUWdpam+zFnMqts zRX+IOD=8aZ*)(mRX3r@5hGE#A=lQFq$#&T=*A2wiJ+F4;IJnNBk7k?J??3y7vhSZ@ z5ZQrBVD-UsZySkaWfRfrouDXk&jtR55OeU2|WoQq#-e?JqTVyi1*&;4Q6j@mk ztGOJ4xErO5rHM`y$1Dc3qvXjJ4X27?uds`M>Wx6p-mBsLeLs^6eXS4-y2YWpQ_hZr$L0$4{{$_$2RvfXUuEExi7>Xz3#z# zL65;O@4&eJlJQ_3*FXCG@649A#{Ykv3(`jY|5rYC^89~R z;5}@!_8Q*6+YT&_&w{(^eD`4n9&jwcXLTw7Bh&&cp3lM@(v4{4M5t|q+FGa)7R`4I zhdh44uv#=Ys{(QP$w6+LeN9NQ0qP6qz>3-4L`S>i% zq4diXYm&bveCOfEbcxPnbeHTYzTZRhokE3210#A*b9O%6C01L<$obp7f<@2zRUtlk zVI-y(5znC61$kA7PwT<@UStrD;4a9kLVQ{mzHUU~2F<~%LR~_9v?|bruNxV}Be?Dt PtL=s9=M`)`>kRw_n6*T! literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards6 - Chars.asm b/charpad_cards/charpad_cards6 - Chars.asm new file mode 100644 index 0000000..9060c54 --- /dev/null +++ b/charpad_cards/charpad_cards6 - Chars.asm @@ -0,0 +1,163 @@ +; CharSet Data... +; 256 images, 8 bytes per image, total size is 2048 ($800) bytes. + +* = addr_charset_data +charset_data + +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$c7,$bb,$fb,$c7,$bf,$bf,$c3,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$bb,$bb,$bb,$c3,$fb,$fb,$fb,$ff +.byte $83,$bf,$bf,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$bf,$87,$bb,$bb,$c7,$ff +.byte $87,$fb,$fb,$f7,$ef,$df,$df,$ff,$c7,$bb,$bb,$c7,$bb,$bb,$c7,$ff +.byte $c7,$bb,$bb,$c3,$fb,$bb,$c7,$ff,$db,$95,$d5,$d5,$d5,$d5,$db,$ff +.byte $f3,$fb,$fb,$fb,$fb,$bb,$c7,$ff,$c7,$bb,$bb,$bb,$bb,$b3,$c3,$fd +.byte $bb,$b7,$af,$9f,$af,$b7,$bb,$ff,$c7,$bb,$bb,$bb,$83,$bb,$bb,$ff +.byte $ef,$c7,$83,$83,$83,$ef,$c7,$ff,$e7,$c3,$a5,$00,$00,$a5,$e7,$c3 +.byte $ff,$93,$01,$01,$83,$c7,$ef,$ff,$ef,$c7,$83,$01,$83,$c7,$ef,$ff +.byte $a2,$55,$2a,$14,$2a,$55,$a2,$41,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $fb,$fb,$fb,$fb,$fb,$fa,$f9,$fb,$ff,$ff,$c3,$81,$00,$00,$00,$00 +.byte $df,$df,$df,$df,$df,$5f,$9f,$df,$fb,$fa,$f9,$fa,$f9,$fa,$f9,$fb +.byte $ff,$ff,$ff,$f7,$e3,$c1,$80,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$7f +.byte $ff,$ff,$ff,$00,$ff,$00,$ff,$ff,$ff,$ff,$ff,$ff,$e7,$c3,$81,$00 +.byte $ff,$00,$ff,$00,$ff,$00,$ff,$ff,$ff,$ff,$ff,$ff,$fe,$fc,$f8,$f0 +.byte $ff,$ff,$ff,$ff,$38,$10,$00,$00,$ff,$ff,$ff,$ff,$ff,$7f,$3f,$1f +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$ff,$ff,$ff,$ff,$ff,$00,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff +.byte $f1,$e0,$c0,$c0,$c0,$c0,$e0,$f1,$00,$81,$42,$00,$00,$00,$c3,$e7 +.byte $8f,$07,$03,$03,$03,$03,$07,$8f,$fe,$fc,$f8,$f8,$f0,$f0,$f0,$f0 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$3f,$1f,$0f,$0f,$07,$07,$07,$0f +.byte $fe,$fc,$f8,$f0,$f0,$f8,$fc,$fe,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $7f,$3f,$1f,$0f,$0f,$1f,$3f,$7f,$f0,$f0,$f0,$f8,$fc,$fe,$ff,$ff +.byte $00,$00,$00,$00,$00,$00,$01,$83,$1f,$1f,$1f,$3f,$7f,$ff,$ff,$ff +.byte $fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fd,$fe,$ff,$ff,$ff,$ff,$ff +.byte $df,$bf,$7f,$ff,$ff,$ff,$ff,$ff,$df,$df,$df,$df,$df,$df,$df,$df +.byte $df,$5f,$9f,$5f,$9f,$5f,$9f,$df,$e7,$e7,$e7,$e7,$c3,$81,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$f8,$fc,$ff,$ff,$ff,$ff,$ff,$ff +.byte $14,$36,$f7,$f7,$e3,$c1,$ff,$ff,$0f,$1f,$ff,$ff,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$fe,$fd,$fa,$f9,$fb,$00,$81,$c3,$e7,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$7f,$bf,$5f,$9f,$df,$ff,$fe,$fd,$fa,$f9,$fa,$f9,$fb +.byte $c7,$ef,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$7f,$bf,$5f,$9f,$5f,$9f,$df +.byte $00,$00,$00,$00,$00,$00,$00,$00,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$bb,$c7,$fb,$bb,$c7,$ff +.byte $db,$95,$d5,$d5,$d5,$d5,$db,$ff,$f3,$fb,$fb,$fb,$fb,$bb,$c7,$ff +.byte $c7,$bb,$bb,$bb,$bb,$b3,$c3,$fd,$bb,$b7,$af,$9f,$af,$b7,$bb,$ff +.byte $00,$05,$0a,$14,$2a,$55,$22,$41,$00,$50,$28,$14,$2a,$54,$a2,$40 +.byte $22,$55,$2a,$14,$0a,$05,$02,$00,$a2,$54,$2a,$14,$28,$50,$a0,$00 +.byte $a2,$55,$2a,$14,$2a,$55,$a2,$41,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$fe,$fd,$fa,$f9,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$fe,$fd,$fb,$f7,$f7,$f7,$fb,$fb +.byte $38,$d7,$ef,$ff,$ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$df,$df,$bf,$bf +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$ff,$ff,$ff,$ff,$ff,$00,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $f1,$e0,$c0,$c0,$c0,$c0,$e0,$f1,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$fe,$fc,$f8,$f8,$f0,$f0,$f0,$f8 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fd,$fe,$ff,$ff,$ff,$ff,$ff +.byte $df,$bf,$7f,$ff,$ff,$ff,$ff,$ff,$df,$df,$df,$df,$df,$df,$df,$df +.byte $ff,$7f,$bf,$5f,$9f,$5f,$9f,$df,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$f8,$fc,$ff,$ff,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$ff,$fe,$fd,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$7f,$bf,$5f,$9f,$df +.byte $00,$00,$00,$00,$00,$00,$00,$00,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$bb,$c7,$fb,$bb,$c7,$ff +.byte $db,$95,$d5,$d5,$d5,$d5,$db,$ff,$f3,$fb,$fb,$fb,$fb,$bb,$c7,$ff +.byte $c7,$bb,$bb,$bb,$bb,$b3,$c3,$fd,$bb,$b7,$af,$9f,$af,$b7,$bb,$ff +.byte $00,$05,$0a,$14,$2a,$55,$22,$41,$00,$50,$28,$14,$2a,$54,$a2,$40 +.byte $22,$55,$2a,$14,$0a,$05,$02,$00,$a2,$54,$2a,$14,$28,$50,$a0,$00 +.byte $a2,$55,$2a,$14,$2a,$55,$a2,$41,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$fe,$fd,$fa,$f9,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$fe,$fd,$fb,$f7,$f7,$f7,$fb,$fb +.byte $38,$d7,$ef,$ff,$ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$df,$df,$bf,$bf +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$ff,$ff,$ff,$ff,$ff,$00,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $f1,$e0,$c0,$c0,$c0,$c0,$e0,$f1,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$fe,$fc,$f8,$f8,$f0,$f0,$f0,$f8 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fd,$fe,$ff,$ff,$ff,$ff,$ff +.byte $df,$bf,$7f,$ff,$ff,$ff,$ff,$ff,$df,$df,$df,$df,$df,$df,$df,$df +.byte $ff,$7f,$bf,$5f,$9f,$5f,$9f,$df,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$f8,$fc,$ff,$ff,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$ff,$fe,$fd,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$7f,$bf,$5f,$9f,$df +.byte $00,$00,$00,$00,$00,$00,$00,$00,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff +.byte $c7,$bb,$fb,$c7,$fb,$bb,$c7,$ff,$c7,$bb,$bb,$c7,$fb,$bb,$c7,$ff +.byte $db,$95,$d5,$d5,$d5,$d5,$db,$ff,$f3,$fb,$fb,$fb,$fb,$bb,$c7,$ff +.byte $c7,$bb,$bb,$bb,$bb,$b3,$c3,$fd,$bb,$b7,$af,$9f,$af,$b7,$bb,$ff +.byte $00,$05,$0a,$14,$2a,$55,$22,$41,$00,$50,$28,$14,$2a,$54,$a2,$40 +.byte $22,$55,$2a,$14,$0a,$05,$02,$00,$a2,$54,$2a,$14,$28,$50,$a0,$00 +.byte $a2,$55,$2a,$14,$2a,$55,$a2,$41,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$fe,$fd,$fa,$f9,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$00,$ff,$ff,$fe,$fd,$fb,$f7,$f7,$f7,$fb,$fb +.byte $38,$d7,$ef,$ff,$ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$df,$df,$bf,$bf +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$ff,$ff,$ff,$ff,$ff,$00,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $f1,$e0,$c0,$c0,$c0,$c0,$e0,$f1,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$fe,$fc,$f8,$f8,$f0,$f0,$f0,$f8 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fb,$fd,$fe,$ff,$ff,$ff,$ff,$ff +.byte $df,$bf,$7f,$ff,$ff,$ff,$ff,$ff,$df,$df,$df,$df,$df,$df,$df,$df +.byte $ff,$7f,$bf,$5f,$9f,$5f,$9f,$df,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$f8,$fc,$ff,$ff,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$fe,$fd,$fb,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $ff,$ff,$ff,$ff,$ff,$7f,$bf,$df,$ff,$ff,$ff,$fe,$fd,$fa,$f9,$fb +.byte $00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$7f,$bf,$5f,$9f,$df + + + +; CharSet Attribute (L1) Data... +; 256 attributes, 1 attribute per image, 8 bits per attribute, total size is 256 ($100) bytes. +; nb. Upper nybbles = material, lower nybbles = colour (colour matrix low). + +* = addr_charset_attrib_L1_data +charset_attrib_L1_data + +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 +.byte $02,$02,$02,$02,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 +.byte $0a,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$02,$02 +.byte $02,$02,$02,$02,$01,$02,$01,$01,$02,$02,$01,$02,$01,$01,$01,$01 +.byte $02,$02,$02,$02,$02,$02,$01,$02,$02,$01,$02,$02,$02,$02,$02,$02 +.byte $02,$02,$02,$02,$01,$02,$02,$02,$02,$01,$01,$02,$01,$01,$02,$01 +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$02,$02 +.byte $01,$01,$02,$02,$01,$02,$01,$01,$02,$02,$01,$01,$01,$01,$01,$01 +.byte $02,$02,$02,$02,$02,$02,$01,$02,$02,$01,$01,$01,$01,$01,$01,$01 +.byte $02,$02,$02,$02,$01,$02,$02,$02,$02,$01,$01,$01,$01,$01,$01,$01 +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$02,$02 +.byte $02,$02,$02,$02,$01,$02,$01,$01,$02,$02,$01,$02,$01,$01,$01,$01 +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 +.byte $01,$01,$01,$01,$01,$02,$02,$02,$02,$01,$01,$02,$01,$01,$02,$01 + + + diff --git a/charpad_cards/charpad_cards6 - Chars.bin b/charpad_cards/charpad_cards6 - Chars.bin new file mode 100644 index 0000000000000000000000000000000000000000..e74a6ea0ab8823d879396c5ce6ed594462bb0bb6 GIT binary patch literal 2048 zcmeHHu};G<5OqN)5=$7mM`A+#0MxC(7cjugr-Uj4BLiYCs>IB|z~I3k5BUPbzz`&c zj4fhFz-`UDiw{sGRD{IVmnzqLw$HwM7kl3z*Dg24da_REUTbZgb3QYM{al+94gSQr zYE%@Mr|C!5()`Esd*IQ(ViEb^wZ67feRDOqy3y=sTKiF+W!WhArM*<@vb6rJ)ynb_ z@oa~Oop$GNxT^vvoO^$B-rGJ=1>tB=fTIVzub%IYRY)U+Rd^UBmarH6*`yxRKzBnm zOxjC$;%rKBXk~-c6ym%*-2zW9s=tTbZFw540?{dA)ngzWQ}vQKj-n{0J;wE9COo!? z#WERRFSc^@IFs&cOT5DI`8MPZD@mvwBv=`V4_e)fY9!G?aQ z=jes*BK`fGPCo^devo`*-d8?cvF~-{zL0r;*S)zf=rZ`tJ+RbYD&E+o{>$&5d*Fxr I|G&Tg4?;zjBme*a literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards6.ctm b/charpad_cards/charpad_cards6.ctm new file mode 100644 index 0000000000000000000000000000000000000000..48eadf30017e00b59d9cd4c0c61cc556c2d910f0 GIT binary patch literal 4593 zcmeHJ%}-N75T8~^wFaMun&_zs@dJBT@dW7Egb+CMr&LlB!U>4+WE9iHi(jZ5IK0I7 z%scdMRFYy+ltYg^#H5u+yUxtM>9$XJZ9q>l+jM7syYt(bo$01`C-0p}CLxVu6w*m6 zaXM9gqd;XD_oa+%yzaSTEs zx?t~Dpp-X5P=#cAxq=uC#8qwU!+pskk2XkFRdiu1Ty5^xeM)!uJZL08k|kR;8^lAN zTJlasjGavvWpf8+?%?TObs=>MCAENDbsr@R6GpO@l}e>7I>WE;HwDKdEGs*b$E}DW zD@&q~%_4}WaenmGz(tgblh-pR%DGN2W4=`sZ-$rntG=)G%*`6+uauQ-tu-5XFEHa( zLnQB@Kjz5yg;%YGP{bupsr%7qoBO0&A3%Q^>pM6G<3pL;WN~y5>z(dTLUA&e85(~I z(fV_+SRaX#ZK_Y0kKi)%I@U32BSo!N3xeVG@B)ydQgIyN7eAic>MuMpxB5YVa1Q&S z9)Y`>-RiF*bvlet`a$xMeI_5ivG#l9zL0%Kue*LP=rQQ!9XP4K+_;`k>VNY4=N>rZ z{r}g$|MJ_;e*FKe^1DPtk|+@<)=YP-k~jJzgZ`#jBM7#HRb1jv#9urm3hhL6T0*v# z-QnIkO;|ctTNab(^UJG4a0%SMJ~rVSkMGc{79WETNw6@=UGSj~&V|}Mw(KHfv#=Dw zy79SiA@l&ah<8BDkKqX{vj*@8xp(1Hn8z2~8#jEo3|HVP`(A?~xX#)ucnvG8J%<F|XcO=; z!e>0IxZfx&}89`Xf&ZHud9Af|&iT|B_A_nHH25>; z%2A$Uo~9pJOY@&DAAl$ShDGFq*ZRgz_3f}fyw&WdTKiF!rs*j2g}qkly0HGD(MYoq z@oWdj?N<7zS!9$Q3F z+)XeQETS?RX&i&ZZk!*zrWgcrlhl?I^?};hgudJR>TsvkJ{s(8L+{OvH8nVDw{{M% z)S~`;Sm-0?WS{g2`3NtT$D!k*j}T>9I@fs&1wf2EH%8*iI<|BAOB}IZ{UAiJp&zOR zda1iif4`v9PXVPLBp;dgoex)RzK+}%GVkxY*Y^co2EVxnR{BfDYrE2a_5E`X{BZyO H_xJw^484{d literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards7 - Map.png b/charpad_cards/charpad_cards7 - Map.png new file mode 100644 index 0000000000000000000000000000000000000000..5edc995432c7ecd7019d403945a13bab7036cee1 GIT binary patch literal 3070 zcmbtWc{r478`o=?F(YFiWSNj9h7m`OB}SIyG)2i+8_JquWHOea*S;h<6-iP@q^w2F z9K;(c#IaQ)>qJVn=CfqU@D1ncy1wiD@qK@Me>~4~Z_n?3p5ODn_jRWpBiimn$|3pq z_;%VIvU23(+hM_zTM+D-R$N>! zSQ%zzrK%8SX=MdjDe8$k+Fz|{LEJoelviLJ zrtYZ*RlbxqIKbZ`0PXBU_NZ{#+QZa&`lL&Raowrr^qIj)$Ex1zHO-hO;gEE6sl2&1u%bZCFA+tQOuzJ=!3bX~@BvZvf8Gptdo=psz^fp6Nd!{BtrF*!NUovYTDbgYsHDs9++mRMe zO2mZqPN(a{deThF#aX2di=bAD0n$Uv*+*2%DWg3wL-31kyGgTWVfQRWHn9ToS0wpJ zx{8f8L<|Lxcf%>8dUFO?{Pv=OioRItoqBNicLmQnnmB-c3hp-TGzdjgp-l!z)c&Jc_mw%qmYpbc5`Ry^see`jDOK|37m8!kUheWoJp zy{D1h+BUUCV8H!O^s>2KB;Da5G}Y&voAxzZwC_+_%~6$eF(p)wBk>y-Eqe;vB;k*< zlb5cS(Vb8A`ae@!9hMH&p7eC?6Ln&Tg}PBMzAlmpkN5BO`BZcrA)f^SN=Y%-W)t!a z^@ZI=6CibLmE}-qKar$>0nxc%QsuLMfTg+EiZ|vWcUmd0RF8hD8bS|QE;4l(bEaA2 zfMTHSP%l$|=ba<`fCNM1#hE_mzV2G_H!BGVs-`D=xaAZ46J12Zk`yiDU0&Ss_T1l+ ze+}liT7*)5=LdrSBvrf840|`GRW~mcJ&<@5G}&mh`Tc780<}&jp#GRyyEst{EJjGeAFcmKSxEq2*huwAPTCyA5Lr{ zXqPj4kD&Gf_T!#2)?CXsF^4$&car(91mBn5#ue`i%aDWmpwLg zBpBiDm7T(#OZkGi5MIl3Q0Sf*EJ=OQIzA5Kz`%dbEoYVX8Sp&+b!~EN7aJcKe~Ols z^U`k)p?Gixl7%y>@id770Rxhp}q!H>Q#I(NC*0e{?Br)a$73#kq0}#VDU3xU+J~!>xt&*-S`BRo6z*B9)RCKNIUY$%(-V16bU-{ zQl(X7Sy?(1ybrmP;BXUuo)XZ%+D}K-Z=AD^y(ujd$>S~$hQV=kV>VxJZp`Xtvep;Y zm&R{M%XC<<^I(2-_REn@Ejqc%%7q*-O0s9iP`YgH#cq7Bs|lp$u!3XO^z@KBPx>S4 z%_9~KFGzuPugvn`e*q7ila)q&L5T&39Cr(%ei3A#hD>yD_{+k;sOnv`uu43%Uq~h~ zDT}a`WdYh1r5_cQvseRl$E$&?x(m8iwf!w_>Qz4Ou4I|qNO0K=C1M+28IC=H2$Ih^ zfWLgX$#pU@!_cpD+}L|b;@6Hb-VId>d1YY=2+w$9x}B({NMJhwa}o49V*r2L+jHwo zJfW79P(@i)@mNouztJlYn<)8q=!wdn6bH>F#R~)=~|>m?SM@h2@aCfp`Q= z5#>r21) zQFVQOCb;kNwsQ5Hugn+oMgNJOI36?tMpWS@E0&w83Zwu0UOmEvho|U-C0|-y)*_RU)Uk7sx!Oh^wEP_#q!uA_!933tu7t^}#RXgD6DV zaYdY>Fs_J@?WFy+I?6wS))IfRqoeQiV`ketFjzCTi8b)=Gz&&S%>wDqf1-9cYerPOq zjmG4hET5ZS>pW_%W^<1_sT{JJlAZwxPV_XDdNLLu7xv;5XM9jQ#9Ognh^ysR6V0+9 z=gs?nbkcQY-_<~Adqj1+k*l{2YY?GFl6Jw#r()sT-YTDn8qOn3VK>1+Vukal-92Ga zy^6ABW6hd!UX65&jO;PPRqZqePPsefR%_(Lp8dRLS{%f1cKVDzh6X*_3lp7r4HLY` ziQQDXw>q;(r^@*!FoB#u@2{5vrADx^ylPPUXkT7PEnt$p#dZ$>7XZap_UxduNmxA4 zs7jVJmM#XC=lK8j>!IZ(xr8#?q=+o;$4X@NPUo!$u|~;DPDHS84?eN?-7K9CI(=gs z3oe2j6xw=6wlV2+JEkB|mSMGt-`YHuwDgW}0sS zs`ggpxFmPllb5!sBKjiI5+RIj0uwP*XzEIPmtLI}7Y8BMf4|fxbQ;_i7QoY9s+_e! zG1$qU%ph@i9LJvbFFv*)!(!|pjWF$-@Mo>>pYz>Dn`$9~vdW`&vO*EE=%N*ykec?AKyNq zBY&evd~l+7*U^5r-t8&+hKOr#jHx|WZmZO`a{M}(7+(vIb~B$%XYHvjiUR)j)Kf#l*goK2kYI+w$gIP6z08 zQN=T+yK0r(LCFfenlm*1rpmK4O(29o@6CJj+nG1>-rcc#XHzLi;~0W;D${zV zqx9N<38m-+Mc4HV>A)!($8o$M2u$8}d0uqfX$S4oK~UW)mvNs%f22Lyzy9zO%DUM> zCz_+(aaO##v%0*nyz1~g?>J_wn9t|8il*W%0W4KK^Pr_AU)&-)>&5Z@p8oO0VF-oj zg595iG2SGZ$|MsDWwg;iyQyxzzb|>@a1N4H1zmU+uI}&Ed`frVJXlL^FiUf@Und^& zw32hnGTGiJplocz7+4Dr06EHK*A;&8V;!l#@Q7~ogBU^` z_CqaZK5}|g{Y|V+hY?CYNItSp^WhVVo+I~#>^pwl?R!Cw!SB2Sr}dW;xB9gHr}v+G z;E?zKf8YPon}hv$@@A#CEip-<#7s@6=>x0ejsD1Be@R;l8XLk8m-sK{FP;*Gbs{<| zAzNd9z`gM`VL7*B0-5_9v>dIA5558xu60inn61QtTuBed_p$1so2xGEmW@m+>1aFu@<*#l;X1<$$S~ypJJACh}sy< zA(~+{k7$aKMoK(gXiMVThD{#dp*hk>bzc%+^Da>jAFFI5Ra+ABp_AjLs&+TfDA0&z zq*2x33lEc+uU0>bM&Uz@Ph(jN`Fr3Srq9_mR(V;T~dg2jO@@=I#U|dS8|hO zS5)h(LVWVV$PTq=jmTzivh0d#ALLabKE-yATB3Y%lcPL>eUMj$_!JADSfYIL<_Jer iQ`gxpAwI=+k6NPV*X$;1mlPr|jC@g7laTB>x%wB(*l>#g literal 0 HcmV?d00001 diff --git a/charpad_cards/charpad_cards7_ecm.bin b/charpad_cards/charpad_cards7_ecm.bin new file mode 100644 index 0000000000000000000000000000000000000000..30329ce02a794845ff0b0cb5bf2407b96260eb4b GIT binary patch literal 512 zcmX|-uWrLI5XK!11ZJQ-ybmyqMcX?R_ffLQps-*Xu2iYGV4&cDI~n*1h6++?ATX^4 zlFdnO-#O{NM84eb{^iU(%8_@*SQhKaF%ensJ!@lv@5rnOeC2&pmnHH@Ua?m4Pj8>l ziN8@KJ~+|4?dh=J><<)tO~kd+TGx&%yHjdcS$>To?P}q{ZWr^}e6f8}lcb&h{qfAU z5LAkmn-WYOaBjXnUevTk4y*74RVWGGd5?FE{CQ)OwA#BrR+j7!@MH;xfWl4dzlLD4lR(rF5aX<{~&$2%&Ja-S|CwXs|zXlK#hr9`~XOTTLW4sAr+zxav_t!T$i=DsdJ7 literal 0 HcmV?d00001 diff --git a/charpad_cards/visualize_chars.sh b/charpad_cards/visualize_chars.sh new file mode 100644 index 0000000..d518eeb --- /dev/null +++ b/charpad_cards/visualize_chars.sh @@ -0,0 +1 @@ +xxd -b -c1 charpad_cards7.bin | cut -d' ' -f2 | tr '01' '.#' diff --git a/charpad_color_sprites/charpad_color_sprites - Project.asm b/charpad_color_sprites/charpad_color_sprites - Project.asm new file mode 100644 index 0000000..b23c425 --- /dev/null +++ b/charpad_color_sprites/charpad_color_sprites - Project.asm @@ -0,0 +1,113 @@ + +; Generated by SpritePad C64 - Subchrist Software, 2003-2025. +; Assemble with 64TASS or similar. + + +; Colour values... + +colr_vic_bg0 = 1 +colr_vic_sprite_mc1 = 0 +colr_vic_sprite_mc2 = 1 + + +; Quantities and dimensions... + +sprite_count = 10 + + +; Data block addresses (dummy values) and sizes... + +addr_spriteset_data = $1000 +size_spriteset_data = $280 ; (640 bytes) + +addr_spriteset_attrib_data = $1000 +size_spriteset_attrib_data = $a ; (10 bytes) + + + + + +; * INSERT EXAMPLE PROGRAM HERE! * (or just include this file in your project). + + + + +; SpriteSet Data... +; 10 images, 64 bytes per image, total size is 640 ($280) bytes. + +* = addr_spriteset_data +spriteset_data + +sprite_image_0 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01 +.byte $c7,$00,$03,$ef,$80,$07,$ff,$c0,$07,$ff,$c0,$07,$ff,$c0,$03,$ff +.byte $80,$01,$ff,$00,$00,$fe,$00,$00,$7c,$00,$00,$38,$00,$00,$10,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$12 + +sprite_image_1 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$41,$00,$00,$20,$80,$00,$00,$80,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0a + +sprite_image_2 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10,$00,$00 +.byte $38,$00,$00,$7c,$00,$00,$fe,$00,$01,$ff,$00,$03,$ff,$80,$03,$ff +.byte $80,$01,$ff,$00,$00,$fe,$00,$00,$7c,$00,$00,$38,$00,$00,$10,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$12 + +sprite_image_3 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$08,$00,$00,$04,$00,$00,$02,$00,$00,$01,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0a + +sprite_image_4 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$38,$00,$00 +.byte $7c,$00,$00,$7c,$00,$00,$7c,$00,$03,$bb,$80,$07,$d7,$c0,$07,$ff +.byte $c0,$07,$d7,$c0,$03,$93,$80,$00,$10,$00,$00,$38,$00,$00,$7c,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10 + +sprite_image_5 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$38,$00,$00 +.byte $0c,$00,$00,$04,$00,$00,$04,$00,$01,$83,$80,$00,$c0,$c0,$00,$00 +.byte $40,$00,$00,$40,$00,$00,$00,$00,$00,$00,$00,$08,$00,$00,$04,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0b + +sprite_image_6 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10,$00,$00 +.byte $38,$00,$00,$7c,$00,$00,$fe,$00,$01,$ff,$00,$03,$ff,$80,$03,$ff +.byte $80,$03,$ff,$80,$01,$d7,$00,$00,$10,$00,$00,$38,$00,$00,$7c,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10 + +sprite_image_7 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10,$00,$00 +.byte $18,$00,$00,$0c,$00,$00,$06,$00,$00,$03,$00,$00,$01,$80,$00,$00 +.byte $80,$00,$00,$80,$00,$00,$00,$00,$00,$00,$00,$08,$00,$00,$04,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0b + +sprite_image_8 +.byte $77,$47,$77,$44,$44,$42,$76,$46,$42,$14,$44,$42,$77,$77,$72,$00 +.byte $00,$00,$00,$00,$00,$77,$76,$00,$45,$55,$00,$47,$75,$00,$45,$65 +.byte $00,$75,$56,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + +sprite_image_9 +.byte $76,$00,$00,$45,$00,$00,$65,$00,$00,$45,$00,$00,$76,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 +.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + + + +; SpriteSet Attribute Data... +; 10 attributes, 1 per image, 8 bits each, total size is 10 ($a) bytes. +; nb. Upper nybbles = MYXV, lower nybbles = colour (0-15). + +* = addr_spriteset_attrib_data +spriteset_attrib_data + +.byte $12,$0a,$12,$0a,$10,$0b,$10,$0b,$00,$00 + + + diff --git a/charpad_color_sprites/charpad_color_sprites.spd b/charpad_color_sprites/charpad_color_sprites.spd new file mode 100644 index 0000000000000000000000000000000000000000..b6cf29407445910008ef4215c22f844df82ef425 GIT binary patch literal 660 zcmbV}!EVAZ3_xE<2h+C0gm&P_XY4qtc3dYR_yc}mQkx5hNc#jnhf8mKLO)}F(9Sqr zMF0tbWyNt~=h@EUD|_IHAO<7aIlVD^?5`iSRZ!b{(?SCqc!$?Mu-lAJ55%9r)Pv}C z`L8BK%JTvdRD)mzei#1{5*Tpa-FZDRKfr~*!S4L1)+yAL-TFi4;WmCF9*9J|C_KUZ zc&Z9MR5dBo73V)kZVx%?|1g4DCx4wU`4cT+2v+*{`U|qET;#jC%d)imOy9E9x$h9^ YO7bQ1f_zP}>e4Tp5^`AUD*Kz|4;|+@ivR!s literal 0 HcmV?d00001 diff --git a/charpad_rank_sprites/charpad_rank_sprites - Project.asm b/charpad_rank_sprites/charpad_rank_sprites - Project.asm new file mode 100644 index 0000000..3938f08 --- /dev/null +++ b/charpad_rank_sprites/charpad_rank_sprites - Project.asm @@ -0,0 +1,143 @@ + +; Generated by SpritePad C64 - Subchrist Software, 2003-2025. +; Assemble with 64TASS or similar. + + +; Colour values... + +colr_vic_bg0 = 0 +colr_vic_sprite_mc1 = 0 +colr_vic_sprite_mc2 = 1 + + +; Quantities and dimensions... + +sprite_count = 15 + + +; Data block addresses (dummy values) and sizes... + +addr_spriteset_data = $1000 +size_spriteset_data = $3c0 ; (960 bytes) + +addr_spriteset_attrib_data = $1000 +size_spriteset_attrib_data = $f ; (15 bytes) + + + + + +; * INSERT EXAMPLE PROGRAM HERE! * (or just include this file in your project). + + + + +; SpriteSet Data... +; 15 images, 64 bytes per image, total size is 960 ($3c0) bytes. + +* = addr_spriteset_data +spriteset_data + +sprite_image_0 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ef,$ff,$ff +.byte $ef,$ff,$ff,$c7,$ff,$ff,$c7,$ff,$ff,$93,$ff,$ff,$93,$ff,$ff,$39 +.byte $ff,$ff,$01,$ff,$fe,$00,$ff,$fe,$7c,$ff,$fc,$38,$7f,$fc,$38,$7f +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_1 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$83,$ff,$ff +.byte $01,$ff,$ff,$39,$ff,$ff,$f9,$ff,$ff,$f1,$ff,$ff,$e3,$ff,$ff,$c7 +.byte $ff,$ff,$8f,$ff,$ff,$19,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$01,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_2 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01,$ff,$ff +.byte $01,$ff,$ff,$33,$ff,$ff,$e7,$ff,$ff,$cf,$ff,$ff,$83,$ff,$ff,$81 +.byte $ff,$ff,$f9,$ff,$ff,$f9,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$83,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_3 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$f3,$ff,$ff +.byte $e3,$ff,$ff,$c3,$ff,$ff,$83,$ff,$ff,$13,$ff,$fe,$33,$ff,$fe,$00 +.byte $ff,$fe,$00,$ff,$ff,$f3,$ff,$ff,$f3,$ff,$ff,$e1,$ff,$ff,$e1,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_4 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01,$ff,$ff +.byte $01,$ff,$ff,$3f,$ff,$ff,$3f,$ff,$ff,$03,$ff,$ff,$01,$ff,$ff,$f9 +.byte $ff,$ff,$f9,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$83,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_5 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$c3,$ff,$ff +.byte $83,$ff,$ff,$1f,$ff,$ff,$3f,$ff,$ff,$03,$ff,$ff,$01,$ff,$ff,$39 +.byte $ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$83,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_6 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01,$ff,$ff +.byte $01,$ff,$ff,$39,$ff,$ff,$f3,$ff,$ff,$f3,$ff,$ff,$e7,$ff,$ff,$e7 +.byte $ff,$ff,$e7,$ff,$ff,$cf,$ff,$ff,$cf,$ff,$ff,$cf,$ff,$ff,$cf,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_7 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$83,$ff,$ff +.byte $01,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$83,$ff,$ff,$01 +.byte $ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$83,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_8 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$83,$ff,$ff +.byte $01,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$01 +.byte $ff,$ff,$81,$ff,$ff,$f9,$ff,$ff,$f1,$ff,$ff,$83,$ff,$ff,$87,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_9 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$fe,$61,$ff,$fe +.byte $40,$ff,$fe,$4c,$ff,$fe,$4c,$ff,$fe,$4c,$ff,$fe,$4c,$ff,$fe,$4c +.byte $ff,$fe,$4c,$ff,$fe,$4c,$ff,$fe,$4c,$ff,$fe,$40,$ff,$fe,$61,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_10 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$e1,$ff,$ff +.byte $e1,$ff,$ff,$f3,$ff,$ff,$f3,$ff,$ff,$f3,$ff,$ff,$f3,$ff,$ff,$f3 +.byte $ff,$ff,$f3,$ff,$ff,$33,$ff,$ff,$33,$ff,$ff,$03,$ff,$ff,$87,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_11 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$83,$ff,$ff +.byte $01,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39 +.byte $ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$39,$ff,$ff,$01,$ff,$ff,$83,$ff +.byte $ff,$f1,$ff,$ff,$f9,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_12 +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$fe,$10,$ff,$fe +.byte $10,$ff,$ff,$33,$ff,$ff,$27,$ff,$ff,$0f,$ff,$ff,$1f,$ff,$ff,$0f +.byte $ff,$ff,$27,$ff,$ff,$33,$ff,$ff,$39,$ff,$fe,$10,$ff,$fe,$10,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_13 +.byte $51,$15,$11,$55,$75,$d5,$11,$73,$b5,$55,$75,$75,$55,$15,$11,$ff +.byte $ff,$ff,$f1,$b3,$11,$f7,$b5,$75,$f1,$b5,$31,$fd,$b5,$73,$f1,$b3 +.byte $15,$ff,$ff,$ff,$ff,$11,$11,$ff,$d5,$d7,$ff,$15,$11,$ff,$75,$75 +.byte $ff,$11,$11,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + +sprite_image_14 +.byte $b3,$ff,$ff,$b5,$ff,$ff,$b5,$ff,$ff,$b5,$ff,$ff,$b3,$ff,$ff,$ff +.byte $ff,$ff,$1f,$ff,$ff,$7f,$ff,$ff,$1f,$ff,$ff,$df,$ff,$ff,$1f,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff +.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01 + + + +; SpriteSet Attribute Data... +; 15 attributes, 1 per image, 8 bits each, total size is 15 ($f) bytes. +; nb. Upper nybbles = MYXV, lower nybbles = colour (0-15). + +* = addr_spriteset_attrib_data +spriteset_attrib_data + +.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 + + + diff --git a/charpad_rank_sprites/charpad_rank_sprites.png b/charpad_rank_sprites/charpad_rank_sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..dba2e575d6fe1243a6f1872bfca8fdf7433f479d GIT binary patch literal 1026 zcmV+d1pWJoP)EX>4Tx04R}tk-ba9P!z>aQ^g`J4t5Z6$WWc^qN0wq3Pq?8YK2xEOkVm0O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~k~4-h9s7b)?+q|hS98y@*_-aUu+?gNBom1$N_0?>5Z z%%qZHF25@Fy&^zg1TzT9%rfRADGlHHx~E>MyExDC@B6bNYQbVaKqO8u!?cMvh^IGg zgY!Odl$B+b_?&p$qyrK^a$WKGjdRImfoDd|Y-XM~N-UPTSm|O`HZ|fY;+U%GlrQ8w zRyl8R)~a>ZJ}19nxS+2rbDh=*QdqWZ_mlRF{y)TaQF$(nU0=bb>jVfs16O*-Uuy!hpQJZB zTI>iI*aj}HJDRcwTHz@`4FEyskJbPH02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00G)bL_t(|+U=adZp0uAM3;*H|7G`-3L%Js)?;G&p47`q z9f-{iHc7UanSF$mQa;Kd&_^GA^r!l2g98BA#Sb9>fK#c2002B%%^r{Du-7%S;b$%e zr|g$i0lL#yjMI*#NN~iwz4armVaNLM%f?j=*{fu z++T497ZK>u;>==h+4IorX8wXf9&>waaIJ7DCFI$dHw9_60#eLheKr#{20-K{{JO_P zl@Ajy!WB_=M~n`@V$jXqm8xI2-R0p7N7B3o@@bB^(--;{{B z4&~WxWPDcmH!s`If}ODi_MUgv*zBCYLVKj?F^)>N*X!Rb@G#U?x4qH%^sNbgE(ri+ w)#7e@;nzd}AVq~siwXdM!2tjU2LOt<8 literal 0 HcmV?d00001 diff --git a/charpad_rank_sprites/charpad_rank_sprites.spd b/charpad_rank_sprites/charpad_rank_sprites.spd new file mode 100644 index 0000000000000000000000000000000000000000..d48f8d9524bfc6a4ffaae6503fc3e1aedb3b909f GIT binary patch literal 980 zcma)4yGq1B6wR<$ufvkUDy6lgaVsq>EfmQI>^Cga3Cm((Y2l}Ye8Co$R*R)97M2n$ zWI^Y6=8j2bB5u4KhCBCh&Yj2A#o1mmP8w4xQ3*nO1BeH~JwQwW1)IQL@9aPULaEC1C`HX;XJfk%LJOW`6NNtt3KHpT*b7tNK_O<;4GoJVAw z*a(7hx^X{u#ED=eK9#-rzx2Iw7WA{N1*r$-_xfz`1M?OgcPQ*JbMJwZ{4h Z%o^kTz*GrT4d~M{U?Jh1^$nIoH6H)~ literal 0 HcmV?d00001 diff --git a/charpad_rank_sprites/charpad_rank_sprites.xcf b/charpad_rank_sprites/charpad_rank_sprites.xcf new file mode 100644 index 0000000000000000000000000000000000000000..5521a7864db60dbf6fb1b3dcf594cffd8fd07913 GIT binary patch literal 5078 zcmeHL-D(p-7@gT9n;6^XXZwSwu-3l_77`Q@6%<8OP|-^-W4cXK+eDN2Q$e;*AYSQ> zFCcoQ-gx5!=ri~R-gzY=<2f_4+ik5Di=*T4W}7F|EzBc+IJswTtdzg42rj4(L_7ZnieOh=HmV4!DN)idmYsO^ z_P)}eOkL@QwSl#F*64#!#vW@qYx8x}Zg_{WMe7R@N|&+)3eZFv*lqoz(2@g`pqVk~ zCu{uz>OHHYjJDLBgZ?Qss&yvS%D96(3VD>u$2yc~FAbJIOC!)! z3c6!mQTr0lM$)HFzd?@*npe6_3H3Z@ucFThVAAF-BG*dJGg{ek3?G5dXD^i3x9&)< z*LrKjl^+o~z_mLxSZ;QX`_H|t*!|@B#eF>qZIv;vQD8`GDy7dc#;C6%>iN4&e-dCG z#@Ux=9OI0!F5b;@iY4j{XL7wp|sPK7!wa zP&y(UaBo*tv5tR=kl$xQJO}ud&2kmsmpscsz<%Aw-!zPGKU;6yYIZw!+tYOar_Z|R zpBbDNN_j~eeTkRAe)^T)nmX^bwePL{U@Wh3AMo;L6ZiO!C=~CEHp0`32>-G1g_yu6 t-8c|t3hGxpYulK1S+$;Dh;vS;UvV>PH1hAfXtXeWKOT8C$86RW)(?hThG_r* literal 0 HcmV?d00001 diff --git a/claude_code_docker.sh b/claude_code_docker.sh new file mode 100644 index 0000000..f022680 --- /dev/null +++ b/claude_code_docker.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export DOCKER_UID=$(id -u) +export DOCKER_GID=$(id -g) +docker compose run --rm claude-solitaire diff --git a/cm.sh b/cm.sh new file mode 100755 index 0000000..c59b649 --- /dev/null +++ b/cm.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Define filename as variable +PROGNAME="cardgame" + +# Only set C65LIBPATH if not already defined +if [ -z "$C65LIBPATH" ]; then + export C65LIBPATH=$(readlink -f "../../lib") +fi + +# Compile +c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s +#c65cm in:${PROGNAME}.c65 out:${PROGNAME}.s hidelicense +echo assemble. +acme ${PROGNAME}.s + +# Only remove if exists +if [ -f ${PROGNAME}.prg ]; then + rm ${PROGNAME}.prg +fi +#mv main.bin ${PROGNAME}.prg +mv main.bin main.prg diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4d9c8a5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +services: + claude-solitaire: + build: . + user: "${DOCKER_UID}:${DOCKER_GID}" + volumes: + - .:/app + environment: + #- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - CLAUDE_CONFIG_DIR=/app/.claude + - HOME=/app + - DISABLE_AUTOUPDATER=1 + - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 + - DISABLE_TELEMETRY=1 + - DISABLE_ERROR_REPORTING=1 + stdin_open: true + tty: true diff --git a/exomizer_compress_prg.sh b/exomizer_compress_prg.sh new file mode 100644 index 0000000..f36edeb --- /dev/null +++ b/exomizer_compress_prg.sh @@ -0,0 +1 @@ +exomizer sfx basic main.prg -o main2.prg diff --git a/gameloop.c65 b/gameloop.c65 new file mode 100644 index 0000000..b8c21ae --- /dev/null +++ b/gameloop.c65 @@ -0,0 +1,967 @@ + +#IFNDEF __lib_gameloop +#DEFINE __lib_gameloop 1 + +#INCLUDE "cardconsts.c65" +#INCLUDE "piles.c65" +#INCLUDE "cardmoves.c65" +#INCLUDE "cardrender.c65" +#INCLUDE "mouse.c65" +#INCLUDE "pointer.c65" +#INCLUDE "cardsprites.c65" + +GOTO __skip_lib_gameloop + +// ============================================================================ +// SOLITAIRE GAME LOOP AND INTERACTION SYSTEM +// ============================================================================ +// This library provides the main game loop, coordinate-to-pile mapping, +// click detection, selection state, and selective rendering. +// +// Usage: +// #INCLUDE "gameloop.c65" +// game_init() +// game_loop() // Never returns +// ============================================================================ + +// Pile identifiers +BYTE CONST PILE_ID_NONE = 0 +BYTE CONST PILE_ID_STOCK = 1 +BYTE CONST PILE_ID_WASTE = 2 +BYTE CONST PILE_ID_FOUND0 = 3 +BYTE CONST PILE_ID_FOUND1 = 4 +BYTE CONST PILE_ID_FOUND2 = 5 +BYTE CONST PILE_ID_FOUND3 = 6 +BYTE CONST PILE_ID_TAB0 = 7 +BYTE CONST PILE_ID_TAB1 = 8 +BYTE CONST PILE_ID_TAB2 = 9 +BYTE CONST PILE_ID_TAB3 = 10 +BYTE CONST PILE_ID_TAB4 = 11 +BYTE CONST PILE_ID_TAB5 = 12 +BYTE CONST PILE_ID_TAB6 = 13 + +// Game state variables +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_prev_button_state // Previous mouse button state for click detection +BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw + +// Layout constants (in character coordinates) +BYTE CONST LAYOUT_STOCK_COL = 0 +BYTE CONST LAYOUT_STOCK_ROW = 0 +BYTE CONST LAYOUT_WASTE_COL = 6 +BYTE CONST LAYOUT_WASTE_ROW = 0 +BYTE CONST LAYOUT_FOUND_COL = 16 // First foundation column +BYTE CONST LAYOUT_FOUND_ROW = 0 +BYTE CONST LAYOUT_TAB_COL = 0 // First tableau column +BYTE CONST LAYOUT_TAB_ROW = 8 + +BYTE CONST PILE_WIDTH = 5 // Cards are 5 chars wide + +// ============================================================================ +// FUNC pointer_to_char_coords +// Convert sprite pixel coordinates to character coordinates +// C64 sprite coords start at (24,50) for top-left of visible screen +// Sprite coords: X (0-511), Y (0-250) +// Char coords: col (0-39), row (0-24) +// ============================================================================ +FUNC pointer_to_char_coords({WORD sprite_x} {BYTE sprite_y} out:{BYTE char_col} out:{BYTE char_row}) + WORD temp_x + BYTE temp_y + + // Subtract sprite offset (24 pixels X, 50 pixels Y) + temp_x = sprite_x - 24 + temp_y = sprite_y - 50 + + // Divide X by 8 (shift right 3 times) + // For WORD: need to shift high byte into low byte + ASM + lda |temp_x|+1 // High byte + lsr // Shift right 1 + sta |temp_x|+1 + lda |temp_x| // Low byte + ror // Rotate right (carry from high byte) + lsr // Shift right 1 + lsr // Shift right 1 + sta |char_col| + ENDASM + + // Divide Y by 8 (shift right 3 times) + ASM + lda |temp_y| + lsr + lsr + lsr + sta |char_row| + ENDASM + + // Bounds checking (handle underflow as well) + IF char_col > 39 + char_col = 0 + ENDIF + + IF char_row > 24 + char_row = 0 + ENDIF +FEND + + +// ============================================================================ +// FUNC get_pile_at_coords +// Determine which pile (if any) is at the given character coordinates +// Returns PILE_ID_* constant +// ============================================================================ +FUNC get_pile_at_coords({BYTE char_col} {BYTE char_row} out:{BYTE pile_id}) + pile_id = PILE_ID_NONE + + // Check top row (stock, waste, foundations) + IF char_row < 7 // Cards are 7 rows tall + + // Stock: cols 0-4 + IF char_col >= LAYOUT_STOCK_COL + IF char_col < LAYOUT_STOCK_COL+PILE_WIDTH + pile_id = PILE_ID_STOCK + EXIT + ENDIF + ENDIF + + // Waste: cols 6-14 (fanned display is 9 chars wide) + IF char_col >= LAYOUT_WASTE_COL + IF char_col < LAYOUT_WASTE_COL+9 + pile_id = PILE_ID_WASTE + EXIT + ENDIF + ENDIF + + // Foundations: cols 16-35 (4 foundations x 5 chars, with spacing) + IF char_col >= LAYOUT_FOUND_COL + // Foundation 0: 16-20 + IF char_col < LAYOUT_FOUND_COL+PILE_WIDTH + pile_id = PILE_ID_FOUND0 + EXIT + ENDIF + + // Foundation 1: 21-25 + IF char_col >= 21 + IF char_col < 21+PILE_WIDTH + pile_id = PILE_ID_FOUND1 + EXIT + ENDIF + ENDIF + + // Foundation 2: 26-30 + IF char_col >= 26 + IF char_col < 26+PILE_WIDTH + pile_id = PILE_ID_FOUND2 + EXIT + ENDIF + ENDIF + + // Foundation 3: 31-35 + IF char_col >= 31 + IF char_col < 31+PILE_WIDTH + pile_id = PILE_ID_FOUND3 + EXIT + ENDIF + ENDIF + ENDIF + ENDIF + + // Check tableau row (row 8+) + #PRAGMA _P_USE_LONG_JUMP 1 + IF char_row >= LAYOUT_TAB_ROW + #PRAGMA _P_USE_LONG_JUMP 0 + // Tableaus can extend many rows down + // Each tableau: 5 chars wide + 1 char gap (except last) + + // Tableau 0: cols 0-4 + IF char_col < 5 + pile_id = PILE_ID_TAB0 + EXIT + ENDIF + + // Tableau 1: cols 5-9 + IF char_col >= 5 + IF char_col < 10 + pile_id = PILE_ID_TAB1 + EXIT + ENDIF + ENDIF + + // Tableau 2: cols 10-14 + IF char_col >= 10 + IF char_col < 15 + pile_id = PILE_ID_TAB2 + EXIT + ENDIF + ENDIF + + // Tableau 3: cols 15-19 + IF char_col >= 15 + IF char_col < 20 + pile_id = PILE_ID_TAB3 + EXIT + ENDIF + ENDIF + + // Tableau 4: cols 20-24 + IF char_col >= 20 + IF char_col < 25 + pile_id = PILE_ID_TAB4 + EXIT + ENDIF + ENDIF + + // Tableau 5: cols 25-29 + IF char_col >= 25 + IF char_col < 30 + pile_id = PILE_ID_TAB5 + EXIT + ENDIF + ENDIF + + // Tableau 6: cols 30-34 + IF char_col >= 30 + IF char_col < 35 + pile_id = PILE_ID_TAB6 + EXIT + ENDIF + ENDIF + ENDIF +FEND + + +// ============================================================================ +// FUNC pile_id_to_pointer +// Convert pile ID to pile data pointer +// ============================================================================ +FUNC pile_id_to_pointer({BYTE pile_id} out:{WORD pile_ptr}) + WORD ptr @ $fa + + SWITCH pile_id + CASE PILE_ID_STOCK + POINTER ptr -> pile_stock + CASE PILE_ID_WASTE + POINTER ptr -> pile_waste + CASE PILE_ID_FOUND0 + POINTER ptr -> pile_found0 + CASE PILE_ID_FOUND1 + POINTER ptr -> pile_found1 + CASE PILE_ID_FOUND2 + POINTER ptr -> pile_found2 + CASE PILE_ID_FOUND3 + POINTER ptr -> pile_found3 + CASE PILE_ID_TAB0 + POINTER ptr -> pile_tab0 + CASE PILE_ID_TAB1 + POINTER ptr -> pile_tab1 + CASE PILE_ID_TAB2 + POINTER ptr -> pile_tab2 + CASE PILE_ID_TAB3 + POINTER ptr -> pile_tab3 + CASE PILE_ID_TAB4 + POINTER ptr -> pile_tab4 + CASE PILE_ID_TAB5 + POINTER ptr -> pile_tab5 + CASE PILE_ID_TAB6 + POINTER ptr -> pile_tab6 + DEFAULT + ptr = 0 + ENDSWITCH + + pile_ptr = ptr +FEND + + +// ============================================================================ +// FUNC get_tableau_card_count +// Determine how many cards to select from tableau based on click position +// Scans screen memory to find which card was clicked, then counts from there +// Returns the number of cards from the clicked position to the top +// ============================================================================ +FUNC get_tableau_card_count({WORD pile_ptr @ $f8} {BYTE click_row} {BYTE tableau_col} out:{BYTE card_count}) + BYTE count + BYTE faceup_count + BYTE card + BYTE is_facedown + BYTE i + BYTE screen_char + BYTE card_rank + BYTE card_suit + BYTE card_id + BYTE clicked_card_id + BYTE found_match + BYTE match_index + BYTE rank_char + WORD rank_map_ptr @ $f4 + WORD screen_pos @ $f6 + WORD row_offset + + count = PEEK pile_ptr[0] + IF count == 0 + card_count = 0 + EXIT + ENDIF + + // Count face-up cards and find top card + faceup_count = 0 + FOR i = 1 TO count + card = PEEK pile_ptr[i] + is_facedown = card & CARD_FACEDOWN + IF is_facedown == 0 + faceup_count++ + ENDIF + NEXT + + IF faceup_count == 0 + card_count = 0 + EXIT + ENDIF + + // Read screen character at click position to identify clicked card + // Tableau columns: 0,5,10,15,20,25,30 - rank is at column +1 + + // Calculate screen position for clicked row + // row_offset = click_row * 40 + row_offset = 0 + FOR i = 1 TO click_row + row_offset = row_offset + 40 + NEXT + + // Rank character is at tableau_col + 1 (see cardrender.c65:113) + screen_pos = $0400 + row_offset + screen_pos = screen_pos + tableau_col + screen_pos = screen_pos + 1 // Rank is at offset +1 + screen_char = PEEK screen_pos[0] + + // Debug: show screen character we read + //POKE $0400+35 , screen_char + + // Try to match screen character to a rank + POINTER rank_map_ptr -> card_charcode_map + clicked_card_id = $FF + found_match = 0 + + // Scan face-up cards in pile to find match (bottom to top) + FOR i = 1 TO count + card = PEEK pile_ptr[i] + is_facedown = card & CARD_FACEDOWN + IF is_facedown == 0 + card_id = card & CARD_MASK + card_id_to_suit_rank(card_id, card_suit, card_rank) + + // Get rank character and compare + rank_char = PEEK rank_map_ptr[card_rank] + + // Adjust for color (red cards have +64) + IF card_suit < 2 + rank_char = rank_char + 64 + ENDIF + + IF rank_char == screen_char + // Found match - use this card + clicked_card_id = card_id + match_index = i + found_match = 1 + BREAK // Exit loop early + ENDIF + ENDIF + NEXT + + // If no match found, select top card + IF found_match == 0 + card_count = 1 + EXIT + ENDIF + + // Count cards from matched position to top + card_count = count - match_index + card_count = card_count + 1 +FEND + + +// ============================================================================ +// FUNC get_selected_card_info +// Returns info about currently selected card (if any) +// Returns card_id (0-51) and valid flag (1=valid, 0=no selection) +// ============================================================================ +FUNC get_selected_card_info(out:{BYTE card_id} out:{BYTE valid}) + WORD pile_ptr @ $fa + BYTE pile_count + BYTE card_index + BYTE card + + valid = 0 + card_id = 0 + + // No selection + IF game_selected_pile == PILE_ID_NONE + EXIT + ENDIF + + // Get pile pointer + pile_id_to_pointer(game_selected_pile, pile_ptr) + IF pile_ptr == 0 + EXIT + ENDIF + + pile_count = PEEK pile_ptr[0] + IF pile_count == 0 + EXIT + ENDIF + + // Waste: top card + IF game_selected_pile == PILE_ID_WASTE + card = PEEK pile_ptr[pile_count] + card_id = card & CARD_MASK + valid = 1 + EXIT + ENDIF + + // Tableau: bottom card of selection (the clicked card) + IF game_selected_pile >= PILE_ID_TAB0 + IF game_selected_pile <= PILE_ID_TAB6 + // Calculate index of clicked card (bottom of selection) + card_index = pile_count - game_selected_card_count + card_index = card_index + 1 + card = PEEK pile_ptr[card_index] + card_id = card & CARD_MASK + valid = 1 + EXIT + ENDIF + ENDIF +FEND + + +// ============================================================================ +// FUNC game_init +// Initialize game state and input devices +// ============================================================================ +FUNC game_init + game_selected_pile = PILE_ID_NONE + game_selected_card_count = 0 + game_prev_button_state = 0 + + // Initialize joystick (Port 2) + joy_state = 0 +FEND + + +// ============================================================================ +// FUNC detect_click +// Detect button click (press and release) +// Returns 1 if clicked, 0 if not +// ============================================================================ +FUNC detect_click({BYTE current_button_state} out:{BYTE clicked}) + clicked = 0 + + // Check for left mouse button (bit 4, as shown in test) + // Button is active-low, so 0 = pressed + BYTE current_pressed + BYTE prev_pressed + BYTE temp + + // Check if current button is pressed (bit 4 = 0) + temp = current_button_state & $10 + current_pressed = 0 + IF temp == 0 + current_pressed = 1 + ENDIF + + // Check if previous button was pressed + temp = game_prev_button_state & $10 + prev_pressed = 0 + IF temp == 0 + prev_pressed = 1 + ENDIF + + // Click = was pressed, now released + IF prev_pressed == 1 + IF current_pressed == 0 + clicked = 1 + ENDIF + ENDIF + + game_prev_button_state = current_button_state +FEND + + +// ============================================================================ +// FUNC clear_tableau_column +// Clear a tableau column from row 8 to bottom of screen +// ============================================================================ +FUNC clear_tableau_column({BYTE col_offset}) + WORD pos @ $f6 + BYTE row + BYTE i + + // Clear from row 8 to row 24 (17 rows) + // 8*40+$0400 is constant expression, then add variable col_offset + pos = 8*40+$0400 + col_offset + FOR row = 8 TO 24 + // Clear 5 characters wide (card width) + FOR i = 0 TO 4 + POKE pos[i] , 0 + NEXT + pos = pos + 40 + NEXT +FEND + + +// ============================================================================ +// FUNC clear_waste_area +// Clear the waste pile area (9 chars wide for fanned display) +// ============================================================================ +FUNC clear_waste_area + WORD pos @ $f6 + BYTE row + BYTE i + + // Clear waste area: row 0, cols 6-14 (9 chars), 7 rows tall + pos = 6+$0400 + FOR row = 0 TO 6 + FOR i = 0 TO 8 + POKE pos[i] , 0 + NEXT + pos = pos + 40 + NEXT +FEND + + +// ============================================================================ +// FUNC render_pile_by_id +// Render a specific pile by ID to its screen location +// Clears the area first to remove artifacts from previous render +// ============================================================================ +FUNC render_pile_by_id({BYTE pile_id}) + WORD pile_ptr @ $fa + WORD screen_offset + + pile_id_to_pointer(pile_id, pile_ptr) + IF pile_ptr == 0 + EXIT + ENDIF + + SWITCH pile_id + CASE PILE_ID_STOCK + render_stock_pile($0400, 0, pile_ptr) + + CASE PILE_ID_WASTE + clear_waste_area() + render_waste_pile($0400, 6, pile_ptr, game_draw_mode) + + CASE PILE_ID_FOUND0 + render_foundation_pile($0400, 16, pile_ptr) + + CASE PILE_ID_FOUND1 + render_foundation_pile($0400, 21, pile_ptr) + + CASE PILE_ID_FOUND2 + render_foundation_pile($0400, 26, pile_ptr) + + CASE PILE_ID_FOUND3 + render_foundation_pile($0400, 31, pile_ptr) + + CASE PILE_ID_TAB0 + clear_tableau_column(0) + render_tableau_pile($0400, 8*40, pile_ptr) + + CASE PILE_ID_TAB1 + clear_tableau_column(5) + render_tableau_pile($0400, 8*40+5, pile_ptr) + + CASE PILE_ID_TAB2 + clear_tableau_column(10) + render_tableau_pile($0400, 8*40+10, pile_ptr) + + CASE PILE_ID_TAB3 + clear_tableau_column(15) + render_tableau_pile($0400, 8*40+15, pile_ptr) + + CASE PILE_ID_TAB4 + clear_tableau_column(20) + render_tableau_pile($0400, 8*40+20, pile_ptr) + + CASE PILE_ID_TAB5 + clear_tableau_column(25) + render_tableau_pile($0400, 8*40+25, pile_ptr) + + CASE PILE_ID_TAB6 + clear_tableau_column(30) + render_tableau_pile($0400, 8*40+30, pile_ptr) + ENDSWITCH +FEND + + +// ============================================================================ +// FUNC check_win_condition +// Check if all 4 foundations are complete (13 cards each = King on top) +// Returns 1 if won, 0 otherwise +// ============================================================================ +FUNC check_win_condition(out:{BYTE is_won}) + WORD ptr @ $fa + BYTE count + + is_won = 1 // Assume won, set to 0 if any foundation incomplete + + // Check foundation 0 + POINTER ptr -> pile_found0 + count = PEEK ptr[0] + IF count != 13 + is_won = 0 + EXIT + ENDIF + + // Check foundation 1 + POINTER ptr -> pile_found1 + count = PEEK ptr[0] + IF count != 13 + is_won = 0 + EXIT + ENDIF + + // Check foundation 2 + POINTER ptr -> pile_found2 + count = PEEK ptr[0] + IF count != 13 + is_won = 0 + EXIT + ENDIF + + // Check foundation 3 + POINTER ptr -> pile_found3 + count = PEEK ptr[0] + IF count != 13 + is_won = 0 + EXIT + ENDIF +FEND + + +// ============================================================================ +// FUNC handle_click_on_pile +// Handle a click on a specific pile +// Implements game logic for selection and move execution +// ============================================================================ +FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row}) + BYTE is_foundation + BYTE is_tableau + BYTE selected_is_tableau + BYTE success + WORD src_ptr + WORD dst_ptr + WORD tab_ptr @ $f8 + BYTE tab_col + BYTE tab_index + BYTE j + + // Determine pile type + is_foundation = 0 + IF clicked_pile >= PILE_ID_FOUND0 + IF clicked_pile <= PILE_ID_FOUND3 + is_foundation = 1 + ENDIF + ENDIF + + is_tableau = 0 + IF clicked_pile >= PILE_ID_TAB0 + IF clicked_pile <= PILE_ID_TAB6 + is_tableau = 1 + ENDIF + ENDIF + + // If nothing selected, select this pile + #PRAGMA _P_USE_LONG_JUMP 1 + IF game_selected_pile == PILE_ID_NONE + #PRAGMA _P_USE_LONG_JUMP 0 + + // Can only select waste or tableau + IF clicked_pile == PILE_ID_WASTE + game_selected_pile = PILE_ID_WASTE + game_selected_card_count = 1 + EXIT + ENDIF + + IF clicked_pile == PILE_ID_STOCK + // Stock: draw cards + WORD stock_ptr @ $fa + POINTER stock_ptr -> pile_stock + move_stock_to_waste(game_draw_mode, success) + IF success + render_pile_by_id(PILE_ID_STOCK) + render_pile_by_id(PILE_ID_WASTE) + ELSE + // Stock empty, reset from waste + move_reset_stock(success) + IF success + render_pile_by_id(PILE_ID_STOCK) + render_pile_by_id(PILE_ID_WASTE) + ENDIF + ENDIF + EXIT + ENDIF + + #PRAGMA _P_USE_LONG_JUMP 1 + IF is_tableau + #PRAGMA _P_USE_LONG_JUMP 0 + // Select tableau - calculate how many cards based on click position + pile_id_to_pointer(clicked_pile, tab_ptr) + + // Calculate tableau column: 0,5,10,15,20,25,30 + tab_index = clicked_pile - PILE_ID_TAB0 + tab_col = 0 + FOR j = 1 TO tab_index + tab_col = tab_col + 5 + NEXT + + get_tableau_card_count(tab_ptr, click_row, tab_col, game_selected_card_count) + IF game_selected_card_count > 0 + game_selected_pile = clicked_pile + ENDIF + EXIT + ENDIF + + EXIT + ENDIF + + // Something is selected, try to move to destination + pile_id_to_pointer(game_selected_pile, src_ptr) + pile_id_to_pointer(clicked_pile, dst_ptr) + + // Waste to Foundation + IF game_selected_pile == PILE_ID_WASTE + IF is_foundation + move_waste_to_found(dst_ptr, success) + IF success + render_pile_by_id(PILE_ID_WASTE) + render_pile_by_id(clicked_pile) + ENDIF + game_selected_pile = PILE_ID_NONE + EXIT + ENDIF + ENDIF + + // Waste to Tableau + IF game_selected_pile == PILE_ID_WASTE + IF is_tableau + move_waste_to_tab(dst_ptr, success) + IF success + render_pile_by_id(PILE_ID_WASTE) + render_pile_by_id(clicked_pile) + ENDIF + game_selected_pile = PILE_ID_NONE + EXIT + ENDIF + ENDIF + + // Tableau to Foundation/Tableau - check if selected pile is tableau + selected_is_tableau = 0 + IF game_selected_pile >= PILE_ID_TAB0 + IF game_selected_pile <= PILE_ID_TAB6 + selected_is_tableau = 1 + ENDIF + ENDIF + + IF selected_is_tableau + IF is_foundation + move_tab_to_found(src_ptr, dst_ptr, success) + IF success + render_pile_by_id(game_selected_pile) + render_pile_by_id(clicked_pile) + ENDIF + game_selected_pile = PILE_ID_NONE + EXIT + ENDIF + ENDIF + + // Tableau to Tableau + IF selected_is_tableau + IF is_tableau + move_tab_to_tab(src_ptr, dst_ptr, game_selected_card_count, success) + IF success + render_pile_by_id(game_selected_pile) + render_pile_by_id(clicked_pile) + ENDIF + game_selected_pile = PILE_ID_NONE + EXIT + ENDIF + ENDIF + + // Click on same pile or invalid destination: deselect + game_selected_pile = PILE_ID_NONE +FEND + + +// ============================================================================ +// FUNC render_all_piles_initial +// Render all piles for initial game display +// ============================================================================ +FUNC render_all_piles_initial + BYTE pile_id + pile_id = PILE_ID_STOCK + + WHILE pile_id <= PILE_ID_TAB6 + render_pile_by_id(pile_id) + pile_id++ + WEND +FEND + + +// ============================================================================ +// FUNC game_loop +// Main game loop - never returns +// ============================================================================ +FUNC game_loop + WORD sprite_x + BYTE sprite_y + BYTE char_col + BYTE char_row + BYTE pile_at_cursor + BYTE button_state + BYTE clicked + BYTE is_won + WORD src_ptr @ $fa + WORD src_end_ptr + WORD dst_ptr @ $fc + + // Copy sprite data to $2200 (sprite block 136) + // $2000-$21FF reserved for charset + POINTER src_ptr -> pointer_sprite_data + POINTER src_end_ptr -> pointer_sprite_data_end + POINTER dst_ptr -> $2200 + mem_copy(src_ptr, src_end_ptr, dst_ptr) + + // Copy card sprite data to $2240 (sprite blocks 137+) + // 25 sprites × 64 bytes = 1600 bytes + POINTER src_ptr -> sprite_rank_ace + POINTER dst_ptr -> $2240 + mem_copy_range(src_ptr, dst_ptr, 64*25) + + // Initialize mouse and pointer + mouse_init() + pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color + + // Enable sprite + pointer_enable(1) + + // Initialize card display sprites + card_display_init() + +#IFDEF TEST_GAMES + // Test game options (comment/uncomment one): + //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_overflow() // K->A in tab3 to test screen overflow +#IFEND + + // Initial render + fill_mem($0400, $0400+999, 0) // Clear screen + render_all_piles_initial() + + BYTE raster @ $d012 + + WHILE 1 + // Wait for raster to avoid tearing + WHILE raster != 250 + WEND + + // Read mouse input (Port 1) + mouse_read() + pointer_update_mouse(mouse_delta_x, mouse_delta_y) + + // Read joystick input (Port 2) + joy_read_port2() + pointer_update_joystick(joy_state) + + // Get pointer position + pointer_get_x(sprite_x) + pointer_get_y(sprite_y) + + // Convert to character coordinates + pointer_to_char_coords(sprite_x, sprite_y, char_col, char_row) + + // Get pile under cursor + get_pile_at_coords(char_col, char_row, pile_at_cursor) + + #IFDEF TEST_GAMES + // Visual feedback - show selection state and hover + IF game_selected_pile != PILE_ID_NONE + // Something selected - show with cyan border + POKE $d020 , color_cyan + ELSE + // Nothing selected - grey when hovering over pile, white otherwise + IF pile_at_cursor != PILE_ID_NONE + POKE $d020 , color_light_grey + ELSE + POKE $d020 , color_white + ENDIF + ENDIF + #IFEND + // Detect click from both mouse button (bit 4) and joystick fire (bit 4) + // Mouse buttons: bit 4 = left button (active low, 0=pressed) + // Joystick state: bit 4 = fire button (active high, 1=pressed after inversion) + // Combine both: if either has bit 4 clear (mouse) or set (joy), trigger click + BYTE combined_button + combined_button = mouse_buttons & %00010000 // Mouse: 0=pressed + IF combined_button != 0 + // Mouse not pressed, check joystick + combined_button = joy_state & %00010000 // Joy: 1=pressed + IF combined_button + combined_button = 0 // Make it 0 (pressed) like mouse + ELSE + combined_button = %00010000 // Not pressed + ENDIF + ENDIF + + button_state = combined_button + detect_click(button_state, clicked) + + // Handle click if occurred + IF clicked + #IFDEF TEST_GAMES + // Visual feedback - flash border on click + POKE $d020 , color_yellow + #IFEND + + IF pile_at_cursor != PILE_ID_NONE + handle_click_on_pile(pile_at_cursor, char_row) + ELSE + // Click on empty area: deselect + game_selected_pile = PILE_ID_NONE + ENDIF + ENDIF + + // Update selected card display (upper right corner using sprites) + BYTE display_card_id + BYTE display_valid + BYTE display_rank + BYTE display_suit + + get_selected_card_info(display_card_id, display_valid) + IF display_valid + // Show selected card rank and suit using sprites + card_id_to_suit_rank(display_card_id, display_suit, display_rank) + card_display_show(display_rank, display_suit) + ELSE + // Hide sprites when nothing selected + card_display_hide() + ENDIF + + // Check win condition + check_win_condition(is_won) + IF is_won + // Flash border or show message + POKE $d020 , color_green + // Could add "YOU WIN" message here + // For now, just keep running to allow admiring the win + ENDIF + + // Small delay to avoid reading mouse too fast + // Could sync to raster if needed for smoother experience + WEND +FEND + + +LABEL __skip_lib_gameloop + +#IFEND diff --git a/joystick.c65 b/joystick.c65 new file mode 100644 index 0000000..70b3113 --- /dev/null +++ b/joystick.c65 @@ -0,0 +1,72 @@ + +#IFNDEF __lib_joystick +#DEFINE __lib_joystick 1 + +GOTO __skip_lib_joystick + +// ============================================================================ +// JOYSTICK INPUT DRIVER FOR COMMODORE 64 +// ============================================================================ +// This library provides joystick input reading for the Commodore 64. +// +// Joystick reading uses CIA port 2 ($DC00) which is preferred for games +// Port 1 ($DC01) conflicts with keyboard matrix scanning +// +// Usage: +// #INCLUDE "joystick.c65" +// WHILE 1 +// joy_read() +// BYTE test_bit +// test_bit = joy_state & JOY_UP_MASK +// IF test_bit +// // handle up +// ENDIF +// WEND +// ============================================================================ + +// CIA Port addresses +WORD CONST JOY_PORT1 = $DC01 // Port 1 (conflicts with keyboard) +WORD CONST JOY_PORT2 = $DC00 // Port 2 (recommended for games) + +// Joystick bit masks (active low - 0 = pressed) +BYTE CONST JOY_UP_MASK = %00000001 // Bit 0 +BYTE CONST JOY_DOWN_MASK = %00000010 // Bit 1 +BYTE CONST JOY_LEFT_MASK = %00000100 // Bit 2 +BYTE CONST JOY_RIGHT_MASK = %00001000 // Bit 3 +BYTE CONST JOY_FIRE_MASK = %00010000 // Bit 4 + +// Joystick state variable +BYTE joy_state // Inverted joystick state (1=pressed, 0=not pressed) + + + +// ============================================================================ +// FUNC joy_read_port1 +// Read joystick port 1 (alternative port, may conflict with keyboard) +// Updates the same joy_state variable as joy_read() +// ============================================================================ +FUNC joy_read_port1 + BYTE joy_raw + + joy_raw = PEEK JOY_PORT1 + joy_state = joy_raw ^ $FF // Invert bits: 1=pressed, 0=not pressed +FEND + +// ============================================================================ +// FUNC joy_read +// Read joystick port 2 and update state variable +// Call this every frame to update joystick state +// Inverts active-low CIA bits to active-high (1=pressed, 0=not pressed) +// ============================================================================ +FUNC joy_read_port2 + BYTE joy_raw + + joy_raw = PEEK JOY_PORT2 + joy_state = joy_raw ^ $FF // Invert bits: 1=pressed, 0=not pressed +FEND + + + +LABEL __skip_lib_joystick + +#IFEND diff --git a/joysticktests.c65 b/joysticktests.c65 new file mode 100644 index 0000000..2b4584e --- /dev/null +++ b/joysticktests.c65 @@ -0,0 +1,272 @@ + +#IFNDEF __lib_joysticktests +#DEFINE __lib_joysticktests 1 + +#INCLUDE "joystick.c65" +#INCLUDE "mouse.c65" +#INCLUDE "pointer.c65" + +GOTO __skip_lib_joysticktests + + +// ============================================================================ +// FUNC test_joystick_read +// Test basic joystick reading using card suit symbols +// Displays suits in 4 directions, lights up when joystick pressed +// Runs forever - reset to exit +// ============================================================================ +//FUNC test_joystick_read +// WORD screen_ptr @ $fb +// BYTE raster @ $d012 +// +// // Initialize joystick +// joy_init() +// +// // Draw suit symbols in cross pattern (center of screen) +// // Hearts (red) = UP, Diamonds (red) = DOWN, Spades (black) = LEFT, Clubs (black) = RIGHT +// +// // UP position - Hearts suit symbol +// POINTER screen_ptr -> 9*40+$0400+18 +// POKE screen_ptr[0] , $50 +// +// // DOWN position - Diamonds suit symbol +// POINTER screen_ptr -> 11*40+$0400+18 +// POKE screen_ptr[0] , $51 +// +// // LEFT position - Spades suit symbol +// POINTER screen_ptr -> 10*40+$0400+16 +// POKE screen_ptr[0] , $0e +// +// // RIGHT position - Clubs suit symbol +// POINTER screen_ptr -> 10*40+$0400+20 +// POKE screen_ptr[0] , $0f +// +// // CENTER - Fire indicator (Ace symbol) +// POINTER screen_ptr -> 10*40+$0400+18 +// POKE screen_ptr[0] , 0 +// +// // Main test loop - runs forever +// WHILE 1 +// joy_read_port2() +// +// // Wait for raster +// WHILE raster != 250 +// WEND +// +// // Update suit displays based on joystick +// +// // UP - Hearts (normal or highlighted) +// POINTER screen_ptr -> 9*40+$0400+18 +// IF joy_up +// POKE screen_ptr[0] , $50+64 // Red/highlighted +// ELSE +// POKE screen_ptr[0] , $50 // Normal +// ENDIF +// +// // DOWN - Diamonds +// POINTER screen_ptr -> 11*40+$0400+18 +// IF joy_down +// POKE screen_ptr[0] , $51+64 // Red/highlighted +// ELSE +// POKE screen_ptr[0] , $51 // Normal +// ENDIF +// +// // LEFT - Spades +// POINTER screen_ptr -> 10*40+$0400+16 +// IF joy_left +// POKE screen_ptr[0] , $0e+64 // Red/highlighted +// ELSE +// POKE screen_ptr[0] , $0e // Normal +// ENDIF +// +// // RIGHT - Clubs +// POINTER screen_ptr -> 10*40+$0400+20 +// IF joy_right +// POKE screen_ptr[0] , $0f+64 // Red/highlighted +// ELSE +// POKE screen_ptr[0] , $0f // Normal +// ENDIF +// +// // FIRE - Center Ace +// POINTER screen_ptr -> 10*40+$0400+18 +// IF joy_fire +// POKE screen_ptr[0] , 13+64 // Ace highlighted +// ELSE +// POKE screen_ptr[0] , 0 // Blank +// ENDIF +// WEND +//FEND + + +// ============================================================================ +// FUNC test_pointer_sprite +// Test sprite pointer movement with joystick +// Initializes sprite, copies sprite data, and moves pointer with joystick +// Run for ~200 frames then returns +// ============================================================================ +FUNC test_pointer_sprite + // Copy sprite data to $2200 (sprite block 136) + // $2000-$21FF reserved for charset + WORD src_ptr @ $fa + WORD src_end_ptr + WORD dst_ptr @ $fc + WORD frame_count + BYTE raster @ $d012 + + POINTER src_ptr -> pointer_sprite_data + POINTER src_end_ptr -> pointer_sprite_data_end + POINTER dst_ptr -> $2200 + mem_copy(src_ptr, src_end_ptr, dst_ptr) + + // Initialize pointer + pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, red color + pointer_set_speed(2) + + // Enable sprite + pointer_enable(1) + + + // Movement loop - runs forever + WHILE 1 + // Wait for raster + WHILE raster != 250 + WEND + + DEC $d020 + // Read joystick and update pointer + joy_read_port2() + pointer_update_joystick(joy_state) + INC $d020 + WEND +FEND + + +// ============================================================================ +// FUNC test_pointer_manual +// Test pointer with manual position setting +// Moves pointer in a square pattern without joystick +// ============================================================================ +//FUNC test_pointer_manual +// WORD x +// BYTE y +// BYTE delay +// WORD x_down +// BYTE y_down +// +// // Copy sprite data to $2200 (sprite block 136) +// WORD src_ptr @ $fa +// WORD src_end_ptr +// WORD dst_ptr @ $fc +// POINTER src_ptr -> pointer_sprite_data +// POINTER src_end_ptr -> pointer_sprite_data_end +// POINTER dst_ptr -> $2200 +// mem_copy(src_ptr, src_end_ptr, dst_ptr) +// +// // Initialize pointer +// pointer_init(50, 50, color_cyan, 136) +// pointer_set_speed(3) +// +// // Move in square pattern +// // Right +// FOR x = 50 TO 200 +// pointer_set_x(x) +// pointer_set_y(50) +// // Small delay +// FOR delay = 0 TO 10 +// NEXT +// NEXT +// +// // Down +// FOR y = 50 TO 150 +// pointer_set_x(200) +// pointer_set_y(y) +// FOR delay = 0 TO 10 +// NEXT +// NEXT +// +// // Left +// x_down = 200 +// WHILE x_down > 50 +// pointer_set_x(x_down) +// pointer_set_y(150) +// x_down-- +// FOR delay = 0 TO 10 +// NEXT +// WEND +// +// // Up +// y_down = 150 +// WHILE y_down > 50 +// pointer_set_x(50) +// pointer_set_y(y_down) +// y_down-- +// FOR delay = 0 TO 10 +// NEXT +// WEND +// +// // Disable sprite +// pointer_enable(0) +//FEND + + +// ============================================================================ +// FUNC test_pointer_mouse +// Test sprite pointer movement with 1351 mouse +// Initializes sprite, copies sprite data, and moves pointer with mouse +// Run forever - press RUN/STOP+RESTORE to exit +// ============================================================================ +FUNC test_pointer_mouse + // Copy sprite data to $2200 (sprite block 136) + // $2000-$21FF reserved for charset + WORD src_ptr @ $fa + WORD src_end_ptr + WORD dst_ptr @ $fc + BYTE raster @ $d012 + + POINTER src_ptr -> pointer_sprite_data + POINTER src_end_ptr -> pointer_sprite_data_end + POINTER dst_ptr -> $2200 + mem_copy(src_ptr, src_end_ptr, dst_ptr) + + // Initialize mouse and pointer + mouse_init() + pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, white color + + // Enable sprite + pointer_enable(1) + + // Movement loop - runs forever + WHILE 1 + // Wait for raster + WHILE raster != 250 + WEND + + DEC $d020 + // Read mouse and update pointer + mouse_read() + pointer_update_mouse(mouse_delta_x, mouse_delta_y) + + // Test buttons (active-low: 0=pressed) + BYTE left_btn + BYTE right_btn + + // Left button = bit 4 + left_btn = mouse_buttons & %00010000 + IF left_btn == 0 + INC $0400 // Pressed + ENDIF + + // Right button = bit 0 + right_btn = mouse_buttons & %00000001 + IF right_btn == 0 + INC $0401 // Pressed + ENDIF + + INC $d020 + WEND +FEND + + +LABEL __skip_lib_joysticktests + +#IFEND diff --git a/mouse.c65 b/mouse.c65 new file mode 100644 index 0000000..ac4ca08 --- /dev/null +++ b/mouse.c65 @@ -0,0 +1,307 @@ + +#IFNDEF __lib_mouse +#DEFINE __lib_mouse 1 + +GOTO __skip_lib_mouse + +// ============================================================================ +// COMMODORE 1351 MOUSE DRIVER FOR C64 +// ============================================================================ +// This library provides mouse input reading for the Commodore 1351 mouse. +// +// The 1351 uses the SID chip's POT lines to report relative movement. +// The SID hardware automatically samples at ~2kHz, but software should +// read once per frame (50-60Hz) to minimize CPU usage. +// +// Mouse is read from CONTROL PORT 1 (POT lines + buttons) +// This allows simultaneous use with joystick on CONTROL PORT 2 +// +// Based on implementations from C64 OS and cc65. +// +// FAST MOVEMENT SMOOTHING: +// By default, directional momentum smoothing is enabled to fix the 1351's +// known issue where fast movements can register in the opposite direction. +// To disable smoothing (saves CPU cycles), define MOUSE_NO_SMOOTHING: +// #DEFINE MOUSE_NO_SMOOTHING 1 +// #INCLUDE "mouse.c65" +// +// Usage: +// #INCLUDE "mouse.c65" +// mouse_init() +// WHILE 1 +// mouse_read() // Call once per frame +// pointer_update_mouse(mouse_delta_x, mouse_delta_y) +// WEND +// ============================================================================ + +// SID POT line addresses +WORD CONST SID_BASE = $D400 +BYTE pot_x @ SID_BASE+$19 // SID+$19: POT X (analog input) +BYTE pot_y @ SID_BASE+$1A // SID+$1A: POT Y (analog input) + +// CIA port for button reading (same as joystick port 1) +WORD CONST CIA1_BASE = $DC00 +BYTE cia_port1 @ CIA1_BASE+1 // $DC01: Port 1 (mouse buttons) + +// Mouse state variables +BYTE mouse_pot_x_old // Previous POT X reading +BYTE mouse_pot_y_old // Previous POT Y reading +BYTE mouse_delta_x // Signed X movement delta +BYTE mouse_delta_y // Signed Y movement delta +BYTE mouse_buttons // Button state (bit 4=right, bit 0=left) + +#IFNDEF MOUSE_NO_SMOOTHING +// Directional momentum tracking (for fast movement disambiguation) +// 0=no momentum, 1-127=rightward, 128-255=leftward (unsigned byte as signed) +// Define MOUSE_NO_SMOOTHING before including this library to disable smoothing +BYTE mouse_momentum_x +BYTE mouse_momentum_y + + +// ============================================================================ +// FUNC mouse_apply_momentum +// Apply directional momentum to disambiguate fast movements +// When delta magnitude is large (ambiguous), use momentum to determine +// if we should flip the interpretation (±64 in 6-bit space) +// ============================================================================ +FUNC mouse_apply_momentum({BYTE delta} {BYTE momentum} out:{BYTE corrected_delta} out:{BYTE new_momentum}) + BYTE abs_delta + BYTE is_negative + BYTE is_ambiguous + BYTE was_corrected + + // Get absolute value and sign of delta + is_negative = delta & $80 + IF is_negative + abs_delta = 0 - delta + ELSE + abs_delta = delta + ENDIF + + // Check if delta is ambiguous (magnitude >= 12 after division by 2 in convert function) + // In raw 6-bit space, deltas near ±32 become ±16 after /2, which is ambiguous + is_ambiguous = 0 + was_corrected = 0 + #PRAGMA _P_USE_LONG_JUMP 1 + IF abs_delta >= 12 + #PRAGMA _P_USE_LONG_JUMP 0 + is_ambiguous = 1 + // Ambiguous - check if flipping (negating) would match momentum better + BYTE flipped_delta + BYTE flipped_negative + + // To flip interpretation, just negate the delta + flipped_delta = abs_delta + IF is_negative + // Delta is negative, flipped would be positive + flipped_negative = 0 + ELSE + // Delta is positive, flipped would be negative + flipped_negative = 1 + ENDIF + + // Use momentum to decide: if momentum sign matches flipped, use flipped + IF momentum != 0 + BYTE momentum_is_left + BYTE signs_match + + momentum_is_left = 0 + IF momentum >= 128 + momentum_is_left = 1 + ENDIF + + signs_match = 0 + IF momentum_is_left == flipped_negative + signs_match = 1 + ENDIF + + IF signs_match == 1 + // Momentum suggests flipped interpretation + IF flipped_negative + corrected_delta = 0 - flipped_delta + ELSE + corrected_delta = flipped_delta + ENDIF + was_corrected = 1 + ELSE + // Keep original + corrected_delta = delta + was_corrected = 1 + ENDIF + ELSE + // No momentum yet - ignore this ambiguous delta, return 0 + corrected_delta = 0 + ENDIF + ELSE + // Unambiguous, use as-is + corrected_delta = delta + ENDIF + + // Update momentum based on corrected delta + // Only update from unambiguous deltas to avoid bootstrapping in wrong direction + #PRAGMA _P_USE_LONG_JUMP 1 + IF is_ambiguous == 0 + #PRAGMA _P_USE_LONG_JUMP 0 + IF corrected_delta == 0 + // No movement - decay momentum toward zero + IF momentum > 0 + IF momentum < 128 + IF momentum > 2 + momentum = momentum - 2 + ELSE + momentum = 0 + ENDIF + ELSE + IF momentum < 253 + momentum = momentum + 2 + ELSE + momentum = 0 + ENDIF + ENDIF + ENDIF + ELSE + // Add corrected delta to momentum (clamped) + BYTE corrected_negative + corrected_negative = corrected_delta & $80 + + IF corrected_negative + // Moving left - push momentum toward 128-255 + IF momentum < 128 + momentum = 128 + ELSE + IF momentum < 240 + momentum = momentum + 8 + ENDIF + ENDIF + ELSE + // Moving right - push momentum toward 1-127 + IF momentum >= 128 + momentum = 127 + ELSE + IF momentum < 120 + momentum = momentum + 8 + ENDIF + ENDIF + ENDIF + ENDIF + ENDIF + + new_momentum = momentum +FEND +#IFEND + + +// ============================================================================ +// FUNC mouse_convert_pot_delta +// Convert 6-bit POT delta to 8-bit signed delta +// The 1351 outputs 6-bit signed values in bits 1-6 (bits 0,7 are noise) +// This converts them to standard 8-bit signed values (-128 to +127) +// ============================================================================ +FUNC mouse_convert_pot_delta({BYTE new_val} {BYTE old_val} out:{BYTE delta}) + BYTE diff + BYTE sign_bit + + // Calculate raw difference (wraps at 64 due to 6-bit counter) + diff = new_val - old_val + + // Mask to 7 bits (ignore bit 7 noise) + diff = diff & %01111111 + + // Check if value is negative (bit 6 set means >= 64 in 6-bit space) + sign_bit = diff & %01000000 + + IF sign_bit + // Negative value: set high bits and divide by 2 + diff = diff | %11000000 // Sign extend + ASM + lda |diff| + cmp #$FF + beq @zero + sec + ror // Arithmetic shift right (preserves sign) + sta |diff| + jmp @done +@zero: + lda #0 + sta |diff| +@done: + ENDASM + delta = diff + ELSE + // Positive value: just divide by 2 + ASM + lda |diff| + beq @zero2 + lsr // Logical shift right + sta |diff| + jmp @done2 +@zero2: + lda #0 + sta |diff| +@done2: + ENDASM + delta = diff + ENDIF +FEND + + +// ============================================================================ +// FUNC mouse_init +// Initialize mouse driver - call once at program start +// ============================================================================ +FUNC mouse_init + // Read initial pot values + mouse_pot_x_old = pot_x + mouse_pot_y_old = pot_y + mouse_delta_x = 0 + mouse_delta_y = 0 + mouse_buttons = 0 +#IFNDEF MOUSE_NO_SMOOTHING + mouse_momentum_x = 0 + mouse_momentum_y = 0 +#IFEND +FEND + + +// ============================================================================ +// FUNC mouse_read +// Read mouse movement and button state +// Call this every frame to update mouse state +// Updates mouse_delta_x, mouse_delta_y (signed 8-bit deltas) +// Applies directional momentum to fix fast movement bugs +// ============================================================================ +FUNC mouse_read + BYTE pot_x_new + BYTE pot_y_new + + // Read current POT values + pot_x_new = pot_x + pot_y_new = pot_y + + // Calculate X delta + mouse_convert_pot_delta(pot_x_new, mouse_pot_x_old, mouse_delta_x) + mouse_pot_x_old = pot_x_new + +#IFNDEF MOUSE_NO_SMOOTHING + // Apply momentum correction to X + mouse_apply_momentum(mouse_delta_x, mouse_momentum_x, mouse_delta_x, mouse_momentum_x) +#IFEND + + // Calculate Y delta (invert for correct screen movement) + mouse_convert_pot_delta(pot_y_new, mouse_pot_y_old, mouse_delta_y) + mouse_delta_y = 0 - mouse_delta_y // Negate Y + mouse_pot_y_old = pot_y_new + +#IFNDEF MOUSE_NO_SMOOTHING + // Apply momentum correction to Y + mouse_apply_momentum(mouse_delta_y, mouse_momentum_y, mouse_delta_y, mouse_momentum_y) +#IFEND + + // Read buttons from CIA port + mouse_buttons = cia_port1 +FEND + + +LABEL __skip_lib_mouse + +#IFEND diff --git a/piles.c65 b/piles.c65 new file mode 100644 index 0000000..929082e --- /dev/null +++ b/piles.c65 @@ -0,0 +1,78 @@ + +#IFNDEF __lib_piles +#DEFINE __lib_piles 1 + +GOTO __skip_lib_piles + +// Pile data structures +// byte 0 = count, bytes 1-52 = cards +// Cards use CARD_FACEDOWN bit for face-down flag + +LABEL pile_stock +ASM + !fill 53, 0 +ENDASM + +LABEL pile_waste +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab0 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab1 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab2 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab3 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab4 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab5 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_tab6 +ASM + !fill 53, 0 +ENDASM + +LABEL pile_found0 +ASM + !fill 14, 0 +ENDASM + +LABEL pile_found1 +ASM + !fill 14, 0 +ENDASM + +LABEL pile_found2 +ASM + !fill 14, 0 +ENDASM + +LABEL pile_found3 +ASM + !fill 14, 0 +ENDASM + +LABEL __skip_lib_piles + +#IFEND diff --git a/pointer.c65 b/pointer.c65 new file mode 100644 index 0000000..74f3a14 --- /dev/null +++ b/pointer.c65 @@ -0,0 +1,371 @@ + +#IFNDEF __lib_pointer +#DEFINE __lib_pointer 1 + +GOTO __skip_lib_pointer + +// ============================================================================ +// SPRITE POINTER DRIVER FOR COMMODORE 64 +// ============================================================================ +// This library provides sprite-based pointer/cursor control. +// Can be driven by joystick, mouse, keyboard, or other input methods. +// +// Usage: +// #INCLUDE "pointer.c65" +// pointer_init(100, 50, 1, 192) // Init at x=100, y=50, white color, sprite data block 192 +// WHILE 1 +// // Read your input device (joystick, mouse, etc.) +// pointer_move(delta_x, delta_y) // Move pointer +// WEND +// ============================================================================ + +// VIC-II Sprite registers +WORD CONST VIC2 = $D000 +BYTE sprite_x0 @ VIC2+0 // Sprite 0 X position +BYTE sprite_y0 @ VIC2+1 // Sprite 0 Y position +BYTE sprite_x_msb @ VIC2+16 // Sprite X MSB (for X > 255) +BYTE sprite_enable @ VIC2+21 // Sprite enable register +BYTE sprite_color0 @ VIC2+39 // Sprite 0 color +BYTE sprite_pointer0 @ $07F8 // Sprite 0 pointer (screen + $3F8) + +// Pointer position variables +WORD pointer_x // Current pointer X position (0-511) +BYTE pointer_y // Current pointer Y position +BYTE pointer_speed // Base movement speed +BYTE pointer_accel // Current acceleration counter +BYTE pointer_max_speed // Maximum speed with acceleration + + +// ============================================================================ +// Compact arrow pointer sprite - 7 pixels tall +// Tip at (0,0) for easy pointing at coordinates +// ============================================================================ +LABEL pointer_sprite_data +ASM + // Simple compact arrow + !8 $80, $00, $00 // Row 0: * + !8 $C0, $00, $00 // Row 1: ** + !8 $E0, $00, $00 // Row 2: *** + !8 $F0, $00, $00 // Row 3: **** + !8 $E0, $00, $00 // Row 4: *** + !8 $C0, $00, $00 // Row 5: ** + !8 $80, $00, $00 // Row 6: * + !8 $00, $00, $00 // Row 7-20: blank + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00, $00, $00 + !8 $00 // Padding +ENDASM +LABEL pointer_sprite_data_end + +// ============================================================================ +// FUNC pointer_init +// Initialize sprite 0 as a pointer at given position +// Parameters: +// x_pos: Initial X position (0-255) +// y_pos: Initial Y position (0-250) +// color: Sprite color (0-15) +// sprite_data: Sprite data block number (0-255) +// ============================================================================ +FUNC pointer_init({BYTE x_pos} {BYTE y_pos} {BYTE color} {BYTE sprite_data}) + BYTE temp + + // Set pointer position + pointer_x = x_pos + pointer_y = y_pos + pointer_speed = 1 // Base speed: 1 pixel + pointer_accel = 0 // No acceleration initially + pointer_max_speed = 10 // Max accelerated speed + + // Set sprite X position (low byte) + sprite_x0 = x_pos + sprite_y0 = y_pos + + // Clear X MSB for sprite 0 (x_pos is < 256) + temp = sprite_x_msb + temp = temp & %11111110 // Clear bit 0 + sprite_x_msb = temp + + // Set sprite color + sprite_color0 = color + + // Set sprite data pointer + sprite_pointer0 = sprite_data + + // Enable sprite 0 + sprite_enable = 1 +FEND + + +// ============================================================================ +// FUNC pointer_move_left +// Move pointer left by speed amount +// ============================================================================ +FUNC pointer_move_left({BYTE speed}) + + // Move left with bounds check + IF pointer_x >= speed + pointer_x = pointer_x - speed + ELSE + pointer_x = 0 + ENDIF + + IF pointer_x < 24 + pointer_x = 24 + ENDIF + + // Update VIC registers + sprite_x0 = pointer_x + + IF pointer_x > 255 + sprite_x_msb = sprite_x_msb | %00000001 + ELSE + sprite_x_msb = sprite_x_msb & %11111110 + ENDIF +FEND + + +// ============================================================================ +// FUNC pointer_move_right +// Move pointer right by speed amount +// ============================================================================ +FUNC pointer_move_right({BYTE speed}) + + // Move right with bounds check + pointer_x = pointer_x + speed + IF pointer_x > 320+20 + pointer_x = 320+20 + ENDIF + + // Update VIC registers + sprite_x0 = pointer_x + + IF pointer_x > 255 + sprite_x_msb = sprite_x_msb | %00000001 + ELSE + sprite_x_msb = sprite_x_msb & %11111110 + ENDIF +FEND + + +// ============================================================================ +// FUNC pointer_move_up +// Move pointer up by speed amount +// ============================================================================ +FUNC pointer_move_up({BYTE speed}) + + IF pointer_y >= speed + pointer_y = pointer_y - speed + ENDIF + + IF pointer_y < 50 + pointer_y = 50 // Stop at top edge, not 0 + ENDIF + + sprite_y0 = pointer_y +FEND + + +// ============================================================================ +// FUNC pointer_move_down +// Move pointer down by speed amount +// ============================================================================ +FUNC pointer_move_down({BYTE speed}) + WORD new_y + new_y = pointer_y + speed + + // Stop at bottom edge (around Y=229 for NTSC, 249 for PAL) + IF new_y > 247 + pointer_y = 247 + ELSE + pointer_y = new_y + ENDIF + + sprite_y0 = pointer_y +FEND + + +// ============================================================================ +// FUNC pointer_update_joystick +// Update pointer based on joystick input state with acceleration +// Expects joy_state to be set (from joystick.c65 library) +// joy_state bits: UP=bit0, DOWN=bit1, LEFT=bit2, RIGHT=bit3, FIRE=bit4 +// ============================================================================ +FUNC pointer_update_joystick({BYTE joy_state}) + BYTE actual_speed + BYTE any_direction + BYTE test_bit + + // Check if any direction is pressed (mask all direction bits) + any_direction = joy_state & %00001111 + + // Reset acceleration if joystick is neutral + IF any_direction == 0 + pointer_accel = 0 + ELSE + // Increase acceleration while moving + IF pointer_accel < 255 + pointer_accel++ + ENDIF + ENDIF + + // Calculate actual speed with acceleration + // Divide accel by 8 (shift right 3 times) + actual_speed = pointer_accel + ASM + lda |actual_speed| + ;lsr + lsr + lsr + lsr + sta |actual_speed| + ENDASM + + actual_speed = pointer_speed + actual_speed + IF actual_speed > pointer_max_speed + actual_speed = pointer_max_speed + ENDIF + + // Move in pressed directions + test_bit = joy_state & %00000100 // LEFT mask + IF test_bit + pointer_move_left(actual_speed) + ENDIF + + test_bit = joy_state & %00001000 // RIGHT mask + IF test_bit + pointer_move_right(actual_speed) + ENDIF + + test_bit = joy_state & %00000001 // UP mask + IF test_bit + pointer_move_up(actual_speed) + ENDIF + + test_bit = joy_state & %00000010 // DOWN mask + IF test_bit + pointer_move_down(actual_speed) + ENDIF +FEND + + +// ============================================================================ +// FUNC pointer_update_mouse +// Update pointer based on mouse delta movements +// Expects mouse_delta_x and mouse_delta_y to be set (from mouse.c65 library) +// Mouse deltas are signed 8-bit values +// ============================================================================ +FUNC pointer_update_mouse({BYTE delta_x} {BYTE delta_y}) + BYTE sign_x + BYTE sign_y + BYTE abs_x + BYTE abs_y + + // Handle X movement + sign_x = delta_x & %10000000 + IF sign_x + // Negative (moving left) + abs_x = 0 - delta_x // Get absolute value + IF abs_x > 0 + pointer_move_left(abs_x) + ENDIF + ELSE + // Positive (moving right) + IF delta_x > 0 + pointer_move_right(delta_x) + ENDIF + ENDIF + + // Handle Y movement + sign_y = delta_y & %10000000 + IF sign_y + // Negative (moving up) + abs_y = 0 - delta_y // Get absolute value + IF abs_y > 0 + pointer_move_up(abs_y) + ENDIF + ELSE + // Positive (moving down) + IF delta_y > 0 + pointer_move_down(delta_y) + ENDIF + ENDIF +FEND + + +// ============================================================================ +// FUNC pointer_set_speed +// Set pointer movement speed +// Parameters: +// speed: Pixels to move per movement (1-8 recommended) +// ============================================================================ +FUNC pointer_set_speed({BYTE speed}) + pointer_speed = speed +FEND + + +// ============================================================================ +// FUNC pointer_get_x +// Get current pointer X position +// Parameters: +// out:x_pos: X position (0-511) +// ============================================================================ +FUNC pointer_get_x(out:{WORD x_pos}) + x_pos = pointer_x +FEND + + +// ============================================================================ +// FUNC pointer_get_y +// Get current pointer Y position +// Returns: Y position in out parameter +// ============================================================================ +FUNC pointer_get_y(out:{BYTE y_pos}) + y_pos = pointer_y +FEND + + +// ============================================================================ +// FUNC pointer_enable +// Enable or disable pointer sprite +// Parameters: +// enable: 1 to enable, 0 to disable +// ============================================================================ +FUNC pointer_enable({BYTE enable}) + BYTE temp + temp = sprite_enable + + IF enable + temp = temp | %00000001 + ELSE + temp = temp & %11111110 + ENDIF + + sprite_enable = temp +FEND + + +// ============================================================================ +// FUNC pointer_set_color +// Change pointer sprite color +// Parameters: +// color: Color value (0-15) +// ============================================================================ +FUNC pointer_set_color({BYTE color}) + sprite_color0 = color +FEND + + +LABEL __skip_lib_pointer + +#IFEND diff --git a/random.c65 b/random.c65 new file mode 100644 index 0000000..5b33040 --- /dev/null +++ b/random.c65 @@ -0,0 +1,131 @@ + +#IFNDEF __lib_random +#DEFINE __lib_random 1 + +GOTO __skip_lib_random + +// LFSR Random Number Generator +// Combined 16-bit (period 65535) and 15-bit (period 32767) generators +// Total period: ~2.1 billion (65535 * 32767 = 2,147,385,345) +// +// Based on Hanno Behrens' implementation (LGPL) +// Adapted for c65 by using local variable storage +// +// Usage: +// BYTE rnd +// rand(rnd) // rnd now contains 0-255 +// +// For ZP acceleration, change the variable declarations below +// from regular addresses to ZP addresses (e.g., @ $f6) + +// Random state - initialized with default seeds +WORD rand_sr1 = $a55a +WORD rand_sr2 = $7653 + +// Initialize the random generator with default seeds +// Call once at startup (or just use rand_seed() with custom seed) +FUNC rand_init + rand_sr1 = $a55a + rand_sr2 = $7653 +FEND + +// Initialize with a custom seed value +// Good for seeding from user input timing +FUNC rand_seed({WORD seed}) + rand_sr1 = seed ^ $a55a + rand_sr2 = seed ^ $7653 + + // Ensure neither seed is zero + IF rand_sr1 == 0 + rand_sr1 = $a55a + ENDIF + IF rand_sr2 == 0 + rand_sr2 = $7653 + ENDIF +FEND + +// Internal: 16-bit LFSR with period 65535 +// Taps: 10, 12, 13, 15 (polynomial) +FUNC rand_lfsr64k + ASM + lda |rand_sr1|+1 + asl + asl + eor |rand_sr1|+1 + asl + eor |rand_sr1|+1 + asl + asl + eor |rand_sr1|+1 + asl + rol |rand_sr1| + rol |rand_sr1|+1 + ENDASM +FEND + +// Internal: 15-bit LFSR with period 32767 +// Taps: 13, 14 (polynomial) +FUNC rand_lfsr32k + ASM + lda |rand_sr2|+1 + asl + eor |rand_sr2|+1 + asl + asl + ror |rand_sr2| + rol |rand_sr2|+1 + ENDASM +FEND + +// Get random byte (0-255) +// Result returned in 'result' parameter +FUNC rand(out:{BYTE result}) + rand_lfsr64k() + rand_lfsr32k() + + ASM + lda |rand_sr1| + eor |rand_sr2| + sta |result| + ENDASM +FEND + +// Get random byte in range [0, max) +// Uses rejection sampling for unbiased results +FUNC rand_max({BYTE max} out:{BYTE result}) + BYTE r + BYTE mask + + // Calculate mask (smear highest bit down to get next power of 2 minus 1) + mask = max - 1 + ASM + lda |mask| + lsr + ora |mask| + sta |mask| + lsr + lsr + ora |mask| + sta |mask| + lsr + lsr + lsr + lsr + ora |mask| + sta |mask| + ENDASM + + // Rejection sampling loop + WHILE 1 + rand(r) + r = r & mask + IF r < max + result = r + EXIT + ENDIF + WEND +FEND + +LABEL __skip_lib_random + +#IFEND diff --git a/testgames.c65 b/testgames.c65 new file mode 100644 index 0000000..b66047b --- /dev/null +++ b/testgames.c65 @@ -0,0 +1,249 @@ + +#IFNDEF __lib_testgames +#DEFINE __lib_testgames 1 + +GOTO __skip_lib_testgames + +// ============================================================================ +// Test game setups for debugging specific scenarios +// To use: call setup_test_game_X() in main() instead of normal deal sequence +// ============================================================================ + +// ============================================================================ +// FUNC setup_test_game_tall_tableau +// Sets up tableau 0 with K->3 (11 cards) and red 2 in waste +// Tests compact rendering mode and edge case of moving 2 onto 3 +// ============================================================================ +FUNC setup_test_game_tall_tableau + WORD ptr @ $fa + + // Clear all piles + POINTER ptr -> pile_stock + POKE ptr[0] , 0 + + POINTER ptr -> pile_waste + POKE ptr[0] , 0 + + POINTER ptr -> pile_found0 + POKE ptr[0] , 0 + POINTER ptr -> pile_found1 + POKE ptr[0] , 0 + POINTER ptr -> pile_found2 + POKE ptr[0] , 0 + POINTER ptr -> pile_found3 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab0 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab1 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab2 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab3 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab4 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab5 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab6 + POKE ptr[0] , 0 + + // Setup tableau 0: K->3 alternating colors (all face-up) + // Spades King (38) -> Hearts Queen (11) -> Spades Jack (36) -> ... + POINTER ptr -> pile_tab0 + POKE ptr[0] , 11 // Count + POKE ptr[1] , 38 // Spades King + POKE ptr[2] , 11 // Hearts Queen + POKE ptr[3] , 36 // Spades Jack + POKE ptr[4] , 9 // Hearts 10 + POKE ptr[5] , 34 // Spades 9 + POKE ptr[6] , 7 // Hearts 8 + POKE ptr[7] , 32 // Spades 7 + POKE ptr[8] , 5 // Hearts 6 + POKE ptr[9] , 30 // Spades 5 + POKE ptr[10] , 3 // Hearts 4 + POKE ptr[11] , 28 // Spades 3 + + // Put red 2 (Hearts 2 = 1) in waste + POINTER ptr -> pile_waste + POKE ptr[0] , 1 // Count + POKE ptr[1] , 1 // Hearts 2 + + // Put some cards in stock for drawing + POINTER ptr -> pile_stock + POKE ptr[0] , 5 + POKE ptr[1] , 13 // Diamonds Ace + POKE ptr[2] , 26 // Spades Ace + POKE ptr[3] , 39 // Clubs Ace + POKE ptr[4] , 0 // Hearts Ace + POKE ptr[5] , 14 // Diamonds 2 +FEND + + +// ============================================================================ +// FUNC setup_test_game_one_move_to_win +// Sets up game 1 move from victory +// 3 foundations complete, 4th has Ace->Queen, Clubs King in waste +// ============================================================================ +FUNC setup_test_game_one_move_to_win + WORD ptr @ $fa + BYTE i + + // Clear all piles + POINTER ptr -> pile_stock + POKE ptr[0] , 0 + + POINTER ptr -> pile_waste + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab0 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab1 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab2 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab3 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab4 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab5 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab6 + POKE ptr[0] , 0 + + // Foundation 0: Hearts Ace->King (complete) + POINTER ptr -> pile_found0 + POKE ptr[0] , 13 + POKE ptr[1] , 0 + POKE ptr[2] , 1 + POKE ptr[3] , 2 + POKE ptr[4] , 3 + POKE ptr[5] , 4 + POKE ptr[6] , 5 + POKE ptr[7] , 6 + POKE ptr[8] , 7 + POKE ptr[9] , 8 + POKE ptr[10] , 9 + POKE ptr[11] , 10 + POKE ptr[12] , 11 + POKE ptr[13] , 12 + + // Foundation 1: Diamonds Ace->King (complete) + POINTER ptr -> pile_found1 + POKE ptr[0] , 13 + POKE ptr[1] , 13 + POKE ptr[2] , 14 + POKE ptr[3] , 15 + POKE ptr[4] , 16 + POKE ptr[5] , 17 + POKE ptr[6] , 18 + POKE ptr[7] , 19 + POKE ptr[8] , 20 + POKE ptr[9] , 21 + POKE ptr[10] , 22 + POKE ptr[11] , 23 + POKE ptr[12] , 24 + POKE ptr[13] , 25 + + // Foundation 2: Spades Ace->King (complete) + POINTER ptr -> pile_found2 + POKE ptr[0] , 13 + POKE ptr[1] , 26 + POKE ptr[2] , 27 + POKE ptr[3] , 28 + POKE ptr[4] , 29 + POKE ptr[5] , 30 + POKE ptr[6] , 31 + POKE ptr[7] , 32 + POKE ptr[8] , 33 + POKE ptr[9] , 34 + POKE ptr[10] , 35 + POKE ptr[11] , 36 + POKE ptr[12] , 37 + POKE ptr[13] , 38 + + // Foundation 3: Clubs Ace->Queen (missing King) + POINTER ptr -> pile_found3 + POKE ptr[0] , 12 + POKE ptr[1] , 39 + POKE ptr[2] , 40 + POKE ptr[3] , 41 + POKE ptr[4] , 42 + POKE ptr[5] , 43 + POKE ptr[6] , 44 + POKE ptr[7] , 45 + POKE ptr[8] , 46 + POKE ptr[9] , 47 + POKE ptr[10] , 48 + POKE ptr[11] , 49 + POKE ptr[12] , 50 + + // Waste: Clubs King (the winning move) + POINTER ptr -> pile_waste + POKE ptr[0] , 1 + POKE ptr[1] , 51 +FEND + + +// ============================================================================ +// FUNC setup_test_game_overflow +// Sets up very tall tableau to test screen overflow bounds checking +// Tableau 3 gets King->Ace (13 cards) all face-up to trigger compact mode +// ============================================================================ +FUNC setup_test_game_overflow + WORD ptr @ $fa + + // Clear all piles + POINTER ptr -> pile_stock + POKE ptr[0] , 0 + + POINTER ptr -> pile_waste + POKE ptr[0] , 0 + + POINTER ptr -> pile_found0 + POKE ptr[0] , 0 + POINTER ptr -> pile_found1 + POKE ptr[0] , 0 + POINTER ptr -> pile_found2 + POKE ptr[0] , 0 + POINTER ptr -> pile_found3 + POKE ptr[0] , 0 + + POINTER ptr -> pile_tab0 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab1 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab2 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab3 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab4 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab5 + POKE ptr[0] , 0 + POINTER ptr -> pile_tab6 + POKE ptr[0] , 0 + + // Setup tableau 3: King->Ace alternating colors (13 cards, all face-up) + // This will trigger compact rendering and test overflow protection + POINTER ptr -> pile_tab3 + POKE ptr[0] , 13 // Count + POKE ptr[1] , 38 // Spades King + POKE ptr[2] , 11 // Hearts Queen + POKE ptr[3] , 36 // Spades Jack + POKE ptr[4] , 9 // Hearts 10 + POKE ptr[5] , 34 // Spades 9 + POKE ptr[6] , 7 // Hearts 8 + POKE ptr[7] , 32 // Spades 7 + POKE ptr[8] , 5 // Hearts 6 + POKE ptr[9] , 30 // Spades 5 + POKE ptr[10] , 3 // Hearts 4 + POKE ptr[11] , 28 // Spades 3 + POKE ptr[12] , 1 // Hearts 2 + POKE ptr[13] , 26 // Spades Ace +FEND + + +LABEL __skip_lib_testgames + +#IFEND diff --git a/utils.c65 b/utils.c65 new file mode 100644 index 0000000..182a4d1 --- /dev/null +++ b/utils.c65 @@ -0,0 +1,142 @@ + +#IFNDEF __lib_utils +#DEFINE __lib_utils 1 + +GOTO __skip_lib_utils + +FUNC fill_mem({WORD start_addr @ $fa} {WORD end_addr} {BYTE value}) + + FOR start_addr = start_addr TO end_addr + POKE start_addr , value + NEXT +FEND + +FUNC mem_copy({WORD start_addr @ $fa} {WORD end_addr} {WORD target_addr @ $fc}) + BYTE value + + FOR start_addr = start_addr TO end_addr + value = PEEK start_addr + POKE target_addr , value + target_addr++ + NEXT +FEND + + +FUNC mem_copy_range({WORD start_addr @ $fa} {WORD target_addr @ $fc} {WORD range}) + BYTE value + + WHILE range + value = PEEK start_addr + POKE target_addr , value + start_addr++ + target_addr++ + range-- + WEND +FEND + +FUNC set_vic_bank({BYTE bank}) + + ASM + lda $dd02 + ora #3 + sta $dd02 + + ; sanitize and reverse value (so bank 0 = $0000. bank 1 = $4000 etc) + lda |bank| + and #3 + eor #3 + sta |bank| + + + lda $dd00 + and #%11111100 + ora |bank| + sta $dd00 + ENDASM +FEND + + +// Screen mem goes in banks of $0400 +// 0=$0000 +// 1=$0400 +// 2=$0800 +// ... +FUNC set_vic_screenmem({BYTE screenmem}) + + ASM + ; sanitize screenmem and move to relevant $d018 bits + lda |screenmem| + and #$0f + clc + rol + rol + rol + rol + sta |screenmem| + + lda $d018 + and #$0f + ora |screenmem| + sta $d018 + ENDASM + +FEND + +// Char mem goes in banks of $0800 +// 0=$0000 +// 1=$0800 +// 2=$1000 +// ... +// (Bitmaps in banks of $2000) +// 0=$0000 +// 4=$2000 +FUNC set_vic_charmem({BYTE charmem}) + + ASM + ; sanitize charmem and move to relevant $d018 bits + lda |charmem| + and #%00000111 + clc + rol + sta |charmem| + + lda $d018 + and #$f0 + ora |charmem| + sta $d018 + ENDASM + +FEND + +FUNC set_vic_ecm + ASM + lda $d011 + and #$1f ; leave DEN bit + all scroll stuff + ora #64 + sta $d011 + ENDASM +FEND + +// Wait for a keypress using direct CIA keyboard scan +// No interrupts needed +FUNC wait_key + BYTE row @ $dc01 + BYTE col @ $dc00 + + // Wait for NO key pressed first (debounce) + col = 0 + WHILE row <> $ff + WEND + + // Now wait for ANY key pressed + WHILE row == $ff + WEND + + // Wait for key release (debounce) + WHILE row <> $ff + WEND +FEND + +LABEL __skip_lib_utils + +#IFEND