// Configuration
const BOARD_HEIGHT = 4;
const BOARD_WIDTH = 7;
const FLIP_DELAY = 800; // ms before non‑matching cards flip back
// SVG icons – fourteen distinct graphics (you can replace these with your own)
const SVG_ICONS = [
``,
``,
``,
``,
``,
``,
``,
``,
``,
``,
``,
``,
``,
``
];
// 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;
}
/* -------------------------------------------------------------------------- */
/* Pre‑load background music */
/* -------------------------------------------------------------------------- */
const bgMusic = new Audio('./background.mp3');
bgMusic.preload = 'auto'; // ensure the file is loaded early
bgMusic.loop = true; // optional – keep playing while the game runs
const flip = new Audio('./flip.mp3');
flip.preload = 'auto';
const match = new Audio('./match.mp3');
match.preload = 'auto';
/* -------------------------------------------------------------------------- */
/* Click handling */
/* -------------------------------------------------------------------------- */
function onCardClick(event) {
if (lockBoard) return;
const card = event.currentTarget;
if (card.classList.contains('flipped')) return; // ignore already revealed cards
flip.play();
// Start timer on the very first flip
if (moves === 0 && seconds === 0) {
startTimer();
bgMusic.play(); // ← play pre‑loaded audio
}
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;
match.play();
// Check for win condition
if (matched === BOARD_HEIGHT * BOARD_WIDTH) {
stopTimer();
setTimeout(() => {
alert(`Congratulations! You completed the game in ${moves} moves and ${seconds} seconds.`);
}, 300);
bgMusic.stop();
}
} 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();