228 lines
6.0 KiB
JavaScript
228 lines
6.0 KiB
JavaScript
'use strict';
|
|
|
|
// Sรญmbolos disponibles (>= 18 รบnicos para niveles altos)
|
|
const SYMBOLS = [
|
|
'๐','๐','๐','๐','๐','๐','๐','๐',
|
|
'๐ฅ','๐','๐ฅฅ','๐','๐','๐','๐','๐',
|
|
'๐','โญ','๐','โ๏ธ','โฝ','๐','๐ฒ','๐ต',
|
|
'๐ถ','๐ฑ','๐ผ','๐ธ','๐ฆ','๐ฆ','๐ฆ','๐ฏ',
|
|
'๐','๐','โ๏ธ','๐ฒ','๐๏ธ','๐ฐ'
|
|
];
|
|
|
|
const DIFFICULTIES = {
|
|
facil: { pairs: 6 },
|
|
normal: { pairs: 8 },
|
|
dificil: { pairs: 12 },
|
|
extremo: { pairs: 18 }
|
|
};
|
|
|
|
// Estado del juego
|
|
let deck = []; // array de sรญmbolos duplicados y mezclados
|
|
let firstCard = null;
|
|
let secondCard = null;
|
|
let lockBoard = false;
|
|
let matches = 0;
|
|
let moves = 0;
|
|
let totalPairs = DIFFICULTIES.normal.pairs;
|
|
|
|
let timerId = null;
|
|
let startTime = 0;
|
|
|
|
// DOM
|
|
const gameBoard = document.getElementById('game-board');
|
|
const infoDiv = document.getElementById('info');
|
|
const resetBtn = document.getElementById('reset-btn');
|
|
const difficultySelect = document.getElementById('difficulty');
|
|
const movesSpan = document.getElementById('moves');
|
|
const timeSpan = document.getElementById('time');
|
|
const bestSpan = document.getElementById('best-score');
|
|
|
|
// Utils
|
|
function shuffle(array) {
|
|
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]];
|
|
}
|
|
}
|
|
function pickSymbols(n) {
|
|
const pool = SYMBOLS.slice();
|
|
shuffle(pool);
|
|
return pool.slice(0, n);
|
|
}
|
|
function formatTime(seconds) {
|
|
const s = Math.max(0, Math.floor(seconds));
|
|
return `${s}s`;
|
|
}
|
|
function bestKey(diff) {
|
|
return `memory_best_time_${diff}`;
|
|
}
|
|
function getBest(diff) {
|
|
const v = localStorage.getItem(bestKey(diff));
|
|
return v ? parseInt(v, 10) : null;
|
|
}
|
|
function setBestIfBetter(diff, timeSec) {
|
|
const prev = getBest(diff);
|
|
if (prev === null || timeSec < prev) {
|
|
localStorage.setItem(bestKey(diff), String(timeSec));
|
|
}
|
|
const best = getBest(diff);
|
|
if (bestSpan) bestSpan.textContent = best !== null ? `${best}s` : 'โ';
|
|
}
|
|
|
|
function startTimer() {
|
|
stopTimer();
|
|
startTime = Date.now();
|
|
timerId = setInterval(() => {
|
|
const elapsed = (Date.now() - startTime) / 1000;
|
|
if (timeSpan) timeSpan.textContent = formatTime(elapsed);
|
|
}, 250);
|
|
}
|
|
function stopTimer() {
|
|
if (timerId) {
|
|
clearInterval(timerId);
|
|
timerId = null;
|
|
}
|
|
}
|
|
|
|
function gridColumnsFor(totalCards) {
|
|
// Aproxima columnas en funciรณn del total para formar rejilla compacta
|
|
if (totalCards <= 12) return 4;
|
|
if (totalCards <= 16) return 4;
|
|
if (totalCards <= 24) return 6;
|
|
if (totalCards <= 30) return 6;
|
|
return 6;
|
|
}
|
|
|
|
function setupBoard() {
|
|
// Leer dificultad
|
|
const diff = (difficultySelect && difficultySelect.value) || 'normal';
|
|
const conf = DIFFICULTIES[diff] || DIFFICULTIES.normal;
|
|
totalPairs = conf.pairs;
|
|
|
|
// Reiniciar estado
|
|
matches = 0;
|
|
moves = 0;
|
|
firstCard = null;
|
|
secondCard = null;
|
|
lockBoard = false;
|
|
if (movesSpan) movesSpan.textContent = String(moves);
|
|
if (infoDiv) infoDiv.textContent = '';
|
|
|
|
// Construir mazo
|
|
const chosen = pickSymbols(totalPairs);
|
|
deck = [...chosen, ...chosen];
|
|
shuffle(deck);
|
|
|
|
// Render tablero accesible
|
|
gameBoard.textContent = '';
|
|
const totalCards = deck.length;
|
|
const cols = gridColumnsFor(totalCards);
|
|
gameBoard.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
|
|
|
|
deck.forEach((symbol, idx) => {
|
|
const cardEl = document.createElement('button');
|
|
cardEl.type = 'button';
|
|
cardEl.className = 'card';
|
|
cardEl.dataset.index = String(idx);
|
|
cardEl.dataset.symbol = symbol;
|
|
cardEl.textContent = '';
|
|
cardEl.setAttribute('role', 'gridcell');
|
|
cardEl.setAttribute('aria-label', 'Carta de memoria');
|
|
cardEl.setAttribute('aria-disabled', 'false');
|
|
|
|
cardEl.addEventListener('click', () => flipCard(cardEl));
|
|
cardEl.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
flipCard(cardEl);
|
|
}
|
|
});
|
|
|
|
gameBoard.appendChild(cardEl);
|
|
});
|
|
|
|
// Mostrar mejor tiempo
|
|
const best = getBest(diff);
|
|
if (bestSpan) bestSpan.textContent = best !== null ? `${best}s` : 'โ';
|
|
|
|
// Arrancar temporizador
|
|
startTimer();
|
|
|
|
// Foco inicial
|
|
const first = gameBoard.querySelector('.card');
|
|
if (first) first.focus();
|
|
}
|
|
|
|
function flipCard(cardEl) {
|
|
if (lockBoard) return;
|
|
if (cardEl.classList.contains('flipped') || cardEl.classList.contains('matched')) return;
|
|
|
|
// Voltear carta
|
|
cardEl.classList.add('flipped');
|
|
cardEl.textContent = cardEl.dataset.symbol;
|
|
cardEl.setAttribute('aria-disabled', 'true');
|
|
|
|
if (!firstCard) {
|
|
firstCard = cardEl;
|
|
return;
|
|
}
|
|
if (!secondCard && cardEl !== firstCard) {
|
|
secondCard = cardEl;
|
|
lockBoard = true;
|
|
moves++;
|
|
if (movesSpan) movesSpan.textContent = String(moves);
|
|
|
|
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
|
|
// Pareja encontrada
|
|
firstCard.classList.add('matched');
|
|
secondCard.classList.add('matched');
|
|
matches++;
|
|
setTimeout(() => {
|
|
resetSelection();
|
|
if (matches === totalPairs) {
|
|
// Fin de partida
|
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
if (infoDiv) infoDiv.textContent = `ยกFelicidades! ๐ Parejas: ${totalPairs} ยท Movimientos: ${moves} ยท Tiempo: ${elapsed}s`;
|
|
stopTimer();
|
|
const diff = (difficultySelect && difficultySelect.value) || 'normal';
|
|
setBestIfBetter(diff, elapsed);
|
|
}
|
|
}, 400);
|
|
} else {
|
|
// No es pareja, desvoltear tras un momento
|
|
setTimeout(() => {
|
|
unflip(firstCard);
|
|
unflip(secondCard);
|
|
resetSelection();
|
|
}, 700);
|
|
}
|
|
}
|
|
}
|
|
|
|
function unflip(el) {
|
|
if (!el) return;
|
|
el.classList.remove('flipped');
|
|
el.textContent = '';
|
|
el.setAttribute('aria-disabled', 'false');
|
|
}
|
|
|
|
function resetSelection() {
|
|
firstCard = null;
|
|
secondCard = null;
|
|
lockBoard = false;
|
|
}
|
|
|
|
// Listeners
|
|
resetBtn.addEventListener('click', () => {
|
|
stopTimer();
|
|
setupBoard();
|
|
});
|
|
if (difficultySelect) {
|
|
difficultySelect.addEventListener('change', () => {
|
|
stopTimer();
|
|
setupBoard();
|
|
});
|
|
}
|
|
|
|
// Init
|
|
setupBoard(); |