feat: add Space Invaders game

This commit is contained in:
Ferdinand
2026-03-31 14:46:24 +02:00
parent a82e246464
commit 740aeb7709

182
spaceinvaders.html Normal file
View File

@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Space Invaders</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<a class="menu-link" href="index.html">← Menü</a>
<h1>SPACE INVADERS</h1>
<div class="hud">
<div>SCORE <span id="score-display">0</span></div>
<div>LIVES <span id="lives-display">3</span></div>
<div>BEST <span id="hs-display">0</span></div>
</div>
<canvas id="canvas" width="480" height="480"></canvas>
<div class="overlay" id="overlay">
<h2 id="overlay-title">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 canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const COLS = 11, ROWS = 5;
const ALIEN_W = 32, ALIEN_H = 24, ALIEN_PAD_X = 10, ALIEN_PAD_Y = 16;
const ALIEN_POINTS = [30, 20, 20, 10, 10];
const PLAYER_W = 36, PLAYER_H = 16, PLAYER_Y = 440, PLAYER_SPEED = 5;
const BULLET_W = 3, BULLET_H = 12, BULLET_SPEED = 8;
const ENEMY_BULLET_SPEED = 4;
let player, bullets, enemyBullets, aliens, alienDir, alienSpeed, alienDropped;
let score, lives, animId, keys, lastEnemyShot, alienMoveTimer, alienMoveInterval;
function createAliens() {
const arr = [];
for (let r = 0; r < ROWS; r++)
for (let c = 0; c < COLS; c++)
arr.push({ x: c * (ALIEN_W + ALIEN_PAD_X) + 40, y: r * (ALIEN_H + ALIEN_PAD_Y) + 60, alive: true, row: r, col: c });
return arr;
}
function updateHUD() {
document.getElementById('score-display').textContent = score.toLocaleString('de-DE');
document.getElementById('lives-display').textContent = lives;
document.getElementById('hs-display').textContent = getHighscore('spaceinvaders').toLocaleString('de-DE');
}
function showOverlay(title) {
cancelAnimationFrame(animId);
setHighscore('spaceinvaders', score);
document.getElementById('overlay-title').textContent = title;
document.getElementById('final-score').textContent = 'SCORE: ' + score.toLocaleString('de-DE');
document.getElementById('hs-display').textContent = getHighscore('spaceinvaders').toLocaleString('de-DE');
overlay.style.display = 'flex';
}
function startGame() {
player = { x: 220 };
bullets = []; enemyBullets = [];
aliens = createAliens();
alienDir = 1; alienSpeed = 20; alienDropped = false;
score = 0; lives = 3;
keys = {}; lastEnemyShot = 0; alienMoveTimer = 0; alienMoveInterval = 800;
overlay.style.display = 'none';
updateHUD();
animId = requestAnimationFrame(loop);
}
function drawAlien(x, y, row) {
ctx.fillStyle = row === 0 ? '#fd79a8' : row < 3 ? '#a29bfe' : '#4ecca3';
ctx.fillRect(x + 4, y + 4, ALIEN_W - 8, ALIEN_H - 8);
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(x + 8, y + 8, 4, 4);
ctx.fillRect(x + ALIEN_W - 12, y + 8, 4, 4);
}
function loop(timestamp) {
// Spieler bewegen
if (keys['ArrowLeft']) player.x = Math.max(0, player.x - PLAYER_SPEED);
if (keys['ArrowRight']) player.x = Math.min(canvas.width - PLAYER_W, player.x + PLAYER_SPEED);
// Spieler-Bullets
bullets.forEach(b => b.y -= BULLET_SPEED);
bullets = bullets.filter(b => b.y > 0);
// Alien-Bewegung
alienMoveTimer += 16;
if (alienMoveTimer >= alienMoveInterval) {
alienMoveTimer = 0;
const alive = aliens.filter(a => a.alive);
const maxX = Math.max(...alive.map(a => a.x));
const minX = Math.min(...alive.map(a => a.x));
if ((alienDir > 0 && maxX + ALIEN_W >= canvas.width - 10) ||
(alienDir < 0 && minX <= 10)) {
aliens.forEach(a => { a.y += 20; });
alienDir *= -1;
alienMoveInterval = Math.max(100, alienMoveInterval - 30);
} else {
aliens.forEach(a => { a.x += alienDir * alienSpeed; });
}
}
// Alien beschießt Spieler zufällig
if (timestamp - lastEnemyShot > 1200) {
lastEnemyShot = timestamp;
const alive = aliens.filter(a => a.alive);
if (alive.length) {
const shooter = alive[Math.floor(Math.random() * alive.length)];
enemyBullets.push({ x: shooter.x + ALIEN_W / 2, y: shooter.y + ALIEN_H });
}
}
enemyBullets.forEach(b => b.y += ENEMY_BULLET_SPEED);
enemyBullets = enemyBullets.filter(b => b.y < canvas.height);
// Kollision: Spieler-Bullets vs Aliens
bullets.forEach(bull => {
aliens.forEach(a => {
if (!a.alive) return;
if (bull.x > a.x && bull.x < a.x + ALIEN_W && bull.y > a.y && bull.y < a.y + ALIEN_H) {
a.alive = false;
bull.y = -100;
score += ALIEN_POINTS[a.row];
updateHUD();
}
});
});
// Kollision: Alien-Bullets vs Spieler
enemyBullets.forEach(b => {
if (b.x > player.x && b.x < player.x + PLAYER_W && b.y > PLAYER_Y && b.y < PLAYER_Y + PLAYER_H) {
b.y = canvas.height + 1;
lives--;
updateHUD();
}
});
if (lives <= 0) { showOverlay('GAME OVER'); return; }
// Aliens erreichen Boden
if (aliens.some(a => a.alive && a.y + ALIEN_H >= PLAYER_Y)) { showOverlay('GAME OVER'); return; }
// Alle Aliens besiegt
if (!aliens.some(a => a.alive)) { showOverlay('GEWONNEN!'); return; }
// Zeichnen
ctx.clearRect(0, 0, canvas.width, canvas.height);
aliens.forEach(a => { if (a.alive) drawAlien(a.x, a.y, a.row); });
ctx.fillStyle = '#4ecca3';
ctx.beginPath();
ctx.moveTo(player.x + PLAYER_W / 2, PLAYER_Y);
ctx.lineTo(player.x, PLAYER_Y + PLAYER_H);
ctx.lineTo(player.x + PLAYER_W, PLAYER_Y + PLAYER_H);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#4ecca3';
bullets.forEach(b => ctx.fillRect(b.x, b.y, BULLET_W, BULLET_H));
ctx.fillStyle = '#ff6b6b';
enemyBullets.forEach(b => ctx.fillRect(b.x, b.y, BULLET_W, BULLET_H));
if (lives > 0) animId = requestAnimationFrame(loop);
}
document.addEventListener('keydown', e => {
keys[e.key] = true;
if (e.key === ' ' && overlay.style.display !== 'flex') {
e.preventDefault();
if (bullets.length < 3) bullets.push({ x: player.x + PLAYER_W / 2 - BULLET_W / 2, y: PLAYER_Y });
}
});
document.addEventListener('keyup', e => { keys[e.key] = false; });
document.getElementById('hs-display').textContent = getHighscore('spaceinvaders').toLocaleString('de-DE');
startGame();
</script>
</body>
</html>