tetris klaar
This commit is contained in:
+274
@@ -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, '&')
|
||||
.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);
|
||||
Reference in New Issue
Block a user