169 lines
5.6 KiB
JavaScript
169 lines
5.6 KiB
JavaScript
// Configuration
|
||
const BOARD_SIZE = 4; // 4x4 grid → 16 cards (8 pairs)
|
||
const FLIP_DELAY = 800; // ms before non‑matching cards flip back
|
||
|
||
// SVG icons – eight distinct graphics (you can replace these with your own)
|
||
const SVG_ICONS = [
|
||
`<svg viewBox="0 0 64 64" fill="#ff9800"><polygon points="32,4 39,24 60,24 42,38 48,58 32,46 16,58 22,38 4,24 25,24"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#4caf50"><circle cx="32" cy="32" r="28"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#2196F3"><rect x="12" y="12" width="40" height="40"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#e91e63"><path d="M32 4 L39 24 H60 L42 38 L48 58 L32 46 L16 58 L22 38 L4 24 H25z"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#9c27b0"><ellipse cx="32" cy="32" rx="28" ry="14"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#ff5722"><line x1="8" y1="8" x2="56" y2="56" stroke-width="8"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#03a9f4"><polygon points="32,8 40,24 58,24 44,36 50,54 32,44 14,54 20,36 6,24 24,24"/></svg>`,
|
||
`<svg viewBox="0 0 64 64" fill="#8bc34a"><polygon points="32,4 39,24 60,24 42,38 48,58 32,46 16,58 22,38 4,24 25,24"/></svg>`
|
||
];
|
||
|
||
// Game state variables
|
||
let firstCard = null; // The previously flipped card
|
||
let lockBoard = false; // Prevent interaction while cards are animating
|
||
let moves = 0; // Number of attempts (pairs flipped)
|
||
let matched = 0; // Number of cards that have been matched
|
||
let timer = null; // Interval ID for the game timer
|
||
let seconds = 0; // Elapsed seconds
|
||
|
||
// Cached DOM references
|
||
const board = document.getElementById('board');
|
||
const movesEl = document.getElementById('moves');
|
||
const timerEl = document.getElementById('timer');
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Utility functions */
|
||
/* -------------------------------------------------------------------------- */
|
||
function shuffle(array) {
|
||
// Fisher‑Yates shuffle – in‑place
|
||
for (let i = array.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[array[i], array[j]] = [array[j], array[i]];
|
||
}
|
||
return array;
|
||
}
|
||
|
||
function updateMoves() {
|
||
movesEl.textContent = `Moves: ${moves}`;
|
||
}
|
||
|
||
function updateTimer() {
|
||
timerEl.textContent = `Time: ${seconds}s`;
|
||
}
|
||
|
||
function startTimer() {
|
||
if (timer) return; // already running
|
||
timer = setInterval(() => {
|
||
seconds++;
|
||
updateTimer();
|
||
}, 1000);
|
||
}
|
||
|
||
function stopTimer() {
|
||
clearInterval(timer);
|
||
timer = null;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Card creation */
|
||
/* -------------------------------------------------------------------------- */
|
||
function createCard(svgMarkup, id) {
|
||
const card = document.createElement('div');
|
||
card.className = 'card';
|
||
card.dataset.id = id; // same id for the two matching cards
|
||
|
||
const inner = document.createElement('div');
|
||
inner.className = 'card-inner';
|
||
|
||
const front = document.createElement('div');
|
||
front.className = 'card-face card-front';
|
||
front.innerHTML = svgMarkup; // SVG goes here
|
||
|
||
const back = document.createElement('div');
|
||
back.className = 'card-face card-back';
|
||
// back can stay empty or contain a pattern
|
||
|
||
inner.appendChild(front);
|
||
inner.appendChild(back);
|
||
card.appendChild(inner);
|
||
|
||
card.addEventListener('click', onCardClick);
|
||
return card;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Click handling */
|
||
/* -------------------------------------------------------------------------- */
|
||
function onCardClick(event) {
|
||
if (lockBoard) return;
|
||
const card = event.currentTarget;
|
||
if (card.classList.contains('flipped')) return; // ignore already revealed cards
|
||
|
||
// Start timer on the very first flip
|
||
if (moves === 0 && seconds === 0) startTimer();
|
||
|
||
card.classList.add('flipped');
|
||
|
||
if (!firstCard) {
|
||
// This is the first card of the pair
|
||
firstCard = card;
|
||
return;
|
||
}
|
||
|
||
// Second card of the pair
|
||
moves++;
|
||
updateMoves();
|
||
|
||
const isMatch = firstCard.dataset.id === card.dataset.id;
|
||
if (isMatch) {
|
||
// Keep both cards flipped
|
||
matched += 2;
|
||
firstCard = null;
|
||
// Check for win condition
|
||
if (matched === BOARD_SIZE * BOARD_SIZE) {
|
||
stopTimer();
|
||
setTimeout(() => {
|
||
alert(`Congratulations! You completed the game in ${moves} moves and ${seconds} seconds.`);
|
||
}, 300);
|
||
}
|
||
} else {
|
||
// Not a match – flip back after a short delay
|
||
lockBoard = true;
|
||
setTimeout(() => {
|
||
firstCard.classList.remove('flipped');
|
||
card.classList.remove('flipped');
|
||
firstCard = null;
|
||
lockBoard = false;
|
||
}, FLIP_DELAY);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------- */
|
||
/* Game initialization */
|
||
/* -------------------------------------------------------------------------- */
|
||
function initGame() {
|
||
// Reset UI and state variables
|
||
board.innerHTML = '';
|
||
firstCard = null;
|
||
lockBoard = false;
|
||
moves = 0;
|
||
matched = 0;
|
||
seconds = 0;
|
||
updateMoves();
|
||
updateTimer();
|
||
stopTimer();
|
||
|
||
// Build a deck: duplicate each SVG, assign an id, then shuffle
|
||
const deck = [];
|
||
SVG_ICONS.forEach((svg, idx) => {
|
||
deck.push({svg, id: idx});
|
||
deck.push({svg, id: idx}); // second copy for the pair
|
||
});
|
||
shuffle(deck);
|
||
|
||
// Create card elements and append them to the board
|
||
deck.forEach(item => {
|
||
const cardEl = createCard(item.svg, item.id);
|
||
board.appendChild(cardEl);
|
||
});
|
||
}
|
||
|
||
// Kick‑off the game when the script loads
|
||
initGame();
|