feat: add Tetris game
This commit is contained in:
237
tetris.html
Normal file
237
tetris.html
Normal 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>
|
||||
Reference in New Issue
Block a user