Added a game menu to the system with possibility to restart the game and to choose game options.
This commit is contained in:
parent
bf7e30d4ea
commit
e4d8e42821
8 changed files with 767 additions and 123 deletions
|
|
@ -14,7 +14,9 @@ ENDASM
|
||||||
|
|
||||||
#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 +24,8 @@ ENDASM
|
||||||
#INCLUDE "joystick.c65"
|
#INCLUDE "joystick.c65"
|
||||||
#INCLUDE "mouse.c65"
|
#INCLUDE "mouse.c65"
|
||||||
#INCLUDE "pointer.c65"
|
#INCLUDE "pointer.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"
|
||||||
|
|
|
||||||
118
gameloop.c65
118
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
|
||||||
|
|
@ -786,11 +771,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
|
||||||
|
|
@ -836,6 +864,12 @@ FUNC game_loop
|
||||||
POINTER dst_ptr -> $2240
|
POINTER dst_ptr -> $2240
|
||||||
mem_copy_range(src_ptr, dst_ptr, 64*25)
|
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()
|
||||||
pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color
|
pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color
|
||||||
|
|
@ -846,24 +880,52 @@ FUNC game_loop
|
||||||
// Initialize card display sprites
|
// Initialize card display sprites
|
||||||
card_display_init()
|
card_display_init()
|
||||||
|
|
||||||
#IFDEF TEST_GAMES
|
#IFDEF TEST_GAMES
|
||||||
// Test game options (comment/uncomment one):
|
// Test game options (comment/uncomment one):
|
||||||
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
|
||||||
//setup_test_game_one_move_to_win() // 1 move from victory
|
//setup_test_game_one_move_to_win() // 1 move from victory
|
||||||
setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
setup_test_game_overflow() // K->A in tab3 to test screen overflow
|
||||||
#IFEND
|
#IFEND
|
||||||
|
|
||||||
|
BYTE raster @ $d012
|
||||||
|
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
|
// Inner game loop
|
||||||
|
|
||||||
WHILE 1
|
WHILE 1
|
||||||
// Wait for raster to avoid tearing
|
// Wait for raster to avoid tearing
|
||||||
WHILE raster != 250
|
WHILE raster != 250
|
||||||
WEND
|
WEND
|
||||||
|
|
||||||
|
// Check for menu key (Return or Left Arrow)
|
||||||
|
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
|
||||||
|
IF menu_key == KEY_LEFT_ARROW
|
||||||
|
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 mouse input (Port 1)
|
// Read mouse input (Port 1)
|
||||||
mouse_read()
|
mouse_read()
|
||||||
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
|
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
|
||||||
|
|
@ -957,7 +1019,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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
313
gamemenu.c65
Normal file
313
gamemenu.c65
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
|
||||||
|
#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_render
|
||||||
|
// Render the menu screen with current settings
|
||||||
|
// ============================================================================
|
||||||
|
FUNC menu_render
|
||||||
|
WORD screen_pos @ $f0
|
||||||
|
WORD str_ptr @ $f2
|
||||||
|
BYTE draw_indicator
|
||||||
|
BYTE found_to_tab_indicator
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
screen_pos = 2*40+$0400+8
|
||||||
|
POINTER str_ptr -> str_title
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
|
||||||
|
// Draw mode option (row 6)
|
||||||
|
screen_pos = 6*40+$0400+4
|
||||||
|
POINTER str_ptr -> str_draw_mode
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
|
||||||
|
// Draw mode value
|
||||||
|
screen_pos = 6*40+$0400+24
|
||||||
|
IF game_draw_mode == 1
|
||||||
|
POKE screen_pos[0] , 49 // '1'
|
||||||
|
ELSE
|
||||||
|
POKE screen_pos[0] , 51 // '3'
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Draw mode indicator
|
||||||
|
IF game_draw_mode == 1
|
||||||
|
draw_indicator = 91 // '['
|
||||||
|
screen_pos = 6*40+$0400+23
|
||||||
|
POKE screen_pos[0] , draw_indicator
|
||||||
|
screen_pos = 6*40+$0400+25
|
||||||
|
POKE screen_pos[0] , 93 // ']'
|
||||||
|
ELSE
|
||||||
|
draw_indicator = 91 // '['
|
||||||
|
screen_pos = 6*40+$0400+26
|
||||||
|
POKE screen_pos[0] , draw_indicator
|
||||||
|
screen_pos = 6*40+$0400+28
|
||||||
|
POKE screen_pos[0] , 93 // ']'
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Foundation to Tableau option (row 9)
|
||||||
|
screen_pos = 9*40+$0400+4
|
||||||
|
POINTER str_ptr -> str_found_to_tab
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Instructions for resume/restart (row 14+)
|
||||||
|
screen_pos = 14*40+$0400+4
|
||||||
|
POINTER str_ptr -> str_inst_f7
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
|
||||||
|
screen_pos = 15*40+$0400+4
|
||||||
|
POINTER str_ptr -> str_inst_f8
|
||||||
|
menu_print_string(screen_pos, str_ptr)
|
||||||
|
|
||||||
|
// 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})
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
// 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_render()
|
||||||
|
|
||||||
|
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_render()
|
||||||
|
|
||||||
|
CASE KEY_F7
|
||||||
|
// Resume game
|
||||||
|
action = MENU_ACTION_RESUME
|
||||||
|
BREAK
|
||||||
|
|
||||||
|
CASE KEY_F8
|
||||||
|
// Restart game (SHIFT+F7)
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Restore border color
|
||||||
|
POKE $d020 , 1 // White
|
||||||
|
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_f8
|
||||||
|
ASM
|
||||||
|
!scr "F8: Restart game", 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
|
||||||
156
keyboard.c65
Normal file
156
keyboard.c65
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
|
||||||
|
#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)
|
||||||
|
//
|
||||||
|
// C64 Keyboard Matrix:
|
||||||
|
// $DC00 (Port A): Column select (write, active-low)
|
||||||
|
// $DC01 (Port B): Row read (read, active-low)
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// #INCLUDE "keyboard.c65"
|
||||||
|
// BYTE key
|
||||||
|
// key_scan(key)
|
||||||
|
// SWITCH key
|
||||||
|
// CASE KEY_F1
|
||||||
|
// // handle F1
|
||||||
|
// CASE KEY_F3
|
||||||
|
// // handle F3
|
||||||
|
// ENDSWITCH
|
||||||
|
//
|
||||||
|
// // Debouncing: wait for KEY_NONE
|
||||||
|
// WHILE key != KEY_NONE
|
||||||
|
// key_scan(key)
|
||||||
|
// WEND
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 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
|
||||||
|
BYTE CONST KEY_NONE = 0
|
||||||
|
BYTE CONST KEY_F1 = 1
|
||||||
|
BYTE CONST KEY_F3 = 2
|
||||||
|
BYTE CONST KEY_F7 = 3
|
||||||
|
BYTE CONST KEY_F8 = 4 // F7 + SHIFT
|
||||||
|
BYTE CONST KEY_RETURN = 5
|
||||||
|
BYTE CONST KEY_LEFT_ARROW = 6
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FUNC key_scan
|
||||||
|
// Scan keyboard and return which key is pressed
|
||||||
|
// Returns KEY_* constant (KEY_NONE if no relevant key pressed)
|
||||||
|
// C64 Keyboard Matrix:
|
||||||
|
// - Write to $DC00 (Port A) to select ROW
|
||||||
|
// - Read from $DC01 (Port B) to detect COLUMN
|
||||||
|
// ============================================================================
|
||||||
|
FUNC key_scan(out:{BYTE key_pressed})
|
||||||
|
BYTE col_data
|
||||||
|
BYTE temp
|
||||||
|
BYTE shift_held
|
||||||
|
BYTE saved_ddra
|
||||||
|
BYTE saved_ddrb
|
||||||
|
BYTE saved_porta
|
||||||
|
BYTE row0_data
|
||||||
|
BYTE row1_data
|
||||||
|
BYTE row6_data
|
||||||
|
|
||||||
|
key_pressed = KEY_NONE
|
||||||
|
shift_held = 0
|
||||||
|
|
||||||
|
// Save current DDR state
|
||||||
|
saved_ddra = PEEK CIA_DDRA
|
||||||
|
saved_ddrb = PEEK CIA_DDRB
|
||||||
|
saved_porta = PEEK CIA_PORTA
|
||||||
|
|
||||||
|
// Set up DDR: Port A = output (rows), Port B = input (columns)
|
||||||
|
POKE CIA_DDRA , $FF
|
||||||
|
POKE CIA_DDRB , $00
|
||||||
|
|
||||||
|
// Check F8 FIRST with two-read atomic check
|
||||||
|
// Read 1: Select rows 0+1+6 simultaneously ($BC = 10111100)
|
||||||
|
POKE CIA_PORTA , $BC
|
||||||
|
BYTE multi_row_data
|
||||||
|
multi_row_data = PEEK CIA_PORTB
|
||||||
|
|
||||||
|
// Read 2: Select ONLY row 0 ($FE = 11111110)
|
||||||
|
POKE CIA_PORTA , $FE
|
||||||
|
col_data = PEEK CIA_PORTB
|
||||||
|
|
||||||
|
// F8 detection: F7 pressed AND SHIFT pressed
|
||||||
|
// F7 at Row 0, Col 3 - check in row0 data
|
||||||
|
temp = col_data & $08
|
||||||
|
IF temp == 0
|
||||||
|
// F7 is pressed, now check if real SHIFT is pressed
|
||||||
|
// Compare multi-row read vs row-0-only read for cols 4 and 7
|
||||||
|
// If col 4 or 7 was low in multi-row but NOT in row-0-only, it's SHIFT
|
||||||
|
BYTE shift_cols
|
||||||
|
shift_cols = multi_row_data & $90 // Cols 4 and 7 in multi-row read
|
||||||
|
temp = col_data & $90 // Cols 4 and 7 in row 0 only read
|
||||||
|
// If multi-row has a low bit that row-0-only doesn't, it's from rows 1 or 6 (SHIFT)
|
||||||
|
IF shift_cols != temp
|
||||||
|
key_pressed = KEY_F8
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// If not F8, check other Row 0 keys (col_data already has row 0 from above)
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
|
||||||
|
// Check F1 (Row 0, Column 4)
|
||||||
|
temp = col_data & $10
|
||||||
|
IF temp == 0
|
||||||
|
key_pressed = KEY_F1
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check F3 (Row 0, Column 5)
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
temp = col_data & $20
|
||||||
|
IF temp == 0
|
||||||
|
key_pressed = KEY_F3
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check F7 (Row 0, Column 3)
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
temp = col_data & $08
|
||||||
|
IF temp == 0
|
||||||
|
key_pressed = KEY_F7
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check RETURN (Row 0, Column 1)
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
temp = col_data & $02
|
||||||
|
IF temp == 0
|
||||||
|
key_pressed = KEY_RETURN
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Check Left Arrow (Row 0, Column 7)
|
||||||
|
IF key_pressed == KEY_NONE
|
||||||
|
temp = col_data & $80
|
||||||
|
IF temp == 0
|
||||||
|
key_pressed = KEY_LEFT_ARROW
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
// Always restore saved DDR and port state
|
||||||
|
POKE CIA_PORTA , saved_porta
|
||||||
|
POKE CIA_DDRA , saved_ddra
|
||||||
|
POKE CIA_DDRB , saved_ddrb
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,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