diff --git a/packages/patterns/card-piles.tsx b/packages/patterns/card-piles.tsx new file mode 100644 index 000000000..0d2a321c5 --- /dev/null +++ b/packages/patterns/card-piles.tsx @@ -0,0 +1,326 @@ +/// +import { + Cell, + computed, + Default, + handler, + lift, + NAME, + pattern, + UI, +} from "commontools"; + +// Card suits and ranks +const SUITS = ["hearts", "diamonds", "clubs", "spades"] as const; +const RANKS = [ + "A", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "J", + "Q", + "K", +] as const; +type Suit = (typeof SUITS)[number]; +type Rank = (typeof RANKS)[number]; + +// Card interface +interface Card { + suit: Suit; + rank: Rank; +} + +// Generate unique random cards for defaults +const defaultPile1: Card[] = [ + { suit: "hearts", rank: "A" }, + { suit: "spades", rank: "K" }, + { suit: "diamonds", rank: "7" }, +]; + +const defaultPile2: Card[] = [ + { suit: "clubs", rank: "Q" }, + { suit: "hearts", rank: "10" }, + { suit: "spades", rank: "3" }, +]; + +interface CardPilesInput { + pile1: Default; + pile2: Default; +} + +interface CardPilesOutput { + pile1: Card[]; + pile2: Card[]; +} + +// Get suit symbol +const getSuitSymbol = lift((suit: Suit): string => { + switch (suit) { + case "hearts": + return "♥"; + case "diamonds": + return "♦"; + case "clubs": + return "♣"; + case "spades": + return "♠"; + } +}); + +// Get suit color +const getSuitColor = lift((suit: Suit): string => { + return suit === "hearts" || suit === "diamonds" ? "#dc2626" : "#1e293b"; +}); + +// Handler to move a card to pile 1 +const moveToPile1 = handler< + { detail: { sourceCell: Cell } }, + { pile1: Cell; pile2: Cell } +>((event, { pile1, pile2 }) => { + const sourceCard = event.detail?.sourceCell?.get() as Card; + if (!sourceCard) return; + + // Remove from pile2 if present + const p2 = pile2.get(); + const idx2 = p2.findIndex( + (c) => c.rank === sourceCard.rank && c.suit === sourceCard.suit, + ); + if (idx2 >= 0) { + pile2.set(p2.filter((_, i) => i !== idx2)); + pile1.push(sourceCard); + } +}); + +// Handler to move a card to pile 2 +const moveToPile2 = handler< + { detail: { sourceCell: Cell } }, + { pile1: Cell; pile2: Cell } +>((event, { pile1, pile2 }) => { + const sourceCard = event.detail?.sourceCell?.get() as Card; + if (!sourceCard) return; + + // Remove from pile1 if present + const p1 = pile1.get(); + const idx1 = p1.findIndex( + (c) => c.rank === sourceCard.rank && c.suit === sourceCard.suit, + ); + if (idx1 >= 0) { + pile1.set(p1.filter((_, i) => i !== idx1)); + pile2.push(sourceCard); + } +}); + +export default pattern(({ pile1, pile2 }) => { + // Create computed versions to ensure reactivity + const cards1 = computed(() => pile1); + const cards2 = computed(() => pile2); + + return { + [NAME]: "Card Piles", + [UI]: ( + + + + + PILE 1 + + + {cards1.map((card) => ( + + + + {card.rank} + + {getSuitSymbol(card.suit)} + + + + {getSuitSymbol(card.suit)} + + + {card.rank} + + {getSuitSymbol(card.suit)} + + + + + ))} + + + + + + + + PILE 2 + + + {cards2.map((card) => ( + + + + {card.rank} + + {getSuitSymbol(card.suit)} + + + + {getSuitSymbol(card.suit)} + + + {card.rank} + + {getSuitSymbol(card.suit)} + + + + + ))} + + + + + ), + pile1, + pile2, + }; +});