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,67 +1,209 @@
const symbols = [
'use strict';
// Símbolos del juego (pool amplio para distintas dificultades)
const SYMBOLS = [
'🐶','🌸','⚽','🍕','🎲','🌞','🚗','🍩',
'⭐','🚀','🎮','💎'
'⭐','🚀','🎮','💎','🐱','🍔','🍟','🎧',
'🍓','🍍','🥝','🍇','🍒','🍉','🍊','🧩',
'🎯','🪙','🧠','🦄','🦊','🦁','🐼','🐸',
'🏀','🏐','🎳','🎹','🎻','🥁','🎺','🎷'
];
let cards = [];
// Configuración por dificultad
const DIFFICULTIES = {
facil: { pairs: 6, maxMoves: 40, timeSec: 90 },
normal: { pairs: 12, maxMoves: 60, timeSec: 120 },
dificil: { pairs: 18, maxMoves: 85, timeSec: 180 }
};
// Estado
let deck = []; // símbolos duplicados y mezclados
let firstCard = null;
let secondCard = null;
let lockBoard = false;
let matches = 0;
let moves = 0;
const maxMoves = 45;
let timer = 120; // segundos
let timer = 0;
let timerInterval = null;
const boardDiv = document.getElementById("game-board");
const statusDiv = document.getElementById("status");
const restartBtn = document.getElementById("restart-btn");
const movesSpan = document.getElementById("moves");
const timerSpan = document.getElementById("timer");
let totalPairs = DIFFICULTIES.normal.pairs;
let maxMoves = DIFFICULTIES.normal.maxMoves;
// DOM
const boardDiv = document.getElementById('game-board');
const statusDiv = document.getElementById('status');
const restartBtn = document.getElementById('restart-btn');
const movesSpan = document.getElementById('moves');
const timerSpan = document.getElementById('timer');
const difficultySelect = document.getElementById('difficulty');
const bestSpan = document.getElementById('best');
// Utils
function shuffle(array) {
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 bestKey(diff) {
return `memoryv2_best_${diff}`;
}
function getBest(diff) {
const v = localStorage.getItem(bestKey(diff));
return v ? parseInt(v, 10) : null;
}
function setBestIfBetter(diff, movesUsed, timeLeft) {
// Métrica compuesta: menor movimientos prioriza, y mayor tiempo restante también importa
// Guardamos mejor (menores movimientos) y desempate por mayor tiempo restante
const key = bestKey(diff);
const prevRaw = localStorage.getItem(key);
let prev = prevRaw ? JSON.parse(prevRaw) : null;
const current = { moves: movesUsed, timeLeft: timeLeft };
if (!prev || current.moves < prev.moves || (current.moves === prev.moves && current.timeLeft > prev.timeLeft)) {
localStorage.setItem(key, JSON.stringify(current));
}
prev = JSON.parse(localStorage.getItem(key));
if (bestSpan) {
if (prev) bestSpan.textContent = `Mejor: ${prev.moves} mov · ${prev.timeLeft}s`;
else bestSpan.textContent = 'Mejor: —';
}
}
function updateBestDisplay(diff) {
const prevRaw = localStorage.getItem(bestKey(diff));
if (!bestSpan) return;
if (!prevRaw) {
bestSpan.textContent = 'Mejor: —';
} else {
const prev = JSON.parse(prevRaw);
bestSpan.textContent = `Mejor: ${prev.moves} mov · ${prev.timeLeft}s`;
}
}
function startGame() {
matches = 0;
moves = 0;
timer = 120;
function updateHUD() {
if (movesSpan) movesSpan.textContent = `Movimientos: ${moves} / ${maxMoves}`;
if (timerSpan) timerSpan.textContent = `Tiempo: ${timer}s`;
}
function stopTimer() {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
}
function startTimer() {
stopTimer();
timerInterval = setInterval(() => {
timer--;
updateHUD();
if (timer <= 0) {
endGame(false, '¡Se acabó el tiempo!');
}
}, 1000);
}
function gridColumnsFor(totalCards) {
// Determina columnas para una rejilla compacta
if (totalCards <= 12) return 4;
if (totalCards <= 16) return 4;
if (totalCards <= 24) return 6;
if (totalCards <= 36) return 6;
return 6;
}
// Render tarjeta
function createCard(symbol, idx) {
const card = document.createElement('button');
card.type = 'button';
card.className = 'card';
card.dataset.index = String(idx);
card.dataset.symbol = symbol;
card.textContent = '';
card.setAttribute('aria-label', 'Carta');
card.setAttribute('aria-disabled', 'false');
card.addEventListener('click', () => flipCard(card));
card.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
flipCard(card);
}
});
return card;
}
function resetTurn() {
firstCard = null;
secondCard = null;
lockBoard = false;
statusDiv.textContent = '';
updateHUD();
clearInterval(timerInterval);
timerInterval = setInterval(updateTimer, 1000);
cards = [...symbols, ...symbols];
shuffle(cards);
boardDiv.innerHTML = '';
cards.forEach((symbol, idx) => {
const card = document.createElement("div");
card.className = "card";
card.dataset.index = idx;
card.dataset.symbol = symbol;
card.textContent = '';
card.onclick = () => flipCard(card);
boardDiv.appendChild(card);
});
}
function unflip(el) {
if (!el) return;
el.classList.remove('flipped');
el.textContent = '';
el.setAttribute('aria-disabled', 'false');
}
// Inicio/reinicio
function startGame() {
// Leer dificultad
const diff = (difficultySelect && difficultySelect.value) || 'normal';
const conf = DIFFICULTIES[diff] || DIFFICULTIES.normal;
totalPairs = conf.pairs;
maxMoves = conf.maxMoves;
timer = conf.timeSec;
// Reiniciar estado
matches = 0;
moves = 0;
firstCard = null;
secondCard = null;
lockBoard = false;
if (statusDiv) statusDiv.textContent = '';
updateHUD();
updateBestDisplay(diff);
// Construir deck
const chosen = pickSymbols(totalPairs);
deck = [...chosen, ...chosen];
shuffle(deck);
// Render tablero
boardDiv.textContent = '';
const totalCards = deck.length;
const cols = gridColumnsFor(totalCards);
boardDiv.style.gridTemplateColumns = `repeat(${cols}, minmax(45px, 1fr))`;
deck.forEach((symbol, idx) => {
const card = createCard(symbol, idx);
boardDiv.appendChild(card);
});
// Foco inicial y arranque de tiempo
const first = boardDiv.querySelector('.card');
if (first) first.focus();
startTimer();
}
// Lógica de flip
function flipCard(card) {
if (lockBoard) return;
if (card.classList.contains('flipped') || card.classList.contains('matched')) return;
card.classList.add('flipped');
card.textContent = card.dataset.symbol;
card.setAttribute('aria-disabled', 'true');
if (!firstCard) {
firstCard = card;
return; // ¡Esperamos por la segunda carta!
return;
}
if (card === firstCard) return;
secondCard = card;
lockBoard = true;
moves++;
@@ -77,55 +219,40 @@ function flipCard(card) {
firstCard.classList.add('hide');
secondCard.classList.add('hide');
resetTurn();
// Verifica victoria DESPUÉS de ocultar
if (matches === symbols.length) {
clearInterval(timerInterval);
if (matches === totalPairs) {
// Victoria
stopTimer();
statusDiv.textContent = `¡Felicidades! Lo lograste en ${moves} movimientos y te sobraron ${timer} segs 🎉`;
lockBoard = true; // Deshabilita el tablero tras terminar
setBestIfBetter((difficultySelect && difficultySelect.value) || 'normal', moves, timer);
lockBoard = true;
}
}, 800);
}, 600);
} else {
// No es PAR
setTimeout(() => {
firstCard.classList.remove('flipped');
secondCard.classList.remove('flipped');
firstCard.textContent = '';
secondCard.textContent = '';
unflip(firstCard);
unflip(secondCard);
resetTurn();
}, 900);
}, 700);
}
// DERROTA: por movimientos
// Derrota por límite de movimientos
if (moves >= maxMoves) {
endGame(false, "Has alcanzado el límite de movimientos. ¡Inténtalo otra vez!");
endGame(false, 'Has alcanzado el límite de movimientos. ¡Inténtalo otra vez!');
}
}
function updateHUD() {
movesSpan.textContent = `Movimientos: ${moves} / ${maxMoves}`;
timerSpan.textContent = `Tiempo: ${timer}s`;
}
function updateTimer() {
timer--;
updateHUD();
if (timer <= 0) {
endGame(false, "¡Se acabó el tiempo!");
}
}
function resetTurn() {
firstCard = null;
secondCard = null;
lockBoard = false;
}
function endGame(win, msg) {
lockBoard = true;
clearInterval(timerInterval);
stopTimer();
statusDiv.textContent = msg;
}
restartBtn.onclick = startGame;
// Listeners
restartBtn.addEventListener('click', startGame);
if (difficultySelect) {
difficultySelect.addEventListener('change', startGame);
}
// Init
startGame();