#IFNDEF __lib_gameloop #DEFINE __lib_gameloop 1 #INCLUDE "cardconsts.c65" #INCLUDE "piles.c65" #INCLUDE "cardmoves.c65" #INCLUDE "cardrender.c65" #INCLUDE "mouse.c65" #INCLUDE "pointer.c65" #INCLUDE "cardsprites.c65" GOTO __skip_lib_gameloop // ============================================================================ // SOLITAIRE GAME LOOP AND INTERACTION SYSTEM // ============================================================================ // This library provides the main game loop, coordinate-to-pile mapping, // click detection, selection state, and selective rendering. // // Usage: // #INCLUDE "gameloop.c65" // game_init() // game_loop() // Never returns // ============================================================================ // Pile identifiers BYTE CONST PILE_ID_NONE = 0 BYTE CONST PILE_ID_STOCK = 1 BYTE CONST PILE_ID_WASTE = 2 BYTE CONST PILE_ID_FOUND0 = 3 BYTE CONST PILE_ID_FOUND1 = 4 BYTE CONST PILE_ID_FOUND2 = 5 BYTE CONST PILE_ID_FOUND3 = 6 BYTE CONST PILE_ID_TAB0 = 7 BYTE CONST PILE_ID_TAB1 = 8 BYTE CONST PILE_ID_TAB2 = 9 BYTE CONST PILE_ID_TAB3 = 10 BYTE CONST PILE_ID_TAB4 = 11 BYTE CONST PILE_ID_TAB5 = 12 BYTE CONST PILE_ID_TAB6 = 13 // Game state variables BYTE game_selected_pile // Currently selected pile ID (PILE_ID_NONE if none) BYTE game_selected_card_count // Number of cards selected from tableau (1+ for tableau stacks) BYTE game_prev_button_state // Previous mouse button state for click detection BYTE game_draw_mode = 1 // Stock draw mode: 1 or 3 cards per draw // Layout constants (in character coordinates) BYTE CONST LAYOUT_STOCK_COL = 0 BYTE CONST LAYOUT_STOCK_ROW = 0 BYTE CONST LAYOUT_WASTE_COL = 6 BYTE CONST LAYOUT_WASTE_ROW = 0 BYTE CONST LAYOUT_FOUND_COL = 16 // First foundation column BYTE CONST LAYOUT_FOUND_ROW = 0 BYTE CONST LAYOUT_TAB_COL = 0 // First tableau column BYTE CONST LAYOUT_TAB_ROW = 8 BYTE CONST PILE_WIDTH = 5 // Cards are 5 chars wide // ============================================================================ // FUNC pointer_to_char_coords // Convert sprite pixel coordinates to character coordinates // C64 sprite coords start at (24,50) for top-left of visible screen // Sprite coords: X (0-511), Y (0-250) // Char coords: col (0-39), row (0-24) // ============================================================================ FUNC pointer_to_char_coords({WORD sprite_x} {BYTE sprite_y} out:{BYTE char_col} out:{BYTE char_row}) WORD temp_x BYTE temp_y // Subtract sprite offset (24 pixels X, 50 pixels Y) temp_x = sprite_x - 24 temp_y = sprite_y - 50 // Divide X by 8 (shift right 3 times) // For WORD: need to shift high byte into low byte ASM lda |temp_x|+1 // High byte lsr // Shift right 1 sta |temp_x|+1 lda |temp_x| // Low byte ror // Rotate right (carry from high byte) lsr // Shift right 1 lsr // Shift right 1 sta |char_col| ENDASM // Divide Y by 8 (shift right 3 times) ASM lda |temp_y| lsr lsr lsr sta |char_row| ENDASM // Bounds checking (handle underflow as well) IF char_col > 39 char_col = 0 ENDIF IF char_row > 24 char_row = 0 ENDIF FEND // ============================================================================ // FUNC get_pile_at_coords // Determine which pile (if any) is at the given character coordinates // Returns PILE_ID_* constant // ============================================================================ FUNC get_pile_at_coords({BYTE char_col} {BYTE char_row} out:{BYTE pile_id}) pile_id = PILE_ID_NONE // Check top row (stock, waste, foundations) IF char_row < 7 // Cards are 7 rows tall // Stock: cols 0-4 IF char_col >= LAYOUT_STOCK_COL IF char_col < LAYOUT_STOCK_COL+PILE_WIDTH pile_id = PILE_ID_STOCK EXIT ENDIF ENDIF // Waste: cols 6-14 (fanned display is 9 chars wide) IF char_col >= LAYOUT_WASTE_COL IF char_col < LAYOUT_WASTE_COL+9 pile_id = PILE_ID_WASTE EXIT ENDIF ENDIF // Foundations: cols 16-35 (4 foundations x 5 chars, with spacing) IF char_col >= LAYOUT_FOUND_COL // Foundation 0: 16-20 IF char_col < LAYOUT_FOUND_COL+PILE_WIDTH pile_id = PILE_ID_FOUND0 EXIT ENDIF // Foundation 1: 21-25 IF char_col >= 21 IF char_col < 21+PILE_WIDTH pile_id = PILE_ID_FOUND1 EXIT ENDIF ENDIF // Foundation 2: 26-30 IF char_col >= 26 IF char_col < 26+PILE_WIDTH pile_id = PILE_ID_FOUND2 EXIT ENDIF ENDIF // Foundation 3: 31-35 IF char_col >= 31 IF char_col < 31+PILE_WIDTH pile_id = PILE_ID_FOUND3 EXIT ENDIF ENDIF ENDIF ENDIF // Check tableau row (row 8+) #PRAGMA _P_USE_LONG_JUMP 1 IF char_row >= LAYOUT_TAB_ROW #PRAGMA _P_USE_LONG_JUMP 0 // Tableaus can extend many rows down // Each tableau: 5 chars wide + 1 char gap (except last) // Tableau 0: cols 0-4 IF char_col < 5 pile_id = PILE_ID_TAB0 EXIT ENDIF // Tableau 1: cols 5-9 IF char_col >= 5 IF char_col < 10 pile_id = PILE_ID_TAB1 EXIT ENDIF ENDIF // Tableau 2: cols 10-14 IF char_col >= 10 IF char_col < 15 pile_id = PILE_ID_TAB2 EXIT ENDIF ENDIF // Tableau 3: cols 15-19 IF char_col >= 15 IF char_col < 20 pile_id = PILE_ID_TAB3 EXIT ENDIF ENDIF // Tableau 4: cols 20-24 IF char_col >= 20 IF char_col < 25 pile_id = PILE_ID_TAB4 EXIT ENDIF ENDIF // Tableau 5: cols 25-29 IF char_col >= 25 IF char_col < 30 pile_id = PILE_ID_TAB5 EXIT ENDIF ENDIF // Tableau 6: cols 30-34 IF char_col >= 30 IF char_col < 35 pile_id = PILE_ID_TAB6 EXIT ENDIF ENDIF ENDIF FEND // ============================================================================ // FUNC pile_id_to_pointer // Convert pile ID to pile data pointer // ============================================================================ FUNC pile_id_to_pointer({BYTE pile_id} out:{WORD pile_ptr}) WORD ptr SWITCH pile_id CASE PILE_ID_STOCK POINTER ptr -> pile_stock CASE PILE_ID_WASTE POINTER ptr -> pile_waste CASE PILE_ID_FOUND0 POINTER ptr -> pile_found0 CASE PILE_ID_FOUND1 POINTER ptr -> pile_found1 CASE PILE_ID_FOUND2 POINTER ptr -> pile_found2 CASE PILE_ID_FOUND3 POINTER ptr -> pile_found3 CASE PILE_ID_TAB0 POINTER ptr -> pile_tab0 CASE PILE_ID_TAB1 POINTER ptr -> pile_tab1 CASE PILE_ID_TAB2 POINTER ptr -> pile_tab2 CASE PILE_ID_TAB3 POINTER ptr -> pile_tab3 CASE PILE_ID_TAB4 POINTER ptr -> pile_tab4 CASE PILE_ID_TAB5 POINTER ptr -> pile_tab5 CASE PILE_ID_TAB6 POINTER ptr -> pile_tab6 DEFAULT ptr = 0 ENDSWITCH pile_ptr = ptr FEND // ============================================================================ // FUNC get_tableau_card_count // Determine how many cards to select from tableau based on click position // Scans screen memory to find which card was clicked, then counts from there // Returns the number of cards from the clicked position to the top // ============================================================================ FUNC get_tableau_card_count({WORD pile_ptr @ $f8} {BYTE click_row} {BYTE tableau_col} out:{BYTE card_count}) BYTE count BYTE faceup_count BYTE card BYTE is_facedown BYTE i BYTE screen_char BYTE card_rank BYTE card_suit BYTE card_id BYTE clicked_card_id BYTE found_match BYTE match_index BYTE rank_char WORD rank_map_ptr @ $f4 WORD screen_pos @ $f6 WORD row_offset count = PEEK pile_ptr[0] IF count == 0 card_count = 0 EXIT ENDIF // Count face-up cards and find top card faceup_count = 0 FOR i = 1 TO count card = PEEK pile_ptr[i] is_facedown = card & CARD_FACEDOWN IF is_facedown == 0 faceup_count++ ENDIF NEXT IF faceup_count == 0 card_count = 0 EXIT ENDIF // Read screen character at click position to identify clicked card // Tableau columns: 0,5,10,15,20,25,30 - rank is at column +1 // Calculate screen position for clicked row // row_offset = click_row * 40 row_offset = 0 FOR i = 1 TO click_row row_offset = row_offset + 40 NEXT // Rank character is at tableau_col + 1 (see cardrender.c65:113) screen_pos = $0400 + row_offset screen_pos = screen_pos + tableau_col screen_pos = screen_pos + 1 // Rank is at offset +1 screen_char = PEEK screen_pos[0] // Debug: show screen character we read //POKE $0400+35 , screen_char // Try to match screen character to a rank POINTER rank_map_ptr -> card_charcode_map clicked_card_id = $FF found_match = 0 // Scan face-up cards in pile to find match (bottom to top) FOR i = 1 TO count card = PEEK pile_ptr[i] is_facedown = card & CARD_FACEDOWN IF is_facedown == 0 card_id = card & CARD_MASK card_id_to_suit_rank(card_id, card_suit, card_rank) // Get rank character and compare rank_char = PEEK rank_map_ptr[card_rank] // Adjust for color (red cards have +64) IF card_suit < 2 rank_char = rank_char + 64 ENDIF IF rank_char == screen_char // Found match - use this card clicked_card_id = card_id match_index = i found_match = 1 BREAK // Exit loop early ENDIF ENDIF NEXT // If no match found, select top card IF found_match == 0 card_count = 1 EXIT ENDIF // Count cards from matched position to top card_count = count - match_index card_count = card_count + 1 FEND // ============================================================================ // FUNC get_selected_card_info // Returns info about currently selected card (if any) // Returns card_id (0-51) and valid flag (1=valid, 0=no selection) // ============================================================================ FUNC get_selected_card_info(out:{BYTE card_id} out:{BYTE valid}) WORD pile_ptr @ $80 BYTE pile_count BYTE card_index BYTE card valid = 0 card_id = 0 // No selection IF game_selected_pile == PILE_ID_NONE EXIT ENDIF // Get pile pointer pile_id_to_pointer(game_selected_pile, pile_ptr) IF pile_ptr == 0 EXIT ENDIF pile_count = PEEK pile_ptr[0] IF pile_count == 0 EXIT ENDIF // Waste: top card IF game_selected_pile == PILE_ID_WASTE card = PEEK pile_ptr[pile_count] card_id = card & CARD_MASK valid = 1 EXIT ENDIF // Tableau: bottom card of selection (the clicked card) IF game_selected_pile >= PILE_ID_TAB0 IF game_selected_pile <= PILE_ID_TAB6 // Calculate index of clicked card (bottom of selection) card_index = pile_count - game_selected_card_count card_index = card_index + 1 card = PEEK pile_ptr[card_index] card_id = card & CARD_MASK valid = 1 EXIT ENDIF ENDIF FEND // ============================================================================ // FUNC game_init // Initialize game state and input devices // ============================================================================ FUNC game_init game_selected_pile = PILE_ID_NONE game_selected_card_count = 0 game_prev_button_state = 0 // Initialize joystick (Port 2) joy_state = 0 FEND // ============================================================================ // FUNC detect_click // Detect button click (press and release) // Returns 1 if clicked, 0 if not // ============================================================================ FUNC detect_click({BYTE current_button_state} out:{BYTE clicked}) clicked = 0 // Check for left mouse button (bit 4, as shown in test) // Button is active-low, so 0 = pressed BYTE current_pressed BYTE prev_pressed BYTE temp // Check if current button is pressed (bit 4 = 0) temp = current_button_state & $10 current_pressed = 0 IF temp == 0 current_pressed = 1 ENDIF // Check if previous button was pressed temp = game_prev_button_state & $10 prev_pressed = 0 IF temp == 0 prev_pressed = 1 ENDIF // Click = was pressed, now released IF prev_pressed == 1 IF current_pressed == 0 clicked = 1 ENDIF ENDIF game_prev_button_state = current_button_state FEND // ============================================================================ // FUNC clear_tableau_column // Clear a tableau column from row 8 to bottom of screen // ============================================================================ FUNC clear_tableau_column({BYTE col_offset}) WORD pos @ $f6 BYTE row BYTE i // Clear from row 8 to row 24 (17 rows) // 8*40+$0400 is constant expression, then add variable col_offset pos = 8*40+$0400 + col_offset FOR row = 8 TO 24 // Clear 5 characters wide (card width) FOR i = 0 TO 4 POKE pos[i] , 0 NEXT pos = pos + 40 NEXT FEND // ============================================================================ // FUNC clear_waste_area // Clear the waste pile area (9 chars wide for fanned display) // ============================================================================ FUNC clear_waste_area WORD pos @ $f6 BYTE row BYTE i // Clear waste area: row 0, cols 6-14 (9 chars), 7 rows tall pos = 6+$0400 FOR row = 0 TO 6 FOR i = 0 TO 8 POKE pos[i] , 0 NEXT pos = pos + 40 NEXT FEND // ============================================================================ // FUNC render_pile_by_id // Render a specific pile by ID to its screen location // Clears the area first to remove artifacts from previous render // ============================================================================ FUNC render_pile_by_id({BYTE pile_id}) WORD pile_ptr @ $92 WORD screen_offset pile_id_to_pointer(pile_id, pile_ptr) IF pile_ptr == 0 EXIT ENDIF SWITCH pile_id CASE PILE_ID_STOCK render_stock_pile($0400, 0, pile_ptr) CASE PILE_ID_WASTE clear_waste_area() render_waste_pile($0400, 6, pile_ptr, game_draw_mode) CASE PILE_ID_FOUND0 render_foundation_pile($0400, 16, pile_ptr) CASE PILE_ID_FOUND1 render_foundation_pile($0400, 21, pile_ptr) CASE PILE_ID_FOUND2 render_foundation_pile($0400, 26, pile_ptr) CASE PILE_ID_FOUND3 render_foundation_pile($0400, 31, pile_ptr) CASE PILE_ID_TAB0 clear_tableau_column(0) render_tableau_pile($0400, 8*40, pile_ptr) CASE PILE_ID_TAB1 clear_tableau_column(5) render_tableau_pile($0400, 8*40+5, pile_ptr) CASE PILE_ID_TAB2 clear_tableau_column(10) render_tableau_pile($0400, 8*40+10, pile_ptr) CASE PILE_ID_TAB3 clear_tableau_column(15) render_tableau_pile($0400, 8*40+15, pile_ptr) CASE PILE_ID_TAB4 clear_tableau_column(20) render_tableau_pile($0400, 8*40+20, pile_ptr) CASE PILE_ID_TAB5 clear_tableau_column(25) render_tableau_pile($0400, 8*40+25, pile_ptr) CASE PILE_ID_TAB6 clear_tableau_column(30) render_tableau_pile($0400, 8*40+30, pile_ptr) ENDSWITCH FEND // ============================================================================ // FUNC check_win_condition // Check if all 4 foundations are complete (13 cards each = King on top) // Returns 1 if won, 0 otherwise // ============================================================================ FUNC check_win_condition(out:{BYTE is_won}) WORD ptr @ $a4 BYTE count is_won = 1 // Assume won, set to 0 if any foundation incomplete // Check foundation 0 POINTER ptr -> pile_found0 count = PEEK ptr[0] IF count != 13 is_won = 0 EXIT ENDIF // Check foundation 1 POINTER ptr -> pile_found1 count = PEEK ptr[0] IF count != 13 is_won = 0 EXIT ENDIF // Check foundation 2 POINTER ptr -> pile_found2 count = PEEK ptr[0] IF count != 13 is_won = 0 EXIT ENDIF // Check foundation 3 POINTER ptr -> pile_found3 count = PEEK ptr[0] IF count != 13 is_won = 0 EXIT ENDIF FEND // ============================================================================ // FUNC handle_click_on_pile // Handle a click on a specific pile // Implements game logic for selection and move execution // ============================================================================ FUNC handle_click_on_pile({BYTE clicked_pile} {BYTE click_row}) BYTE is_foundation BYTE is_tableau BYTE selected_is_tableau BYTE success WORD src_ptr WORD dst_ptr //WORD tab_ptr @ $f8 WORD tab_ptr BYTE tab_col BYTE tab_index BYTE j // Determine pile type is_foundation = 0 IF clicked_pile >= PILE_ID_FOUND0 IF clicked_pile <= PILE_ID_FOUND3 is_foundation = 1 ENDIF ENDIF is_tableau = 0 IF clicked_pile >= PILE_ID_TAB0 IF clicked_pile <= PILE_ID_TAB6 is_tableau = 1 ENDIF ENDIF // If nothing selected, select this pile #PRAGMA _P_USE_LONG_JUMP 1 IF game_selected_pile == PILE_ID_NONE #PRAGMA _P_USE_LONG_JUMP 0 // Can only select waste or tableau IF clicked_pile == PILE_ID_WASTE game_selected_pile = PILE_ID_WASTE game_selected_card_count = 1 EXIT ENDIF IF clicked_pile == PILE_ID_STOCK // Stock: draw cards move_stock_to_waste(game_draw_mode, success) IF success render_pile_by_id(PILE_ID_STOCK) render_pile_by_id(PILE_ID_WASTE) ELSE // Stock empty, reset from waste move_reset_stock(success) IF success render_pile_by_id(PILE_ID_STOCK) render_pile_by_id(PILE_ID_WASTE) ENDIF ENDIF EXIT ENDIF #PRAGMA _P_USE_LONG_JUMP 1 IF is_tableau #PRAGMA _P_USE_LONG_JUMP 0 // Select tableau - calculate how many cards based on click position pile_id_to_pointer(clicked_pile, tab_ptr) // Calculate tableau column: 0,5,10,15,20,25,30 tab_index = clicked_pile - PILE_ID_TAB0 tab_col = 0 FOR j = 1 TO tab_index tab_col = tab_col + 5 NEXT get_tableau_card_count(tab_ptr, click_row, tab_col, game_selected_card_count) IF game_selected_card_count > 0 game_selected_pile = clicked_pile ENDIF EXIT ENDIF EXIT ENDIF // Something is selected, try to move to destination pile_id_to_pointer(game_selected_pile, src_ptr) pile_id_to_pointer(clicked_pile, dst_ptr) // Waste to Foundation IF game_selected_pile == PILE_ID_WASTE IF is_foundation move_waste_to_found(dst_ptr, success) IF success render_pile_by_id(PILE_ID_WASTE) render_pile_by_id(clicked_pile) ENDIF game_selected_pile = PILE_ID_NONE EXIT ENDIF ENDIF // Waste to Tableau IF game_selected_pile == PILE_ID_WASTE IF is_tableau move_waste_to_tab(dst_ptr, success) IF success render_pile_by_id(PILE_ID_WASTE) render_pile_by_id(clicked_pile) ENDIF game_selected_pile = PILE_ID_NONE EXIT ENDIF ENDIF // Tableau to Foundation/Tableau - check if selected pile is tableau selected_is_tableau = 0 IF game_selected_pile >= PILE_ID_TAB0 IF game_selected_pile <= PILE_ID_TAB6 selected_is_tableau = 1 ENDIF ENDIF IF selected_is_tableau IF is_foundation move_tab_to_found(src_ptr, dst_ptr, success) IF success render_pile_by_id(game_selected_pile) render_pile_by_id(clicked_pile) ENDIF game_selected_pile = PILE_ID_NONE EXIT ENDIF ENDIF // Tableau to Tableau IF selected_is_tableau IF is_tableau move_tab_to_tab(src_ptr, dst_ptr, game_selected_card_count, success) IF success render_pile_by_id(game_selected_pile) render_pile_by_id(clicked_pile) ENDIF game_selected_pile = PILE_ID_NONE EXIT ENDIF ENDIF // Click on same pile or invalid destination: deselect game_selected_pile = PILE_ID_NONE FEND // ============================================================================ // FUNC render_all_piles_initial // Render all piles for initial game display // ============================================================================ FUNC render_all_piles_initial BYTE pile_id pile_id = PILE_ID_STOCK WHILE pile_id <= PILE_ID_TAB6 render_pile_by_id(pile_id) pile_id++ WEND FEND // ============================================================================ // FUNC game_loop // Main game loop - never returns // ============================================================================ FUNC game_loop WORD sprite_x BYTE sprite_y BYTE char_col BYTE char_row BYTE pile_at_cursor BYTE button_state BYTE clicked BYTE is_won WORD src_ptr @ $8a WORD src_end_ptr WORD dst_ptr // Copy sprite data to $2200 (sprite block 136) // $2000-$21FF reserved for charset POINTER src_ptr -> pointer_sprite_data POINTER src_end_ptr -> pointer_sprite_data_end POINTER dst_ptr -> $2200 mem_copy(src_ptr, src_end_ptr, dst_ptr) // Copy card sprite data to $2240 (sprite blocks 137+) // 25 sprites × 64 bytes = 1600 bytes POINTER src_ptr -> sprite_rank_ace POINTER dst_ptr -> $2240 mem_copy_range(src_ptr, dst_ptr, 64*25) // Initialize mouse and pointer mouse_init() pointer_init(160, 100, color_red, 136) // Sprite block 136 = $2200, bright red color // Enable sprite pointer_enable(1) // Initialize card display sprites card_display_init() #IFDEF TEST_GAMES // Test game options (comment/uncomment one): //setup_test_game_tall_tableau() // K->3 in tab0, red 2 in waste //setup_test_game_one_move_to_win() // 1 move from victory setup_test_game_overflow() // K->A in tab3 to test screen overflow #IFEND // Initial render fill_mem($0400, $0400+999, 0) // Clear screen render_all_piles_initial() BYTE raster @ $d012 WHILE 1 // Wait for raster to avoid tearing WHILE raster != 250 WEND // Read mouse input (Port 1) mouse_read() pointer_update_mouse(mouse_delta_x, mouse_delta_y) // Read joystick input (Port 2) joy_read_port2() pointer_update_joystick(joy_state) // Get pointer position pointer_get_x(sprite_x) pointer_get_y(sprite_y) // Convert to character coordinates pointer_to_char_coords(sprite_x, sprite_y, char_col, char_row) // Get pile under cursor get_pile_at_coords(char_col, char_row, pile_at_cursor) #IFDEF TEST_GAMES // Visual feedback - show selection state and hover IF game_selected_pile != PILE_ID_NONE // Something selected - show with cyan border POKE $d020 , color_cyan ELSE // Nothing selected - grey when hovering over pile, white otherwise IF pile_at_cursor != PILE_ID_NONE POKE $d020 , color_light_grey ELSE POKE $d020 , color_white ENDIF ENDIF #IFEND // Detect click from both mouse button (bit 4) and joystick fire (bit 4) // Mouse buttons: bit 4 = left button (active low, 0=pressed) // Joystick state: bit 4 = fire button (active high, 1=pressed after inversion) // Combine both: if either has bit 4 clear (mouse) or set (joy), trigger click BYTE combined_button combined_button = mouse_buttons & %00010000 // Mouse: 0=pressed IF combined_button != 0 // Mouse not pressed, check joystick combined_button = joy_state & %00010000 // Joy: 1=pressed IF combined_button combined_button = 0 // Make it 0 (pressed) like mouse ELSE combined_button = %00010000 // Not pressed ENDIF ENDIF button_state = combined_button detect_click(button_state, clicked) // Handle click if occurred IF clicked #IFDEF TEST_GAMES // Visual feedback - flash border on click POKE $d020 , color_yellow #IFEND IF pile_at_cursor != PILE_ID_NONE handle_click_on_pile(pile_at_cursor, char_row) ELSE // Click on empty area: deselect game_selected_pile = PILE_ID_NONE ENDIF ENDIF // Update selected card display (upper right corner using sprites) BYTE display_card_id BYTE display_valid BYTE display_rank BYTE display_suit get_selected_card_info(display_card_id, display_valid) IF display_valid // Show selected card rank and suit using sprites card_id_to_suit_rank(display_card_id, display_suit, display_rank) card_display_show(display_rank, display_suit) ELSE // Hide sprites when nothing selected card_display_hide() ENDIF // Check win condition check_win_condition(is_won) IF is_won // Flash border or show message POKE $d020 , color_green // Could add "YOU WIN" message here // For now, just keep running to allow admiring the win ENDIF // Small delay to avoid reading mouse too fast // Could sync to raster if needed for smoother experience WEND FEND LABEL __skip_lib_gameloop #IFEND