managarten/games/arcade/apps/web/static/games/space_defender_game.html
Till JS 9e82e40e16 rename(mana-games): rebrand to Arcade
Rename games/mana-games/ to games/arcade/, update all package names
(@mana-games/* → @arcade/*), appIds, display names, docker-compose
service, root scripts, and documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:31:37 +02:00

508 lines
No EOL
17 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Space Defender</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #0c0c1e 0%, #1a0033 50%, #000814 100%);
font-family: 'Courier New', monospace;
color: #00ff88;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.game-container {
position: relative;
background: rgba(0, 0, 0, 0.3);
border: 2px solid #00ff88;
border-radius: 10px;
box-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
}
#gameCanvas {
background: linear-gradient(180deg, #000814 0%, #001a2e 100%);
border-radius: 8px;
}
.ui {
position: absolute;
top: 10px;
left: 10px;
font-size: 18px;
text-shadow: 0 0 10px #00ff88;
z-index: 10;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background: rgba(0, 0, 0, 0.8);
padding: 30px;
border: 2px solid #ff0044;
border-radius: 10px;
box-shadow: 0 0 30px rgba(255, 0, 68, 0.5);
display: none;
z-index: 20;
}
.game-over h2 {
color: #ff0044;
font-size: 28px;
margin-bottom: 15px;
text-shadow: 0 0 15px #ff0044;
}
.restart-btn {
background: linear-gradient(45deg, #00ff88, #00cc6a);
color: #000;
border: none;
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
border-radius: 25px;
cursor: pointer;
margin-top: 15px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 255, 136, 0.3);
}
.restart-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 255, 136, 0.5);
}
.controls {
margin-top: 20px;
text-align: center;
color: #888;
font-size: 14px;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="ui">
<div>Score: <span id="score">0</span></div>
<div>Schwierigkeit: <span id="difficulty">1.0</span></div>
<div>Zeit: <span id="time">0</span>s</div>
</div>
<div class="game-over" id="gameOver">
<h2>GAME OVER</h2>
<p>Finaler Score: <span id="finalScore">0</span></p>
<button class="restart-btn" onclick="restartGame()">Nochmal spielen</button>
</div>
</div>
<div class="controls">
<p>🎮 Steuerung: A/D oder ←/→ zum Bewegen • LEERTASTE zum Schießen</p>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game ID für Statistiken
const GAME_ID = 'space-defender';
// Spielzustand
let gameState = {
player: { x: 375, y: 550, width: 50, height: 30, speed: 8 },
bullets: [],
enemies: [],
particles: [],
score: 0,
lives: 1,
gameRunning: true,
keys: {},
enemySpawnTimer: 0,
level: 1,
difficulty: 1,
timeAlive: 0
};
// Tasteneingaben
document.addEventListener('keydown', (e) => {
gameState.keys[e.key.toLowerCase()] = true;
});
document.addEventListener('keyup', (e) => {
gameState.keys[e.key.toLowerCase()] = false;
});
// Spieler
function updatePlayer() {
if (gameState.keys['a'] || gameState.keys['arrowleft']) {
gameState.player.x -= gameState.player.speed;
}
if (gameState.keys['d'] || gameState.keys['arrowright']) {
gameState.player.x += gameState.player.speed;
}
// Grenzen
gameState.player.x = Math.max(0, Math.min(canvas.width - gameState.player.width, gameState.player.x));
// Schießen
if (gameState.keys[' ']) {
shootBullet();
gameState.keys[' '] = false; // Verhindert Dauerfeuer
}
}
function drawPlayer() {
const p = gameState.player;
// Raumschiff-Design
ctx.fillStyle = '#00ff88';
ctx.fillRect(p.x + 20, p.y, 10, 20);
ctx.fillStyle = '#00cc6a';
ctx.fillRect(p.x + 15, p.y + 10, 20, 15);
ctx.fillStyle = '#0088ff';
ctx.fillRect(p.x + 5, p.y + 20, 10, 10);
ctx.fillRect(p.x + 35, p.y + 20, 10, 10);
// Glowing effect
ctx.shadowColor = '#00ff88';
ctx.shadowBlur = 10;
ctx.fillStyle = '#ffffff';
ctx.fillRect(p.x + 22, p.y + 2, 6, 8);
ctx.shadowBlur = 0;
}
// Projektile
function shootBullet() {
gameState.bullets.push({
x: gameState.player.x + gameState.player.width / 2 - 2,
y: gameState.player.y,
width: 4,
height: 10,
speed: 12
});
}
function updateBullets() {
gameState.bullets = gameState.bullets.filter(bullet => {
bullet.y -= bullet.speed;
return bullet.y > -bullet.height;
});
}
function drawBullets() {
gameState.bullets.forEach(bullet => {
ctx.fillStyle = '#ffff00';
ctx.shadowColor = '#ffff00';
ctx.shadowBlur = 5;
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
ctx.shadowBlur = 0;
});
}
// Gegner
function spawnEnemy() {
const baseSpeed = 1 + (gameState.difficulty * 0.3);
const types = [
{ width: 40, height: 30, speed: baseSpeed * 1.2, color: '#ff0044', points: 10 },
{ width: 30, height: 25, speed: baseSpeed * 1.8, color: '#ff8800', points: 15 },
{ width: 35, height: 35, speed: baseSpeed * 0.8, color: '#8800ff', points: 20 },
{ width: 25, height: 20, speed: baseSpeed * 2.5, color: '#00ff44', points: 25 }, // Schneller grüner Gegner
{ width: 50, height: 40, speed: baseSpeed * 0.6, color: '#ff00ff', points: 35 } // Großer langsamer Boss
];
// Mehr Gegnertypen freischalten mit steigender Schwierigkeit
const availableTypes = types.slice(0, Math.min(3 + Math.floor(gameState.difficulty / 2), types.length));
const type = availableTypes[Math.floor(Math.random() * availableTypes.length)];
gameState.enemies.push({
x: Math.random() * (canvas.width - type.width),
y: -type.height,
...type
});
}
function updateEnemies() {
// Schwierigkeit erhöhen über Zeit
gameState.timeAlive++;
if (gameState.timeAlive % 300 === 0) { // Alle 5 Sekunden
gameState.difficulty += 0.5;
}
// Dynamische Spawn-Rate basierend auf Schwierigkeit
const spawnRate = Math.max(8 - Math.floor(gameState.difficulty), 3);
gameState.enemySpawnTimer++;
if (gameState.enemySpawnTimer > spawnRate) {
spawnEnemy();
// Bei höherer Schwierigkeit manchmal 2 Gegner gleichzeitig spawnen
if (gameState.difficulty > 3 && Math.random() < 0.3) {
spawnEnemy();
}
// Bei sehr hoher Schwierigkeit gelegentlich 3 Gegner
if (gameState.difficulty > 6 && Math.random() < 0.15) {
spawnEnemy();
}
gameState.enemySpawnTimer = 0;
}
// Gegner bewegen
gameState.enemies = gameState.enemies.filter(enemy => {
enemy.y += enemy.speed;
// Kollision mit Spieler
if (isColliding(enemy, gameState.player)) {
createExplosion(enemy.x + enemy.width/2, enemy.y + enemy.height/2);
gameState.lives = 0; // Sofortiges Game Over
return false;
}
return enemy.y < canvas.height + enemy.height;
});
}
function drawEnemies() {
gameState.enemies.forEach(enemy => {
ctx.fillStyle = enemy.color;
ctx.shadowColor = enemy.color;
ctx.shadowBlur = 8;
// Alien-Design
ctx.fillRect(enemy.x + 5, enemy.y, enemy.width - 10, enemy.height - 5);
ctx.fillRect(enemy.x, enemy.y + 10, enemy.width, enemy.height - 15);
// Augen
ctx.fillStyle = '#ffffff';
ctx.fillRect(enemy.x + 8, enemy.y + 5, 6, 6);
ctx.fillRect(enemy.x + enemy.width - 14, enemy.y + 5, 6, 6);
ctx.shadowBlur = 0;
});
}
// Kollisionserkennung
function isColliding(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
// Kollisionen zwischen Projektilen und Gegnern
function checkCollisions() {
gameState.bullets.forEach((bullet, bulletIndex) => {
gameState.enemies.forEach((enemy, enemyIndex) => {
if (isColliding(bullet, enemy)) {
// Explosion
createExplosion(enemy.x + enemy.width/2, enemy.y + enemy.height/2);
// Score und Entfernung
gameState.score += Math.floor(enemy.points * gameState.difficulty);
// Sende Score Update für Statistiken
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'SCORE_UPDATE',
data: { score: gameState.score }
}, '*');
gameState.bullets.splice(bulletIndex, 1);
gameState.enemies.splice(enemyIndex, 1);
}
});
});
}
// Partikeleffekte
function createExplosion(x, y) {
for (let i = 0; i < 10; i++) {
gameState.particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
life: 30,
color: `hsl(${Math.random() * 60 + 10}, 100%, 60%)`
});
}
}
function updateParticles() {
gameState.particles = gameState.particles.filter(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.life--;
particle.vx *= 0.98;
particle.vy *= 0.98;
return particle.life > 0;
});
}
function drawParticles() {
gameState.particles.forEach(particle => {
ctx.globalAlpha = particle.life / 30;
ctx.fillStyle = particle.color;
ctx.fillRect(particle.x, particle.y, 3, 3);
ctx.globalAlpha = 1;
});
}
// Hintergrund mit Sternen
function drawBackground() {
ctx.fillStyle = '#000814';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Bewegende Sterne
for (let i = 0; i < 100; i++) {
const x = (i * 7 + Date.now() * 0.01) % canvas.width;
const y = (i * 11 + Date.now() * 0.005) % canvas.height;
const opacity = Math.sin(Date.now() * 0.001 + i) * 0.5 + 0.5;
ctx.globalAlpha = opacity * 0.7;
ctx.fillStyle = '#ffffff';
ctx.fillRect(x, y, 1, 1);
}
ctx.globalAlpha = 1;
}
// UI Update
function updateUI() {
document.getElementById('score').textContent = gameState.score;
document.getElementById('difficulty').textContent = gameState.difficulty.toFixed(1);
document.getElementById('time').textContent = Math.floor(gameState.timeAlive / 60);
}
// Game Over
function gameOver() {
gameState.gameRunning = false;
document.getElementById('finalScore').textContent = gameState.score;
document.getElementById('gameOver').style.display = 'block';
// Sende Game Over Event mit Score für Statistiken
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'GAME_OVER',
data: { score: gameState.score }
}, '*');
// Achievement prüfen
if (gameState.score >= 1000) {
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'ACHIEVEMENT_UNLOCKED',
data: {
achievement: {
id: 'space-defender-1000',
name: 'Weltraum Veteran',
description: '1000 Punkte erreicht!'
}
}
}, '*');
}
if (gameState.score >= 5000) {
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'ACHIEVEMENT_UNLOCKED',
data: {
achievement: {
id: 'space-defender-5000',
name: 'Weltraum Legende',
description: '5000 Punkte erreicht!'
}
}
}, '*');
}
if (gameState.timeAlive >= 300) { // 5 Minuten
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'ACHIEVEMENT_UNLOCKED',
data: {
achievement: {
id: 'space-survivor',
name: 'Überlebenskünstler',
description: '5 Minuten überlebt!'
}
}
}, '*');
}
}
function restartGame() {
gameState = {
player: { x: 375, y: 550, width: 50, height: 30, speed: 8 },
bullets: [],
enemies: [],
particles: [],
score: 0,
lives: 1,
gameRunning: true,
keys: {},
enemySpawnTimer: 0,
level: 1,
difficulty: 1,
timeAlive: 0
};
document.getElementById('gameOver').style.display = 'none';
gameLoop();
}
// Hauptspiel-Loop
function gameLoop() {
if (!gameState.gameRunning) return;
// Update
updatePlayer();
updateBullets();
updateEnemies();
updateParticles();
checkCollisions();
updateUI();
// Game Over prüfen
if (gameState.lives <= 0) {
gameOver();
return;
}
// Zeichnen
drawBackground();
drawPlayer();
drawBullets();
drawEnemies();
drawParticles();
requestAnimationFrame(gameLoop);
}
// Sende Game Loaded Event für Statistiken
window.parent.postMessage({
type: 'GAME_LOADED',
gameId: GAME_ID
}, '*');
// Spiel starten
gameLoop();
</script>
</body>
</html>