solitaire-c64/cardrender.c65

963 lines
20 KiB
Text

#IFNDEF __lib_cardrender
#DEFINE __lib_cardrender 1
#INCLUDE "cardconsts.c65"
GOTO __skip_lib_cardrender
LABEL card_charcode_map
ASM
!8 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
ENDASM
LABEL card_suit_charcode_map
ASM
!8 $50, $51, $0e, $0f
ENDASM
LABEL suit_graphic_hearts
ASM
!8 $1d+64, $1e+64, $1f+64, $2d+64, $2e+64, $2f+64, 0+64, $3e+64, 0+64
ENDASM
LABEL suit_graphic_diamonds
ASM
!8 0+64, $1b+64, 0+64, $2a+64, $2b+64, $2c+64, 0+64, $3b+64, 0+64
ENDASM
LABEL suit_graphic_spades
ASM
!8 0, $18, $19, $27, $28, $29, $37, $38, $39
ENDASM
LABEL suit_graphic_clubs
ASM
!8 0, $15, 0, $24, $25, $26, 0, $35, $36
ENDASM
// Convert card ID (0-51) to suit and rank
// 0-12: Hearts, 13-25: Diamonds, 26-38: Spades, 39-51: Clubs
FUNC card_id_to_suit_rank({BYTE card_id} out:{BYTE card_suit} out:{BYTE card_rank})
card_rank = card_id
card_suit = 0
IF card_rank >= 13
card_rank = card_rank - 13
card_suit = 1
ENDIF
IF card_rank >= 13
card_rank = card_rank - 13
card_suit = 2
ENDIF
IF card_rank >= 13
card_rank = card_rank - 13
card_suit = 3
ENDIF
FEND
// ZP usage $fa-$fd
// Render full card using card_id (0-51)
FUNC render_card({WORD screen_address} {WORD offset} {BYTE card_id})
BYTE card_suit
BYTE card_rank
card_id_to_suit_rank(card_id, card_suit, card_rank)
// Get rank charcode
WORD card_charcode_map_ptr @ $fa
POINTER card_charcode_map_ptr -> card_charcode_map
BYTE card_rank_charcode
card_rank_charcode = PEEK card_charcode_map_ptr[card_rank]
// Adjust color for red suit
IF card_suit < CARD_SUIT_SPADES
card_rank_charcode = card_rank_charcode + 64 //red color for hearts and diamonds
ENDIF
// Get suit charcode
WORD card_suit_charcode_map_ptr @ $fa
POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map
BYTE suit_charcode
suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit]
WORD p2 @ $fa
SWITCH card_suit
CASE CARD_SUIT_HEARTS
POINTER p2 -> suit_graphic_hearts
CASE CARD_SUIT_DIAMONDS
POINTER p2 -> suit_graphic_diamonds
CASE CARD_SUIT_SPADES
POINTER p2 -> suit_graphic_spades
CASE CARD_SUIT_CLUBS
POINTER p2 -> suit_graphic_clubs
ENDSWITCH
WORD p @ $fc
p = screen_address + offset
POKE p[0] , 224 // top left corner
POKE p[1] , 225
POKE p[2] , 225
POKE p[3] , 225
POKE p[4] , 226
p = p + 40
POKE p[0] , 240
POKE p[1] , card_rank_charcode
POKE p[2] , 0
POKE p[3] , suit_charcode
POKE p[4] , 243
p = p + 40
// Here we draw the bigger suit graphic in the middle of the card
BYTE g
POKE p[0] , 240
g = PEEK p2[0]
POKE p[1] , g
g = PEEK p2[1]
POKE p[2] , g
g = PEEK p2[2]
POKE p[3] , g
POKE p[4] , 243
p = p + 40
POKE p[0] , 240
g = PEEK p2[3]
POKE p[1] , g
g = PEEK p2[4]
POKE p[2] , g
g = PEEK p2[5]
POKE p[3] , g
POKE p[4] , 243
p = p + 40
POKE p[0] , 240
g = PEEK p2[6]
POKE p[1] , g
g = PEEK p2[7]
POKE p[2] , g
g = PEEK p2[8]
POKE p[3] , g
POKE p[4] , 243
p = p + 40
POKE p[0] , 240
POKE p[1] , suit_charcode
POKE p[2] , 0
POKE p[3] , card_rank_charcode
POKE p[4] , 243
p = p + 40
POKE p[0] , 241
POKE p[1] , 227
POKE p[2] , 227
POKE p[3] , 227
POKE p[4] , 242
FEND
// Render face-down card stack and top border of visible card
// Returns number of rows drawn (for offset calculation)
// n=0: just draws normal top border ($e0)
// n=1-2: draws modified top border showing stacked cards
// n=3+: draws multiple rows ending with top border
FUNC render_facedown_stack({WORD screen_address} {WORD offset} {BYTE num_facedown} out:{BYTE rows_drawn})
WORD p @ $fc
p = screen_address + offset
BYTE middle_rows
BYTE top_type
IF num_facedown == 0
// Normal top border
POKE p[0] , $e0
POKE p[1] , $e1
POKE p[2] , $e1
POKE p[3] , $e1
POKE p[4] , $e2
rows_drawn = 1
EXIT
ENDIF
IF num_facedown == 1
// 1 face-down: $fa row
POKE p[0] , $fa
POKE p[1] , $da
POKE p[2] , $da
POKE p[3] , $da
POKE p[4] , $fc
rows_drawn = 1
EXIT
ENDIF
IF num_facedown == 2
// 2 face-down: $fd row
POKE p[0] , $fd
POKE p[1] , $dc
POKE p[2] , $dc
POKE p[3] , $dc
POKE p[4] , $ff
rows_drawn = 1
EXIT
ENDIF
// 3+ face-down cards
// Top row based on (num_facedown mod 3)
top_type = num_facedown
WHILE top_type >= 3
top_type = top_type - 3
WEND
// Draw top row
IF top_type == 0
POKE p[0] , $e0
POKE p[1] , $e1
POKE p[2] , $e1
POKE p[3] , $e1
POKE p[4] , $e2
ENDIF
IF top_type == 1
POKE p[0] , $fa
POKE p[1] , $da
POKE p[2] , $da
POKE p[3] , $da
POKE p[4] , $fc
ENDIF
IF top_type == 2
POKE p[0] , $fd
POKE p[1] , $dc
POKE p[2] , $dc
POKE p[3] , $dc
POKE p[4] , $ff
ENDIF
p = p + 40
rows_drawn = 1
// Calculate middle $d7 rows: (num_facedown - 3) / 3
middle_rows = num_facedown - 3
WHILE middle_rows >= 3
middle_rows = middle_rows - 3
// Draw $d7 row
POKE p[0] , $d7
POKE p[1] , $dc
POKE p[2] , $dc
POKE p[3] , $dc
POKE p[4] , $f4
p = p + 40
rows_drawn++
WEND
// Draw card top row: $d7 for all n>=3
POKE p[0] , $d7
POKE p[1] , $dc
POKE p[2] , $dc
POKE p[3] , $dc
POKE p[4] , $f4
rows_drawn++
FEND
// Draw connecting border between stacked face-up cards
// Used before each face-up card after the first
FUNC render_connecting_border({WORD screen_address} {WORD offset})
WORD p @ $fc
p = screen_address + offset
POKE p[0] , $d4
POKE p[1] , $e1
POKE p[2] , $e1
POKE p[3] , $e1
POKE p[4] , $d6
FEND
// Render just the rank/suit row (no top border)
// Used for face-up cards in a stack where top border is already drawn
FUNC render_card_body_partial({WORD screen_address} {WORD offset} {BYTE card_id})
BYTE card_suit
BYTE card_rank
card_id_to_suit_rank(card_id, card_suit, card_rank)
// Get rank charcode
WORD card_charcode_map_ptr @ $fa
POINTER card_charcode_map_ptr -> card_charcode_map
BYTE card_rank_charcode
card_rank_charcode = PEEK card_charcode_map_ptr[card_rank]
// Adjust color for red suit
IF card_suit < CARD_SUIT_SPADES
card_rank_charcode = card_rank_charcode + 64
ENDIF
// Get suit charcode
WORD card_suit_charcode_map_ptr @ $fa
POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map
BYTE suit_charcode
suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit]
WORD p @ $fc
p = screen_address + offset
// Rank/suit row
POKE p[0] , $f0
POKE p[1] , card_rank_charcode
POKE p[2] , 0
POKE p[3] , suit_charcode
POKE p[4] , $f3
FEND
// Render card body without top border (rank/suit + middle + bottom)
// Used for the last face-up card in a stack
FUNC render_card_body_full({WORD screen_address} {WORD offset} {BYTE card_id})
BYTE card_suit
BYTE card_rank
card_id_to_suit_rank(card_id, card_suit, card_rank)
// Get rank charcode
WORD card_charcode_map_ptr @ $fa
POINTER card_charcode_map_ptr -> card_charcode_map
BYTE card_rank_charcode
card_rank_charcode = PEEK card_charcode_map_ptr[card_rank]
// Adjust color for red suit
IF card_suit < CARD_SUIT_SPADES
card_rank_charcode = card_rank_charcode + 64
ENDIF
// Get suit charcode
WORD card_suit_charcode_map_ptr @ $fa
POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map
BYTE suit_charcode
suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit]
// Get suit graphic pointer
WORD p2 @ $fa
SWITCH card_suit
CASE CARD_SUIT_HEARTS
POINTER p2 -> suit_graphic_hearts
CASE CARD_SUIT_DIAMONDS
POINTER p2 -> suit_graphic_diamonds
CASE CARD_SUIT_SPADES
POINTER p2 -> suit_graphic_spades
CASE CARD_SUIT_CLUBS
POINTER p2 -> suit_graphic_clubs
ENDSWITCH
WORD p @ $fc
p = screen_address + offset
WORD CONST SCREEN_MEM_END = $0400+999
// Bounds check: skip last 2 rows if they'd overflow screen (compact mode)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
// Row 0: Rank/suit row
POKE p[0] , $f0
POKE p[1] , card_rank_charcode
POKE p[2] , 0
POKE p[3] , suit_charcode
POKE p[4] , $f3
p = p + 40
// Bounds check: skip last 2 rows if they'd overflow screen (compact mode)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
// Rows 1-3: Suit graphic
BYTE g
POKE p[0] , $f0
g = PEEK p2[0]
POKE p[1] , g
g = PEEK p2[1]
POKE p[2] , g
g = PEEK p2[2]
POKE p[3] , g
POKE p[4] , $f3
p = p + 40
// Bounds check: skip last 2 rows if they'd overflow screen (compact mode)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
POKE p[0] , $f0
g = PEEK p2[3]
POKE p[1] , g
g = PEEK p2[4]
POKE p[2] , g
g = PEEK p2[5]
POKE p[3] , g
POKE p[4] , $f3
p = p + 40
// Bounds check: skip last 2 rows if they'd overflow screen (compact mode)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
POKE p[0] , $f0
g = PEEK p2[6]
POKE p[1] , g
g = PEEK p2[7]
POKE p[2] , g
g = PEEK p2[8]
POKE p[3] , g
POKE p[4] , $f3
p = p + 40
// Bounds check: skip last 2 rows if they'd overflow screen (compact mode)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
// Row 4: Bottom rank/suit
POKE p[0] , $f0
POKE p[1] , suit_charcode
POKE p[2] , 0
POKE p[3] , card_rank_charcode
POKE p[4] , $f3
p = p + 40
// Bounds check: skip bottom border if it'd overflow (K-2 case)
IF p > SCREEN_MEM_END-4
EXIT
ENDIF
// Row 5: Bottom border
POKE p[0] , $f1
POKE p[1] , $e3
POKE p[2] , $e3
POKE p[3] , $e3
POKE p[4] , $f2
FEND
// Render a complete tableau pile from pile data
// Handles face-down stack and all face-up cards
FUNC render_tableau_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe})
BYTE count
BYTE facedown_count
BYTE faceup_count
BYTE faceup_index
BYTE card
BYTE card_id
BYTE i
BYTE rows
WORD pos
BYTE use_compact
count = PEEK pile_ptr[0]
// Empty pile - nothing to render
IF count == 0
EXIT
ENDIF
// Count face-down cards
facedown_count = 0
BYTE is_facedown
FOR i = 1 TO count
card = PEEK pile_ptr[i]
is_facedown = card & CARD_FACEDOWN
IF is_facedown
facedown_count++
ENDIF
NEXT
faceup_count = count - facedown_count
// Calculate if we need compact mode (total rows > 17)
BYTE facedown_rows
BYTE faceup_rows_normal
BYTE total_rows
BYTE temp_count
// Calculate facedown rows
IF facedown_count == 0
facedown_rows = 0
ELSE
IF facedown_count <= 2
facedown_rows = 1
ELSE
facedown_rows = 2
temp_count = facedown_count - 3
WHILE temp_count >= 3
temp_count = temp_count - 3
facedown_rows++
WEND
ENDIF
ENDIF
// Calculate faceup rows (normal mode)
IF faceup_count == 0
faceup_rows_normal = 0
ELSE
IF faceup_count == 1
faceup_rows_normal = 6
ELSE
// Normal mode: 4 + 2*N rows
faceup_rows_normal = faceup_count + faceup_count
faceup_rows_normal = faceup_rows_normal + 4
ENDIF
ENDIF
// Check if we need compact mode (screen rows 8-24 = 17 rows available)
total_rows = facedown_rows + faceup_rows_normal
use_compact = 0
IF total_rows > 17
use_compact = 1
ENDIF
// Draw face-down stack + first face-up top border
pos = offset
render_facedown_stack(screen_address, pos, facedown_count, rows)
FOR i = 1 TO rows
pos = pos + 40
NEXT
// If no face-up cards, we're done (shouldn't happen in valid game)
IF faceup_count == 0
EXIT
ENDIF
// Render face-up cards
faceup_index = 0
#PRAGMA _P_USE_LONG_JUMP 1
FOR i = 1 TO count
#PRAGMA _P_USE_LONG_JUMP 0
card = PEEK pile_ptr[i]
// Skip face-down cards
is_facedown = card & CARD_FACEDOWN
IF is_facedown
// do nothing
ELSE
faceup_index++
card_id = card & CARD_MASK
IF faceup_index == 1
// First face-up: top border already drawn, just body
IF faceup_count == 1
// Only one face-up card - render full
render_card_body_full(screen_address, pos, card_id)
ELSE
// More cards follow - render partial
render_card_body_partial(screen_address, pos, card_id)
pos = pos + 40
ENDIF
ELSE
// Subsequent face-up cards
IF use_compact == 0
// Normal mode: draw connecting border
render_connecting_border(screen_address, pos)
pos = pos + 40
ENDIF
IF faceup_index == faceup_count
// Last face-up card - render full
render_card_body_full(screen_address, pos, card_id)
ELSE
// More cards follow - render partial
render_card_body_partial(screen_address, pos, card_id)
pos = pos + 40
ENDIF
ENDIF
ENDIF
NEXT
FEND
// Render partial card (top 2 rows only) for stacked display
// Used for face-up cards underneath the top card
FUNC render_card_partial({WORD screen_address} {WORD offset} {BYTE card_id})
BYTE card_suit
BYTE card_rank
card_id_to_suit_rank(card_id, card_suit, card_rank)
// Get rank charcode
WORD card_charcode_map_ptr @ $fa
POINTER card_charcode_map_ptr -> card_charcode_map
BYTE card_rank_charcode
card_rank_charcode = PEEK card_charcode_map_ptr[card_rank]
// Adjust color for red suit
IF card_suit < CARD_SUIT_SPADES
card_rank_charcode = card_rank_charcode + 64
ENDIF
// Get suit charcode
WORD card_suit_charcode_map_ptr @ $fa
POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map
BYTE suit_charcode
suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit]
WORD p @ $fc
p = screen_address + offset
// Row 0: top border
POKE p[0] , 224
POKE p[1] , 225
POKE p[2] , 225
POKE p[3] , 225
POKE p[4] , 226
p = p + 40
// Row 1: rank and suit
POKE p[0] , 240
POKE p[1] , card_rank_charcode
POKE p[2] , 0
POKE p[3] , suit_charcode
POKE p[4] , 243
FEND
// Render back side of a card (face-down full card)
// Same border as face card, middle filled with $52
FUNC render_card_back({WORD screen_address} {WORD offset})
WORD p @ $fc
p = screen_address + offset
// Row 0: top border
POKE p[0] , $e0
POKE p[1] , $e1
POKE p[2] , $e1
POKE p[3] , $e1
POKE p[4] , $e2
p = p + 40
// Row 1: side borders + $52 fill
POKE p[0] , $f0
POKE p[1] , $52
POKE p[2] , $52
POKE p[3] , $52
POKE p[4] , $f3
p = p + 40
// Row 2: side borders + $52 fill
POKE p[0] , $f0
POKE p[1] , $52
POKE p[2] , $52
POKE p[3] , $52
POKE p[4] , $f3
p = p + 40
// Row 3: side borders + $52 fill
POKE p[0] , $f0
POKE p[1] , $52
POKE p[2] , $52
POKE p[3] , $52
POKE p[4] , $f3
p = p + 40
// Row 4: side borders + $52 fill
POKE p[0] , $f0
POKE p[1] , $52
POKE p[2] , $52
POKE p[3] , $52
POKE p[4] , $f3
p = p + 40
// Row 5: side borders + $52 fill
POKE p[0] , $f0
POKE p[1] , $52
POKE p[2] , $52
POKE p[3] , $52
POKE p[4] , $f3
p = p + 40
// Row 6: bottom border
POKE p[0] , $f1
POKE p[1] , $e3
POKE p[2] , $e3
POKE p[3] , $e3
POKE p[4] , $f2
FEND
// Render empty pile placeholder
// Same border shape but -$80 for light pink color, interior filled with 0
FUNC render_empty_pile({WORD screen_address} {WORD offset})
WORD p @ $fc
p = screen_address + offset
// Row 0: top border (pink)
POKE p[0] , $60
POKE p[1] , $61
POKE p[2] , $61
POKE p[3] , $61
POKE p[4] , $62
p = p + 40
// Row 1: side borders + empty fill
POKE p[0] , $70
POKE p[1] , 0
POKE p[2] , 0
POKE p[3] , 0
POKE p[4] , $73
p = p + 40
// Row 2: side borders + empty fill
POKE p[0] , $70
POKE p[1] , 0
POKE p[2] , 0
POKE p[3] , 0
POKE p[4] , $73
p = p + 40
// Row 3: side borders + empty fill
POKE p[0] , $70
POKE p[1] , 0
POKE p[2] , 0
POKE p[3] , 0
POKE p[4] , $73
p = p + 40
// Row 4: side borders + empty fill
POKE p[0] , $70
POKE p[1] , 0
POKE p[2] , 0
POKE p[3] , 0
POKE p[4] , $73
p = p + 40
// Row 5: side borders + empty fill
POKE p[0] , $70
POKE p[1] , 0
POKE p[2] , 0
POKE p[3] , 0
POKE p[4] , $73
p = p + 40
// Row 6: bottom border (pink)
POKE p[0] , $71
POKE p[1] , $63
POKE p[2] , $63
POKE p[3] , $63
POKE p[4] , $72
FEND
// Render foundation pile - shows top card or empty placeholder
FUNC render_foundation_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe})
BYTE count
BYTE card
BYTE card_id
count = PEEK pile_ptr[0]
IF count == 0
render_empty_pile(screen_address, offset)
ELSE
card = PEEK pile_ptr[count]
card_id = card & CARD_MASK
render_card(screen_address, offset, card_id)
ENDIF
FEND
// Render stock pile - shows card back or empty placeholder
FUNC render_stock_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe})
BYTE count
count = PEEK pile_ptr[0]
IF count == 0
render_empty_pile(screen_address, offset)
ELSE
render_card_back(screen_address, offset)
ENDIF
FEND
// Render left edge of card (2 columns) for fanned display
FUNC render_card_left_edge({WORD screen_address} {WORD offset} {BYTE card_id})
BYTE card_suit
BYTE card_rank
card_id_to_suit_rank(card_id, card_suit, card_rank)
// Get rank charcode
WORD card_charcode_map_ptr @ $fa
POINTER card_charcode_map_ptr -> card_charcode_map
BYTE card_rank_charcode
card_rank_charcode = PEEK card_charcode_map_ptr[card_rank]
// Adjust color for red suit
IF card_suit < CARD_SUIT_SPADES
card_rank_charcode = card_rank_charcode + 64
ENDIF
// Get suit charcode
WORD card_suit_charcode_map_ptr @ $fa
POINTER card_suit_charcode_map_ptr -> card_suit_charcode_map
BYTE suit_charcode
suit_charcode = PEEK card_suit_charcode_map_ptr[card_suit]
// Get suit graphic pointer
WORD p2 @ $fa
SWITCH card_suit
CASE CARD_SUIT_HEARTS
POINTER p2 -> suit_graphic_hearts
CASE CARD_SUIT_DIAMONDS
POINTER p2 -> suit_graphic_diamonds
CASE CARD_SUIT_SPADES
POINTER p2 -> suit_graphic_spades
CASE CARD_SUIT_CLUBS
POINTER p2 -> suit_graphic_clubs
ENDSWITCH
WORD p @ $fc
p = screen_address + offset
BYTE g
// Row 0: top border (2 cols)
POKE p[0] , $e0
POKE p[1] , $e1
p = p + 40
// Row 1: left border + rank
POKE p[0] , $f0
POKE p[1] , card_rank_charcode
p = p + 40
// Row 2: left border + suit graphic
POKE p[0] , $f0
g = PEEK p2[0]
POKE p[1] , g
p = p + 40
// Row 3: left border + suit graphic
POKE p[0] , $f0
g = PEEK p2[3]
POKE p[1] , g
p = p + 40
// Row 4: left border + suit graphic
POKE p[0] , $f0
g = PEEK p2[6]
POKE p[1] , g
p = p + 40
// Row 5: left border + suit
POKE p[0] , $f0
POKE p[1] , suit_charcode
p = p + 40
// Row 6: bottom border (2 cols)
POKE p[0] , $f1
POKE p[1] , $e3
FEND
// Render waste pile - draw-3 style fanned display
// Shows up to 3 cards fanned horizontally (2 col offset per card)
FUNC render_waste_pile({WORD screen_address} {WORD offset} {WORD pile_ptr @ $fe} {BYTE draw_mode})
BYTE count
BYTE card
BYTE card_id
WORD off2
WORD off4
count = PEEK pile_ptr[0]
// Draw-1 mode: only show top card
IF draw_mode == 1
IF count == 0
render_empty_pile(screen_address, offset)
ELSE
card = PEEK pile_ptr[count]
card_id = card & CARD_MASK
render_card(screen_address, offset, card_id)
ENDIF
EXIT
ENDIF
// Draw-3 mode: fan out top 3 cards
off2 = offset + 2
off4 = offset + 4
SWITCH count
CASE 0
render_empty_pile(screen_address, offset)
CASE 1
card = PEEK pile_ptr[1]
card_id = card & CARD_MASK
render_card(screen_address, offset, card_id)
CASE 2
// 2nd from top (left edge)
card = PEEK pile_ptr[1]
card_id = card & CARD_MASK
render_card_left_edge(screen_address, offset, card_id)
// Top card (full)
card = PEEK pile_ptr[2]
card_id = card & CARD_MASK
render_card(screen_address, off2, card_id)
DEFAULT
// 3+ cards: show top 3 fanned
BYTE idx
// 3rd from top (left edge)
idx = count - 2
card = PEEK pile_ptr[idx]
card_id = card & CARD_MASK
render_card_left_edge(screen_address, offset, card_id)
// 2nd from top (left edge)
idx = count - 1
card = PEEK pile_ptr[idx]
card_id = card & CARD_MASK
render_card_left_edge(screen_address, off2, card_id)
// Top card (full)
card = PEEK pile_ptr[count]
card_id = card & CARD_MASK
render_card(screen_address, off4, card_id)
ENDSWITCH
FEND
LABEL __skip_lib_cardrender
#IFEND