Mejoras y optimizaciones en general.

This commit is contained in:
2025-10-03 00:05:08 +02:00
parent bd76741bd2
commit d1a7442ffa
32 changed files with 3336 additions and 783 deletions

View File

@@ -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();