diff --git a/cardgame.c65 b/cardgame.c65 index 980dfa7..bd9a178 100644 --- a/cardgame.c65 +++ b/cardgame.c65 @@ -14,7 +14,9 @@ ENDASM #INCLUDE "utils.c65" #INCLUDE "cardconsts.c65" +#INCLUDE "pileconsts.c65" #INCLUDE "piles.c65" +#INCLUDE "gamestate.c65" #INCLUDE "random.c65" #INCLUDE "carddeck.c65" #INCLUDE "cardrender.c65" @@ -22,6 +24,8 @@ ENDASM #INCLUDE "joystick.c65" #INCLUDE "mouse.c65" #INCLUDE "pointer.c65" +#INCLUDE "keyboard.c65" +#INCLUDE "gamemenu.c65" //#INCLUDE "joysticktests.c65" #IFDEF TEST_GAMES #INCLUDE "testgames.c65" diff --git a/gameloop.c65 b/gameloop.c65 index cc0baf5..2857265 100644 --- a/gameloop.c65 +++ b/gameloop.c65 @@ -3,12 +3,16 @@ #DEFINE __lib_gameloop 1 #INCLUDE "cardconsts.c65" +#INCLUDE "pileconsts.c65" #INCLUDE "piles.c65" +#INCLUDE "gamestate.c65" #INCLUDE "cardmoves.c65" #INCLUDE "cardrender.c65" #INCLUDE "mouse.c65" #INCLUDE "pointer.c65" #INCLUDE "cardsprites.c65" +#INCLUDE "keyboard.c65" +#INCLUDE "gamemenu.c65" GOTO __skip_lib_gameloop @@ -24,27 +28,8 @@ GOTO __skip_lib_gameloop // game_loop() // Never returns // ============================================================================ -// Pile identifiers -BYTE CONST PILE_ID_NONE = 0 -BYTE CONST PILE_ID_STOCK = 1 -BYTE CONST PILE_ID_WASTE = 2 -BYTE CONST PILE_ID_FOUND0 = 3 -BYTE CONST PILE_ID_FOUND1 = 4 -BYTE CONST PILE_ID_FOUND2 = 5 -BYTE CONST PILE_ID_FOUND3 = 6 -BYTE CONST PILE_ID_TAB0 = 7 -BYTE CONST PILE_ID_TAB1 = 8 -BYTE CONST PILE_ID_TAB2 = 9 -BYTE CONST PILE_ID_TAB3 = 10 -BYTE CONST PILE_ID_TAB4 = 11 -BYTE CONST PILE_ID_TAB5 = 12 -BYTE CONST PILE_ID_TAB6 = 13 - -// Game state variables -BYTE game_selected_pile // Currently selected pile ID (PILE_ID_NONE if none) -BYTE game_selected_card_count // Number of cards selected from tableau (1+ for tableau stacks) -BYTE game_prev_button_state // Previous mouse button state for click detection -BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw +// Pile identifiers now in pileconsts.c65 +// Game state variables now in gamestate.c65 // Layout constants (in character coordinates) BYTE CONST LAYOUT_STOCK_COL = 0 @@ -786,11 +771,54 @@ FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row}) 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 game_selected_pile = PILE_ID_NONE 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 // Render all piles for initial game display @@ -836,128 +864,166 @@ FUNC game_loop POINTER dst_ptr -> $2240 mem_copy_range(src_ptr, dst_ptr, 64*25) - // Initialize mouse and pointer - mouse_init() - pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color - // Enable sprite - pointer_enable(1) - - // Initialize card display sprites - card_display_init() - -#IFDEF TEST_GAMES - // Test game options (comment/uncomment one): - //setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste - //setup_test_game_one_move_to_win() // 1 move from victory - setup_test_game_overflow() // K->A in tab3 to test screen overflow -#IFEND - - // Initial render - fill_mem($0400, $0400+999, 0) // Clear screen - render_all_piles_initial() - - BYTE raster @ $d012 + // Outer loop for restart handling WHILE 1 - // Wait for raster to avoid tearing - WHILE raster != 250 - WEND - // Read mouse input (Port 1) - mouse_read() - pointer_update_mouse(mouse_delta_x, mouse_delta_y) - // Read joystick input (Port 2) - joy_read_port2() - pointer_update_joystick(joy_state) + // Initialize mouse and pointer + mouse_init() + pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color - // Get pointer position - pointer_get_x(sprite_x) - pointer_get_y(sprite_y) + // Enable sprite + pointer_enable(1) - // 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) + // Initialize card display sprites + card_display_init() #IFDEF TEST_GAMES - // Visual feedback - show selection state and hover - IF game_selected_pile != PILE_ID_NONE - // Something selected - show with cyan border - POKE $d020 , color_cyan - ELSE - // Nothing selected - grey when hovering over pile, white otherwise - IF pile_at_cursor != PILE_ID_NONE - POKE $d020 , color_light_grey - ELSE - POKE $d020 , color_white - ENDIF - ENDIF + // 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 - // 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 + + BYTE raster @ $d012 + BYTE menu_key + BYTE menu_action + + + // Initial render + fill_mem($0400, $0400+999, 0) // Clear screen + + render_all_piles_initial() + + // Inner game loop + 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 - button_state = combined_button - detect_click(button_state, clicked) + // Read mouse input (Port 1) + mouse_read() + pointer_update_mouse(mouse_delta_x, mouse_delta_y) + + // Read joystick input (Port 2) + joy_read_port2() + pointer_update_joystick(joy_state) + + // Get pointer position + pointer_get_x(sprite_x) + pointer_get_y(sprite_y) + + // Convert to character coordinates + pointer_to_char_coords(sprite_x, sprite_y, char_col, char_row) + + // Get pile under cursor + get_pile_at_coords(char_col, char_row, pile_at_cursor) - // Handle click if occurred - IF clicked #IFDEF TEST_GAMES - // Visual feedback - flash border on click - POKE $d020 , color_yellow - #IFEND - - IF pile_at_cursor != PILE_ID_NONE - handle_click_on_pile(pile_at_cursor, char_row) + // Visual feedback - show selection state and hover + IF game_selected_pile != PILE_ID_NONE + // Something selected - show with cyan border + POKE $d020 , color_cyan ELSE - // Click on empty area: deselect - game_selected_pile = PILE_ID_NONE + // Nothing selected - grey when hovering over pile, white otherwise + IF pile_at_cursor != PILE_ID_NONE + POKE $d020 , color_light_grey + ELSE + POKE $d020 , color_white + ENDIF + ENDIF + #IFEND + // Detect click from both mouse button (bit 4) and joystick fire (bit 4) + // Mouse buttons: bit 4 = left button (active low, 0=pressed) + // Joystick state: bit 4 = fire button (active high, 1=pressed after inversion) + // Combine both: if either has bit 4 clear (mouse) or set (joy), trigger click + BYTE combined_button + combined_button = mouse_buttons & %00010000 // Mouse: 0=pressed + IF combined_button != 0 + // Mouse not pressed, check joystick + combined_button = joy_state & %00010000 // Joy: 1=pressed + IF combined_button + combined_button = 0 // Make it 0 (pressed) like mouse + ELSE + combined_button = %00010000 // Not pressed + ENDIF ENDIF - ENDIF - // Update selected card display (upper right corner using sprites) - BYTE display_card_id - BYTE display_valid - BYTE display_rank - BYTE display_suit + button_state = combined_button + detect_click(button_state, clicked) - 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 + // Handle click if occurred + IF clicked + #IFDEF TEST_GAMES + // Visual feedback - flash border on click + POKE $d020 , color_yellow + #IFEND - // 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 + IF pile_at_cursor != PILE_ID_NONE + handle_click_on_pile(pile_at_cursor, char_row) + ELSE + // Click on empty area: deselect + game_selected_pile = PILE_ID_NONE + ENDIF + ENDIF - // Small delay to avoid reading mouse too fast - // Could sync to raster if needed for smoother experience - WEND + // Update selected card display (upper right corner using sprites) + BYTE display_card_id + BYTE display_valid + BYTE display_rank + BYTE display_suit + + get_selected_card_info(display_card_id, display_valid) + IF display_valid + // Show selected card rank and suit using sprites + card_id_to_suit_rank(display_card_id, display_suit, display_rank) + card_display_show(display_rank, display_suit) + ELSE + // Hide sprites when nothing selected + card_display_hide() + ENDIF + + // Check win condition + check_win_condition(is_won) + IF is_won + // Flash border or show message + POKE $d020 , color_green + // Could add "YOU WIN" message here + // For now, just keep running to allow admiring the win + ENDIF + + // Small delay to avoid reading mouse too fast + // Could sync to raster if needed for smoother experience + WEND // End inner game loop + + // If we get here, restart was requested + restart_game() + WEND // End outer restart loop FEND diff --git a/gamemenu.c65 b/gamemenu.c65 new file mode 100644 index 0000000..db9be21 --- /dev/null +++ b/gamemenu.c65 @@ -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 diff --git a/gamestate.c65 b/gamestate.c65 new file mode 100644 index 0000000..2f33195 --- /dev/null +++ b/gamestate.c65 @@ -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 diff --git a/keyboard.c65 b/keyboard.c65 new file mode 100644 index 0000000..b28cdc3 --- /dev/null +++ b/keyboard.c65 @@ -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 diff --git a/pileconsts.c65 b/pileconsts.c65 new file mode 100644 index 0000000..c0f8c83 --- /dev/null +++ b/pileconsts.c65 @@ -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 diff --git a/piles.c65 b/piles.c65 index 929082e..cf10014 100644 --- a/piles.c65 +++ b/piles.c65 @@ -73,6 +73,47 @@ ASM !fill 14, 0 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 #IFEND diff --git a/utils.c65 b/utils.c65 index 9935784..ed82796 100644 --- a/utils.c65 +++ b/utils.c65 @@ -117,6 +117,14 @@ FUNC set_vic_ecm ENDASM 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 // No interrupts needed FUNC wait_key