385 lines
8.6 KiB
Text
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
|