change double click to single click
This commit is contained in:
parent
1840280d23
commit
65f74bd0c6
@ -65,7 +65,7 @@
|
|||||||
<button id="modal-close-btn" class="btn">Close</button>
|
<button id="modal-close-btn" class="btn">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
163
script.js
163
script.js
@ -11,8 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let deck, stock, waste, foundations, tableau;
|
let deck, stock, waste, foundations, tableau;
|
||||||
let cardElements = {};
|
let cardElements = {};
|
||||||
let dragged = { cards: [], sourcePile: null, startIndex: -1 };
|
let dragged = { cards: [], sourcePile: null, startIndex: -1 };
|
||||||
let selected = { card: null, sourcePile: null, cardIndex: -1 };
|
// 'selected' state is no longer needed for click controls and has been removed.
|
||||||
let lastClick = { time: 0, cardId: null };
|
|
||||||
const history = [];
|
const history = [];
|
||||||
|
|
||||||
let isAutoCompleting = false;
|
let isAutoCompleting = false;
|
||||||
@ -50,7 +49,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// ====== SAVE / LOAD ======
|
// ====== SAVE / LOAD ======
|
||||||
function saveGame() {
|
function saveGame() {
|
||||||
// Also save score in the game state
|
|
||||||
const gameState = { stock, waste, foundations, tableau, history, drawCount: DRAW_COUNT, score };
|
const gameState = { stock, waste, foundations, tableau, history, drawCount: DRAW_COUNT, score };
|
||||||
localStorage.setItem('solitaire-save-game', JSON.stringify(gameState));
|
localStorage.setItem('solitaire-save-game', JSON.stringify(gameState));
|
||||||
}
|
}
|
||||||
@ -66,7 +64,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
history.length = 0;
|
history.length = 0;
|
||||||
history.push(...gameState.history);
|
history.push(...gameState.history);
|
||||||
DRAW_COUNT = gameState.drawCount || 3;
|
DRAW_COUNT = gameState.drawCount || 3;
|
||||||
score = gameState.score || 0; // Load score
|
score = gameState.score || 0;
|
||||||
drawSelect.value = DRAW_COUNT;
|
drawSelect.value = DRAW_COUNT;
|
||||||
Object.values(cardElements).forEach(el => el.remove());
|
Object.values(cardElements).forEach(el => el.remove());
|
||||||
cardElements = {};
|
cardElements = {};
|
||||||
@ -90,11 +88,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ====== UNDO SNAPSHOTS ======
|
// ====== 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 restoreState = (json) => {
|
||||||
const s = JSON.parse(json);
|
const s = JSON.parse(json);
|
||||||
stock = s.stock; waste = s.waste; foundations = s.foundations; tableau = s.tableau;
|
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 = () => {
|
const pushHistory = () => {
|
||||||
if (history.length > 50) history.shift();
|
if (history.length > 50) history.shift();
|
||||||
@ -132,14 +130,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
stock = deck;
|
stock = deck;
|
||||||
winMessage.classList.add('hidden');
|
winMessage.classList.add('hidden');
|
||||||
history.length = 0;
|
history.length = 0;
|
||||||
pushHistory(); // Initial state for undo
|
pushHistory();
|
||||||
updateBoard(true);
|
updateBoard(true);
|
||||||
saveGame();
|
saveGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== RENDER ======
|
// ====== RENDER ======
|
||||||
function updateBoard(initial=false) {
|
function updateBoard(initial=false) {
|
||||||
updateScoreDisplay(); // Keep score display fresh on every update
|
updateScoreDisplay();
|
||||||
const gameBoardRect = gameBoard.getBoundingClientRect();
|
const gameBoardRect = gameBoard.getBoundingClientRect();
|
||||||
const updatePile = (pileData, pileEl, type) => {
|
const updatePile = (pileData, pileEl, type) => {
|
||||||
if (!pileEl) return;
|
if (!pileEl) return;
|
||||||
@ -166,7 +164,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cardEl.classList.remove('is-flipped');
|
cardEl.classList.remove('is-flipped');
|
||||||
cardEl.draggable = false;
|
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;
|
const zBase = type === 'tableau' ? 100 : type === 'foundation' ? 400 : 600;
|
||||||
cardEl.style.zIndex = zBase + i;
|
cardEl.style.zIndex = zBase + i;
|
||||||
});
|
});
|
||||||
@ -303,10 +301,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return card.suit === top.suit && getValueRank(card.value) === getValueRank(top.value) + 1;
|
return card.suit === top.suit && getValueRank(card.value) === getValueRank(top.value) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function clearSelection() {
|
|
||||||
if (selected.card) cardElements[selected.card.id]?.classList.remove('selected');
|
// The `clearSelection` function is no longer needed.
|
||||||
selected = { card:null, sourcePile:null, cardIndex:-1 };
|
|
||||||
}
|
|
||||||
function moveSequence(sourcePile, startIndex, destPile) {
|
function moveSequence(sourcePile, startIndex, destPile) {
|
||||||
const seq = sourcePile.splice(startIndex);
|
const seq = sourcePile.splice(startIndex);
|
||||||
destPile.push(...seq);
|
destPile.push(...seq);
|
||||||
@ -316,10 +313,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const topCard = sourcePile[sourcePile.length - 1];
|
const topCard = sourcePile[sourcePile.length - 1];
|
||||||
if (!topCard.faceUp) {
|
if (!topCard.faceUp) {
|
||||||
topCard.faceUp = true;
|
topCard.faceUp = true;
|
||||||
score += 5; // +5 points for revealing a card
|
score += 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clearSelection();
|
|
||||||
updateBoard();
|
updateBoard();
|
||||||
saveGame();
|
saveGame();
|
||||||
if (!isAutoCompleting) {
|
if (!isAutoCompleting) {
|
||||||
@ -350,57 +346,58 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cardsToMove.forEach(c => c.faceUp = true);
|
cardsToMove.forEach(c => c.faceUp = true);
|
||||||
waste.push(...cardsToMove);
|
waste.push(...cardsToMove);
|
||||||
} else if (waste.length > 0) {
|
} 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 = waste.reverse();
|
||||||
stock.forEach(c => c.faceUp = false);
|
stock.forEach(c => c.faceUp = false);
|
||||||
waste = [];
|
waste = [];
|
||||||
}
|
}
|
||||||
clearSelection();
|
|
||||||
updateBoard();
|
updateBoard();
|
||||||
saveGame();
|
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;
|
if (isAutoCompleting) return;
|
||||||
const { sourcePile, cardIndex, cardsToMove } = findCardData(cardEl);
|
const { sourcePile, cardIndex, cardsToMove } = findCardData(cardEl);
|
||||||
|
|
||||||
if (!sourcePile || !cardsToMove || cardsToMove.length === 0) return;
|
if (!sourcePile || !cardsToMove || cardsToMove.length === 0) return;
|
||||||
const cardToMove = cardsToMove[0];
|
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) {
|
if (cardsToMove.length === 1) {
|
||||||
for (const f of foundations) {
|
for (const f of foundations) {
|
||||||
if (canMove(cardToMove, f)) {
|
if (canMove(cardToMove, f)) {
|
||||||
score += 10;
|
score += 10;
|
||||||
updateScoreDisplay();
|
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(sourcePile, cardIndex, f);
|
moveSequence(sourcePile, cardIndex, f);
|
||||||
performMoveCleanup(sourcePile);
|
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) {
|
for (const t of tableau) {
|
||||||
if (t !== sourcePile && canMove(cardToMove, t)) {
|
if (t !== sourcePile && canMove(cardToMove, t)) {
|
||||||
// --- SCORING LOGIC ---
|
if (sourcePile === waste) score += 5;
|
||||||
if (sourcePile === waste) score += 5; // FIX: Added +5 for waste to tableau move
|
|
||||||
if (foundations.includes(sourcePile)) score -= 15;
|
if (foundations.includes(sourcePile)) score -= 15;
|
||||||
updateScoreDisplay();
|
|
||||||
// --- END SCORING LOGIC ---
|
|
||||||
|
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(sourcePile, cardIndex, t);
|
moveSequence(sourcePile, cardIndex, t);
|
||||||
performMoveCleanup(sourcePile);
|
performMoveCleanup(sourcePile);
|
||||||
return;
|
return; // Move executed, action is complete.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function undo() {
|
function undo() {
|
||||||
if (isAutoCompleting) return;
|
if (isAutoCompleting) return;
|
||||||
if (history.length <= 1) return;
|
if (history.length <= 1) return;
|
||||||
history.pop();
|
history.pop();
|
||||||
restoreState(history[history.length-1]);
|
restoreState(history[history.length-1]);
|
||||||
clearSelection();
|
|
||||||
winMessage.classList.add('hidden');
|
winMessage.classList.add('hidden');
|
||||||
updateBoard(true);
|
updateBoard(true);
|
||||||
saveGame();
|
saveGame();
|
||||||
@ -417,14 +414,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function autoComplete() {
|
function autoComplete() {
|
||||||
const isWinnable = stock.length === 0 && tableau.flat().every(card => card.faceUp);
|
const isWinnable = stock.length === 0 && tableau.flat().every(card => card.faceUp);
|
||||||
if (!isWinnable) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
isAutoCompleting = true;
|
isAutoCompleting = true;
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
let movedCard = false;
|
let movedCard = false;
|
||||||
let source = null;
|
let source = null;
|
||||||
// First check waste pile
|
|
||||||
if (waste.length > 0) {
|
if (waste.length > 0) {
|
||||||
const card = waste[waste.length - 1];
|
const card = waste[waste.length - 1];
|
||||||
for (const f of foundations) {
|
for (const f of foundations) {
|
||||||
@ -437,7 +438,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Then check tableau piles
|
|
||||||
if (!movedCard) {
|
if (!movedCard) {
|
||||||
for (const t of tableau) {
|
for (const t of tableau) {
|
||||||
if (t.length > 0) {
|
if (t.length > 0) {
|
||||||
@ -461,7 +462,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!movedCard) {
|
if (!movedCard) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
isAutoCompleting = false;
|
isAutoCompleting = false;
|
||||||
checkWin(); // Final check
|
checkWin();
|
||||||
}
|
}
|
||||||
}, 120);
|
}, 120);
|
||||||
}
|
}
|
||||||
@ -478,7 +479,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
for(const f of foundations) if(canMove(topCard, f)) return true;
|
for(const f of foundations) if(canMove(topCard, f)) return true;
|
||||||
|
|
||||||
const movableStackIndex = sourcePile.findIndex(card => card.faceUp);
|
const movableStackIndex = sourcePile.findIndex(card => card.faceUp);
|
||||||
if (movableStackIndex > 0) {
|
if (movableStackIndex > -1) { // Fixed: check should be > -1
|
||||||
const cardToMove = sourcePile[movableStackIndex];
|
const cardToMove = sourcePile[movableStackIndex];
|
||||||
for (const destPile of tableau) {
|
for (const destPile of tableau) {
|
||||||
if (sourcePile !== destPile && canMove(cardToMove, destPile)) return true;
|
if (sourcePile !== destPile && canMove(cardToMove, destPile)) return true;
|
||||||
@ -491,14 +492,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
function handleStuckCheck() {
|
function handleStuckCheck() {
|
||||||
if (checkForAnyAvailableMove()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let tempStock = JSON.parse(JSON.stringify(stock));
|
let tempStock = JSON.parse(JSON.stringify(stock));
|
||||||
let tempWaste = JSON.parse(JSON.stringify(waste));
|
let tempWaste = JSON.parse(JSON.stringify(waste));
|
||||||
const originalStockSize = tempStock.length + tempWaste.length;
|
const originalStockSize = tempStock.length + tempWaste.length;
|
||||||
if (originalStockSize === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let cardsCycled = 0;
|
let cardsCycled = 0;
|
||||||
@ -513,82 +522,44 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cardsCycled += drawnCards.length;
|
cardsCycled += drawnCards.length;
|
||||||
const newTopWasteCard = tempWaste.length > 0 ? tempWaste[tempWaste.length - 1] : null;
|
const newTopWasteCard = tempWaste.length > 0 ? tempWaste[tempWaste.length - 1] : null;
|
||||||
if (newTopWasteCard) {
|
if (newTopWasteCard) {
|
||||||
|
const hintText = "A move will become available by drawing from the stock!";
|
||||||
for (const f of foundations) if (canMove(newTopWasteCard, f)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
for (const t of tableau) if (canMove(newTopWasteCard, t)) {
|
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;
|
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 ======
|
// ====== INPUT HANDLERS ======
|
||||||
gameBoard.addEventListener('click', e => {
|
gameBoard.addEventListener('click', e => {
|
||||||
if (isAutoCompleting) return;
|
if (isAutoCompleting) return;
|
||||||
const pileEl = e.target.closest('.pile');
|
|
||||||
const cardEl = e.target.closest('.card');
|
const clickedCardEl = e.target.closest('.card');
|
||||||
if (pileEl && pileEl.id === 'stock' && !cardEl) {
|
const clickedPileEl = e.target.closest('.pile');
|
||||||
|
|
||||||
|
// If the stock pile background was clicked, draw cards.
|
||||||
|
if (clickedPileEl && clickedPileEl.id === 'stock' && !clickedCardEl) {
|
||||||
handleStockClick();
|
handleStockClick();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cardEl) {
|
|
||||||
const { sourcePile, cardIndex, card } = findCardData(cardEl);
|
// If a card was clicked, attempt to auto-move it.
|
||||||
if (!sourcePile || !card) return;
|
if (clickedCardEl) {
|
||||||
const now = Date.now();
|
performAutoMove(clickedCardEl);
|
||||||
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();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pileEl && selected.card) {
|
|
||||||
const destPile = getPileArrayFromElement(pileEl);
|
// Clicks on anything else (background, empty piles, etc.) will do nothing.
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gameBoard.addEventListener('dragstart', e => {
|
gameBoard.addEventListener('dragstart', e => {
|
||||||
@ -609,11 +580,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!dropTargetEl) return;
|
if (!dropTargetEl) return;
|
||||||
const destPile = getPileArrayFromElement(dropTargetEl.closest('.pile'));
|
const destPile = getPileArrayFromElement(dropTargetEl.closest('.pile'));
|
||||||
if (destPile && canMove(dragged.cards[0], destPile)) {
|
if (destPile && canMove(dragged.cards[0], destPile)) {
|
||||||
// --- SCORING LOGIC ---
|
|
||||||
if (foundations.includes(destPile)) score += 10;
|
if (foundations.includes(destPile)) score += 10;
|
||||||
if (tableau.includes(destPile) && dragged.sourcePile === waste) score += 5;
|
if (tableau.includes(destPile) && dragged.sourcePile === waste) score += 5;
|
||||||
if (tableau.includes(destPile) && foundations.includes(dragged.sourcePile)) score -= 15;
|
if (tableau.includes(destPile) && foundations.includes(dragged.sourcePile)) score -= 15;
|
||||||
// --- END SCORING LOGIC ---
|
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(dragged.sourcePile, dragged.startIndex, destPile);
|
moveSequence(dragged.sourcePile, dragged.startIndex, destPile);
|
||||||
performMoveCleanup(dragged.sourcePile);
|
performMoveCleanup(dragged.sourcePile);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user