#IFNDEF __lib_cardmoves #DEFINE __lib_cardmoves 1 #INCLUDE "cardconsts.c65" #INCLUDE "piles.c65" #INCLUDE "cardrender.c65" GOTO __skip_lib_cardmoves // Card colors BYTE CONST COLOR_RED = 0 BYTE CONST COLOR_BLACK = 1 // Move result codes BYTE CONST MOVE_OK = 1 BYTE CONST MOVE_INVALID = 0 // ============================================================================ // Helper Functions // ============================================================================ // Get card color (0=red, 1=black) // Cards 0-25 are red (Hearts, Diamonds), 26-51 are black (Spades, Clubs) FUNC card_color({BYTE card_id} out:{BYTE color}) IF card_id < 26 color = COLOR_RED ELSE color = COLOR_BLACK ENDIF FEND // Get top card from pile (returns card with facedown bit, or PILE_END if empty) // Uses $ac - for source pile operations FUNC pile_top_card({WORD pile_ptr @ $ac} out:{BYTE card}) BYTE count count = PEEK pile_ptr[0] IF count == 0 card = PILE_END ELSE card = PEEK pile_ptr[count] ENDIF FEND // Remove top card from pile (returns the card, updates count) // Uses $9a - for source pile operations FUNC pile_pop({WORD pile_ptr @ $9a} out:{BYTE card}) BYTE count count = PEEK pile_ptr[0] card = PEEK pile_ptr[count] count-- POKE pile_ptr[0] , count FEND // Add card to top of pile (updates count) // Uses $fc - for destination pile operations FUNC pile_push({WORD pile_ptr @ $fc} {BYTE card}) BYTE count count = PEEK pile_ptr[0] count++ POKE pile_ptr[count] , card POKE pile_ptr[0] , count FEND // Flip top card face-up if it's face-down // Uses $9e - for source pile operations FUNC pile_flip_top({WORD pile_ptr @ $9e}) BYTE count BYTE card BYTE is_facedown count = PEEK pile_ptr[0] IF count > 0 card = PEEK pile_ptr[count] is_facedown = card & CARD_FACEDOWN IF is_facedown card = card & CARD_MASK POKE pile_ptr[count] , card ENDIF ENDIF FEND // ============================================================================ // Validation Functions // ============================================================================ // Can card go on foundation? (Ace if empty, else same suit +1 rank) // Uses $fc - destination pile FUNC can_place_on_foundation({BYTE card_id} {WORD found_ptr @ $fc} out:{BYTE valid}) BYTE card_suit BYTE card_rank BYTE top_card BYTE top_suit BYTE top_rank BYTE count BYTE expected_rank card_id_to_suit_rank(card_id, card_suit, card_rank) count = PEEK found_ptr[0] IF count == 0 // Empty foundation: only Ace allowed IF card_rank == CARD_ACE valid = 1 ELSE valid = 0 ENDIF ELSE // Must be same suit, one rank higher top_card = PEEK found_ptr[count] top_card = top_card & CARD_MASK card_id_to_suit_rank(top_card, top_suit, top_rank) expected_rank = top_rank + 1 valid = 0 IF card_suit == top_suit IF card_rank == expected_rank valid = 1 ENDIF ENDIF ENDIF FEND // Can card go on tableau? (King if empty, else opposite color -1 rank) // Uses $fc - destination pile FUNC can_place_on_tableau({BYTE card_id} {WORD tab_ptr @ $fc} out:{BYTE valid}) BYTE card_rank BYTE card_col BYTE top_card BYTE top_rank BYTE top_col BYTE count BYTE card_suit BYTE top_suit BYTE expected_rank card_id_to_suit_rank(card_id, card_suit, card_rank) card_color(card_id, card_col) count = PEEK tab_ptr[0] IF count == 0 // Empty tableau: only King allowed IF card_rank == CARD_KING valid = 1 ELSE valid = 0 ENDIF ELSE // Must be opposite color, one rank lower top_card = PEEK tab_ptr[count] top_card = top_card & CARD_MASK card_id_to_suit_rank(top_card, top_suit, top_rank) card_color(top_card, top_col) expected_rank = card_rank + 1 valid = 0 IF card_col <> top_col IF expected_rank == top_rank valid = 1 ENDIF ENDIF ENDIF FEND // ============================================================================ // Move Functions // ============================================================================ // Stock to Waste: Draw cards from stock to waste // draw_count = 1 or 3 depending on game variant // Stock is source @ $94, Waste is destination FUNC move_stock_to_waste({BYTE draw_count} out:{BYTE success}) WORD stock_ptr @ $94 WORD waste_ptr POINTER stock_ptr -> pile_stock POINTER waste_ptr -> pile_waste BYTE stock_count BYTE card BYTE i stock_count = PEEK stock_ptr[0] IF stock_count == 0 success = MOVE_INVALID EXIT ENDIF // Limit draw_count to available cards IF draw_count > stock_count draw_count = stock_count ENDIF FOR i = 1 TO draw_count pile_pop(stock_ptr, card) card = card & CARD_MASK // Flip face-up pile_push(waste_ptr, card) NEXT success = MOVE_OK FEND // Reset Stock: Flip waste back to stock (all face-down) // Waste is source @ $9c, Stock is destination FUNC move_reset_stock(out:{BYTE success}) WORD waste_ptr @ $9c WORD stock_ptr POINTER waste_ptr -> pile_waste POINTER stock_ptr -> pile_stock BYTE waste_count BYTE card BYTE i waste_count = PEEK waste_ptr[0] IF waste_count == 0 success = MOVE_INVALID EXIT ENDIF // Move all waste cards to stock (reversed, face-down) FOR i = 1 TO waste_count pile_pop(waste_ptr, card) card = card | CARD_FACEDOWN pile_push(stock_ptr, card) NEXT success = MOVE_OK FEND // Waste to Tableau FUNC move_waste_to_tab({WORD tab_ptr} out:{BYTE success}) WORD waste_ptr @ $8c POINTER waste_ptr -> pile_waste BYTE card BYTE valid pile_top_card(waste_ptr, card) IF card == PILE_END success = MOVE_INVALID EXIT ENDIF card = card & CARD_MASK can_place_on_tableau(card, tab_ptr, valid) IF valid == 0 success = MOVE_INVALID EXIT ENDIF pile_pop(waste_ptr, card) pile_push(tab_ptr, card) success = MOVE_OK FEND // Waste to Foundation FUNC move_waste_to_found({WORD found_ptr} out:{BYTE success}) WORD waste_ptr @ $aa POINTER waste_ptr -> pile_waste BYTE card BYTE valid pile_top_card(waste_ptr, card) IF card == PILE_END success = MOVE_INVALID EXIT ENDIF card = card & CARD_MASK can_place_on_foundation(card, found_ptr, valid) IF valid == 0 success = MOVE_INVALID EXIT ENDIF pile_pop(waste_ptr, card) pile_push(found_ptr, card) success = MOVE_OK FEND // Tableau to Foundation (top card only) FUNC move_tab_to_found({WORD tab_ptr @ $8e} {WORD found_ptr} out:{BYTE success}) BYTE card BYTE valid BYTE is_facedown pile_top_card(tab_ptr, card) IF card == PILE_END success = MOVE_INVALID EXIT ENDIF // Must be face-up is_facedown = card & CARD_FACEDOWN IF is_facedown success = MOVE_INVALID EXIT ENDIF card = card & CARD_MASK can_place_on_foundation(card, found_ptr, valid) IF valid == 0 success = MOVE_INVALID EXIT ENDIF pile_pop(tab_ptr, card) pile_push(found_ptr, card) pile_flip_top(tab_ptr) // Reveal next card success = MOVE_OK FEND // Tableau to Tableau: Move card_count cards from src to dst FUNC move_tab_to_tab({WORD src_ptr @ $b0} {WORD dst_ptr} {BYTE card_count} out:{BYTE success}) BYTE src_count BYTE start_idx BYTE bottom_card BYTE valid BYTE i BYTE card BYTE is_facedown src_count = PEEK src_ptr[0] IF card_count > src_count success = MOVE_INVALID EXIT ENDIF IF card_count == 0 success = MOVE_INVALID EXIT ENDIF // Find the bottom card of the stack to move start_idx = src_count - card_count start_idx = start_idx + 1 bottom_card = PEEK src_ptr[start_idx] // Bottom card must be face-up is_facedown = bottom_card & CARD_FACEDOWN IF is_facedown success = MOVE_INVALID EXIT ENDIF bottom_card = bottom_card & CARD_MASK can_place_on_tableau(bottom_card, dst_ptr, valid) IF valid == 0 success = MOVE_INVALID EXIT ENDIF // Move cards (preserve order) FOR i = start_idx TO src_count card = PEEK src_ptr[i] pile_push(dst_ptr, card) NEXT // Update source count src_count = start_idx - 1 POKE src_ptr[0] , src_count pile_flip_top(src_ptr) // Reveal next card success = MOVE_OK FEND // Foundation to Tableau (optional rule - some variants allow this) FUNC move_found_to_tab({WORD found_ptr @ $84} {WORD tab_ptr} out:{BYTE success}) BYTE card BYTE valid pile_top_card(found_ptr, card) IF card == PILE_END success = MOVE_INVALID EXIT ENDIF card = card & CARD_MASK can_place_on_tableau(card, tab_ptr, valid) IF valid == 0 success = MOVE_INVALID EXIT ENDIF pile_pop(found_ptr, card) pile_push(tab_ptr, card) success = MOVE_OK FEND LABEL __skip_lib_cardmoves #IFEND