Mejoras y optimizaciones en general.
This commit is contained in:
@@ -1,85 +1,228 @@
|
||||
const symbols = ['🍎','🍌','🍒','🍇','🍉','🍑','🍊','🍓'];
|
||||
let cards = [];
|
||||
'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) {
|
||||
// Fisher-Yates shuffle
|
||||
for(let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i+1));
|
||||
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;
|
||||
infoDiv.textContent = '';
|
||||
// Duplica los símbolos y los mezcla
|
||||
cards = [...symbols, ...symbols];
|
||||
shuffle(cards);
|
||||
gameBoard.innerHTML = '';
|
||||
cards.forEach((symbol, idx) => {
|
||||
const cardEl = document.createElement('div');
|
||||
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 = idx;
|
||||
cardEl.dataset.index = String(idx);
|
||||
cardEl.dataset.symbol = symbol;
|
||||
cardEl.textContent = '';
|
||||
cardEl.onclick = () => flipCard(cardEl);
|
||||
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;
|
||||
} else if(!secondCard && cardEl !== firstCard) {
|
||||
return;
|
||||
}
|
||||
if (!secondCard && cardEl !== firstCard) {
|
||||
secondCard = cardEl;
|
||||
lockBoard = true;
|
||||
moves++;
|
||||
if (movesSpan) movesSpan.textContent = String(moves);
|
||||
|
||||
if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
|
||||
// ¡Es pareja!
|
||||
// Pareja encontrada
|
||||
firstCard.classList.add('matched');
|
||||
secondCard.classList.add('matched');
|
||||
matches++;
|
||||
resetFlipped(700);
|
||||
if (matches === symbols.length) {
|
||||
infoDiv.textContent = '¡Felicidades! Has encontrado todas las parejas 🎉';
|
||||
}
|
||||
} else {
|
||||
// No es pareja, voltea las cartas después de un momento
|
||||
setTimeout(() => {
|
||||
firstCard.classList.remove('flipped');
|
||||
secondCard.classList.remove('flipped');
|
||||
firstCard.textContent = '';
|
||||
secondCard.textContent = '';
|
||||
resetFlipped(0);
|
||||
}, 900);
|
||||
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 resetFlipped(delay) {
|
||||
setTimeout(() => {
|
||||
firstCard = null;
|
||||
secondCard = null;
|
||||
lockBoard = false;
|
||||
}, delay);
|
||||
function unflip(el) {
|
||||
if (!el) return;
|
||||
el.classList.remove('flipped');
|
||||
el.textContent = '';
|
||||
el.setAttribute('aria-disabled', 'false');
|
||||
}
|
||||
|
||||
resetBtn.onclick = setupBoard;
|
||||
function resetSelection() {
|
||||
firstCard = null;
|
||||
secondCard = null;
|
||||
lockBoard = false;
|
||||
}
|
||||
|
||||
// Listeners
|
||||
resetBtn.addEventListener('click', () => {
|
||||
stopTimer();
|
||||
setupBoard();
|
||||
});
|
||||
if (difficultySelect) {
|
||||
difficultySelect.addEventListener('change', () => {
|
||||
stopTimer();
|
||||
setupBoard();
|
||||
});
|
||||
}
|
||||
|
||||
// Init
|
||||
setupBoard();
|
Reference in New Issue
Block a user