1
0

Neu: Breakout

This commit is contained in:
Akamaru 2025-03-05 20:59:34 +01:00
parent dc2b59c282
commit a54e9cdd60
3 changed files with 833 additions and 0 deletions

828
breakout/index.html Normal file
View File

@ -0,0 +1,828 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<meta property="og:title" content="Breakout">
<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">
<!-- 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;
}
@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">&times;</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">&times;</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
</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>

View File

@ -211,6 +211,10 @@
<div class="category-section">
<h2 class="category-title">🎮 Spiele</h2>
<div class="tools-grid">
<a href="https://tools.ponywave.de/breakout" class="tool-bubble">
<h2 class="tool-title">Breakout</h2>
<p class="tool-description">Ein klassisches Breakout-Spiel mit farbigen Blöcken und Highscore-System</p>
</a>
<a href="https://tools.ponywave.de/emoji_jump" class="tool-bubble">
<h2 class="tool-title">Emoji Doodle Jump</h2>
<p class="tool-description">Ein Doodle Jump Klon mit Emojis als Charaktere</p>

View File

@ -5,6 +5,7 @@ https://tools.ponywave.de/text_sorter
https://tools.ponywave.de/yt_thumb
https://tools.ponywave.de/flash_dl
https://tools.ponywave.de/kemonogen
https://tools.ponywave.de/breakout
https://tools.ponywave.de/emoji_jump
https://tools.ponywave.de/2048
https://tools.ponywave.de/solitaire