841 lines
29 KiB
HTML
841 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Breakout | PonyWave Tools</title>
|
|
<link rel="icon" href="https://tools.ponywave.de/breakout/icon.png">
|
|
<meta property="og:title" content="Breakout | PonyWave Tools">
|
|
<meta property="og:description" content="Ein klassisches Breakout-Spiel in HTML">
|
|
<meta property="og:type" content="game">
|
|
<meta property="og:url" content="https://tools.ponywave.de/breakout">
|
|
<meta property="og:image" content="https://tools.ponywave.de/breakout/icon.png">
|
|
|
|
<!-- Umami Tracking -->
|
|
<script defer src="https://stats.ponywave.de/script" data-website-id="9ef713d2-adb9-4906-9df5-708d8a8b9131" data-tag="breakout"></script>
|
|
|
|
<style>
|
|
:root {
|
|
--bg-color: #f0f0f0;
|
|
--text-color: #333;
|
|
--primary-color: #7F006E;
|
|
--secondary-color: #FF7FED;
|
|
--accent-color: #5a189a;
|
|
--border-color: #ddd;
|
|
--paddle-color: #2c3e50;
|
|
--ball-color: #e74c3c;
|
|
--modal-bg: rgba(255, 255, 255, 0.95);
|
|
--modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
--btn-primary: #7F006E;
|
|
--btn-hover: #FF7FED;
|
|
--btn-text: #fff;
|
|
}
|
|
|
|
.dark-mode {
|
|
--bg-color: #222;
|
|
--text-color: #f0f0f0;
|
|
--primary-color: #9d4edd;
|
|
--secondary-color: #c77dff;
|
|
--accent-color: #7b2cbf;
|
|
--border-color: #444;
|
|
--paddle-color: #7b2cbf;
|
|
--ball-color: #ff5e78;
|
|
--modal-bg: rgba(30, 30, 30, 0.95);
|
|
--modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
--btn-primary: #9d4edd;
|
|
--btn-hover: #c77dff;
|
|
--btn-text: #fff;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
transition: background-color 0.3s, color 0.3s;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
header {
|
|
width: 100%;
|
|
padding: 1rem;
|
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
|
color: #fff;
|
|
text-align: center;
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
h1 {
|
|
margin: 0;
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.game-container {
|
|
margin: 20px auto;
|
|
position: relative;
|
|
max-width: 100%;
|
|
}
|
|
|
|
canvas {
|
|
background-color: var(--bg-color);
|
|
border: 2px solid var(--border-color);
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
|
display: block;
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin: 10px 0 20px;
|
|
width: 100%;
|
|
max-width: 800px;
|
|
}
|
|
|
|
.info {
|
|
display: flex;
|
|
gap: 20px;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn {
|
|
background-color: var(--btn-primary);
|
|
color: var(--btn-text);
|
|
border: none;
|
|
padding: 8px 15px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn:hover {
|
|
background-color: var(--btn-hover);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.theme-toggle {
|
|
background: transparent;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
padding: 5px;
|
|
margin-left: 20px;
|
|
}
|
|
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.modal-overlay.active {
|
|
opacity: 1;
|
|
pointer-events: all;
|
|
}
|
|
|
|
.modal {
|
|
background-color: var(--modal-bg);
|
|
padding: 2rem;
|
|
border-radius: 10px;
|
|
box-shadow: var(--modal-shadow);
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
position: relative;
|
|
transform: translateY(-20px);
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.modal-overlay.active .modal {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.modal h2 {
|
|
margin-top: 0;
|
|
color: var(--primary-color);
|
|
border-bottom: 2px solid var(--border-color);
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.modal p {
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.close-modal {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
background: transparent;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.leaderboard {
|
|
width: 100%;
|
|
max-width: 500px;
|
|
margin: 20px auto;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.leaderboard th, .leaderboard td {
|
|
padding: 10px;
|
|
text-align: center;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.leaderboard th {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.leaderboard tr:nth-child(even) {
|
|
background-color: rgba(128, 128, 128, 0.1);
|
|
}
|
|
|
|
.lives {
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.score {
|
|
color: var(--accent-color);
|
|
}
|
|
|
|
footer {
|
|
margin-top: auto;
|
|
width: 100%;
|
|
padding: 1rem;
|
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
|
color: #fff;
|
|
text-align: center;
|
|
}
|
|
|
|
footer a {
|
|
color: #fff;
|
|
text-decoration: none;
|
|
}
|
|
|
|
footer a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.heart {
|
|
color: #ff5e78;
|
|
animation: heartbeat 1.5s infinite;
|
|
}
|
|
|
|
@keyframes heartbeat {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.2); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.controls {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
.info {
|
|
width: 100%;
|
|
justify-content: space-around;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: center;
|
|
}
|
|
|
|
canvas {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Breakout</h1>
|
|
<button id="themeToggle" class="theme-toggle">🌙</button>
|
|
</header>
|
|
|
|
<div class="controls">
|
|
<div class="info">
|
|
<div class="lives">Leben: <span id="lives">3</span></div>
|
|
<div class="score">Punkte: <span id="score">0</span></div>
|
|
</div>
|
|
<div class="buttons">
|
|
<button id="startBtn" class="btn">Start</button>
|
|
<button id="leaderboardBtn" class="btn">Bestenliste</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="game-container">
|
|
<canvas id="gameCanvas" width="600" height="450"></canvas>
|
|
</div>
|
|
|
|
<!-- Anleitungs-Modal -->
|
|
<div id="instructionModal" class="modal-overlay">
|
|
<div class="modal">
|
|
<button class="close-modal">×</button>
|
|
<h2>Spielanleitung</h2>
|
|
<p>Willkommen bei Breakout! Hier ist, wie man spielt:</p>
|
|
<ul>
|
|
<li>Benutze die <strong>Pfeiltasten (Links/Rechts)</strong> um die Plattform zu steuern.</li>
|
|
<li>Klicke auf <strong>Start</strong> oder drücke die <strong>Leertaste</strong>, um das Spiel zu beginnen.</li>
|
|
<li>Drücke die <strong>Leertaste</strong> ein zweites Mal, um den Ball zu starten.</li>
|
|
<li>Zerstöre alle Blöcke, um das Level zu gewinnen.</li>
|
|
<li>Farbige Blöcke benötigen unterschiedliche Anzahl von Treffern:</li>
|
|
<ul>
|
|
<li>Grüne Blöcke: 1 Treffer</li>
|
|
<li>Blaue Blöcke: 2 Treffer</li>
|
|
<li>Rote Blöcke: 3 Treffer</li>
|
|
</ul>
|
|
<li>Du hast 3 Leben. Wenn der Ball den unteren Bildschirmrand berührt, verlierst du ein Leben.</li>
|
|
<li>Das Spiel endet, wenn du keine Leben mehr hast.</li>
|
|
</ul>
|
|
<p>Viel Spaß beim Spielen!</p>
|
|
<button id="startGameBtn" class="btn">Verstanden, Los geht's!</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Game Over Modal -->
|
|
<div id="gameOverModal" class="modal-overlay">
|
|
<div class="modal">
|
|
<h2>Game Over</h2>
|
|
<p>Dein Punktestand: <span id="finalScore">0</span></p>
|
|
<button id="saveScoreBtn" class="btn">Zur Bestenliste hinzufügen</button>
|
|
<button id="restartBtn" class="btn">Nochmal spielen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Leaderboard Modal -->
|
|
<div id="leaderboardModal" class="modal-overlay">
|
|
<div class="modal">
|
|
<button class="close-modal">×</button>
|
|
<h2>Bestenliste</h2>
|
|
<table class="leaderboard">
|
|
<thead>
|
|
<tr>
|
|
<th>Rang</th>
|
|
<th>Punkte</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="leaderboardBody">
|
|
<!-- Wird durch JavaScript gefüllt -->
|
|
</tbody>
|
|
</table>
|
|
<button id="closeLeaderboardBtn" class="btn">Schließen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<footer>
|
|
<a href="/">Zurück zur Startseite</a> | © <span id="currentYear"></span> Akamaru | Made with <span class="heart">❤️</span> by Claude
|
|
</footer>
|
|
|
|
<script>
|
|
// Basis-Spiel-Variablen
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const startBtn = document.getElementById('startBtn');
|
|
const scoreDisplay = document.getElementById('score');
|
|
const livesDisplay = document.getElementById('lives');
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
const instructionModal = document.getElementById('instructionModal');
|
|
const gameOverModal = document.getElementById('gameOverModal');
|
|
const leaderboardModal = document.getElementById('leaderboardModal');
|
|
const finalScoreDisplay = document.getElementById('finalScore');
|
|
const playerNameInput = document.getElementById('playerName');
|
|
const saveScoreBtn = document.getElementById('saveScoreBtn');
|
|
const restartBtn = document.getElementById('restartBtn');
|
|
const leaderboardBtn = document.getElementById('leaderboardBtn');
|
|
const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn');
|
|
const leaderboardBody = document.getElementById('leaderboardBody');
|
|
const startGameBtn = document.getElementById('startGameBtn');
|
|
const closeModalButtons = document.querySelectorAll('.close-modal');
|
|
|
|
// Spielzustand
|
|
let ball = {
|
|
x: canvas.width / 2,
|
|
y: canvas.height - 30,
|
|
dx: 4,
|
|
dy: -4,
|
|
radius: 8,
|
|
color: getComputedStyle(document.documentElement).getPropertyValue('--ball-color')
|
|
};
|
|
|
|
let paddle = {
|
|
width: 100,
|
|
height: 12,
|
|
x: (canvas.width - 100) / 2,
|
|
color: getComputedStyle(document.documentElement).getPropertyValue('--paddle-color')
|
|
};
|
|
|
|
// Farben und Treffer für jeden Block-Typ
|
|
const blockTypes = [
|
|
{ color: '#4CAF50', hits: 1 }, // Grün - 1 Treffer
|
|
{ color: '#2196F3', hits: 2 }, // Blau - 2 Treffer
|
|
{ color: '#F44336', hits: 3 } // Rot - 3 Treffer
|
|
];
|
|
|
|
// Blöcke konfiguration
|
|
const blockRowCount = 5;
|
|
const blockColumnCount = 8;
|
|
const blockWidth = 60;
|
|
const blockHeight = 20;
|
|
const blockPadding = 10;
|
|
const blockOffsetTop = 40;
|
|
const blockOffsetLeft = 30;
|
|
|
|
let blocks = [];
|
|
|
|
// Spielwerte
|
|
let score = 0;
|
|
let lives = 3;
|
|
let gameStarted = false;
|
|
let ballReleased = false;
|
|
let gameOver = false;
|
|
let highScores = JSON.parse(localStorage.getItem('breakoutHighScores')) || [];
|
|
|
|
// Event-Listener für Tastaturbedienung
|
|
let rightPressed = false;
|
|
let leftPressed = false;
|
|
let spacePressed = false;
|
|
|
|
// Blöcke initialisieren
|
|
function initBlocks() {
|
|
blocks = [];
|
|
for (let c = 0; c < blockColumnCount; c++) {
|
|
blocks[c] = [];
|
|
for (let r = 0; r < blockRowCount; r++) {
|
|
// Zufälligen Block-Typ auswählen
|
|
const typeIndex = Math.floor(Math.random() * blockTypes.length);
|
|
const type = blockTypes[typeIndex];
|
|
|
|
blocks[c][r] = {
|
|
x: 0,
|
|
y: 0,
|
|
status: type.hits, // Status = Anzahl der Treffer, die benötigt werden
|
|
maxHits: type.hits,
|
|
color: type.color
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spiel initialisieren
|
|
function init() {
|
|
initBlocks();
|
|
resetPaddle();
|
|
resetBall();
|
|
score = 0;
|
|
lives = 3;
|
|
gameOver = false;
|
|
ballReleased = false;
|
|
updateScoreDisplay();
|
|
updateLivesDisplay();
|
|
}
|
|
|
|
function resetBall() {
|
|
ball.x = canvas.width / 2;
|
|
ball.y = canvas.height - paddle.height - ball.radius;
|
|
// Ball-Geschwindigkeit wird erst beim Loslassen bestimmt
|
|
ball.dx = 0;
|
|
ball.dy = 0;
|
|
ballReleased = false; // Ball ist noch nicht losgelassen
|
|
}
|
|
|
|
function resetPaddle() {
|
|
paddle.x = (canvas.width - paddle.width) / 2;
|
|
}
|
|
|
|
// Ball zeichnen
|
|
function drawBall() {
|
|
ctx.beginPath();
|
|
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
|
|
ctx.fillStyle = ball.color;
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
}
|
|
|
|
// Paddle zeichnen
|
|
function drawPaddle() {
|
|
ctx.beginPath();
|
|
ctx.rect(paddle.x, canvas.height - paddle.height, paddle.width, paddle.height);
|
|
ctx.fillStyle = paddle.color;
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
}
|
|
|
|
// Blöcke zeichnen
|
|
function drawBlocks() {
|
|
for (let c = 0; c < blockColumnCount; c++) {
|
|
for (let r = 0; r < blockRowCount; r++) {
|
|
if (blocks[c][r].status > 0) {
|
|
const blockX = c * (blockWidth + blockPadding) + blockOffsetLeft;
|
|
const blockY = r * (blockHeight + blockPadding) + blockOffsetTop;
|
|
|
|
blocks[c][r].x = blockX;
|
|
blocks[c][r].y = blockY;
|
|
|
|
// Helligkeit abhängig von verbleibenden Treffern anpassen
|
|
const hitRatio = blocks[c][r].status / blocks[c][r].maxHits;
|
|
const baseColor = blocks[c][r].color;
|
|
|
|
ctx.beginPath();
|
|
ctx.rect(blockX, blockY, blockWidth, blockHeight);
|
|
ctx.fillStyle = adjustColorBrightness(baseColor, hitRatio);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#333';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Farbe je nach Zustand anpassen
|
|
function adjustColorBrightness(hexColor, ratio) {
|
|
// Je niedriger das Verhältnis, desto heller der Block (wurde öfter getroffen)
|
|
const r = parseInt(hexColor.slice(1, 3), 16);
|
|
const g = parseInt(hexColor.slice(3, 5), 16);
|
|
const b = parseInt(hexColor.slice(5, 7), 16);
|
|
|
|
// Luminanz anpassen
|
|
const brightnessAdjust = 0.7 + (0.3 * ratio);
|
|
|
|
const adjustedR = Math.floor(r * brightnessAdjust);
|
|
const adjustedG = Math.floor(g * brightnessAdjust);
|
|
const adjustedB = Math.floor(b * brightnessAdjust);
|
|
|
|
return `rgb(${adjustedR}, ${adjustedG}, ${adjustedB})`;
|
|
}
|
|
|
|
// Kollisionserkennung
|
|
function collisionDetection() {
|
|
for (let c = 0; c < blockColumnCount; c++) {
|
|
for (let r = 0; r < blockRowCount; r++) {
|
|
const b = blocks[c][r];
|
|
if (b.status > 0) {
|
|
if (ball.x > b.x && ball.x < b.x + blockWidth && ball.y > b.y && ball.y < b.y + blockHeight) {
|
|
ball.dy = -ball.dy;
|
|
b.status -= 1;
|
|
if (b.status === 0) {
|
|
score += b.maxHits * 10; // Punkte basierend auf der Blockstärke
|
|
updateScoreDisplay();
|
|
|
|
// Überprüfen, ob alle Blöcke zerstört wurden
|
|
if (checkLevelComplete()) {
|
|
alert("Glückwunsch! Du hast alle Blöcke zerstört!");
|
|
resetBall();
|
|
resetPaddle();
|
|
initBlocks();
|
|
}
|
|
} else {
|
|
// Teilpunkte für Treffer, die den Block nicht komplett zerstören
|
|
score += 5;
|
|
updateScoreDisplay();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Überprüfen, ob alle Blöcke zerstört wurden
|
|
function checkLevelComplete() {
|
|
for (let c = 0; c < blockColumnCount; c++) {
|
|
for (let r = 0; r < blockRowCount; r++) {
|
|
if (blocks[c][r].status > 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Punkteanzeige aktualisieren
|
|
function updateScoreDisplay() {
|
|
scoreDisplay.textContent = score;
|
|
}
|
|
|
|
// Lebenanzeige aktualisieren
|
|
function updateLivesDisplay() {
|
|
livesDisplay.textContent = lives;
|
|
}
|
|
|
|
// Ball bewegen
|
|
function moveBall() {
|
|
// Wenn der Ball noch nicht losgelassen wurde, folgt er dem Paddle
|
|
if (!ballReleased) {
|
|
ball.x = paddle.x + paddle.width / 2;
|
|
ball.y = canvas.height - paddle.height - ball.radius;
|
|
return;
|
|
}
|
|
|
|
ball.x += ball.dx;
|
|
ball.y += ball.dy;
|
|
|
|
// Kollision mit Wänden
|
|
if (ball.x + ball.dx > canvas.width - ball.radius || ball.x + ball.dx < ball.radius) {
|
|
ball.dx = -ball.dx;
|
|
}
|
|
|
|
// Kollision mit oberem Rand
|
|
if (ball.y + ball.dy < ball.radius) {
|
|
ball.dy = -ball.dy;
|
|
}
|
|
// Kollision mit unterem Rand (Leben verlieren)
|
|
else if (ball.y + ball.dy > canvas.height - ball.radius) {
|
|
// Kollision mit Paddle
|
|
if (ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
|
|
// Ball-Richtung abhängig vom Treffpunkt auf dem Paddle beeinflussen
|
|
const hitPosition = (ball.x - paddle.x) / paddle.width; // 0 bis 1
|
|
const angle = hitPosition * Math.PI - Math.PI/2; // -90 bis +90 Grad
|
|
const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
|
|
|
|
ball.dx = Math.cos(angle) * speed;
|
|
ball.dy = -Math.sin(angle) * speed;
|
|
|
|
// Leicht beschleunigen, aber nicht zu stark
|
|
ball.dx *= 1.002; // Von 1.005 auf 1.002 reduziert
|
|
ball.dy *= 1.002; // Von 1.005 auf 1.002 reduziert
|
|
} else {
|
|
// Leben verlieren
|
|
lives--;
|
|
updateLivesDisplay();
|
|
|
|
if (lives <= 0) {
|
|
endGame();
|
|
} else {
|
|
resetBall();
|
|
resetPaddle();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Paddle bewegen
|
|
function movePaddle() {
|
|
if (rightPressed && paddle.x < canvas.width - paddle.width) {
|
|
paddle.x += 5;
|
|
} else if (leftPressed && paddle.x > 0) {
|
|
paddle.x -= 5;
|
|
}
|
|
}
|
|
|
|
// Spielschleife
|
|
function draw() {
|
|
// Canvas löschen
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Spielelemente zeichnen
|
|
drawBlocks();
|
|
drawBall();
|
|
drawPaddle();
|
|
|
|
// Kollisionserkennung
|
|
collisionDetection();
|
|
|
|
if (gameStarted && !gameOver) {
|
|
// Ball und Paddle bewegen
|
|
moveBall();
|
|
movePaddle();
|
|
}
|
|
|
|
// Nächsten Frame anfordern
|
|
requestAnimationFrame(draw);
|
|
}
|
|
|
|
// Spiel starten
|
|
function startGame() {
|
|
if (!gameStarted) {
|
|
gameStarted = true;
|
|
startBtn.textContent = 'Pause';
|
|
resetBall(); // Ball zurücksetzen beim Spielstart
|
|
} else {
|
|
gameStarted = false;
|
|
startBtn.textContent = 'Fortsetzen';
|
|
}
|
|
}
|
|
|
|
// Spiel beenden
|
|
function endGame() {
|
|
gameOver = true;
|
|
gameStarted = false;
|
|
finalScoreDisplay.textContent = score;
|
|
showModal(gameOverModal);
|
|
}
|
|
|
|
// Spiel neustarten
|
|
function restartGame() {
|
|
hideModal(gameOverModal);
|
|
init();
|
|
gameStarted = true;
|
|
startBtn.textContent = 'Pause';
|
|
}
|
|
|
|
// Punktzahl speichern
|
|
function saveScore() {
|
|
highScores.push({ name: "Spieler", score: score });
|
|
highScores.sort((a, b) => b.score - a.score);
|
|
highScores = highScores.slice(0, 10); // Nur Top 10 behalten
|
|
|
|
localStorage.setItem('breakoutHighScores', JSON.stringify(highScores));
|
|
|
|
hideModal(gameOverModal);
|
|
showLeaderboard();
|
|
}
|
|
|
|
// Bestenliste anzeigen
|
|
function showLeaderboard() {
|
|
leaderboardBody.innerHTML = '';
|
|
|
|
highScores.forEach((entry, index) => {
|
|
const row = document.createElement('tr');
|
|
|
|
const rankCell = document.createElement('td');
|
|
rankCell.textContent = index + 1;
|
|
|
|
const scoreCell = document.createElement('td');
|
|
scoreCell.textContent = entry.score;
|
|
|
|
row.appendChild(rankCell);
|
|
row.appendChild(scoreCell);
|
|
|
|
leaderboardBody.appendChild(row);
|
|
});
|
|
|
|
showModal(leaderboardModal);
|
|
}
|
|
|
|
// Modal anzeigen
|
|
function showModal(modal) {
|
|
modal.classList.add('active');
|
|
}
|
|
|
|
// Modal schließen
|
|
function hideModal(modal) {
|
|
modal.classList.remove('active');
|
|
}
|
|
|
|
// Theme initialisieren und auf Benutzersystemeinstellungen prüfen
|
|
function initTheme() {
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
const savedTheme = localStorage.getItem('breakoutTheme');
|
|
|
|
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
|
|
document.body.classList.add('dark-mode');
|
|
themeToggle.textContent = '☀️';
|
|
ball.color = getComputedStyle(document.documentElement).getPropertyValue('--ball-color');
|
|
paddle.color = getComputedStyle(document.documentElement).getPropertyValue('--paddle-color');
|
|
}
|
|
}
|
|
|
|
// Theme umschalten
|
|
function toggleTheme() {
|
|
document.body.classList.toggle('dark-mode');
|
|
ball.color = getComputedStyle(document.documentElement).getPropertyValue('--ball-color');
|
|
paddle.color = getComputedStyle(document.documentElement).getPropertyValue('--paddle-color');
|
|
|
|
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌙';
|
|
|
|
// Speichern der Präferenz
|
|
const isDark = document.body.classList.contains('dark-mode');
|
|
localStorage.setItem('breakoutTheme', isDark ? 'dark' : 'light');
|
|
}
|
|
|
|
// Ball loslassen
|
|
function releaseBall() {
|
|
if (gameStarted && !ballReleased && !gameOver) {
|
|
ballReleased = true;
|
|
// Zufällige Richtung, aber nicht zu steil
|
|
let angle = Math.random() * Math.PI / 3 - Math.PI / 6; // zwischen -30 und +30 Grad
|
|
let speed = 2; // Von 3 auf 2 reduziert für langsameren Ball
|
|
ball.dx = Math.sin(angle) * speed;
|
|
ball.dy = -Math.cos(angle) * speed;
|
|
}
|
|
}
|
|
|
|
// Event Listener
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Right' || e.key === 'ArrowRight') {
|
|
rightPressed = true;
|
|
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
|
|
leftPressed = true;
|
|
} else if (e.key === ' ') {
|
|
if (!gameStarted && !gameOver) {
|
|
startGame();
|
|
} else if (gameStarted && !ballReleased && !gameOver) {
|
|
releaseBall();
|
|
}
|
|
e.preventDefault(); // Verhindert Seiten-Scrollen bei Leertaste
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keyup', (e) => {
|
|
if (e.key === 'Right' || e.key === 'ArrowRight') {
|
|
rightPressed = false;
|
|
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
|
|
leftPressed = false;
|
|
}
|
|
});
|
|
|
|
startBtn.addEventListener('click', startGame);
|
|
themeToggle.addEventListener('click', toggleTheme);
|
|
restartBtn.addEventListener('click', restartGame);
|
|
saveScoreBtn.addEventListener('click', saveScore);
|
|
leaderboardBtn.addEventListener('click', showLeaderboard);
|
|
closeLeaderboardBtn.addEventListener('click', () => hideModal(leaderboardModal));
|
|
startGameBtn.addEventListener('click', () => {
|
|
hideModal(instructionModal);
|
|
startGame();
|
|
});
|
|
|
|
closeModalButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const modal = button.closest('.modal-overlay');
|
|
hideModal(modal);
|
|
});
|
|
});
|
|
|
|
// Aktuelles Jahr im Footer anzeigen
|
|
document.getElementById('currentYear').textContent = new Date().getFullYear();
|
|
|
|
// Spiel initialisieren und starten
|
|
init();
|
|
initTheme(); // Theme automatisch entsprechend den Systemeinstellungen initialisieren
|
|
showModal(instructionModal); // Anleitung beim Start anzeigen
|
|
draw(); // Zeichenschleife starten
|
|
</script>
|
|
</body>
|
|
</html> |