tetris klaar

This commit is contained in:
martijn.diersmann@pm.me
2026-09-26 20:38:55 +02:00
commit 4c1f670e9f
16 changed files with 1565 additions and 0 deletions
+274
View File
@@ -0,0 +1,274 @@
'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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
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);