// Constants const CARD_WIDTH = 100; const CARD_HEIGHT = 150; const PADDING = 10; const SUITS = ['❤️', '♦️', '♣️', '♠️']; const VALUES = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']; const PATTERN_SIZE = 10; const INITIAL_CARD_X = 20; const INITIAL_CARD_Y = 20; const FONT_SIZE = '34px "pokemon-font", monospace'; const CARD_BORDER_COLOR = '#000000'; const CARD_FACE_COLOR = '#FFFFFF'; // Canvas setup const canvas = document.getElementById('cards'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Pure functions const shuffle = array => [...array].reduce((acc, _, i) => { const j = Math.floor(Math.random() * (i + 1)); [acc[i], acc[j]] = [acc[j], acc[i]]; return acc; }, [...array]); const createDeck = () => SUITS.flatMap(suit => VALUES.map(value => ({ suit, value }))); const createCard = (x, y, cardData) => ({ x: x + PADDING, y: y + PADDING, card: cardData, isFaceUp: false // Cards start face down }); // Function to check if a point is within a card const isPointInCard = (x, y, card) => x >= card.x && x <= card.x + CARD_WIDTH && y >= card.y && y <= card.y + CARD_HEIGHT; // Rendering functions const clearCanvas = () => { ctx.fillStyle = 'beige'; ctx.fillRect(0, 0, canvas.width, canvas.height); }; const drawCardBack = card => { ctx.fillRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT); drawRetroPattern(card); ctx.strokeStyle = CARD_BORDER_COLOR; ctx.strokeRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT); }; const drawRetroPattern = card => { const checkeredSize = 10; // Size of each square in the checkered pattern for (let i = 0; i < CARD_WIDTH; i += checkeredSize) { for (let j = 0; j < CARD_HEIGHT; j += checkeredSize) { // Alternate colors for the checkered pattern ctx.fillStyle = (Math.floor(i / checkeredSize) + Math.floor(j / checkeredSize)) % 2 === 0 ? '#FF9900' : '#FFCC00'; ctx.fillRect(card.x + i, card.y + j, checkeredSize, checkeredSize); } } }; const drawCardFront = card => { ctx.fillStyle = CARD_FACE_COLOR; ctx.fillRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT); ctx.fillStyle = CARD_BORDER_COLOR; ctx.font = FONT_SIZE; ctx.strokeRect(card.x, card.y, CARD_WIDTH, CARD_HEIGHT); // Draw value and suit with a retro font style drawCardValue(card.card.value, card.x + 12, card.y + 42, 'left'); drawCardSuit(card.card.suit, card.x + CARD_WIDTH / 2, card.y + CARD_HEIGHT / 2 + 20); }; const drawCardValue = (value, x, y, alignment) => { ctx.textAlign = alignment; ctx.fillStyle = CARD_BORDER_COLOR; ctx.fillText(value, x, y); }; const drawCardSuit = (suit, x, y) => { ctx.textAlign = 'center'; ctx.fillStyle = CARD_BORDER_COLOR; ctx.fillText(suit, x, y); }; const renderCard = card => { card.isFaceUp ? drawCardFront(card) : drawCardBack(card); }; const renderAllCards = cards => { clearCanvas(); cards.forEach(renderCard); // Renders cards in the order they are in the array }; // State management let gameState; const initializeGameState = () => ({ cards: [], draggingCard: null, deck: shuffle(createDeck()), stackPosition: { x: 0, y: 0 } }); const initializeGame = () => { gameState = initializeGameState(); gameState.cards = gameState.deck.map(cardData => createCard(INITIAL_CARD_X, INITIAL_CARD_Y, cardData)); gameState.cards.forEach((card, index) => { card.y += index * 5; // Stack cards with a slight offset for visibility }); clearCanvas(); renderAllCards(gameState.cards); // Add event listeners canvas.addEventListener('mousedown', handleMouseDown); canvas.addEventListener('contextmenu', e => e.preventDefault()); // Add event listener for the 'r' key document.addEventListener('keydown', e => { if (e.key === 'q') { handleResetGame(); } }); }; // Event handlers const handleMouseMove = e => { if (!gameState.draggingCard) return; const rect = canvas.getBoundingClientRect(); // Update the card's position using the offset gameState.draggingCard.x = e.clientX - rect.left - dragOffset.x; gameState.draggingCard.y = e.clientY - rect.top - dragOffset.y; renderAllCards(gameState.cards); }; const handleMouseUp = e => { if (!gameState.draggingCard) { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Check if a card was clicked const clickedCard = gameState.cards.slice().reverse().find(card => isPointInCard(x, y, card)); if (clickedCard) { // Move the clicked card to the top of the stack gameState.cards = gameState.cards.filter(card => card !== clickedCard); gameState.cards.push(clickedCard); renderAllCards(gameState.cards); // Re-render all cards } } gameState.draggingCard = null; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; let dragOffset = { x: 0, y: 0 }; // To store the offset of the click position const handleMouseDown = e => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (e.button === 2) { // Right click e.preventDefault(); const clickedCard = gameState.cards.find(card => isPointInCard(x, y, card)); if (clickedCard) { clickedCard.isFaceUp = !clickedCard.isFaceUp; // Toggle card face renderAllCards(gameState.cards); // Re-render all cards } return; } const clickedCard = gameState.cards.slice().reverse().find(card => isPointInCard(x, y, card)); if (clickedCard) { gameState.draggingCard = clickedCard; // Calculate the offset between the mouse position and the card's position dragOffset.x = x - clickedCard.x; dragOffset.y = y - clickedCard.y; // Move the dragged card to the top of the stack gameState.cards = gameState.cards.filter(card => card !== clickedCard); gameState.cards.push(clickedCard); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } }; // Add this function to handle the reset confirmation const handleResetGame = () => { if (confirm("Would you like to reset the cards?")) { initializeGame(); // Reset the game state } }; // Start the game initializeGame(); // Clean up on window unload window.addEventListener('unload', () => { canvas.removeEventListener('mousedown', handleMouseDown); canvas.removeEventListener('contextmenu', e => e.preventDefault()); });