341 lines
13 KiB
HTML
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> |