'use strict'; /* ---------- DOM-referenties ---------- */ const boardCanvas = document.getElementById('canvas-board'); const holdCanvas = document.getElementById('canvas-hold'); const nextCanvas = document.getElementById('canvas-next'); const elScore = document.getElementById('stat-score'); const elLines = document.getElementById('stat-lines'); const elLevel = document.getElementById('stat-level'); const overlay = document.getElementById('overlay'); const overlayTitle = document.getElementById('overlay-title'); const overlaySub = document.getElementById('overlay-sub'); const nameForm = document.getElementById('name-form'); const nameInput = document.getElementById('name-input'); const nameError = document.getElementById('name-error'); const submitBtn = document.getElementById('btn-submit'); const btnPrimary = document.getElementById('btn-primary'); const btnSecondary = document.getElementById('btn-secondary'); const highscoreList = document.getElementById('highscore-list'); const game = new TetrisGame(); game.onUpdate = renderUI; let gameOverHandled = false; /* ---------- highscore-opslag in localStorage ---------- */ function loadScores() { try { const raw = localStorage.getItem(CONSTANTS.HIGHSCORE_KEY); const list = raw ? JSON.parse(raw) : []; return Array.isArray(list) ? list : []; } catch (e) { return []; } } function saveScores(list) { try { localStorage.setItem( CONSTANTS.HIGHSCORE_KEY, JSON.stringify(list.slice(0, CONSTANTS.MAX_HIGHSCORES)) ); } catch (e) { /* opslag niet beschikbaar (bv. private browsing) - negeren */ } } function qualifies(score) { if (score <= 0) return false; const list = loadScores(); if (list.length < CONSTANTS.MAX_HIGHSCORES) return true; return score > list[list.length - 1].score; } function recordScore(name, score, lines, level) { const list = loadScores(); const entry = { name: name || 'Speler', score, lines, level, date: new Date().toISOString() }; list.push(entry); list.sort((a, b) => b.score - a.score || a.date.localeCompare(b.date)); saveScores(list); return list; } function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function renderHighscores() { const list = loadScores(); highscoreList.innerHTML = ''; if (list.length === 0) { const li = document.createElement('li'); li.className = 'no-scores'; li.textContent = 'Nog geen scores - wees de eerste!'; highscoreList.appendChild(li); return; } list.forEach((entry, i) => { const li = document.createElement('li'); const rank = document.createElement('span'); rank.className = 'hs-rank'; rank.textContent = i + 1; const name = document.createElement('span'); name.className = 'hs-name'; name.textContent = entry.name; const score = document.createElement('span'); score.className = 'hs-score'; score.textContent = entry.score; li.appendChild(rank); li.appendChild(name); li.appendChild(score); highscoreList.appendChild(li); }); } /* ---------- UI-panelen verversen ---------- */ function renderUI() { elScore.textContent = game.score; elLines.textContent = game.lines; elLevel.textContent = game.level; drawHold(holdCanvas, game.hold); drawNext(nextCanvas, game); } /* ---------- overlays (pauze / game over) ---------- */ function setOverlay(title, sub, opts = {}) { overlayTitle.textContent = title; overlaySub.textContent = sub || ''; nameForm.classList.toggle('hidden', !opts.showName); btnPrimary.textContent = opts.primaryLabel || 'Opnieuw spelen'; btnSecondary.classList.toggle('hidden', !opts.showSecondary); overlay.classList.remove('hidden'); } function hideOverlay() { overlay.classList.add('hidden'); } function togglePause() { if (game.over) return; game.paused = !game.paused; if (game.paused) { setOverlay('PAUZE', 'Druk op P of "Doorgaan" om verder te spelen.', { showSecondary: true, primaryLabel: 'Opnieuw spelen' }); } else { hideOverlay(); } } function restartGame() { game.reset(); game.paused = false; gameOverHandled = false; nameForm.classList.add('hidden'); nameError.classList.add('hidden'); hideOverlay(); renderUI(); } function onGameOver() { const topTen = qualifies(game.score); if (topTen) { nameInput.value = localStorage.getItem(CONSTANTS.PLAYER_NAME_KEY) || ''; setOverlay('GAME OVER', 'Top 10! Voer je naam in:', { showName: true, primaryLabel: 'Opnieuw spelen' }); nameInput.focus(); } else { setOverlay('GAME OVER', `Score: ${game.score}`, { primaryLabel: 'Opnieuw spelen' }); } renderHighscores(); } function handleSubmitScore() { const name = nameInput.value.trim(); if (!name) { nameError.classList.remove('hidden'); nameInput.focus(); return; } localStorage.setItem(CONSTANTS.PLAYER_NAME_KEY, name); recordScore(name.slice(0, 12), game.score, game.lines, game.level); nameForm.classList.add('hidden'); overlaySub.textContent = 'Score opgeslagen in de highscore-lijst!'; renderHighscores(); btnPrimary.focus(); } /* ---------- game-loop ---------- */ let lastTime = performance.now(); function loop(now) { const dt = Math.min((now - lastTime) / 1000, 0.25); lastTime = now; game.update(dt * 1000); drawBoard(boardCanvas, game); renderUI(); if (game.over && !gameOverHandled) { gameOverHandled = true; onGameOver(); } requestAnimationFrame(loop); } /* ---------- toetsenbord ---------- */ const KEYMAP = { ArrowLeft: { fn: () => game.move(-1), prevent: true }, ArrowRight: { fn: () => game.move(1), prevent: true }, ArrowDown: { fn: () => game.softDrop(), prevent: true }, ArrowUp: { fn: () => game.rotate(1), prevent: true }, KeyX: { fn: () => game.rotate(1), prevent: true }, KeyZ: { fn: () => game.rotate(-1), prevent: false }, Space: { fn: () => game.hardDrop(), prevent: true }, KeyC: { fn: () => game.holdPiece(), prevent: false }, KeyP: { fn: () => togglePause(), prevent: false }, KeyR: { fn: () => restartGame(), prevent: false } }; window.addEventListener('keydown', (event) => { // typen in het naamveld: alleen Enter voor opslaan if (event.target === nameInput) { if (event.key === 'Enter') { event.preventDefault(); handleSubmitScore(); } return; } if (event.key === 'Enter' && !overlay.classList.contains('hidden')) { if (game.over) restartGame(); else if (game.paused) togglePause(); return; } const binding = KEYMAP[event.code]; if (!binding) return; if (binding.prevent) event.preventDefault(); binding.fn(); }); /* ---------- knoppen + touch-controls ---------- */ btnPrimary.addEventListener('click', () => restartGame()); btnSecondary.addEventListener('click', () => { if (game.paused) togglePause(); }); submitBtn.addEventListener('click', handleSubmitScore); // voorkom dat Enter in het naamveld de pagina ververst (default form-submit) nameForm.addEventListener('submit', (e) => { e.preventDefault(); handleSubmitScore(); }); const ACTION_MAP = { rotate: () => game.rotate(1), rotateCcw: () => game.rotate(-1), hold: () => game.holdPiece(), hard: () => game.hardDrop(), left: () => game.move(-1), right: () => game.move(1), drop: () => game.softDrop() }; document.querySelectorAll('.t-btn').forEach((btn) => { btn.addEventListener('click', () => { if (ACTION_MAP[btn.dataset.action]) ACTION_MAP[btn.dataset.action](); }); }); /* ---------- start ---------- */ renderUI(); renderHighscores(); requestAnimationFrame(loop);