solitaire-c64/cardmoves.c65

385 lines
8.6 KiB
Text

#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