From 65f74bd0c6f131d8c574165d76da8e6986ee763c Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 31 Aug 2025 21:22:17 -0400 Subject: [PATCH] change double click to single click --- index.html | 2 +- script.js | 163 ++++++++++++++++++++++------------------------------- 2 files changed, 67 insertions(+), 98 deletions(-) diff --git a/index.html b/index.html index c06e960..1713128 100644 --- a/index.html +++ b/index.html @@ -65,7 +65,7 @@ - + diff --git a/script.js b/script.js index e1a2f2f..97b145f 100644 --- a/script.js +++ b/script.js @@ -11,8 +11,7 @@ document.addEventListener('DOMContentLoaded', () => { let deck, stock, waste, foundations, tableau; let cardElements = {}; let dragged = { cards: [], sourcePile: null, startIndex: -1 }; - let selected = { card: null, sourcePile: null, cardIndex: -1 }; - let lastClick = { time: 0, cardId: null }; + // 'selected' state is no longer needed for click controls and has been removed. const history = []; let isAutoCompleting = false; @@ -50,7 +49,6 @@ document.addEventListener('DOMContentLoaded', () => { // ====== SAVE / LOAD ====== function saveGame() { - // Also save score in the game state const gameState = { stock, waste, foundations, tableau, history, drawCount: DRAW_COUNT, score }; localStorage.setItem('solitaire-save-game', JSON.stringify(gameState)); } @@ -66,7 +64,7 @@ document.addEventListener('DOMContentLoaded', () => { history.length = 0; history.push(...gameState.history); DRAW_COUNT = gameState.drawCount || 3; - score = gameState.score || 0; // Load score + score = gameState.score || 0; drawSelect.value = DRAW_COUNT; Object.values(cardElements).forEach(el => el.remove()); cardElements = {}; @@ -90,11 +88,11 @@ document.addEventListener('DOMContentLoaded', () => { } // ====== UNDO SNAPSHOTS ====== - const snapshotState = () => JSON.stringify({ stock, waste, foundations, tableau, score }); // Include score in snapshots + const snapshotState = () => JSON.stringify({ stock, waste, foundations, tableau, score }); const restoreState = (json) => { const s = JSON.parse(json); stock = s.stock; waste = s.waste; foundations = s.foundations; tableau = s.tableau; - score = s.score !== undefined ? s.score : score; // Restore score + score = s.score !== undefined ? s.score : score; }; const pushHistory = () => { if (history.length > 50) history.shift(); @@ -132,14 +130,14 @@ document.addEventListener('DOMContentLoaded', () => { stock = deck; winMessage.classList.add('hidden'); history.length = 0; - pushHistory(); // Initial state for undo + pushHistory(); updateBoard(true); saveGame(); } // ====== RENDER ====== function updateBoard(initial=false) { - updateScoreDisplay(); // Keep score display fresh on every update + updateScoreDisplay(); const gameBoardRect = gameBoard.getBoundingClientRect(); const updatePile = (pileData, pileEl, type) => { if (!pileEl) return; @@ -166,7 +164,7 @@ document.addEventListener('DOMContentLoaded', () => { cardEl.classList.remove('is-flipped'); cardEl.draggable = false; } - cardEl.classList.toggle('selected', selected.card?.id === cardData.id); + // The 'selected' class is no longer applied via click. const zBase = type === 'tableau' ? 100 : type === 'foundation' ? 400 : 600; cardEl.style.zIndex = zBase + i; }); @@ -303,10 +301,9 @@ document.addEventListener('DOMContentLoaded', () => { return card.suit === top.suit && getValueRank(card.value) === getValueRank(top.value) + 1; } } - function clearSelection() { - if (selected.card) cardElements[selected.card.id]?.classList.remove('selected'); - selected = { card:null, sourcePile:null, cardIndex:-1 }; - } + + // The `clearSelection` function is no longer needed. + function moveSequence(sourcePile, startIndex, destPile) { const seq = sourcePile.splice(startIndex); destPile.push(...seq); @@ -316,10 +313,9 @@ document.addEventListener('DOMContentLoaded', () => { const topCard = sourcePile[sourcePile.length - 1]; if (!topCard.faceUp) { topCard.faceUp = true; - score += 5; // +5 points for revealing a card + score += 5; } } - clearSelection(); updateBoard(); saveGame(); if (!isAutoCompleting) { @@ -350,57 +346,58 @@ document.addEventListener('DOMContentLoaded', () => { cardsToMove.forEach(c => c.faceUp = true); waste.push(...cardsToMove); } else if (waste.length > 0) { - score = Math.max(0, score - 20); // Penalty for recycling, but not below 0 + score = Math.max(0, score - 20); stock = waste.reverse(); stock.forEach(c => c.faceUp = false); waste = []; } - clearSelection(); updateBoard(); saveGame(); } - function handleCardDoubleClick(cardEl) { + + /** + * This new function handles the single-click auto-move logic. + * It finds the best valid move for a card and executes it. + */ + function performAutoMove(cardEl) { if (isAutoCompleting) return; const { sourcePile, cardIndex, cardsToMove } = findCardData(cardEl); + if (!sourcePile || !cardsToMove || cardsToMove.length === 0) return; const cardToMove = cardsToMove[0]; - - // Try moving single cards to foundation first + + // Priority 1: Try to move a single card to a foundation. if (cardsToMove.length === 1) { for (const f of foundations) { if (canMove(cardToMove, f)) { score += 10; - updateScoreDisplay(); pushHistory(); moveSequence(sourcePile, cardIndex, f); performMoveCleanup(sourcePile); - return; + return; // Move executed, action is complete. } } } - - // Then try moving any valid stack to another tableau + + // Priority 2: Try to move a stack to another tableau pile. for (const t of tableau) { if (t !== sourcePile && canMove(cardToMove, t)) { - // --- SCORING LOGIC --- - if (sourcePile === waste) score += 5; // FIX: Added +5 for waste to tableau move + if (sourcePile === waste) score += 5; if (foundations.includes(sourcePile)) score -= 15; - updateScoreDisplay(); - // --- END SCORING LOGIC --- pushHistory(); moveSequence(sourcePile, cardIndex, t); performMoveCleanup(sourcePile); - return; + return; // Move executed, action is complete. } } } + function undo() { if (isAutoCompleting) return; if (history.length <= 1) return; history.pop(); restoreState(history[history.length-1]); - clearSelection(); winMessage.classList.add('hidden'); updateBoard(true); saveGame(); @@ -417,14 +414,18 @@ document.addEventListener('DOMContentLoaded', () => { function autoComplete() { const isWinnable = stock.length === 0 && tableau.flat().every(card => card.faceUp); if (!isWinnable) { - alert("You can only auto-complete when all cards are face-up and the stock is empty."); + Swal.fire({ + title: 'Cannot Auto-Complete', + text: 'You can only auto-complete when all cards are face-up and the stock is empty.', + icon: 'info' + }); return; } isAutoCompleting = true; const intervalId = setInterval(() => { let movedCard = false; let source = null; - // First check waste pile + if (waste.length > 0) { const card = waste[waste.length - 1]; for (const f of foundations) { @@ -437,7 +438,7 @@ document.addEventListener('DOMContentLoaded', () => { } } } - // Then check tableau piles + if (!movedCard) { for (const t of tableau) { if (t.length > 0) { @@ -461,7 +462,7 @@ document.addEventListener('DOMContentLoaded', () => { if (!movedCard) { clearInterval(intervalId); isAutoCompleting = false; - checkWin(); // Final check + checkWin(); } }, 120); } @@ -478,7 +479,7 @@ document.addEventListener('DOMContentLoaded', () => { for(const f of foundations) if(canMove(topCard, f)) return true; const movableStackIndex = sourcePile.findIndex(card => card.faceUp); - if (movableStackIndex > 0) { + if (movableStackIndex > -1) { // Fixed: check should be > -1 const cardToMove = sourcePile[movableStackIndex]; for (const destPile of tableau) { if (sourcePile !== destPile && canMove(cardToMove, destPile)) return true; @@ -491,14 +492,22 @@ document.addEventListener('DOMContentLoaded', () => { function handleStuckCheck() { if (checkForAnyAvailableMove()) { - alert("Hint: A move is available on the board!"); + Swal.fire({ + title: 'Hint!', + text: 'A move is available on the board!', + icon: 'info' + }); return; } let tempStock = JSON.parse(JSON.stringify(stock)); let tempWaste = JSON.parse(JSON.stringify(waste)); const originalStockSize = tempStock.length + tempWaste.length; if (originalStockSize === 0) { - alert("No more moves available. The game appears to be unwinnable from this state."); + Swal.fire({ + title: 'No Moves Found', + text: 'The game appears to be unwinnable from this state.', + icon: 'warning' + }); return; } let cardsCycled = 0; @@ -513,82 +522,44 @@ document.addEventListener('DOMContentLoaded', () => { cardsCycled += drawnCards.length; const newTopWasteCard = tempWaste.length > 0 ? tempWaste[tempWaste.length - 1] : null; if (newTopWasteCard) { + const hintText = "A move will become available by drawing from the stock!"; for (const f of foundations) if (canMove(newTopWasteCard, f)) { - alert("Hint: A move will become available by drawing from the stock!"); + Swal.fire({ title: 'Hint!', text: hintText, icon: 'info' }); return; } for (const t of tableau) if (canMove(newTopWasteCard, t)) { - alert("Hint: A move will become available by drawing from the stock!"); + Swal.fire({ title: 'Hint!', text: hintText, icon: 'info' }); return; } } } - alert("No more moves available. The game appears to be unwinnable from this state."); + Swal.fire({ + title: 'No Moves Found', + text: 'The game appears to be unwinnable from this state.', + icon: 'warning' + }); } // ====== INPUT HANDLERS ====== gameBoard.addEventListener('click', e => { if (isAutoCompleting) return; - const pileEl = e.target.closest('.pile'); - const cardEl = e.target.closest('.card'); - if (pileEl && pileEl.id === 'stock' && !cardEl) { + + const clickedCardEl = e.target.closest('.card'); + const clickedPileEl = e.target.closest('.pile'); + + // If the stock pile background was clicked, draw cards. + if (clickedPileEl && clickedPileEl.id === 'stock' && !clickedCardEl) { handleStockClick(); return; } - if (cardEl) { - const { sourcePile, cardIndex, card } = findCardData(cardEl); - if (!sourcePile || !card) return; - const now = Date.now(); - if (now - lastClick.time < 300 && lastClick.cardId === card.id) { - handleCardDoubleClick(cardEl); - clearSelection(); - lastClick = { time: 0, cardId: null }; - return; - } - lastClick = { time: now, cardId: card.id }; - if (selected.card) { - if (canMove(selected.card, sourcePile)) { - // --- SCORING LOGIC --- - if (foundations.includes(sourcePile)) score += 10; - if (tableau.includes(sourcePile) && selected.sourcePile === waste) score += 5; - if (tableau.includes(sourcePile) && foundations.includes(selected.sourcePile)) score -= 15; - // --- END SCORING LOGIC --- - pushHistory(); - moveSequence(selected.sourcePile, selected.cardIndex, sourcePile); - performMoveCleanup(selected.sourcePile); - } else { - if (selected.card.id === card.id) { - clearSelection(); - } else { - selected = { card, sourcePile, cardIndex }; - } - updateBoard(); - } - } else { - selected = { card, sourcePile, cardIndex }; - updateBoard(); - } + + // If a card was clicked, attempt to auto-move it. + if (clickedCardEl) { + performAutoMove(clickedCardEl); return; } - if (pileEl && selected.card) { - const destPile = getPileArrayFromElement(pileEl); - if (destPile && canMove(selected.card, destPile)) { - // --- SCORING LOGIC --- - if (foundations.includes(destPile)) score += 10; - if (tableau.includes(destPile) && selected.sourcePile === waste) score += 5; - if (tableau.includes(destPile) && foundations.includes(selected.sourcePile)) score -= 15; - // --- END SCORING LOGIC --- - pushHistory(); - moveSequence(selected.sourcePile, selected.cardIndex, destPile); - performMoveCleanup(selected.sourcePile); - } else { - clearSelection(); - updateBoard(); - } - return; - } - clearSelection(); - updateBoard(); + + // Clicks on anything else (background, empty piles, etc.) will do nothing. }); gameBoard.addEventListener('dragstart', e => { @@ -609,11 +580,9 @@ document.addEventListener('DOMContentLoaded', () => { if (!dropTargetEl) return; const destPile = getPileArrayFromElement(dropTargetEl.closest('.pile')); if (destPile && canMove(dragged.cards[0], destPile)) { - // --- SCORING LOGIC --- if (foundations.includes(destPile)) score += 10; if (tableau.includes(destPile) && dragged.sourcePile === waste) score += 5; if (tableau.includes(destPile) && foundations.includes(dragged.sourcePile)) score -= 15; - // --- END SCORING LOGIC --- pushHistory(); moveSequence(dragged.sourcePile, dragged.startIndex, destPile); performMoveCleanup(dragged.sourcePile);