Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b27cadc740 | |||
| 788c6ed60d | |||
| f4bff18f29 | |||
| a799d7695c | |||
| 263e019669 | |||
| e4d8e42821 |
14 changed files with 1162 additions and 215 deletions
21
README.md
21
README.md
|
|
@ -1,26 +1,17 @@
|
||||||
# Solitaire C64
|
# Solitaire C64
|
||||||
|
|
||||||
A classic Klondike solitaire card game for the Commodore 64, written in c65gm.
|
A classic Klondike solitaire card game for the Commodore 64.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Full Klondike Solitaire**: Stock, waste, 7 tableaus, and 4 foundation piles
|
- Full Klondike with draw-1 and draw-3 modes
|
||||||
- **Dual Input Support**: Play with joystick or 1351 mouse
|
- Joystick and 1351 mouse support
|
||||||
- **Draw Modes**: Toggle between draw-1 and draw-3 gameplay
|
- Extended color mode graphics with sprite cursor
|
||||||
- **Custom Graphics**: Character-based card rendering using extended color mode
|
- Hardware-seeded RNG for shuffling
|
||||||
- **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
|
## Building
|
||||||
|
|
||||||
Requires the c65gm compiler and ACME assembler:
|
Requires the [c65gm compiler](https://git.techserio.com/mattiashz/c65gm) and ACME assembler:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cm.sh
|
./cm.sh
|
||||||
|
|
|
||||||
69
carddeck.c65
69
carddeck.c65
|
|
@ -95,28 +95,13 @@ FEND
|
||||||
// Tab0=1, Tab1=2, Tab2=3, Tab3=4, Tab4=5, Tab5=6, Tab6=7 cards
|
// Tab0=1, Tab1=2, Tab2=3, Tab3=4, Tab4=5, Tab5=6, Tab6=7 cards
|
||||||
// Top card of each tableau is face up
|
// Top card of each tableau is face up
|
||||||
FUNC deal_tableaus
|
FUNC deal_tableaus
|
||||||
WORD ptr
|
deal_to_tableau(@pile_tab0, 1)
|
||||||
|
deal_to_tableau(@pile_tab1, 2)
|
||||||
POINTER ptr -> pile_tab0
|
deal_to_tableau(@pile_tab2, 3)
|
||||||
deal_to_tableau(ptr, 1)
|
deal_to_tableau(@pile_tab3, 4)
|
||||||
|
deal_to_tableau(@pile_tab4, 5)
|
||||||
POINTER ptr -> pile_tab1
|
deal_to_tableau(@pile_tab5, 6)
|
||||||
deal_to_tableau(ptr, 2)
|
deal_to_tableau(@pile_tab6, 7)
|
||||||
|
|
||||||
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
|
FEND
|
||||||
|
|
||||||
// Clear the seen array
|
// Clear the seen array
|
||||||
|
|
@ -166,7 +151,6 @@ FEND
|
||||||
// Validate entire deck across all piles
|
// Validate entire deck across all piles
|
||||||
// Returns: 0=OK, 1=duplicate, 2=missing
|
// Returns: 0=OK, 1=duplicate, 2=missing
|
||||||
FUNC validate_deck(out:{BYTE result})
|
FUNC validate_deck(out:{BYTE result})
|
||||||
WORD ptr @ $98
|
|
||||||
WORD seen_ptr @ $fc
|
WORD seen_ptr @ $fc
|
||||||
BYTE pile_result
|
BYTE pile_result
|
||||||
BYTE i
|
BYTE i
|
||||||
|
|
@ -178,95 +162,82 @@ FUNC validate_deck(out:{BYTE result})
|
||||||
result = VALIDATE_OK
|
result = VALIDATE_OK
|
||||||
|
|
||||||
// Check stock
|
// Check stock
|
||||||
POINTER ptr -> pile_stock
|
validate_pile(@pile_stock, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Check waste
|
// Check waste
|
||||||
POINTER ptr -> pile_waste
|
validate_pile(@pile_waste, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Check tableau piles
|
// Check tableau piles
|
||||||
POINTER ptr -> pile_tab0
|
validate_pile(@pile_tab0, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab1
|
validate_pile(@pile_tab1, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab2
|
validate_pile(@pile_tab2, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab3
|
validate_pile(@pile_tab3, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab4
|
validate_pile(@pile_tab4, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab5
|
validate_pile(@pile_tab5, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_tab6
|
validate_pile(@pile_tab6, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
// Check foundation piles
|
// Check foundation piles
|
||||||
POINTER ptr -> pile_found0
|
validate_pile(@pile_found0, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_found1
|
validate_pile(@pile_found1, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_found2
|
validate_pile(@pile_found2, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
POINTER ptr -> pile_found3
|
validate_pile(@pile_found3, pile_result)
|
||||||
validate_pile(ptr, pile_result)
|
|
||||||
IF pile_result
|
IF pile_result
|
||||||
result = pile_result
|
result = pile_result
|
||||||
EXIT
|
EXIT
|
||||||
|
|
|
||||||
20
cardgame.c65
20
cardgame.c65
|
|
@ -12,9 +12,12 @@ ASM
|
||||||
*=$3000
|
*=$3000
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
|
#INCLUDE <c64scr.c65>
|
||||||
#INCLUDE "utils.c65"
|
#INCLUDE "utils.c65"
|
||||||
#INCLUDE "cardconsts.c65"
|
#INCLUDE "cardconsts.c65"
|
||||||
|
#INCLUDE "pileconsts.c65"
|
||||||
#INCLUDE "piles.c65"
|
#INCLUDE "piles.c65"
|
||||||
|
#INCLUDE "gamestate.c65"
|
||||||
#INCLUDE "random.c65"
|
#INCLUDE "random.c65"
|
||||||
#INCLUDE "carddeck.c65"
|
#INCLUDE "carddeck.c65"
|
||||||
#INCLUDE "cardrender.c65"
|
#INCLUDE "cardrender.c65"
|
||||||
|
|
@ -22,6 +25,9 @@ ENDASM
|
||||||
#INCLUDE "joystick.c65"
|
#INCLUDE "joystick.c65"
|
||||||
#INCLUDE "mouse.c65"
|
#INCLUDE "mouse.c65"
|
||||||
#INCLUDE "pointer.c65"
|
#INCLUDE "pointer.c65"
|
||||||
|
#INCLUDE "cardsprites.c65"
|
||||||
|
#INCLUDE "keyboard.c65"
|
||||||
|
#INCLUDE "gamemenu.c65"
|
||||||
//#INCLUDE "joysticktests.c65"
|
//#INCLUDE "joysticktests.c65"
|
||||||
#IFDEF TEST_GAMES
|
#IFDEF TEST_GAMES
|
||||||
#INCLUDE "testgames.c65"
|
#INCLUDE "testgames.c65"
|
||||||
|
|
@ -36,6 +42,9 @@ FUNC main
|
||||||
sei
|
sei
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
|
lib_c64scr_blank() // screen later enabled just before gameloop starts.
|
||||||
|
|
||||||
|
|
||||||
// Save zero page for potential kernal operations later
|
// Save zero page for potential kernal operations later
|
||||||
save_zeropage()
|
save_zeropage()
|
||||||
|
|
||||||
|
|
@ -44,13 +53,12 @@ FUNC main
|
||||||
// Normal game: random shuffle and deal
|
// Normal game: random shuffle and deal
|
||||||
// Seed RNG with multiple entropy sources for better randomness
|
// Seed RNG with multiple entropy sources for better randomness
|
||||||
WORD timer_seed
|
WORD timer_seed
|
||||||
BYTE raster @ $d012
|
|
||||||
BYTE extra_entropy
|
BYTE extra_entropy
|
||||||
BYTE warmup
|
BYTE warmup
|
||||||
|
|
||||||
// Combine CIA timer with raster position
|
// Combine CIA timer with raster position
|
||||||
timer_seed = PEEKW $DC04
|
timer_seed = PEEKW $DC04
|
||||||
extra_entropy = raster
|
extra_entropy = vic_raster
|
||||||
timer_seed = timer_seed ^ extra_entropy
|
timer_seed = timer_seed ^ extra_entropy
|
||||||
rand_seed(timer_seed)
|
rand_seed(timer_seed)
|
||||||
|
|
||||||
|
|
@ -76,13 +84,7 @@ FUNC main
|
||||||
set_vic_charmem(4) // $2000
|
set_vic_charmem(4) // $2000
|
||||||
set_vic_ecm()
|
set_vic_ecm()
|
||||||
|
|
||||||
|
mem_copy(@card_charset, @card_charset_end, $2000)
|
||||||
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
|
fill_mem($d800, $d800+999, color_white) // Fill color mem
|
||||||
POKE $d020 , color_white //color_grey
|
POKE $d020 , color_white //color_grey
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ FEND
|
||||||
|
|
||||||
// Waste to Tableau
|
// Waste to Tableau
|
||||||
FUNC move_waste_to_tab({WORD tab_ptr} out:{BYTE success})
|
FUNC move_waste_to_tab({WORD tab_ptr} out:{BYTE success})
|
||||||
WORD waste_ptr @ $8c
|
WORD waste_ptr
|
||||||
POINTER waste_ptr -> pile_waste
|
POINTER waste_ptr -> pile_waste
|
||||||
BYTE card
|
BYTE card
|
||||||
BYTE valid
|
BYTE valid
|
||||||
|
|
@ -249,7 +249,7 @@ FEND
|
||||||
|
|
||||||
// Waste to Foundation
|
// Waste to Foundation
|
||||||
FUNC move_waste_to_found({WORD found_ptr} out:{BYTE success})
|
FUNC move_waste_to_found({WORD found_ptr} out:{BYTE success})
|
||||||
WORD waste_ptr @ $aa
|
WORD waste_ptr
|
||||||
POINTER waste_ptr -> pile_waste
|
POINTER waste_ptr -> pile_waste
|
||||||
BYTE card
|
BYTE card
|
||||||
BYTE valid
|
BYTE valid
|
||||||
|
|
@ -273,7 +273,7 @@ FUNC move_waste_to_found({WORD found_ptr} out:{BYTE success})
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
// Tableau to Foundation (top card only)
|
// Tableau to Foundation (top card only)
|
||||||
FUNC move_tab_to_found({WORD tab_ptr @ $8e} {WORD found_ptr} out:{BYTE success})
|
FUNC move_tab_to_found({WORD tab_ptr} {WORD found_ptr} out:{BYTE success})
|
||||||
BYTE card
|
BYTE card
|
||||||
BYTE valid
|
BYTE valid
|
||||||
BYTE is_facedown
|
BYTE is_facedown
|
||||||
|
|
@ -358,7 +358,7 @@ FUNC move_tab_to_tab({WORD src_ptr @ $b0} {WORD dst_ptr} {BYTE card_count} out:{
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
// Foundation to Tableau (optional rule - some variants allow this)
|
// Foundation to Tableau (optional rule - some variants allow this)
|
||||||
FUNC move_found_to_tab({WORD found_ptr @ $84} {WORD tab_ptr} out:{BYTE success})
|
FUNC move_found_to_tab({WORD found_ptr} {WORD tab_ptr} out:{BYTE success})
|
||||||
BYTE card
|
BYTE card
|
||||||
BYTE valid
|
BYTE valid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -709,6 +709,33 @@ ASM
|
||||||
!8 $00
|
!8 $00
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
|
// Sprite 25: "RETURN FOR MENU" text (XORed with $ff)
|
||||||
|
LABEL sprite_text_return_menu
|
||||||
|
ASM
|
||||||
|
!8 $ee, $ea, $e9
|
||||||
|
!8 $a8, $4a, $ad
|
||||||
|
!8 $cc, $4a, $cb
|
||||||
|
!8 $a8, $4a, $a9
|
||||||
|
!8 $ae, $4e, $a9
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $03, $bb, $80
|
||||||
|
!8 $02, $2a, $80
|
||||||
|
!8 $03, $2b, $00
|
||||||
|
!8 $02, $2a, $80
|
||||||
|
!8 $02, $3a, $80
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $0a, $e9, $50
|
||||||
|
!8 $0e, $8d, $50
|
||||||
|
!8 $0a, $cb, $50
|
||||||
|
!8 $0a, $89, $50
|
||||||
|
!8 $0a, $e9, $70
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $00, $00, $00
|
||||||
|
!8 $fe
|
||||||
|
ENDASM
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CARD DISPLAY SPRITE MANAGEMENT
|
// CARD DISPLAY SPRITE MANAGEMENT
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -716,7 +743,7 @@ ENDASM
|
||||||
// Uses sprites 1-5 (sprite 0 is reserved for pointer)
|
// Uses sprites 1-5 (sprite 0 is reserved for pointer)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// VIC-II Sprite registers for card display (sprites 1-5)
|
// VIC-II Sprite registers for card display (sprites 1-5) and menu hint (sprite 6)
|
||||||
// Note: sprite_x_msb and sprite_enable already declared in pointer.c65
|
// Note: sprite_x_msb and sprite_enable already declared in pointer.c65
|
||||||
BYTE sprite_x1 @ $d002 // Sprite 1 X
|
BYTE sprite_x1 @ $d002 // Sprite 1 X
|
||||||
BYTE sprite_y1 @ $d003 // Sprite 1 Y
|
BYTE sprite_y1 @ $d003 // Sprite 1 Y
|
||||||
|
|
@ -728,16 +755,20 @@ BYTE sprite_x4 @ $d008 // Sprite 4 X
|
||||||
BYTE sprite_y4 @ $d009 // Sprite 4 Y
|
BYTE sprite_y4 @ $d009 // Sprite 4 Y
|
||||||
BYTE sprite_x5 @ $d00a // Sprite 5 X
|
BYTE sprite_x5 @ $d00a // Sprite 5 X
|
||||||
BYTE sprite_y5 @ $d00b // Sprite 5 Y
|
BYTE sprite_y5 @ $d00b // Sprite 5 Y
|
||||||
|
BYTE sprite_x6 @ $d00c // Sprite 6 X (menu hint)
|
||||||
|
BYTE sprite_y6 @ $d00d // Sprite 6 Y (menu hint)
|
||||||
BYTE sprite_color1 @ $d028 // Sprite 1 color
|
BYTE sprite_color1 @ $d028 // Sprite 1 color
|
||||||
BYTE sprite_color2 @ $d029 // Sprite 2 color
|
BYTE sprite_color2 @ $d029 // Sprite 2 color
|
||||||
BYTE sprite_color3 @ $d02a // Sprite 3 color
|
BYTE sprite_color3 @ $d02a // Sprite 3 color
|
||||||
BYTE sprite_color4 @ $d02b // Sprite 4 color
|
BYTE sprite_color4 @ $d02b // Sprite 4 color
|
||||||
BYTE sprite_color5 @ $d02c // Sprite 5 color
|
BYTE sprite_color5 @ $d02c // Sprite 5 color
|
||||||
|
BYTE sprite_color6 @ $d02d // Sprite 6 color (menu hint)
|
||||||
BYTE sprite_pointer1 @ $07f9 // Sprite 1 pointer
|
BYTE sprite_pointer1 @ $07f9 // Sprite 1 pointer
|
||||||
BYTE sprite_pointer2 @ $07fa // Sprite 2 pointer
|
BYTE sprite_pointer2 @ $07fa // Sprite 2 pointer
|
||||||
BYTE sprite_pointer3 @ $07fb // Sprite 3 pointer
|
BYTE sprite_pointer3 @ $07fb // Sprite 3 pointer
|
||||||
BYTE sprite_pointer4 @ $07fc // Sprite 4 pointer
|
BYTE sprite_pointer4 @ $07fc // Sprite 4 pointer
|
||||||
BYTE sprite_pointer5 @ $07fd // Sprite 5 pointer
|
BYTE sprite_pointer5 @ $07fd // Sprite 5 pointer
|
||||||
|
BYTE sprite_pointer6 @ $07fe // Sprite 6 pointer (menu hint)
|
||||||
|
|
||||||
// Card display sprite positions (upper right corner)
|
// Card display sprite positions (upper right corner)
|
||||||
// Screen starts at (24,50), size 320x200
|
// Screen starts at (24,50), size 320x200
|
||||||
|
|
@ -888,12 +919,64 @@ FEND
|
||||||
FUNC card_display_hide
|
FUNC card_display_hide
|
||||||
BYTE temp
|
BYTE temp
|
||||||
|
|
||||||
// Disable sprites 1-5, keep sprite 0 (pointer)
|
// Disable sprites 1-5, keep sprite 0 (pointer) and sprite 6 (menu hint)
|
||||||
temp = sprite_enable
|
temp = sprite_enable
|
||||||
temp = temp & %11000001
|
temp = temp & %11000001
|
||||||
sprite_enable = temp
|
sprite_enable = temp
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MENU HINT SPRITE MANAGEMENT
|
||||||
|
// ============================================================================
|
||||||
|
// "RETURN FOR MENU" text displayed at bottom of screen during gameplay
|
||||||
|
// Uses sprite 6
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Menu hint sprite position (bottom right of screen)
|
||||||
|
WORD CONST MENU_HINT_X = 320 // Right edge (beyond 255, needs X MSB)
|
||||||
|
BYTE CONST MENU_HINT_Y = 230 // Bottom of screen (50 + 200 - 21 sprite height)
|
||||||
|
|
||||||
|
// Sprite block for "RETURN FOR MENU" text (sprite 25)
|
||||||
|
BYTE CONST SPRITE_BLOCK_MENU_HINT = 162 // 137 + 25 = 162
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_hint_show
|
||||||
|
// Show "RETURN FOR MENU" sprite at bottom of screen
|
||||||
|
// Call this when entering gameplay
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_hint_show
|
||||||
|
// Set sprite 6 pointer to "RETURN FOR MENU" sprite
|
||||||
|
sprite_pointer6 = SPRITE_BLOCK_MENU_HINT
|
||||||
|
|
||||||
|
// Position at bottom right
|
||||||
|
sprite_x6 = MENU_HINT_X
|
||||||
|
sprite_y6 = MENU_HINT_Y
|
||||||
|
|
||||||
|
// Set X MSB for sprite 6 (bit 6) if X > 255
|
||||||
|
IF MENU_HINT_X > 255
|
||||||
|
sprite_x_msb = sprite_x_msb | %01000000
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Set color to blue (6)
|
||||||
|
sprite_color6 = 6
|
||||||
|
|
||||||
|
// Enable sprite 6
|
||||||
|
sprite_enable = sprite_enable | %01000000
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_hint_hide
|
||||||
|
// Hide "RETURN FOR MENU" sprite
|
||||||
|
// Call this when entering menu
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_hint_hide
|
||||||
|
// Disable sprite 6
|
||||||
|
sprite_enable = sprite_enable & %10111111
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
LABEL __skip_lib_cardsprites
|
LABEL __skip_lib_cardsprites
|
||||||
|
|
||||||
#IFEND
|
#IFEND
|
||||||
|
|
|
||||||
149
charpad_rank_sprites/charpad_rank_sprites - Projectv2.asm
Normal file
149
charpad_rank_sprites/charpad_rank_sprites - Projectv2.asm
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
|
||||||
|
; 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 = 16
|
||||||
|
|
||||||
|
|
||||||
|
; Data block addresses (dummy values) and sizes...
|
||||||
|
|
||||||
|
addr_spriteset_data = $1000
|
||||||
|
size_spriteset_data = $400 ; (1024 bytes)
|
||||||
|
|
||||||
|
addr_spriteset_attrib_data = $1000
|
||||||
|
size_spriteset_attrib_data = $10 ; (16 bytes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; * INSERT EXAMPLE PROGRAM HERE! * (or just include this file in your project).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; SpriteSet Data...
|
||||||
|
; 16 images, 64 bytes per image, total size is 1024 ($400) 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
|
||||||
|
|
||||||
|
sprite_image_15
|
||||||
|
.byte $11,$15,$16,$57,$b5,$52,$33,$b5,$34,$57,$b5,$56,$51,$b1,$56,$ff
|
||||||
|
.byte $ff,$ff,$fc,$44,$7f,$fd,$d5,$7f,$fc,$d4,$ff,$fd,$d5,$7f,$fd,$c5
|
||||||
|
.byte $7f,$ff,$ff,$ff,$f5,$16,$af,$f1,$72,$af,$f5,$34,$af,$f5,$76,$af
|
||||||
|
.byte $f5,$16,$8f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$01
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; SpriteSet Attribute Data...
|
||||||
|
; 16 attributes, 1 per image, 8 bits each, total size is 16 ($10) 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,$01
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
157
gameloop.c65
157
gameloop.c65
|
|
@ -3,12 +3,16 @@
|
||||||
#DEFINE __lib_gameloop 1
|
#DEFINE __lib_gameloop 1
|
||||||
|
|
||||||
#INCLUDE "cardconsts.c65"
|
#INCLUDE "cardconsts.c65"
|
||||||
|
#INCLUDE "pileconsts.c65"
|
||||||
#INCLUDE "piles.c65"
|
#INCLUDE "piles.c65"
|
||||||
|
#INCLUDE "gamestate.c65"
|
||||||
#INCLUDE "cardmoves.c65"
|
#INCLUDE "cardmoves.c65"
|
||||||
#INCLUDE "cardrender.c65"
|
#INCLUDE "cardrender.c65"
|
||||||
#INCLUDE "mouse.c65"
|
#INCLUDE "mouse.c65"
|
||||||
#INCLUDE "pointer.c65"
|
#INCLUDE "pointer.c65"
|
||||||
#INCLUDE "cardsprites.c65"
|
#INCLUDE "cardsprites.c65"
|
||||||
|
#INCLUDE "keyboard.c65"
|
||||||
|
#INCLUDE "gamemenu.c65"
|
||||||
|
|
||||||
GOTO __skip_lib_gameloop
|
GOTO __skip_lib_gameloop
|
||||||
|
|
||||||
|
|
@ -24,27 +28,8 @@ GOTO __skip_lib_gameloop
|
||||||
// game_loop() // Never returns
|
// game_loop() // Never returns
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Pile identifiers
|
// Pile identifiers now in pileconsts.c65
|
||||||
BYTE CONST PILE_ID_NONE = 0
|
// Game state variables now in gamestate.c65
|
||||||
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)
|
// Layout constants (in character coordinates)
|
||||||
BYTE CONST LAYOUT_STOCK_COL = 0
|
BYTE CONST LAYOUT_STOCK_COL = 0
|
||||||
|
|
@ -418,6 +403,16 @@ FUNC get_selected_card_info(out:{BYTE card_id} out:{BYTE valid})
|
||||||
EXIT
|
EXIT
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
|
// Foundation: top card
|
||||||
|
IF game_selected_pile >= PILE_ID_FOUND0
|
||||||
|
IF game_selected_pile <= PILE_ID_FOUND3
|
||||||
|
card = PEEK pile_ptr[pile_count]
|
||||||
|
card_id = card & CARD_MASK
|
||||||
|
valid = 1
|
||||||
|
EXIT
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
// Tableau: bottom card of selection (the clicked card)
|
// Tableau: bottom card of selection (the clicked card)
|
||||||
IF game_selected_pile >= PILE_ID_TAB0
|
IF game_selected_pile >= PILE_ID_TAB0
|
||||||
IF game_selected_pile <= PILE_ID_TAB6
|
IF game_selected_pile <= PILE_ID_TAB6
|
||||||
|
|
@ -676,7 +671,7 @@ FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row})
|
||||||
IF game_selected_pile == PILE_ID_NONE
|
IF game_selected_pile == PILE_ID_NONE
|
||||||
#PRAGMA _P_USE_LONG_JUMP 0
|
#PRAGMA _P_USE_LONG_JUMP 0
|
||||||
|
|
||||||
// Can only select waste or tableau
|
// Can only select waste, tableau, or foundation (if enabled)
|
||||||
IF clicked_pile == PILE_ID_WASTE
|
IF clicked_pile == PILE_ID_WASTE
|
||||||
game_selected_pile = PILE_ID_WASTE
|
game_selected_pile = PILE_ID_WASTE
|
||||||
game_selected_card_count = 1
|
game_selected_card_count = 1
|
||||||
|
|
@ -717,7 +712,21 @@ FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row})
|
||||||
IF game_selected_card_count > 0
|
IF game_selected_card_count > 0
|
||||||
game_selected_pile = clicked_pile
|
game_selected_pile = clicked_pile
|
||||||
ENDIF
|
ENDIF
|
||||||
EXIT
|
ENDIF
|
||||||
|
|
||||||
|
// Foundation selection (if enabled)
|
||||||
|
IF game_allow_found_to_tab
|
||||||
|
IF is_foundation
|
||||||
|
// Check if foundation has cards before allowing selection
|
||||||
|
WORD found_ptr @ $e8
|
||||||
|
pile_id_to_pointer(clicked_pile, found_ptr)
|
||||||
|
BYTE found_count
|
||||||
|
found_count = PEEK found_ptr[0]
|
||||||
|
IF found_count > 0
|
||||||
|
game_selected_pile = clicked_pile
|
||||||
|
game_selected_card_count = 1
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
EXIT
|
EXIT
|
||||||
|
|
@ -786,11 +795,54 @@ FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row})
|
||||||
ENDIF
|
ENDIF
|
||||||
ENDIF
|
ENDIF
|
||||||
|
|
||||||
|
// Foundation to Tableau (if enabled)
|
||||||
|
IF game_allow_found_to_tab
|
||||||
|
BYTE selected_is_foundation
|
||||||
|
selected_is_foundation = 0
|
||||||
|
IF game_selected_pile >= PILE_ID_FOUND0
|
||||||
|
IF game_selected_pile <= PILE_ID_FOUND3
|
||||||
|
selected_is_foundation = 1
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
IF selected_is_foundation
|
||||||
|
IF is_tableau
|
||||||
|
move_found_to_tab(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
|
||||||
|
ENDIF
|
||||||
|
|
||||||
// Click on same pile or invalid destination: deselect
|
// Click on same pile or invalid destination: deselect
|
||||||
game_selected_pile = PILE_ID_NONE
|
game_selected_pile = PILE_ID_NONE
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC restart_game
|
||||||
|
// Restart the game with new shuffle and deal
|
||||||
|
// ============================================================================
|
||||||
|
FUNC restart_game
|
||||||
|
// Clear all piles (waste, foundations, tableaus)
|
||||||
|
clear_all_piles()
|
||||||
|
|
||||||
|
// Re-initialize deck and shuffle (RNG continues from current state)
|
||||||
|
stock_init()
|
||||||
|
stock_shuffle()
|
||||||
|
deal_tableaus()
|
||||||
|
|
||||||
|
// Reset game state
|
||||||
|
game_selected_pile = PILE_ID_NONE
|
||||||
|
game_selected_card_count = 0
|
||||||
|
game_prev_button_state = 0
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// FUNC render_all_piles_initial
|
// FUNC render_all_piles_initial
|
||||||
// Render all piles for initial game display
|
// Render all piles for initial game display
|
||||||
|
|
@ -819,22 +871,19 @@ FUNC game_loop
|
||||||
BYTE button_state
|
BYTE button_state
|
||||||
BYTE clicked
|
BYTE clicked
|
||||||
BYTE is_won
|
BYTE is_won
|
||||||
WORD src_ptr @ $8a
|
|
||||||
WORD src_end_ptr
|
|
||||||
WORD dst_ptr
|
|
||||||
|
|
||||||
// Copy sprite data to $2200 (sprite block 136)
|
// Copy sprite data to $2200 (sprite block 136)
|
||||||
// $2000-$21FF reserved for charset
|
// $2000-$21FF reserved for charset
|
||||||
POINTER src_ptr -> pointer_sprite_data
|
mem_copy(@pointer_sprite_data, @pointer_sprite_data_end, $2200)
|
||||||
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+)
|
// Copy card sprite data to $2240 (sprite blocks 137+)
|
||||||
// 25 sprites × 64 bytes = 1600 bytes
|
// 26 sprites × 64 bytes = 1664 bytes (includes "RETURN FOR MENU" sprite)
|
||||||
POINTER src_ptr -> sprite_rank_ace
|
mem_copy_range(@sprite_rank_ace, $2240, 64*26)
|
||||||
POINTER dst_ptr -> $2240
|
|
||||||
mem_copy_range(src_ptr, dst_ptr, 64*25)
|
|
||||||
|
|
||||||
|
// Outer loop for restart handling
|
||||||
|
WHILE 1
|
||||||
|
|
||||||
// Initialize mouse and pointer
|
// Initialize mouse and pointer
|
||||||
mouse_init()
|
mouse_init()
|
||||||
|
|
@ -846,28 +895,52 @@ FUNC game_loop
|
||||||
// Initialize card display sprites
|
// Initialize card display sprites
|
||||||
card_display_init()
|
card_display_init()
|
||||||
|
|
||||||
#IFDEF TEST_GAMES
|
// Show menu hint sprite
|
||||||
|
menu_hint_show()
|
||||||
|
|
||||||
|
#IFDEF TEST_GAMES
|
||||||
// Test game options (comment/uncomment one):
|
// Test game options (comment/uncomment one):
|
||||||
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
||||||
//setup_test_game_one_move_to_win() // 1 move from victory
|
//setup_test_game_one_move_to_win() // 1 move from victory
|
||||||
setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
||||||
#IFEND
|
#IFEND
|
||||||
|
|
||||||
|
BYTE menu_key
|
||||||
|
BYTE menu_action
|
||||||
|
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
fill_mem($0400, $0400+999, 0) // Clear screen
|
fill_mem($0400, $0400+999, 0) // Clear screen
|
||||||
|
|
||||||
render_all_piles_initial()
|
render_all_piles_initial()
|
||||||
|
|
||||||
BYTE raster @ $d012
|
lib_c64scr_show()
|
||||||
|
|
||||||
|
// Inner game loop
|
||||||
WHILE 1
|
WHILE 1
|
||||||
// Wait for raster to avoid tearing
|
// Wait for raster to avoid tearing
|
||||||
WHILE raster != 250
|
WHILE vic_raster != 250
|
||||||
WEND
|
WEND
|
||||||
|
|
||||||
// Read mouse input (Port 1)
|
// Read mouse input FIRST (before keyboard scan)
|
||||||
|
// Per Commodore 1351 manual: keyboard scan lines affect POT registers
|
||||||
|
// so mouse must be read before keyboard to avoid interference
|
||||||
mouse_read()
|
mouse_read()
|
||||||
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
|
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
|
||||||
|
|
||||||
|
// Check for menu key (Return or Left Arrow) AFTER mouse
|
||||||
|
key_scan(menu_key)
|
||||||
|
IF menu_key == KEY_RETURN
|
||||||
|
menu_show(menu_action)
|
||||||
|
IF menu_action == MENU_ACTION_RESTART
|
||||||
|
// Restart requested - break out to restart
|
||||||
|
BREAK
|
||||||
|
ENDIF
|
||||||
|
// Clear screen and re-render after resume
|
||||||
|
fill_mem($0400, $0400+999, 0)
|
||||||
|
render_all_piles_initial()
|
||||||
|
ENDIF
|
||||||
|
|
||||||
// Read joystick input (Port 2)
|
// Read joystick input (Port 2)
|
||||||
joy_read_port2()
|
joy_read_port2()
|
||||||
pointer_update_joystick(joy_state)
|
pointer_update_joystick(joy_state)
|
||||||
|
|
@ -957,7 +1030,11 @@ FUNC game_loop
|
||||||
|
|
||||||
// Small delay to avoid reading mouse too fast
|
// Small delay to avoid reading mouse too fast
|
||||||
// Could sync to raster if needed for smoother experience
|
// Could sync to raster if needed for smoother experience
|
||||||
WEND
|
WEND // End inner game loop
|
||||||
|
|
||||||
|
// If we get here, restart was requested
|
||||||
|
restart_game()
|
||||||
|
WEND // End outer restart loop
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
367
gamemenu.c65
Normal file
367
gamemenu.c65
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
|
||||||
|
#IFNDEF __lib_gamemenu
|
||||||
|
#DEFINE __lib_gamemenu 1
|
||||||
|
|
||||||
|
#INCLUDE "pileconsts.c65"
|
||||||
|
#INCLUDE "keyboard.c65"
|
||||||
|
#INCLUDE "utils.c65"
|
||||||
|
|
||||||
|
GOTO __skip_lib_gamemenu
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GAME MENU SYSTEM
|
||||||
|
// ============================================================================
|
||||||
|
// Pause game and display menu for configuration and restart
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// #INCLUDE "gamemenu.c65"
|
||||||
|
// BYTE action
|
||||||
|
// menu_show(action) // Returns MENU_ACTION_* constant
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Menu action constants
|
||||||
|
BYTE CONST MENU_ACTION_RESUME = 0
|
||||||
|
BYTE CONST MENU_ACTION_RESTART = 1
|
||||||
|
|
||||||
|
// Screen backup buffer (1000 bytes for screen, 1000 for color)
|
||||||
|
// Located at $C000-$C7CF (2000 bytes)
|
||||||
|
WORD CONST SCREEN_BACKUP = $C000
|
||||||
|
WORD CONST COLOR_BACKUP = $C3E8
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_save_screen
|
||||||
|
// Save current screen and color memory to backup buffer
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_save_screen
|
||||||
|
mem_copy_range($0400, SCREEN_BACKUP, 1000)
|
||||||
|
mem_copy_range($D800, COLOR_BACKUP, 1000)
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_restore_screen
|
||||||
|
// Restore screen and color memory from backup buffer
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_restore_screen
|
||||||
|
mem_copy_range(SCREEN_BACKUP, $0400, 1000)
|
||||||
|
mem_copy_range(COLOR_BACKUP, $D800, 1000)
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_switch_to_rom_charset
|
||||||
|
// Switch VIC-II to use ROM charset at $1800 (uppercase/lowercase)
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_switch_to_rom_charset
|
||||||
|
set_vic_charmem(3) // $1800 / $0800 = 3
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_switch_to_card_charset
|
||||||
|
// Switch VIC-II back to custom card charset at $2000
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_switch_to_card_charset
|
||||||
|
set_vic_charmem(4) // $2000 / $0800 = 4
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_print_string
|
||||||
|
// Print a string at a specific screen position
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_print_string({WORD screen_pos @ $e0} {WORD str_ptr @ $e2})
|
||||||
|
BYTE char
|
||||||
|
BYTE str_offset
|
||||||
|
|
||||||
|
str_offset = 0
|
||||||
|
char = PEEK str_ptr[str_offset]
|
||||||
|
WHILE char != 0
|
||||||
|
POKE screen_pos[0] , char
|
||||||
|
screen_pos++
|
||||||
|
str_offset++
|
||||||
|
char = PEEK str_ptr[str_offset]
|
||||||
|
WEND
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_update_draw_mode
|
||||||
|
// Update only the draw mode display (row 6) - no screen clear
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_update_draw_mode
|
||||||
|
WORD screen_pos @ $e0
|
||||||
|
|
||||||
|
// Clear the value area (cols 19-23)
|
||||||
|
screen_pos = 6*40+$0400+19
|
||||||
|
POKE screen_pos[0] , 32 // space
|
||||||
|
POKE screen_pos[1] , 32
|
||||||
|
POKE screen_pos[2] , 32
|
||||||
|
POKE screen_pos[3] , 32
|
||||||
|
POKE screen_pos[4] , 32
|
||||||
|
|
||||||
|
// Draw mode value - both values at same position (col 19)
|
||||||
|
// Show both [1] [3] with brackets around the selected one
|
||||||
|
screen_pos = 6*40+$0400+19
|
||||||
|
IF game_draw_mode == 1
|
||||||
|
POKE screen_pos[0] , 27 // '[' screen code
|
||||||
|
POKE screen_pos[1] , 49 // '1'
|
||||||
|
POKE screen_pos[2] , 29 // ']' screen code
|
||||||
|
POKE screen_pos[3] , 32 // space
|
||||||
|
POKE screen_pos[4] , 51 // '3'
|
||||||
|
ELSE
|
||||||
|
POKE screen_pos[0] , 49 // '1'
|
||||||
|
POKE screen_pos[1] , 32 // space
|
||||||
|
POKE screen_pos[2] , 27 // '[' screen code
|
||||||
|
POKE screen_pos[3] , 51 // '3'
|
||||||
|
POKE screen_pos[4] , 29 // ']' screen code
|
||||||
|
ENDIF
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_update_found_to_tab
|
||||||
|
// Update only the foundation to tableau display (row 9) - no screen clear
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_update_found_to_tab
|
||||||
|
WORD screen_pos @ $e4
|
||||||
|
WORD str_ptr @ $e6
|
||||||
|
|
||||||
|
// Clear the value area (cols 28-30)
|
||||||
|
screen_pos = 9*40+$0400+28
|
||||||
|
POKE screen_pos[0] , 32 // space
|
||||||
|
POKE screen_pos[1] , 32
|
||||||
|
POKE screen_pos[2] , 32
|
||||||
|
|
||||||
|
// Foundation to Tableau value
|
||||||
|
screen_pos = 9*40+$0400+28
|
||||||
|
IF game_allow_found_to_tab == 0
|
||||||
|
POINTER str_ptr -> str_off
|
||||||
|
ELSE
|
||||||
|
POINTER str_ptr -> str_on
|
||||||
|
ENDIF
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_render
|
||||||
|
// Render the full menu screen with current settings
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_render
|
||||||
|
// Clear screen
|
||||||
|
fill_mem($0400, $0400+999, 32) // 32 = space character
|
||||||
|
fill_mem($D800, $D800+999, 14) // Light blue text
|
||||||
|
|
||||||
|
// Title (centered at row 2)
|
||||||
|
menu_print_string(2*40+$0400+8, @str_title)
|
||||||
|
|
||||||
|
// Draw mode option (row 6)
|
||||||
|
menu_print_string(6*40+$0400+4, @str_draw_mode)
|
||||||
|
|
||||||
|
// Draw mode value and indicators
|
||||||
|
menu_update_draw_mode()
|
||||||
|
|
||||||
|
// Foundation to Tableau option (row 9)
|
||||||
|
menu_print_string(9*40+$0400+4, @str_found_to_tab)
|
||||||
|
|
||||||
|
// Foundation to Tableau value
|
||||||
|
menu_update_found_to_tab()
|
||||||
|
|
||||||
|
// Instructions for resume/restart (row 12+)
|
||||||
|
menu_print_string(12*40+$0400+4, @str_inst_f7)
|
||||||
|
menu_print_string(15*40+$0400+4, @str_inst_runstop)
|
||||||
|
|
||||||
|
// Controls info (row 20)
|
||||||
|
menu_print_string(20*40+$0400+2, @str_controls)
|
||||||
|
|
||||||
|
// License (row 23)
|
||||||
|
menu_print_string(23*40+$0400+1, @str_license)
|
||||||
|
|
||||||
|
// Set border color to menu color (purple)
|
||||||
|
POKE $d020 , 4
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC menu_show
|
||||||
|
// Display menu and handle input, return action to take
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_show(out:{BYTE action})
|
||||||
|
|
||||||
|
lib_c64scr_blank()
|
||||||
|
|
||||||
|
BYTE key
|
||||||
|
BYTE prev_key
|
||||||
|
|
||||||
|
action = MENU_ACTION_RESUME
|
||||||
|
prev_key = KEY_NONE
|
||||||
|
|
||||||
|
// Clear any card selection before entering menu
|
||||||
|
game_selected_pile = PILE_ID_NONE
|
||||||
|
game_selected_card_count = 0
|
||||||
|
|
||||||
|
// Hide menu hint sprite
|
||||||
|
menu_hint_hide()
|
||||||
|
|
||||||
|
// Save current screen state
|
||||||
|
menu_save_screen()
|
||||||
|
|
||||||
|
// Disable all sprites
|
||||||
|
POKE $D015 , 0 // Sprite enable register - disable all
|
||||||
|
|
||||||
|
// Switch to ROM charset and normal character mode
|
||||||
|
menu_switch_to_rom_charset()
|
||||||
|
clear_vic_ecm()
|
||||||
|
|
||||||
|
// Render menu
|
||||||
|
menu_render()
|
||||||
|
|
||||||
|
lib_c64scr_show()
|
||||||
|
|
||||||
|
// Wait for key release first (debounce)
|
||||||
|
key_scan(key)
|
||||||
|
WHILE key != KEY_NONE
|
||||||
|
key_scan(key)
|
||||||
|
WEND
|
||||||
|
|
||||||
|
// Menu loop
|
||||||
|
WHILE 1
|
||||||
|
key_scan(key)
|
||||||
|
|
||||||
|
// Only process on key press (transition from NONE to key)
|
||||||
|
IF key != KEY_NONE
|
||||||
|
IF prev_key == KEY_NONE
|
||||||
|
SWITCH key
|
||||||
|
CASE KEY_F1
|
||||||
|
// Toggle draw mode
|
||||||
|
IF game_draw_mode == 1
|
||||||
|
game_draw_mode = 3
|
||||||
|
ELSE
|
||||||
|
game_draw_mode = 1
|
||||||
|
ENDIF
|
||||||
|
menu_update_draw_mode()
|
||||||
|
|
||||||
|
CASE KEY_F3
|
||||||
|
// Toggle foundation to tableau
|
||||||
|
IF game_allow_found_to_tab == 0
|
||||||
|
game_allow_found_to_tab = 1
|
||||||
|
ELSE
|
||||||
|
game_allow_found_to_tab = 0
|
||||||
|
ENDIF
|
||||||
|
menu_update_found_to_tab()
|
||||||
|
|
||||||
|
CASE KEY_F7
|
||||||
|
// Resume game
|
||||||
|
action = MENU_ACTION_RESUME
|
||||||
|
BREAK
|
||||||
|
|
||||||
|
CASE KEY_RETURN
|
||||||
|
// Resume game
|
||||||
|
action = MENU_ACTION_RESUME
|
||||||
|
BREAK
|
||||||
|
|
||||||
|
CASE KEY_RUNSTOP
|
||||||
|
// Restart game
|
||||||
|
action = MENU_ACTION_RESTART
|
||||||
|
BREAK
|
||||||
|
ENDSWITCH
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
prev_key = key
|
||||||
|
WEND
|
||||||
|
|
||||||
|
// Wait for key release before returning
|
||||||
|
WHILE key != KEY_NONE
|
||||||
|
key_scan(key)
|
||||||
|
WEND
|
||||||
|
|
||||||
|
lib_c64scr_blank()
|
||||||
|
|
||||||
|
// Restore screen or clear for restart
|
||||||
|
IF action == MENU_ACTION_RESUME
|
||||||
|
menu_restore_screen()
|
||||||
|
ELSE
|
||||||
|
// Restarting - clear screen and reset colors
|
||||||
|
fill_mem($0400, $0400+999, 0)
|
||||||
|
fill_mem($d800, $d800+999, 1) // White color
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Switch back to card charset and ECM mode
|
||||||
|
menu_switch_to_card_charset()
|
||||||
|
set_vic_ecm()
|
||||||
|
|
||||||
|
// Re-enable pointer sprite (game loop will handle card display sprites)
|
||||||
|
pointer_enable(1)
|
||||||
|
|
||||||
|
// Show menu hint sprite again
|
||||||
|
menu_hint_show()
|
||||||
|
|
||||||
|
// Restore border color
|
||||||
|
POKE $d020 , 1 // White
|
||||||
|
|
||||||
|
lib_c64scr_show()
|
||||||
|
FEND
|
||||||
|
|
||||||
|
// Menu strings (null-terminated) - Using !scr for screen codes
|
||||||
|
LABEL str_title
|
||||||
|
ASM
|
||||||
|
!scr "Klondike Solitaire Menu", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_draw_mode
|
||||||
|
ASM
|
||||||
|
!scr "F1: Draw mode:", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_found_to_tab
|
||||||
|
ASM
|
||||||
|
!scr "F3: Foundation>Tableau:", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_on
|
||||||
|
ASM
|
||||||
|
!scr "On", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_off
|
||||||
|
ASM
|
||||||
|
!scr "Off", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_inst_f1
|
||||||
|
ASM
|
||||||
|
!scr "F1: Toggle draw mode (1/3)", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_inst_f3
|
||||||
|
ASM
|
||||||
|
!scr "F3: Toggle foundation moves", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_inst_f7
|
||||||
|
ASM
|
||||||
|
!scr "F7: Resume game", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_inst_runstop
|
||||||
|
ASM
|
||||||
|
!scr "Run/Stop: Restart game", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_controls
|
||||||
|
ASM
|
||||||
|
!scr "1351 Mouse Port 1 / Joystick Port 2", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL str_license
|
||||||
|
ASM
|
||||||
|
!scr "by Hackz0id/Siders 2026 (GNU GPL v2.0)", 0
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LABEL __skip_lib_gamemenu
|
||||||
|
|
||||||
|
#IFEND
|
||||||
25
gamestate.c65
Normal file
25
gamestate.c65
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
#IFNDEF __lib_gamestate
|
||||||
|
#DEFINE __lib_gamestate 1
|
||||||
|
|
||||||
|
GOTO __skip_lib_gamestate
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GAME STATE VARIABLES
|
||||||
|
// ============================================================================
|
||||||
|
// Global game state that needs to be accessible from multiple modules
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Game configuration
|
||||||
|
BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw
|
||||||
|
BYTE game_allow_found_to_tab = 0 // Allow foundation to tableau moves: 0=disallow, 1=allow
|
||||||
|
|
||||||
|
// Game interaction state
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
LABEL __skip_lib_gamestate
|
||||||
|
|
||||||
|
#IFEND
|
||||||
190
keyboard.c65
Normal file
190
keyboard.c65
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
|
||||||
|
#IFNDEF __lib_keyboard
|
||||||
|
#DEFINE __lib_keyboard 1
|
||||||
|
|
||||||
|
GOTO __skip_lib_keyboard
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// KEYBOARD INPUT DRIVER FOR COMMODORE 64
|
||||||
|
// ============================================================================
|
||||||
|
// Direct CIA keyboard matrix reading (no IRQ required)
|
||||||
|
// Uses non-interfering keyboard scan technique to avoid conflicts with
|
||||||
|
// joysticks on ports 1 and 2.
|
||||||
|
//
|
||||||
|
// Based on code from:
|
||||||
|
// https://codebase64.c64.org/doku.php?id=base:scanning_the_keyboard_without_joysticks_interfere
|
||||||
|
//
|
||||||
|
// This method converts keycodes to row/column values and performs double-check
|
||||||
|
// scanning to ensure joystick inputs don't interfere with keyboard detection.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// #INCLUDE "keyboard.c65"
|
||||||
|
// BYTE key
|
||||||
|
// key_scan(key)
|
||||||
|
// SWITCH key
|
||||||
|
// CASE KEY_F1
|
||||||
|
// // handle F1
|
||||||
|
// CASE KEY_F3
|
||||||
|
// // handle F3
|
||||||
|
// ENDSWITCH
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// CIA Port addresses
|
||||||
|
WORD CONST CIA_PORTA = $DC00 // Port A - ROW selection (write)
|
||||||
|
WORD CONST CIA_PORTB = $DC01 // Port B - COLUMN reading (read)
|
||||||
|
WORD CONST CIA_DDRA = $DC02 // Port A Data Direction Register
|
||||||
|
WORD CONST CIA_DDRB = $DC03 // Port B Data Direction Register
|
||||||
|
|
||||||
|
// Key constants (returned by key_scan)
|
||||||
|
BYTE CONST KEY_NONE = 0
|
||||||
|
BYTE CONST KEY_F1 = 1
|
||||||
|
BYTE CONST KEY_F3 = 2
|
||||||
|
BYTE CONST KEY_F7 = 3
|
||||||
|
BYTE CONST KEY_RUNSTOP = 4
|
||||||
|
BYTE CONST KEY_RETURN = 5
|
||||||
|
|
||||||
|
// C64 Keycode constants (for internal use)
|
||||||
|
// Based on: https://www.c64-wiki.com/wiki/Keyboard
|
||||||
|
BYTE CONST KEYCODE_RETURN = $01
|
||||||
|
BYTE CONST KEYCODE_F7F8 = $03
|
||||||
|
BYTE CONST KEYCODE_F1F2 = $04
|
||||||
|
BYTE CONST KEYCODE_F3F4 = $05
|
||||||
|
BYTE CONST KEYCODE_RUNSTOP = $3F
|
||||||
|
|
||||||
|
// Row and column lookup tables for keyboard scanning
|
||||||
|
LABEL key_row_table
|
||||||
|
ASM
|
||||||
|
!8 $FE, $FD, $FB, $F7, $EF, $DF, $BF, $7F
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
LABEL key_column_table
|
||||||
|
ASM
|
||||||
|
!8 $01, $02, $04, $08, $10, $20, $40, $80
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
// Key history for debouncing (one byte per tracked key)
|
||||||
|
BYTE key_history_f1
|
||||||
|
BYTE key_history_f3
|
||||||
|
BYTE key_history_f7
|
||||||
|
BYTE key_history_return
|
||||||
|
BYTE key_history_runstop
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC key_check_raw
|
||||||
|
// Low-level keyboard check for a specific keycode
|
||||||
|
// Uses non-interfering scan method with double-check to avoid joystick conflicts
|
||||||
|
// Returns: result = 1 if pressed, 0 if not pressed
|
||||||
|
// This comes from here:
|
||||||
|
// https://codebase64.c64.org/doku.php?id=base:scanning_the_keyboard_without_joysticks_interfere
|
||||||
|
// ============================================================================
|
||||||
|
FUNC key_check_raw({BYTE keycode} io:{BYTE histv} out:{BYTE result})
|
||||||
|
ASM
|
||||||
|
lda |keycode|
|
||||||
|
pha
|
||||||
|
lsr
|
||||||
|
lsr
|
||||||
|
lsr
|
||||||
|
tay
|
||||||
|
lda key_row_table,y
|
||||||
|
sta $dc00
|
||||||
|
pla
|
||||||
|
and #$07
|
||||||
|
tay
|
||||||
|
lda $dc01
|
||||||
|
and key_column_table,y
|
||||||
|
bne _nokey
|
||||||
|
lda #$ff ; key is checked 2nd time, to be sure
|
||||||
|
sta $dc00 ; joy #1 and #2 are not interfering
|
||||||
|
lda $dc01
|
||||||
|
and key_column_table,y
|
||||||
|
beq _nokey
|
||||||
|
|
||||||
|
cmp |histv| ; history value?
|
||||||
|
bne _newkey
|
||||||
|
clc ; case #3, key is held down. keep history & set C=0
|
||||||
|
bcc _backy
|
||||||
|
|
||||||
|
_nokey
|
||||||
|
asl ; case #1, no keypress, update history value 2x & set C=0
|
||||||
|
clc
|
||||||
|
!byte $24 ; BIT zero page - skips next 2-byte instruction
|
||||||
|
_newkey
|
||||||
|
sec ; case #2, key pressed&released. update history & set C=1
|
||||||
|
sta |histv|
|
||||||
|
|
||||||
|
_backy
|
||||||
|
lda #$ff
|
||||||
|
sta $dc00 ; set default value
|
||||||
|
lda #$7f
|
||||||
|
sta $dc01 ; set default value
|
||||||
|
|
||||||
|
; Convert carry flag to result: C=1 -> pressed (1), C=0 -> not pressed (0)
|
||||||
|
lda #0
|
||||||
|
rol ; shift carry into bit 0
|
||||||
|
sta |result|
|
||||||
|
ENDASM
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC key_scan
|
||||||
|
// Scan keyboard and return which key is pressed
|
||||||
|
// Returns KEY_* constant (KEY_NONE if no relevant key pressed)
|
||||||
|
// Uses non-interfering keyboard scan to avoid joystick conflicts
|
||||||
|
// ============================================================================
|
||||||
|
FUNC key_scan(out:{BYTE key_pressed})
|
||||||
|
BYTE check_result
|
||||||
|
|
||||||
|
key_pressed = KEY_NONE
|
||||||
|
|
||||||
|
// Set up DDR: Port A = output (rows), Port B = input (columns)
|
||||||
|
ASM
|
||||||
|
lda #$ff
|
||||||
|
sta $dc02 // CIA_DDRA
|
||||||
|
lda #$00
|
||||||
|
sta $dc03 // CIA_DDRB
|
||||||
|
ENDASM
|
||||||
|
|
||||||
|
// Check F1
|
||||||
|
key_check_raw(KEYCODE_F1F2, key_history_f1, check_result)
|
||||||
|
IF check_result > 0
|
||||||
|
key_pressed = KEY_F1
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check F3
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
key_check_raw(KEYCODE_F3F4, key_history_f3, check_result)
|
||||||
|
IF check_result > 0
|
||||||
|
key_pressed = KEY_F3
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check F7
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
key_check_raw(KEYCODE_F7F8, key_history_f7, check_result)
|
||||||
|
IF check_result > 0
|
||||||
|
key_pressed = KEY_F7
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check RUN/STOP
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
key_check_raw(KEYCODE_RUNSTOP, key_history_runstop, check_result)
|
||||||
|
IF check_result > 0
|
||||||
|
key_pressed = KEY_RUNSTOP
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check RETURN
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
key_check_raw(KEYCODE_RETURN, key_history_return, check_result)
|
||||||
|
IF check_result > 0
|
||||||
|
key_pressed = KEY_RETURN
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
|
LABEL __skip_lib_keyboard
|
||||||
|
|
||||||
|
#IFEND
|
||||||
31
pileconsts.c65
Normal file
31
pileconsts.c65
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
#IFNDEF __lib_pileconsts
|
||||||
|
#DEFINE __lib_pileconsts 1
|
||||||
|
|
||||||
|
GOTO __skip_lib_pileconsts
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PILE ID CONSTANTS
|
||||||
|
// ============================================================================
|
||||||
|
// Pile identifiers used throughout the game
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
LABEL __skip_lib_pileconsts
|
||||||
|
|
||||||
|
#IFEND
|
||||||
41
piles.c65
41
piles.c65
|
|
@ -73,6 +73,47 @@ ASM
|
||||||
!fill 14, 0
|
!fill 14, 0
|
||||||
ENDASM
|
ENDASM
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC clear_all_piles
|
||||||
|
// Clear all piles (waste, foundations, tableaus) by setting their counts to 0
|
||||||
|
// Stock is not cleared as it's managed by stock_init()
|
||||||
|
// ============================================================================
|
||||||
|
FUNC clear_all_piles
|
||||||
|
WORD ptr @ $f0
|
||||||
|
|
||||||
|
// Clear waste pile
|
||||||
|
POINTER ptr -> pile_waste
|
||||||
|
POKE ptr[0] , 0
|
||||||
|
|
||||||
|
// Clear all foundation piles
|
||||||
|
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
|
||||||
|
|
||||||
|
// Clear all tableau piles
|
||||||
|
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
|
||||||
|
FEND
|
||||||
|
|
||||||
|
|
||||||
LABEL __skip_lib_piles
|
LABEL __skip_lib_piles
|
||||||
|
|
||||||
#IFEND
|
#IFEND
|
||||||
|
|
|
||||||
20
utils.c65
20
utils.c65
|
|
@ -4,6 +4,18 @@
|
||||||
|
|
||||||
GOTO __skip_lib_utils
|
GOTO __skip_lib_utils
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// HARDWARE REGISTER DEFINITIONS
|
||||||
|
// ============================================================================
|
||||||
|
// Shared hardware register references to avoid duplicate declarations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
BYTE vic_raster @ $d012 // VIC-II raster line register
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// UTILITY FUNCTIONS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
FUNC fill_mem({WORD start_addr @ $fb} {WORD end_addr} {BYTE value})
|
FUNC fill_mem({WORD start_addr @ $fb} {WORD end_addr} {BYTE value})
|
||||||
|
|
||||||
FOR start_addr = start_addr TO end_addr
|
FOR start_addr = start_addr TO end_addr
|
||||||
|
|
@ -117,6 +129,14 @@ FUNC set_vic_ecm
|
||||||
ENDASM
|
ENDASM
|
||||||
FEND
|
FEND
|
||||||
|
|
||||||
|
FUNC clear_vic_ecm
|
||||||
|
ASM
|
||||||
|
lda $d011
|
||||||
|
and #$1f ; leave DEN bit + all scroll stuff, clear ECM bit
|
||||||
|
sta $d011
|
||||||
|
ENDASM
|
||||||
|
FEND
|
||||||
|
|
||||||
// Wait for a keypress using direct CIA keyboard scan
|
// Wait for a keypress using direct CIA keyboard scan
|
||||||
// No interrupts needed
|
// No interrupts needed
|
||||||
FUNC wait_key
|
FUNC wait_key
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue