ulti-match/script.js

193 lines
6.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Configuration
const BOARD_HEIGHT = 4;
const BOARD_WIDTH = 7;
const FLIP_DELAY = 800; // ms before nonmatching cards flip back
// SVG icons fourteen 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"><line x1="8" y1="8" x2="56" y2="56" stroke="#ff5722" stroke-width="8"/></svg>`,
`<svg viewBox="0 0 64 64" fill="#795548"><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"><line x1="8" y1="56" x2="56" y2="8" stroke="#e91e63" stroke-width="8"/></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="#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>`,
`<svg viewBox="0 0 64 64" fill="#607d8b"><polygon points="16,32 48,32 32,48"/></svg>`,
`<svg viewBox="0 0 64 64" fill="#ff9800"><circle cx="32" cy="32" r="12"/></svg>`,
`<svg viewBox="0 0 64 64" fill="#4caf50"><rect x="20" y="20" width="24" height="24"/></svg>`,
`<svg viewBox="0 0 64 64" fill="#2196F3"><path d="M16,32 Q32,4 48,32 T80,32"/></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) {
// FisherYates shuffle inplace
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;
}
/* -------------------------------------------------------------------------- */
/* Preload 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 preloaded 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);
});
}
// Kickoff the game when the script loads
initGame();