Files
Kinderspiele/game-server/public/spiele/tetris.html
T
2026-04-26 09:44:19 +02:00

341 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Tetris - KinderWelt</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; touch-action: manipulation; }
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 5px;
}
h1 { color: white; margin: 5px 0; font-size: 20px; }
.score-board { color: #4ade80; font-size: 16px; margin-bottom: 5px; }
.game-container { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; justify-content: center; }
#gameCanvas {
background: #0f0f23; border: 3px solid #4ade80; border-radius: 8px;
box-shadow: 0 0 15px rgba(74, 222, 128, 0.3); touch-action: none;
}
.side-panel { display: flex; flex-direction: column; gap: 10px; }
.next-piece {
background: #1a1a2e; border: 2px solid #4ade80; border-radius: 8px;
padding: 8px; text-align: center;
}
.next-piece h3 { color: white; margin-bottom: 5px; font-size: 12px; }
#nextCanvas { background: #0f0f23; }
.controls-panel {
display: flex; flex-direction: column; gap: 8px;
}
.btn {
background: linear-gradient(135deg, #4ade80, #22c55e); border: none; border-radius: 8px;
padding: 10px 15px; font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
color: white; box-shadow: 0 3px 8px rgba(0,0,0,0.3);
}
.btn:active { transform: scale(0.95); }
.btn-back { background: #ff6b6b; text-decoration: none; display: inline-block; text-align: center; }
/* Touch Controls - D-Pad Style */
.touch-controls {
display: grid;
grid-template-columns: 60px 60px 60px;
gap: 5px;
margin-top: 10px;
}
.touch-btn {
width: 60px; height: 60px;
background: rgba(255,255,255,0.15);
border: 2px solid #4ade80;
border-radius: 10px;
font-size: 28px;
color: #4ade80;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
touch-action: manipulation;
}
.touch-btn:active {
background: rgba(74, 222, 128, 0.3);
transform: scale(0.95);
}
.touch-btn.action {
background: rgba(74, 222, 128, 0.25);
font-size: 22px;
}
@media (min-width: 600px) {
h1 { font-size: 24px; }
.score-board { font-size: 18px; }
}
</style>
</head>
<body>
<h1>🧱 Tetris</h1>
<div class="score-board">Punkte: <span id="score">0</span> | Level: <span id="level">1</span></div>
<div class="game-container">
<canvas id="gameCanvas" width="240" height="480"></canvas>
<div class="side-panel">
<div class="next-piece">
<h3>Nächstes</h3>
<canvas id="nextCanvas" width="80" height="80"></canvas>
</div>
<div class="controls-panel">
<button class="btn" id="startBtn" onclick="startGame()">▶️ Start</button>
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
</div>
<!-- Touch Controls -->
<div class="touch-controls">
<div></div>
<button class="touch-btn" ontouchstart="rotatePiece(); return false;" onclick="rotatePiece()"></button>
<div></div>
<button class="touch-btn" ontouchstart="movePiece(-1, 0); return false;" onclick="movePiece(-1, 0)"></button>
<button class="touch-btn action" ontouchstart="hardDrop(); return false;" onclick="hardDrop()"></button>
<button class="touch-btn" ontouchstart="movePiece(1, 0); return false;" onclick="movePiece(1, 0)"></button>
<div></div>
<button class="touch-btn" ontouchstart="movePiece(0, 1); return false;" onclick="movePiece(0, 1)"></button>
<div></div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const nextCanvas = document.getElementById('nextCanvas');
const nextCtx = nextCanvas.getContext('2d');
// Game variables
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 24;
let board = Array(ROWS).fill(null).map(() => Array(COLS).fill(0));
let score = 0;
let level = 1;
let gameRunning = false;
let dropInterval = 1000;
let lastDrop = 0;
let animationId;
// Tetrominos
const PIECES = [
[[1,1,1,1]], [[1,1],[1,1]], [[0,1,0],[1,1,1]],
[[1,1,0],[0,1,1]], [[0,1,1],[1,1,0]],
[[1,0,0],[1,1,1]], [[0,0,1],[1,1,1]]
];
const COLORS = ['#00f0f0', '#f0f000', '#a000f0', '#00f000', '#f00000', '#f0a000', '#0000f0'];
let currentPiece = null;
let nextPiece = null;
let currentX = 0, currentY = 0, currentColor = 0;
function createPiece() {
const type = Math.floor(Math.random() * PIECES.length);
return { shape: PIECES[type], color: type };
}
function spawnPiece() {
currentPiece = nextPiece || createPiece();
nextPiece = createPiece();
currentX = Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2);
currentY = 0;
currentColor = currentPiece.color;
drawNext();
if (collision(currentPiece.shape, currentX, currentY)) {
gameOver();
}
}
function drawNext() {
nextCtx.fillStyle = '#0f0f23';
nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
if (!nextPiece) return;
const blockSize = 18;
const offsetX = (nextCanvas.width - nextPiece.shape[0].length * blockSize) / 2;
const offsetY = (nextCanvas.height - nextPiece.shape.length * blockSize) / 2;
nextCtx.fillStyle = COLORS[nextPiece.color];
nextCtx.strokeStyle = 'white';
for (let y = 0; y < nextPiece.shape.length; y++) {
for (let x = 0; x < nextPiece.shape[y].length; x++) {
if (nextPiece.shape[y][x]) {
nextCtx.fillRect(offsetX + x * blockSize, offsetY + y * blockSize, blockSize - 1, blockSize - 1);
nextCtx.strokeRect(offsetX + x * blockSize, offsetY + y * blockSize, blockSize - 1, blockSize - 1);
}
}
}
}
function collision(shape, x, y) {
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row].length; col++) {
if (shape[row][col]) {
const newX = x + col;
const newY = y + row;
if (newX < 0 || newX >= COLS || newY >= ROWS) return true;
if (newY >= 0 && board[newY][newX]) return true;
}
}
}
return false;
}
function mergePiece() {
for (let y = 0; y < currentPiece.shape.length; y++) {
for (let x = 0; x < currentPiece.shape[y].length; x++) {
if (currentPiece.shape[y][x]) {
const boardY = currentY + y;
const boardX = currentX + x;
if (boardY >= 0) board[boardY][boardX] = currentColor + 1;
}
}
}
}
function clearLines() {
let linesCleared = 0;
for (let y = ROWS - 1; y >= 0; y--) {
if (board[y].every(cell => cell !== 0)) {
board.splice(y, 1);
board.unshift(Array(COLS).fill(0));
linesCleared++;
y++;
}
}
if (linesCleared > 0) {
score += linesCleared * 100 * level;
level = Math.floor(score / 1000) + 1;
dropInterval = Math.max(100, 1000 - (level - 1) * 80);
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
}
}
function rotatePiece() {
if (!gameRunning || !currentPiece) return;
const rotated = currentPiece.shape[0].map((_, i) =>
currentPiece.shape.map(row => row[i]).reverse()
);
if (!collision(rotated, currentX, currentY)) {
currentPiece.shape = rotated;
}
}
function movePiece(dx, dy) {
if (!gameRunning || !currentPiece) return;
const newX = currentX + dx;
const newY = currentY + dy;
if (!collision(currentPiece.shape, newX, newY)) {
currentX = newX;
currentY = newY;
} else if (dy > 0) {
mergePiece();
clearLines();
spawnPiece();
}
}
function hardDrop() {
if (!gameRunning) return;
while (!collision(currentPiece.shape, currentX, currentY + 1)) {
currentY++;
score += 2;
}
document.getElementById('score').textContent = score;
movePiece(0, 1);
}
function draw() {
// Hintergrund
ctx.fillStyle = '#0f0f23';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Board
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if (board[y][x]) {
ctx.fillStyle = COLORS[board[y][x] - 1];
ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
}
}
}
// Aktuelles Piece
if (currentPiece) {
ctx.fillStyle = COLORS[currentColor];
for (let y = 0; y < currentPiece.shape.length; y++) {
for (let x = 0; x < currentPiece.shape[y].length; x++) {
if (currentPiece.shape[y][x]) {
const px = (currentX + x) * BLOCK_SIZE;
const py = (currentY + y) * BLOCK_SIZE;
ctx.fillRect(px, py, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.strokeRect(px, py, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
}
}
}
}
}
function gameLoop(timestamp) {
if (gameRunning) {
if (timestamp - lastDrop > dropInterval) {
movePiece(0, 1);
lastDrop = timestamp;
}
draw();
}
animationId = requestAnimationFrame(gameLoop);
}
function startGame() {
board = Array(ROWS).fill(null).map(() => Array(COLS).fill(0));
score = 0;
level = 1;
dropInterval = 1000;
gameRunning = true;
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('startBtn').textContent = '🔄 Neustart';
spawnPiece();
}
function gameOver() {
gameRunning = false;
alert('Game Over! Punkte: ' + score);
}
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (!gameRunning) return;
switch(e.key) {
case 'ArrowLeft': case 'a': movePiece(-1, 0); break;
case 'ArrowRight': case 'd': movePiece(1, 0); break;
case 'ArrowDown': case 's': movePiece(0, 1); break;
case 'ArrowUp': case 'w': case ' ': rotatePiece(); break;
}
});
// Start
gameLoop(0);
</script>
</body>
</html>