Added a game menu to the system with possibility to restart the game and to choose game options.

This commit is contained in:
Mattias Hansson 2026-01-10 15:38:46 +01:00
parent bf7e30d4ea
commit e4d8e42821
8 changed files with 767 additions and 123 deletions

View file

@ -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"

View file

@ -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,128 +864,166 @@ 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)
// Initialize mouse and pointer
mouse_init()
pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color
// Enable sprite
pointer_enable(1)
// Initialize card display sprites
card_display_init()
#IFDEF TEST_GAMES
// Test game options (comment/uncomment one):
//setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
//setup_test_game_one_move_to_win() // 1 move from victory
setup_test_game_overflow() // K->A in tab3 to test screen overflow
#IFEND
// Initial render
fill_mem($0400, $0400+999, 0) // Clear screen
render_all_piles_initial()
BYTE raster @ $d012
// Outer loop for restart handling
WHILE 1 WHILE 1
// Wait for raster to avoid tearing
WHILE raster != 250
WEND
// Read mouse input (Port 1)
mouse_read()
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
// Read joystick input (Port 2) // Initialize mouse and pointer
joy_read_port2() mouse_init()
pointer_update_joystick(joy_state) pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color
// Get pointer position // Enable sprite
pointer_get_x(sprite_x) pointer_enable(1)
pointer_get_y(sprite_y)
// Convert to character coordinates // Initialize card display sprites
pointer_to_char_coords(sprite_x, sprite_y, char_col, char_row) card_display_init()
// Get pile under cursor
get_pile_at_coords(char_col, char_row, pile_at_cursor)
#IFDEF TEST_GAMES #IFDEF TEST_GAMES
// Visual feedback - show selection state and hover // Test game options (comment/uncomment one):
IF game_selected_pile != PILE_ID_NONE //setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste
// Something selected - show with cyan border //setup_test_game_one_move_to_win() // 1 move from victory
POKE $d020 , color_cyan setup_test_game_overflow() // K->A in tab3 to test screen overflow
ELSE
// Nothing selected - grey when hovering over pile, white otherwise
IF pile_at_cursor != PILE_ID_NONE
POKE $d020 , color_light_grey
ELSE
POKE $d020 , color_white
ENDIF
ENDIF
#IFEND #IFEND
// Detect click from both mouse button (bit 4) and joystick fire (bit 4)
// Mouse buttons: bit 4 = left button (active low, 0=pressed) BYTE raster @ $d012
// Joystick state: bit 4 = fire button (active high, 1=pressed after inversion) BYTE menu_key
// Combine both: if either has bit 4 clear (mouse) or set (joy), trigger click BYTE menu_action
BYTE combined_button
combined_button = mouse_buttons & %00010000 // Mouse: 0=pressed
IF combined_button != 0 // Initial render
// Mouse not pressed, check joystick fill_mem($0400, $0400+999, 0) // Clear screen
combined_button = joy_state & %00010000 // Joy: 1=pressed
IF combined_button render_all_piles_initial()
combined_button = 0 // Make it 0 (pressed) like mouse
ELSE // Inner game loop
combined_button = %00010000 // Not pressed WHILE 1
// Wait for raster to avoid tearing
WHILE raster != 250
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 ENDIF
ENDIF
button_state = combined_button // Read mouse input (Port 1)
detect_click(button_state, clicked) mouse_read()
pointer_update_mouse(mouse_delta_x, mouse_delta_y)
// Read joystick input (Port 2)
joy_read_port2()
pointer_update_joystick(joy_state)
// Get pointer position
pointer_get_x(sprite_x)
pointer_get_y(sprite_y)
// Convert to character coordinates
pointer_to_char_coords(sprite_x, sprite_y, char_col, char_row)
// Get pile under cursor
get_pile_at_coords(char_col, char_row, pile_at_cursor)
// Handle click if occurred
IF clicked
#IFDEF TEST_GAMES #IFDEF TEST_GAMES
// Visual feedback - flash border on click // Visual feedback - show selection state and hover
POKE $d020 , color_yellow IF game_selected_pile != PILE_ID_NONE
#IFEND // Something selected - show with cyan border
POKE $d020 , color_cyan
IF pile_at_cursor != PILE_ID_NONE
handle_click_on_pile(pile_at_cursor, char_row)
ELSE ELSE
// Click on empty area: deselect // Nothing selected - grey when hovering over pile, white otherwise
game_selected_pile = PILE_ID_NONE IF pile_at_cursor != PILE_ID_NONE
POKE $d020 , color_light_grey
ELSE
POKE $d020 , color_white
ENDIF
ENDIF
#IFEND
// Detect click from both mouse button (bit 4) and joystick fire (bit 4)
// Mouse buttons: bit 4 = left button (active low, 0=pressed)
// Joystick state: bit 4 = fire button (active high, 1=pressed after inversion)
// Combine both: if either has bit 4 clear (mouse) or set (joy), trigger click
BYTE combined_button
combined_button = mouse_buttons & %00010000 // Mouse: 0=pressed
IF combined_button != 0
// Mouse not pressed, check joystick
combined_button = joy_state & %00010000 // Joy: 1=pressed
IF combined_button
combined_button = 0 // Make it 0 (pressed) like mouse
ELSE
combined_button = %00010000 // Not pressed
ENDIF
ENDIF ENDIF
ENDIF
// Update selected card display (upper right corner using sprites) button_state = combined_button
BYTE display_card_id detect_click(button_state, clicked)
BYTE display_valid
BYTE display_rank
BYTE display_suit
get_selected_card_info(display_card_id, display_valid) // Handle click if occurred
IF display_valid IF clicked
// Show selected card rank and suit using sprites #IFDEF TEST_GAMES
card_id_to_suit_rank(display_card_id, display_suit, display_rank) // Visual feedback - flash border on click
card_display_show(display_rank, display_suit) POKE $d020 , color_yellow
ELSE #IFEND
// Hide sprites when nothing selected
card_display_hide()
ENDIF
// Check win condition IF pile_at_cursor != PILE_ID_NONE
check_win_condition(is_won) handle_click_on_pile(pile_at_cursor, char_row)
IF is_won ELSE
// Flash border or show message // Click on empty area: deselect
POKE $d020 , color_green game_selected_pile = PILE_ID_NONE
// Could add "YOU WIN" message here ENDIF
// For now, just keep running to allow admiring the win ENDIF
ENDIF
// Small delay to avoid reading mouse too fast // Update selected card display (upper right corner using sprites)
// Could sync to raster if needed for smoother experience BYTE display_card_id
WEND BYTE display_valid
BYTE display_rank
BYTE display_suit
get_selected_card_info(display_card_id, display_valid)
IF display_valid
// Show selected card rank and suit using sprites
card_id_to_suit_rank(display_card_id, display_suit, display_rank)
card_display_show(display_rank, display_suit)
ELSE
// Hide sprites when nothing selected
card_display_hide()
ENDIF
// Check win condition
check_win_condition(is_won)
IF is_won
// Flash border or show message
POKE $d020 , color_green
// Could add "YOU WIN" message here
// For now, just keep running to allow admiring the win
ENDIF
// Small delay to avoid reading mouse too fast
// Could sync to raster if needed for smoother experience
WEND // 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
View 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
View 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
View 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
View 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

View file

@ -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

View file

@ -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