managarten/games/arcade/apps/web/static/games/asteroid_dash.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

666 lines
No EOL
22 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Asteroid Dash</title>
<style>
body {
margin: 0;
background: #000814;
color: #fff;
font-family: 'Courier New', monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
canvas {
border: 2px solid #001d3d;
background: radial-gradient(circle at 30% 20%, #001d3d 0%, #000814 70%);
box-shadow: 0 0 30px rgba(0, 123, 255, 0.3);
}
.ui {
position: absolute;
top: 20px;
left: 20px;
font-size: 18px;
z-index: 10;
}
.score {
color: #00f5ff;
text-shadow: 0 0 10px rgba(0, 245, 255, 0.5);
}
.lives {
color: #ff0080;
margin-top: 10px;
text-shadow: 0 0 10px rgba(255, 0, 128, 0.5);
}
.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 #00f5ff;
border-radius: 10px;
z-index: 20;
display: none;
}
.controls {
position: absolute;
bottom: 20px;
left: 20px;
font-size: 12px;
color: #666;
}
button {
background: #001d3d;
color: #00f5ff;
border: 2px solid #00f5ff;
padding: 10px 20px;
margin: 10px;
cursor: pointer;
font-family: inherit;
border-radius: 5px;
transition: all 0.3s;
}
button:hover {
background: #00f5ff;
color: #000814;
box-shadow: 0 0 15px rgba(0, 245, 255, 0.5);
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="ui">
<div class="score">Score: <span id="score">0</span></div>
<div class="lives">Lives: <span id="lives">3</span></div>
</div>
<div class="controls">
WASD / Pfeiltasten: Bewegung | Leertaste: Boost
</div>
<div class="game-over" id="gameOver">
<h2>Game Over!</h2>
<p>Endpunktzahl: <span id="finalScore">0</span></p>
<button onclick="restartGame()">Nochmal spielen</button>
</div>
<script>
// Game ID für Statistiken
const GAME_ID = 'asteroid-dash';
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Spielvariablen
let gameRunning = true;
let score = 0;
let lives = 3;
let stars = [];
// Eingabe-System
const keys = {};
// Spieler-Objekt
const player = {
x: canvas.width / 2,
y: canvas.height / 2,
width: 20,
height: 15,
vx: 0,
vy: 0,
speed: 0.3,
maxSpeed: 8,
friction: 0.95,
boost: false,
boostCooldown: 0,
invulnerable: 0,
trail: []
};
// Asteroid-Array
const asteroids = [];
// Kristall-Array
const crystals = [];
// Power-up Array
const powerups = [];
// Partikel-Array
const particles = [];
// Sterne für Hintergrund erzeugen
function createStars() {
for (let i = 0; i < 100; i++) {
stars.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2,
brightness: Math.random()
});
}
}
// Asteroid erstellen
function createAsteroid() {
const side = Math.floor(Math.random() * 4);
let x, y;
switch(side) {
case 0: x = -30; y = Math.random() * canvas.height; break;
case 1: x = canvas.width + 30; y = Math.random() * canvas.height; break;
case 2: x = Math.random() * canvas.width; y = -30; break;
case 3: x = Math.random() * canvas.width; y = canvas.height + 30; break;
}
const asteroid = {
x: x,
y: y,
size: 15 + Math.random() * 25,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
rotation: 0,
rotationSpeed: (Math.random() - 0.5) * 0.1,
color: `hsl(${20 + Math.random() * 40}, 70%, ${40 + Math.random() * 20}%)`
};
asteroids.push(asteroid);
}
// Kristall erstellen
function createCrystal() {
crystals.push({
x: Math.random() * (canvas.width - 40) + 20,
y: Math.random() * (canvas.height - 40) + 20,
size: 8,
rotation: 0,
rotationSpeed: 0.05,
pulse: 0,
collected: false
});
}
// Power-up erstellen
function createPowerup() {
const types = ['shield', 'boost', 'magnet'];
const type = types[Math.floor(Math.random() * types.length)];
powerups.push({
x: Math.random() * (canvas.width - 40) + 20,
y: Math.random() * (canvas.height - 40) + 20,
type: type,
size: 12,
rotation: 0,
life: 300
});
}
// Partikel erstellen
function createParticles(x, y, count, color) {
for (let i = 0; i < count; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
size: Math.random() * 3 + 1,
life: 30,
maxLife: 30,
color: color || '#00f5ff'
});
}
}
// Kollisionserkennung
function checkCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.size &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.size &&
rect1.y + rect1.height > rect2.y;
}
// Distanz zwischen zwei Punkten
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
// Eingabe-Event-Listener
document.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
if (e.key === ' ') {
e.preventDefault();
if (player.boostCooldown <= 0) {
player.boost = true;
player.boostCooldown = 60;
createParticles(player.x + player.width/2, player.y + player.height, 5, '#ff6b00');
}
}
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
if (e.key === ' ') {
player.boost = false;
}
});
// Spieler updaten
function updatePlayer() {
// Bewegung
if (keys['w'] || keys['arrowup']) player.vy -= player.speed;
if (keys['s'] || keys['arrowdown']) player.vy += player.speed;
if (keys['a'] || keys['arrowleft']) player.vx -= player.speed;
if (keys['d'] || keys['arrowright']) player.vx += player.speed;
// Boost
let currentMaxSpeed = player.maxSpeed;
if (player.boost) {
currentMaxSpeed *= 2;
}
// Geschwindigkeit begrenzen
const speed = Math.sqrt(player.vx ** 2 + player.vy ** 2);
if (speed > currentMaxSpeed) {
player.vx = (player.vx / speed) * currentMaxSpeed;
player.vy = (player.vy / speed) * currentMaxSpeed;
}
// Reibung
player.vx *= player.friction;
player.vy *= player.friction;
// Position updaten
player.x += player.vx;
player.y += player.vy;
// Bildschirmgrenzen
if (player.x < 0) player.x = 0;
if (player.x + player.width > canvas.width) player.x = canvas.width - player.width;
if (player.y < 0) player.y = 0;
if (player.y + player.height > canvas.height) player.y = canvas.height - player.height;
// Cooldowns
if (player.boostCooldown > 0) player.boostCooldown--;
if (player.invulnerable > 0) player.invulnerable--;
// Trail für Boost-Effekt
if (player.boost) {
player.trail.push({
x: player.x + player.width/2,
y: player.y + player.height/2,
life: 10
});
}
// Trail updaten
player.trail = player.trail.filter(t => {
t.life--;
return t.life > 0;
});
}
// Asteroiden updaten
function updateAsteroids() {
for (let i = asteroids.length - 1; i >= 0; i--) {
const asteroid = asteroids[i];
asteroid.x += asteroid.vx;
asteroid.y += asteroid.vy;
asteroid.rotation += asteroid.rotationSpeed;
// Asteroiden entfernen die zu weit weg sind
if (asteroid.x < -100 || asteroid.x > canvas.width + 100 ||
asteroid.y < -100 || asteroid.y > canvas.height + 100) {
asteroids.splice(i, 1);
continue;
}
// Kollision mit Spieler
if (player.invulnerable <= 0 &&
distance(player.x + player.width/2, player.y + player.height/2,
asteroid.x, asteroid.y) < asteroid.size + 10) {
player.invulnerable = 120;
lives--;
createParticles(player.x + player.width/2, player.y + player.height/2, 10, '#ff0080');
if (lives <= 0) {
gameOver();
}
}
}
}
// Kristalle updaten
function updateCrystals() {
for (let i = crystals.length - 1; i >= 0; i--) {
const crystal = crystals[i];
crystal.rotation += crystal.rotationSpeed;
crystal.pulse += 0.1;
// Kollision mit Spieler
if (distance(player.x + player.width/2, player.y + player.height/2,
crystal.x, crystal.y) < crystal.size + 10) {
score += 100;
createParticles(crystal.x, crystal.y, 8, '#00ff80');
crystals.splice(i, 1);
// Sende Score Update für Statistiken
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'SCORE_UPDATE',
data: { score: score }
}, '*');
}
}
}
// Power-ups updaten
function updatePowerups() {
for (let i = powerups.length - 1; i >= 0; i--) {
const powerup = powerups[i];
powerup.rotation += 0.03;
powerup.life--;
if (powerup.life <= 0) {
powerups.splice(i, 1);
continue;
}
// Kollision mit Spieler
if (distance(player.x + player.width/2, player.y + player.height/2,
powerup.x, powerup.y) < powerup.size + 10) {
if (powerup.type === 'shield') {
player.invulnerable = 180;
} else if (powerup.type === 'boost') {
player.boostCooldown = 0;
}
createParticles(powerup.x, powerup.y, 6, '#ffff00');
powerups.splice(i, 1);
}
}
}
// Partikel updaten
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const particle = particles[i];
particle.x += particle.vx;
particle.y += particle.vy;
particle.vx *= 0.98;
particle.vy *= 0.98;
particle.life--;
if (particle.life <= 0) {
particles.splice(i, 1);
}
}
}
// Zeichnen
function draw() {
// Hintergrund löschen
ctx.fillStyle = '#000814';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Sterne zeichnen
ctx.fillStyle = '#ffffff';
for (const star of stars) {
ctx.globalAlpha = star.brightness;
ctx.fillRect(star.x, star.y, star.size, star.size);
}
ctx.globalAlpha = 1;
// Spieler-Trail zeichnen
for (const trail of player.trail) {
ctx.globalAlpha = trail.life / 10;
ctx.fillStyle = '#ff6b00';
ctx.fillRect(trail.x - 2, trail.y - 2, 4, 4);
}
ctx.globalAlpha = 1;
// Spieler zeichnen
ctx.save();
ctx.translate(player.x + player.width/2, player.y + player.height/2);
if (player.invulnerable > 0 && Math.floor(player.invulnerable / 5) % 2) {
ctx.globalAlpha = 0.5;
}
ctx.fillStyle = player.boost ? '#ff6b00' : '#00f5ff';
ctx.fillRect(-player.width/2, -player.height/2, player.width, player.height);
// Spieler-Spitze
ctx.fillStyle = '#ffffff';
ctx.fillRect(-2, -player.height/2 - 3, 4, 3);
ctx.restore();
ctx.globalAlpha = 1;
// Asteroiden zeichnen
for (const asteroid of asteroids) {
ctx.save();
ctx.translate(asteroid.x, asteroid.y);
ctx.rotate(asteroid.rotation);
ctx.fillStyle = asteroid.color;
ctx.fillRect(-asteroid.size/2, -asteroid.size/2, asteroid.size, asteroid.size);
// Dunkle Kanten für 3D-Effekt
ctx.fillStyle = '#2d1810';
ctx.fillRect(-asteroid.size/2, -asteroid.size/2, asteroid.size, 3);
ctx.fillRect(-asteroid.size/2, -asteroid.size/2, 3, asteroid.size);
ctx.restore();
}
// Kristalle zeichnen
for (const crystal of crystals) {
ctx.save();
ctx.translate(crystal.x, crystal.y);
ctx.rotate(crystal.rotation);
const pulseSize = crystal.size + Math.sin(crystal.pulse) * 2;
ctx.fillStyle = '#00ff80';
ctx.fillRect(-pulseSize/2, -pulseSize/2, pulseSize, pulseSize);
// Glitzer-Effekt
ctx.fillStyle = '#ffffff';
ctx.fillRect(-2, -2, 4, 4);
ctx.restore();
}
// Power-ups zeichnen
for (const powerup of powerups) {
ctx.save();
ctx.translate(powerup.x, powerup.y);
ctx.rotate(powerup.rotation);
let color = '#ffff00';
if (powerup.type === 'shield') color = '#ff00ff';
if (powerup.type === 'boost') color = '#ff6b00';
ctx.fillStyle = color;
ctx.fillRect(-powerup.size/2, -powerup.size/2, powerup.size, powerup.size);
// Symbol
ctx.fillStyle = '#000000';
if (powerup.type === 'shield') {
ctx.fillRect(-4, -6, 8, 12);
} else if (powerup.type === 'boost') {
ctx.fillRect(-2, -6, 4, 12);
}
ctx.restore();
}
// Partikel zeichnen
for (const particle of particles) {
ctx.globalAlpha = particle.life / particle.maxLife;
ctx.fillStyle = particle.color;
ctx.fillRect(particle.x - particle.size/2, particle.y - particle.size/2,
particle.size, particle.size);
}
ctx.globalAlpha = 1;
}
// Spawn-System
let asteroidSpawnTimer = 0;
let crystalSpawnTimer = 0;
let powerupSpawnTimer = 0;
function handleSpawning() {
asteroidSpawnTimer++;
crystalSpawnTimer++;
powerupSpawnTimer++;
// Asteroiden spawnen (Schwierigkeit steigt)
const asteroidDelay = Math.max(30, 120 - Math.floor(score / 500));
if (asteroidSpawnTimer >= asteroidDelay) {
createAsteroid();
asteroidSpawnTimer = 0;
}
// Kristalle spawnen
if (crystalSpawnTimer >= 180 && crystals.length < 3) {
createCrystal();
crystalSpawnTimer = 0;
}
// Power-ups spawnen
if (powerupSpawnTimer >= 600 && powerups.length < 1) {
createPowerup();
powerupSpawnTimer = 0;
}
}
// Spiel-Loop
function gameLoop() {
if (!gameRunning) return;
updatePlayer();
updateAsteroids();
updateCrystals();
updatePowerups();
updateParticles();
handleSpawning();
draw();
// UI updaten
document.getElementById('score').textContent = score;
document.getElementById('lives').textContent = lives;
score++;
requestAnimationFrame(gameLoop);
}
// Game Over
function gameOver() {
gameRunning = false;
document.getElementById('finalScore').textContent = score;
document.getElementById('gameOver').style.display = 'block';
// Sende Game Over Event
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'GAME_OVER',
data: { score: score }
}, '*');
// Achievement prüfen
if (score >= 5000) {
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'ACHIEVEMENT_UNLOCKED',
data: {
achievementId: 'asteroid_survivor',
name: 'Asteroid Survivor',
description: 'Score 5000 points in Asteroid Dash',
icon: '🚀'
}
}, '*');
}
if (score >= 10000) {
window.parent.postMessage({
type: 'GAME_EVENT',
gameId: GAME_ID,
event: 'ACHIEVEMENT_UNLOCKED',
data: {
achievementId: 'space_ace',
name: 'Space Ace',
description: 'Score 10000 points in Asteroid Dash',
icon: '⭐'
}
}, '*');
}
}
// Spiel neustarten
function restartGame() {
gameRunning = true;
score = 0;
lives = 3;
// Arrays leeren
asteroids.length = 0;
crystals.length = 0;
powerups.length = 0;
particles.length = 0;
// Spieler zurücksetzen
player.x = canvas.width / 2;
player.y = canvas.height / 2;
player.vx = 0;
player.vy = 0;
player.invulnerable = 60;
player.boostCooldown = 0;
player.trail.length = 0;
// Timer zurücksetzen
asteroidSpawnTimer = 0;
crystalSpawnTimer = 0;
powerupSpawnTimer = 0;
document.getElementById('gameOver').style.display = 'none';
gameLoop();
}
// Spiel initialisieren
createStars();
gameLoop();
// Sende Game Loaded Event für Statistiken
window.parent.postMessage({
type: 'GAME_LOADED',
gameId: GAME_ID
}, '*');
</script>
</body>
</html>