feat: add Tetris game

This commit is contained in:
Ferdinand
2026-03-31 14:39:05 +02:00
parent 658c1c71c6
commit 88ac5d8f52

237
tetris.html Normal file
View File

@@ -0,0 +1,237 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Tetris</title>
<link rel="stylesheet" href="style.css">
<style>
#wrapper {
position: relative;
display: flex;
gap: 24px;
align-items: flex-start;
}
#sidebar {
display: flex;
flex-direction: column;
gap: 16px;
color: #a0a0c0;
font-size: 0.85rem;
padding-top: 4px;
min-width: 80px;
}
#sidebar .label { color: #555; font-size: 0.7rem; letter-spacing: 2px; }
#sidebar .value { color: #4ecca3; font-size: 1.1rem; font-weight: bold; }
#next-canvas { border: 1px solid #2a2a4a; display: block; }
</style>
</head>
<body>
<a class="menu-link" href="index.html">← Menü</a>
<h1>TETRIS</h1>
<div id="wrapper">
<canvas id="canvas" width="300" height="600"></canvas>
<div id="sidebar">
<div>
<div class="label">SCORE</div>
<div class="value" id="score-display">0</div>
</div>
<div>
<div class="label">LEVEL</div>
<div class="value" id="level-display">1</div>
</div>
<div>
<div class="label">LINES</div>
<div class="value" id="lines-display">0</div>
</div>
<div>
<div class="label">BEST</div>
<div class="value" id="hs-display">0</div>
</div>
<div>
<div class="label">NÄCHSTES</div>
<canvas id="next-canvas" width="80" height="80"></canvas>
</div>
</div>
</div>
<div class="overlay" id="overlay">
<h2>GAME OVER</h2>
<p id="final-score"></p>
<button class="retro-btn" onclick="startGame()">NEU STARTEN</button>
<a class="retro-btn" href="index.html" style="text-decoration:none;text-align:center;">← MENÜ</a>
</div>
<script src="scores.js"></script>
<script>
const COLS = 10, ROWS = 20, BLOCK = 30;
const COLORS = ['', '#ff6b6b','#ffd93d','#6bcb77','#4d96ff','#ff9f43','#a29bfe','#fd79a8'];
const SHAPES = [
[],
[[1,1,1,1]],
[[2,2],[2,2]],
[[0,3,0],[3,3,3]],
[[0,4,4],[4,4,0]],
[[5,5,0],[0,5,5]],
[[6,0,0],[6,6,6]],
[[0,0,7],[7,7,7]],
];
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const nextCanvas = document.getElementById('next-canvas');
const nextCtx = nextCanvas.getContext('2d');
const overlay = document.getElementById('overlay');
let board, piece, nextPiece, score, level, lines, dropInterval, lastTime, animId;
function createBoard() {
return Array.from({length: ROWS}, () => Array(COLS).fill(0));
}
function randomPiece() {
const id = Math.ceil(Math.random() * 7);
const shape = SHAPES[id].map(r => [...r]);
return { id, shape, x: Math.floor(COLS / 2) - Math.floor(shape[0].length / 2), y: 0 };
}
function drawBlock(context, x, y, colorId, size) {
context.fillStyle = COLORS[colorId];
context.fillRect(x * size + 1, y * size + 1, size - 2, size - 2);
}
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
board.forEach((row, y) => row.forEach((val, x) => { if (val) drawBlock(ctx, x, y, val, BLOCK); }));
}
function drawPiece(p) {
p.shape.forEach((row, dy) => row.forEach((val, dx) => {
if (val) drawBlock(ctx, p.x + dx, p.y + dy, val, BLOCK);
}));
}
function drawNext() {
nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
const size = 20;
const offX = Math.floor((4 - nextPiece.shape[0].length) / 2);
const offY = Math.floor((4 - nextPiece.shape.length) / 2);
nextPiece.shape.forEach((row, dy) => row.forEach((val, dx) => {
if (val) drawBlock(nextCtx, offX + dx, offY + dy, val, size);
}));
}
function collides(p, board) {
return p.shape.some((row, dy) => row.some((val, dx) => {
if (!val) return false;
const nx = p.x + dx, ny = p.y + dy;
return nx < 0 || nx >= COLS || ny >= ROWS || (ny >= 0 && board[ny][nx]);
}));
}
function merge(p) {
p.shape.forEach((row, dy) => row.forEach((val, dx) => {
if (val) board[p.y + dy][p.x + dx] = val;
}));
}
function clearLines() {
let cleared = 0;
for (let y = ROWS - 1; y >= 0; y--) {
if (board[y].every(v => v)) {
board.splice(y, 1);
board.unshift(Array(COLS).fill(0));
cleared++;
y++;
}
}
if (cleared) {
const pts = [0, 100, 300, 500, 800][cleared] * level;
score += pts;
lines += cleared;
level = Math.floor(lines / 10) + 1;
dropInterval = Math.max(100, 1000 - (level - 1) * 90);
updateHUD();
}
}
function rotate(shape) {
return shape[0].map((_, i) => shape.map(row => row[i]).reverse());
}
function updateHUD() {
document.getElementById('score-display').textContent = score.toLocaleString('de-DE');
document.getElementById('level-display').textContent = level;
document.getElementById('lines-display').textContent = lines;
document.getElementById('hs-display').textContent = getHighscore('tetris').toLocaleString('de-DE');
}
function gameOver() {
cancelAnimationFrame(animId);
setHighscore('tetris', score);
document.getElementById('final-score').textContent = 'SCORE: ' + score.toLocaleString('de-DE');
document.getElementById('hs-display').textContent = getHighscore('tetris').toLocaleString('de-DE');
overlay.style.display = 'flex';
}
function startGame() {
board = createBoard();
score = 0; level = 1; lines = 0; dropInterval = 1000; lastTime = 0;
piece = randomPiece();
nextPiece = randomPiece();
overlay.style.display = 'none';
updateHUD();
drawNext();
animId = requestAnimationFrame(loop);
}
function loop(timestamp) {
const delta = timestamp - lastTime;
if (delta > dropInterval) {
lastTime = timestamp;
const moved = { ...piece, shape: piece.shape.map(r => [...r]), y: piece.y + 1 };
if (!collides(moved, board)) {
piece = moved;
} else {
merge(piece);
clearLines();
piece = nextPiece;
nextPiece = randomPiece();
drawNext();
if (collides(piece, board)) { gameOver(); return; }
}
}
drawBoard();
drawPiece(piece);
animId = requestAnimationFrame(loop);
}
document.addEventListener('keydown', e => {
if (overlay.style.display === 'flex') return;
if (e.key === 'ArrowLeft') {
const m = { ...piece, shape: piece.shape.map(r => [...r]), x: piece.x - 1 };
if (!collides(m, board)) piece = m;
} else if (e.key === 'ArrowRight') {
const m = { ...piece, shape: piece.shape.map(r => [...r]), x: piece.x + 1 };
if (!collides(m, board)) piece = m;
} else if (e.key === 'ArrowDown') {
const m = { ...piece, shape: piece.shape.map(r => [...r]), y: piece.y + 1 };
if (!collides(m, board)) piece = m; else { merge(piece); clearLines(); piece = nextPiece; nextPiece = randomPiece(); drawNext(); if (collides(piece, board)) { gameOver(); return; } }
} else if (e.key === 'ArrowUp') {
const rotated = rotate(piece.shape);
const m = { ...piece, shape: rotated };
if (!collides(m, board)) piece = m;
} else if (e.key === ' ') {
e.preventDefault();
while (true) {
const m = { ...piece, shape: piece.shape.map(r => [...r]), y: piece.y + 1 };
if (collides(m, board)) { merge(piece); clearLines(); piece = nextPiece; nextPiece = randomPiece(); drawNext(); if (collides(piece, board)) gameOver(); break; }
piece = m;
}
}
});
startGame();
</script>
</body>
</html>