#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 @ $a0 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 @ $a0 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 @ $a0 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 @ $96 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 @ $96 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 @ $90 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 @ $90 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 @ $90 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 @ $a6 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 @ $a6 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 @ $a6 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