mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 17:46:43 +02:00
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>
791 lines
No EOL
27 KiB
HTML
791 lines
No EOL
27 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Turbo Racer</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: #0a0a0a;
|
|
color: #fff;
|
|
font-family: 'Arial Black', sans-serif;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.game-container {
|
|
position: relative;
|
|
filter: drop-shadow(0 0 30px rgba(255, 0, 100, 0.3));
|
|
}
|
|
|
|
canvas {
|
|
border: 3px solid #ff0066;
|
|
background: #1a1a1a;
|
|
box-shadow:
|
|
inset 0 0 50px rgba(255, 0, 100, 0.1),
|
|
0 0 30px rgba(0, 255, 255, 0.3);
|
|
}
|
|
|
|
.ui {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
font-size: 20px;
|
|
z-index: 10;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
|
|
}
|
|
|
|
.speed-meter {
|
|
color: #00ff88;
|
|
font-size: 28px;
|
|
margin-bottom: 10px;
|
|
font-style: italic;
|
|
}
|
|
|
|
.lap-counter {
|
|
color: #ffcc00;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.position {
|
|
color: #ff0066;
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.boost-bar {
|
|
position: absolute;
|
|
bottom: 30px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 200px;
|
|
height: 20px;
|
|
background: rgba(0,0,0,0.5);
|
|
border: 2px solid #00ffff;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.boost-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #00ffff, #ff00ff);
|
|
width: 100%;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.start-screen {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
background: rgba(0,0,0,0.9);
|
|
padding: 40px;
|
|
border: 3px solid #ff0066;
|
|
border-radius: 20px;
|
|
box-shadow: 0 0 30px rgba(255,0,100,0.5);
|
|
}
|
|
|
|
.start-screen h1 {
|
|
font-size: 48px;
|
|
margin-bottom: 20px;
|
|
background: linear-gradient(45deg, #ff0066, #00ffff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
text-shadow: none;
|
|
}
|
|
|
|
.start-screen p {
|
|
font-size: 18px;
|
|
margin-bottom: 30px;
|
|
color: #ccc;
|
|
}
|
|
|
|
button {
|
|
padding: 15px 40px;
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
background: linear-gradient(45deg, #ff0066, #ff3388);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
button:hover {
|
|
transform: scale(1.1);
|
|
box-shadow: 0 0 20px rgba(255,0,100,0.5);
|
|
}
|
|
|
|
button:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.game-over {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
background: rgba(0,0,0,0.95);
|
|
padding: 40px;
|
|
border: 3px solid #ffcc00;
|
|
border-radius: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.game-over h2 {
|
|
font-size: 36px;
|
|
margin-bottom: 20px;
|
|
color: #ffcc00;
|
|
}
|
|
|
|
.final-time {
|
|
font-size: 48px;
|
|
color: #00ff88;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.controls-info {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
font-size: 14px;
|
|
color: #666;
|
|
text-align: right;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="game-container">
|
|
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
|
|
|
<div class="ui">
|
|
<div class="speed-meter">
|
|
<span id="speed">0</span> km/h
|
|
</div>
|
|
<div class="lap-counter">
|
|
Runde: <span id="lap">0</span>
|
|
</div>
|
|
<div class="position">
|
|
Zeit: <span id="time">0:00</span>
|
|
</div>
|
|
<div class="best-lap" style="color: #00ff88; font-size: 18px;">
|
|
Beste Runde: <span id="bestLap">--:--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="boost-bar">
|
|
<div class="boost-fill" id="boostFill"></div>
|
|
</div>
|
|
|
|
<div class="start-screen" id="startScreen">
|
|
<h1>TURBO RACER</h1>
|
|
<p>Drift durch die Kurven und stelle Bestzeiten auf!</p>
|
|
<p>🏁 Endlos-Runden • ⚡ Nitro-Boost • 🏆 Drift-Punkte</p>
|
|
<button onclick="startGame()">RENNEN STARTEN</button>
|
|
</div>
|
|
|
|
<div class="game-over" id="gameOver">
|
|
<h2 id="gameOverTitle">ZEIT-HERAUSFORDERUNG BEENDET!</h2>
|
|
<div class="final-time" id="finalTime">0 Runden</div>
|
|
<p id="finalPosition">Beste Runde: --:--</p>
|
|
<button onclick="restartGame()">NOCHMAL FAHREN</button>
|
|
</div>
|
|
|
|
<div class="controls-info">
|
|
↑↓ oder WS: Gas/Bremse | ←→ oder AD: Lenken | Leertaste: Boost
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Game ID für Statistiken
|
|
const GAME_ID = 'turbo-racer';
|
|
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Spielvariablen
|
|
let gameRunning = false;
|
|
let raceStartTime = 0;
|
|
let currentLap = 1;
|
|
|
|
// Eingabe
|
|
const keys = {};
|
|
|
|
// Spieler Auto mit Drift-Physik
|
|
const player = {
|
|
x: 400,
|
|
y: 400,
|
|
angle: -Math.PI / 2, // Nach oben zeigend
|
|
velocity: { x: 0, y: 0 },
|
|
speed: 0,
|
|
maxSpeed: 4, // Noch langsamer
|
|
acceleration: 0.2, // Noch sanftere Beschleunigung
|
|
deceleration: 0.15,
|
|
turnSpeed: 0.08, // Noch weniger aggressiv
|
|
width: 30,
|
|
height: 20,
|
|
boost: 100,
|
|
boosting: false,
|
|
color: '#ff0066',
|
|
trail: [],
|
|
driftFactor: 0,
|
|
driftAngle: 0,
|
|
lapCount: 0,
|
|
bestLapTime: null,
|
|
currentLapStart: 0,
|
|
lastAngle: 0,
|
|
crossed: false
|
|
};
|
|
|
|
// Streckenmitte und Radien
|
|
const trackCenter = { x: 400, y: 300 };
|
|
const outerRadius = 250;
|
|
const innerRadius = 120;
|
|
const trackWidth = outerRadius - innerRadius;
|
|
|
|
// Zeit und Runden
|
|
let currentTime = 0;
|
|
let lastLapTime = 0;
|
|
let bestLapTime = Infinity;
|
|
|
|
// Partikel für Effekte
|
|
const particles = [];
|
|
|
|
// Sterne für Geschwindigkeitseffekt
|
|
const stars = [];
|
|
for (let i = 0; i < 50; i++) {
|
|
stars.push({
|
|
x: Math.random() * canvas.width,
|
|
y: Math.random() * canvas.height,
|
|
size: Math.random() * 2
|
|
});
|
|
}
|
|
|
|
// Input Handling
|
|
document.addEventListener('keydown', (e) => {
|
|
keys[e.key.toLowerCase()] = true;
|
|
if (e.key === ' ') e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('keyup', (e) => {
|
|
keys[e.key.toLowerCase()] = false;
|
|
});
|
|
|
|
// Auto zeichnen
|
|
function drawCar(car, isPlayer = false) {
|
|
ctx.save();
|
|
ctx.translate(car.x, car.y);
|
|
ctx.rotate(car.angle);
|
|
|
|
// Drift-Rauch bei starkem Drift
|
|
if (isPlayer && player.driftFactor > 0.5) {
|
|
ctx.save();
|
|
ctx.globalAlpha = player.driftFactor * 0.3;
|
|
ctx.fillStyle = '#666';
|
|
ctx.beginPath();
|
|
ctx.arc(-car.width/2 - 5, 0, 8, 0, Math.PI * 2);
|
|
ctx.arc(car.width/2 + 5, 0, 8, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
|
|
// Auto Body
|
|
ctx.fillStyle = car.color;
|
|
ctx.fillRect(-car.width/2, -car.height/2, car.width, car.height);
|
|
|
|
// Windschutzscheibe
|
|
ctx.fillStyle = 'rgba(100,100,100,0.8)';
|
|
ctx.fillRect(-car.width/4, -car.height/3, car.width/2, car.height/3);
|
|
|
|
// Räder
|
|
ctx.fillStyle = '#333';
|
|
ctx.fillRect(-car.width/2 - 2, -car.height/2 + 2, 4, 6);
|
|
ctx.fillRect(-car.width/2 - 2, car.height/2 - 8, 4, 6);
|
|
ctx.fillRect(car.width/2 - 2, -car.height/2 + 2, 4, 6);
|
|
ctx.fillRect(car.width/2 - 2, car.height/2 - 8, 4, 6);
|
|
|
|
// Spieler-Indikator
|
|
if (isPlayer) {
|
|
ctx.strokeStyle = '#fff';
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, -car.height);
|
|
ctx.lineTo(-5, -car.height - 10);
|
|
ctx.lineTo(5, -car.height - 10);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
|
|
// Kreisförmige Strecke zeichnen
|
|
function drawTrack() {
|
|
// Hintergrund
|
|
ctx.fillStyle = '#0a3d0a';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Äußerer Kreis (Strecke)
|
|
ctx.fillStyle = '#333';
|
|
ctx.beginPath();
|
|
ctx.arc(trackCenter.x, trackCenter.y, outerRadius, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Innerer Kreis (Gras)
|
|
ctx.fillStyle = '#0a3d0a';
|
|
ctx.beginPath();
|
|
ctx.arc(trackCenter.x, trackCenter.y, innerRadius, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Streckenbegrenzungen
|
|
ctx.strokeStyle = '#fff';
|
|
ctx.lineWidth = 4;
|
|
ctx.setLineDash([20, 10]);
|
|
|
|
// Äußere Linie
|
|
ctx.beginPath();
|
|
ctx.arc(trackCenter.x, trackCenter.y, outerRadius, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
// Innere Linie
|
|
ctx.beginPath();
|
|
ctx.arc(trackCenter.x, trackCenter.y, innerRadius, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
ctx.setLineDash([]);
|
|
|
|
// Start/Ziel Linie
|
|
ctx.strokeStyle = '#fff';
|
|
ctx.lineWidth = 8;
|
|
ctx.beginPath();
|
|
ctx.moveTo(trackCenter.x + innerRadius, trackCenter.y);
|
|
ctx.lineTo(trackCenter.x + outerRadius, trackCenter.y);
|
|
ctx.stroke();
|
|
|
|
// Schachbrettmuster auf Ziellinie
|
|
const lineWidth = outerRadius - innerRadius;
|
|
for (let i = 0; i < lineWidth / 10; i++) {
|
|
for (let j = 0; j < 2; j++) {
|
|
if ((i + j) % 2 === 0) {
|
|
ctx.fillStyle = '#fff';
|
|
ctx.fillRect(trackCenter.x + innerRadius + i * 10, trackCenter.y - 4 + j * 4, 10, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Driftspuren
|
|
if (player.driftFactor > 0.3) {
|
|
ctx.globalAlpha = player.driftFactor * 0.3;
|
|
ctx.strokeStyle = '#000';
|
|
ctx.lineWidth = 2;
|
|
player.trail.forEach((point, i) => {
|
|
if (i > 0) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(player.trail[i-1].x, player.trail[i-1].y);
|
|
ctx.lineTo(point.x, point.y);
|
|
ctx.stroke();
|
|
}
|
|
});
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
}
|
|
|
|
// Kollisionserkennung mit kreisförmiger Strecke
|
|
function checkTrackCollision() {
|
|
const dx = player.x - trackCenter.x;
|
|
const dy = player.y - trackCenter.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// Prüfe ob Auto außerhalb der Strecke
|
|
if (distance > outerRadius - 15 || distance < innerRadius + 15) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Rundenzählung
|
|
function checkLapCrossing() {
|
|
// Prüfe ob Ziellinie überquert wurde
|
|
const angle = Math.atan2(player.y - trackCenter.y, player.x - trackCenter.x);
|
|
const normalizedAngle = angle < 0 ? angle + Math.PI * 2 : angle;
|
|
|
|
// Ziellinie ist bei 0° (rechts)
|
|
if (normalizedAngle < 0.1 && player.lastAngle > 6.2) {
|
|
if (!player.crossed) {
|
|
player.crossed = true;
|
|
player.lapCount++;
|
|
|
|
// Rundenzeit berechnen
|
|
if (player.currentLapStart > 0) {
|
|
const lapTime = currentTime - player.currentLapStart;
|
|
if (lapTime < bestLapTime) {
|
|
bestLapTime = lapTime;
|
|
document.getElementById('bestLap').textContent = formatTime(bestLapTime);
|
|
}
|
|
}
|
|
player.currentLapStart = currentTime;
|
|
|
|
document.getElementById('lap').textContent = player.lapCount;
|
|
|
|
// Effekt für neue Runde
|
|
createParticles(player.x, player.y, '#00ff88');
|
|
}
|
|
} else if (normalizedAngle > 0.2) {
|
|
player.crossed = false;
|
|
}
|
|
|
|
player.lastAngle = normalizedAngle;
|
|
}
|
|
|
|
// Hilfsfunktion für Linien-Kreuzung
|
|
function lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
|
|
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
if (denom === 0) return false;
|
|
|
|
const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
|
|
const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
|
|
|
|
return t >= 0 && t <= 1 && u >= 0 && u <= 1;
|
|
}
|
|
|
|
// Spieler Update mit Drift-Physik
|
|
function updatePlayer() {
|
|
const oldAngle = player.angle;
|
|
|
|
// Steuerung
|
|
if (keys['arrowup'] || keys['w']) {
|
|
player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed);
|
|
} else if (keys['arrowdown'] || keys['s']) {
|
|
player.speed = Math.max(player.speed - player.deceleration * 2, -player.maxSpeed / 2);
|
|
} else {
|
|
// Automatisches Abbremsen
|
|
if (player.speed > 0) {
|
|
player.speed = Math.max(0, player.speed - player.deceleration);
|
|
} else {
|
|
player.speed = Math.min(0, player.speed + player.deceleration);
|
|
}
|
|
}
|
|
|
|
// Lenken mit Drift
|
|
let turnAmount = 0;
|
|
if (Math.abs(player.speed) > 0.5) {
|
|
if (keys['arrowleft'] || keys['a']) {
|
|
turnAmount = -player.turnSpeed * (player.speed / player.maxSpeed);
|
|
player.angle += turnAmount;
|
|
}
|
|
if (keys['arrowright'] || keys['d']) {
|
|
turnAmount = player.turnSpeed * (player.speed / player.maxSpeed);
|
|
player.angle += turnAmount;
|
|
}
|
|
}
|
|
|
|
// Drift-Berechnung
|
|
const angleDiff = Math.abs(player.angle - oldAngle);
|
|
if (angleDiff > 0.04 && player.speed > 2.5) { // Angepasst für langsamere Geschwindigkeit
|
|
player.driftFactor = Math.min(1, player.driftFactor + 0.1);
|
|
player.driftAngle = player.angle - Math.atan2(player.velocity.y, player.velocity.x);
|
|
} else {
|
|
player.driftFactor = Math.max(0, player.driftFactor - 0.05);
|
|
}
|
|
|
|
// Velocity mit Drift
|
|
const targetVx = Math.cos(player.angle) * player.speed;
|
|
const targetVy = Math.sin(player.angle) * player.speed;
|
|
|
|
// Drift-Effekt: Velocity passt sich langsamer an die Richtung an
|
|
const driftStrength = 0.15 + (1 - player.driftFactor) * 0.1;
|
|
player.velocity.x += (targetVx - player.velocity.x) * driftStrength;
|
|
player.velocity.y += (targetVy - player.velocity.y) * driftStrength;
|
|
|
|
// Boost
|
|
if (keys[' '] && player.boost > 0 && player.speed > 2) {
|
|
player.boosting = true;
|
|
player.boost -= 2;
|
|
player.speed = Math.min(player.speed + 0.2, player.maxSpeed * 1.4); // Angepasster Boost für langsamere Geschwindigkeit
|
|
|
|
// Boost Partikel
|
|
createParticles(
|
|
player.x - Math.cos(player.angle) * 20,
|
|
player.y - Math.sin(player.angle) * 20,
|
|
'#00ffff'
|
|
);
|
|
} else {
|
|
player.boosting = false;
|
|
// Boost regeneriert langsam
|
|
player.boost = Math.min(100, player.boost + 0.3);
|
|
}
|
|
|
|
// Position update mit Velocity
|
|
const oldX = player.x;
|
|
const oldY = player.y;
|
|
player.x += player.velocity.x;
|
|
player.y += player.velocity.y;
|
|
|
|
// Kollision prüfen mit Abprall-Effekt
|
|
if (checkTrackCollision()) {
|
|
// Zurück zur alten Position
|
|
player.x = oldX;
|
|
player.y = oldY;
|
|
|
|
// Berechne Abprall-Richtung vom Streckenzentrum
|
|
const dx = player.x - trackCenter.x;
|
|
const dy = player.y - trackCenter.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// Normalisiere und kehre Richtung um
|
|
let bounceX = dx / distance;
|
|
let bounceY = dy / distance;
|
|
|
|
// Wenn zu nah am Inneren, kehre um
|
|
if (distance < innerRadius + 15) {
|
|
bounceX = -bounceX;
|
|
bounceY = -bounceY;
|
|
}
|
|
|
|
// Wende Abprall-Kraft an
|
|
player.velocity.x = bounceX * player.speed * 0.6;
|
|
player.velocity.y = bounceY * player.speed * 0.6;
|
|
player.speed *= 0.5;
|
|
|
|
// Leichte Drehung beim Aufprall
|
|
player.angle += (Math.random() - 0.5) * 0.3;
|
|
|
|
// Kollisions-Partikel
|
|
createParticles(player.x, player.y, '#ff0066');
|
|
}
|
|
|
|
// Trail für Driftspuren
|
|
if (player.driftFactor > 0.3 && player.speed > 3) {
|
|
player.trail.push({x: player.x, y: player.y, life: 30});
|
|
if (player.trail.length > 100) {
|
|
player.trail.shift();
|
|
}
|
|
}
|
|
|
|
// Rundenzählung
|
|
checkLapCrossing();
|
|
|
|
// UI Update
|
|
document.getElementById('speed').textContent = Math.floor(Math.abs(player.speed) * 30); // Angepasst für noch langsamere Geschwindigkeit
|
|
document.getElementById('boostFill').style.width = player.boost + '%';
|
|
|
|
// Drift-Anzeige
|
|
if (player.driftFactor > 0.5) {
|
|
createParticles(player.x - 10, player.y - 10, '#ffcc00');
|
|
}
|
|
}
|
|
|
|
// Zeit formatieren
|
|
function formatTime(ms) {
|
|
const totalSeconds = Math.floor(ms / 1000);
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = totalSeconds % 60;
|
|
const milliseconds = Math.floor((ms % 1000) / 10);
|
|
return `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
// Partikel erstellen
|
|
function createParticles(x, y, color) {
|
|
for (let i = 0; i < 5; i++) {
|
|
particles.push({
|
|
x: x,
|
|
y: y,
|
|
vx: (Math.random() - 0.5) * 4,
|
|
vy: (Math.random() - 0.5) * 4,
|
|
life: 20,
|
|
color: color
|
|
});
|
|
}
|
|
}
|
|
|
|
// Partikel update
|
|
function updateParticles() {
|
|
particles.forEach(p => {
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
p.life--;
|
|
p.vx *= 0.95;
|
|
p.vy *= 0.95;
|
|
});
|
|
|
|
// Alte Partikel entfernen
|
|
for (let i = particles.length - 1; i >= 0; i--) {
|
|
if (particles[i].life <= 0) {
|
|
particles.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Partikel zeichnen
|
|
function drawParticles() {
|
|
particles.forEach(p => {
|
|
ctx.globalAlpha = p.life / 20;
|
|
ctx.fillStyle = p.color;
|
|
ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
|
|
});
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
// Sterne für Geschwindigkeitseffekt
|
|
function drawStars() {
|
|
ctx.fillStyle = 'rgba(255,255,255,0.3)';
|
|
stars.forEach(star => {
|
|
// Bewege Sterne basierend auf Spielergeschwindigkeit
|
|
star.y += player.speed * 0.5;
|
|
if (star.y > canvas.height) {
|
|
star.y = 0;
|
|
star.x = Math.random() * canvas.width;
|
|
}
|
|
|
|
ctx.globalAlpha = player.speed / player.maxSpeed * 0.5;
|
|
ctx.fillRect(star.x, star.y, star.size, star.size * 3);
|
|
});
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
// Zeit-Update
|
|
function updateTime() {
|
|
if (gameRunning) {
|
|
currentTime += 16; // ~60 FPS
|
|
document.getElementById('time').textContent = formatTime(currentTime);
|
|
}
|
|
}
|
|
|
|
// Game Loop
|
|
function gameLoop() {
|
|
if (!gameRunning) return;
|
|
|
|
// Clear
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw
|
|
drawStars();
|
|
drawTrack();
|
|
|
|
// Update
|
|
updatePlayer();
|
|
updateParticles();
|
|
updateTime();
|
|
|
|
// Draw car
|
|
drawCar(player, true);
|
|
|
|
// Effects
|
|
drawParticles();
|
|
|
|
requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
// Spiel starten
|
|
function startGame() {
|
|
document.getElementById('startScreen').style.display = 'none';
|
|
gameRunning = true;
|
|
currentTime = 0;
|
|
|
|
// Reset Spieler
|
|
player.x = trackCenter.x + (innerRadius + outerRadius) / 2;
|
|
player.y = trackCenter.y;
|
|
player.angle = -Math.PI / 2; // Nach oben zeigend
|
|
player.velocity = { x: 0, y: 0 };
|
|
player.speed = 0;
|
|
player.boost = 100;
|
|
player.lapCount = 0;
|
|
player.currentLapStart = 0;
|
|
player.bestLapTime = null;
|
|
player.trail = [];
|
|
player.driftFactor = 0;
|
|
player.lastAngle = 0;
|
|
player.crossed = false;
|
|
|
|
bestLapTime = Infinity;
|
|
document.getElementById('lap').textContent = '0';
|
|
document.getElementById('bestLap').textContent = '--:--';
|
|
|
|
// Event senden
|
|
window.parent.postMessage({
|
|
type: 'GAME_LOADED',
|
|
gameId: GAME_ID
|
|
}, '*');
|
|
|
|
gameLoop();
|
|
}
|
|
|
|
// Rennen beenden (optional - könnte nach X Runden aufgerufen werden)
|
|
function endRace() {
|
|
gameRunning = false;
|
|
|
|
document.getElementById('gameOverTitle').textContent = '🏆 ZEIT-HERAUSFORDERUNG BEENDET!';
|
|
document.getElementById('finalTime').textContent = `${player.lapCount} Runden`;
|
|
document.getElementById('finalPosition').textContent =
|
|
`Beste Runde: ${bestLapTime === Infinity ? '--:--' : formatTime(bestLapTime)}`;
|
|
document.getElementById('gameOver').style.display = 'block';
|
|
|
|
// Score basierend auf bester Rundenzeit
|
|
const score = bestLapTime === Infinity ? 0 : Math.max(0, 50000 - bestLapTime);
|
|
|
|
// Events senden
|
|
window.parent.postMessage({
|
|
type: 'GAME_EVENT',
|
|
gameId: GAME_ID,
|
|
event: 'GAME_OVER',
|
|
data: {
|
|
score: Math.floor(score),
|
|
laps: player.lapCount,
|
|
bestLap: bestLapTime
|
|
}
|
|
}, '*');
|
|
|
|
// Achievements
|
|
if (bestLapTime < 15000) { // Unter 15 Sekunden
|
|
window.parent.postMessage({
|
|
type: 'GAME_EVENT',
|
|
gameId: GAME_ID,
|
|
event: 'ACHIEVEMENT_UNLOCKED',
|
|
data: {
|
|
achievementId: 'drift_master',
|
|
name: 'Drift Master',
|
|
description: 'Schaffe eine Runde unter 15 Sekunden',
|
|
icon: '🏎️'
|
|
}
|
|
}, '*');
|
|
}
|
|
|
|
if (player.lapCount >= 10) {
|
|
window.parent.postMessage({
|
|
type: 'GAME_EVENT',
|
|
gameId: GAME_ID,
|
|
event: 'ACHIEVEMENT_UNLOCKED',
|
|
data: {
|
|
achievementId: 'endurance_racer',
|
|
name: 'Ausdauer-Rennfahrer',
|
|
description: 'Fahre 10 Runden in einer Session',
|
|
icon: '🎯'
|
|
}
|
|
}, '*');
|
|
}
|
|
}
|
|
|
|
// Neustart
|
|
function restartGame() {
|
|
document.getElementById('gameOver').style.display = 'none';
|
|
startGame();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |