'use strict'; // Símbolos del juego (pool amplio para distintas dificultades) const SYMBOLS = [ '🐶','🌸','⚽','🍕','🎲','🌞','🚗','🍩', '⭐','🚀','🎮','💎','🐱','🍔','🍟','🎧', '🍓','🍍','🥝','🍇','🍒','🍉','🍊','🧩', '🎯','🪙','🧠','🦄','🦊','🦁','🐼','🐸', '🏀','🏐','🎳','🎹','🎻','🥁','🎺','🎷' ]; // 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; let timer = 0; let timerInterval = null; 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)); [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 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; } 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; } if (card === firstCard) return; secondCard = card; lockBoard = true; moves++; updateHUD(); if (firstCard.dataset.symbol === secondCard.dataset.symbol) { // Es PAR firstCard.classList.add('matched'); secondCard.classList.add('matched'); matches++; setTimeout(() => { firstCard.classList.add('hide'); secondCard.classList.add('hide'); resetTurn(); if (matches === totalPairs) { // Victoria stopTimer(); statusDiv.textContent = `¡Felicidades! Lo lograste en ${moves} movimientos y te sobraron ${timer} segs 🎉`; setBestIfBetter((difficultySelect && difficultySelect.value) || 'normal', moves, timer); lockBoard = true; } }, 600); } else { // No es PAR setTimeout(() => { unflip(firstCard); unflip(secondCard); resetTurn(); }, 700); } // Derrota por límite de movimientos if (moves >= maxMoves) { endGame(false, 'Has alcanzado el límite de movimientos. ¡Inténtalo otra vez!'); } } function endGame(win, msg) { lockBoard = true; stopTimer(); statusDiv.textContent = msg; } // Listeners restartBtn.addEventListener('click', startGame); if (difficultySelect) { difficultySelect.addEventListener('change', startGame); } // Init startGame();