Herzlich willkommen zum SELF-Treffen 2026
vom 24.04. – 26.04.2026 in Halle (Saale)

Beispiel:Breakout.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

<!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <title>Breakout</title> <style>

 body { 
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
   height: 100vh; 
   margin: 0;
   background: #111;
   color: #fff;
   font-family: monospace;
 }
 canvas { 
   background: #000;
   border: medium solid #555;
   display: block;
   width: 100%;
   max-width: 800px;
   margin: 0 0.5rem;
   cursor: none;
 }
 p { 
   margin: 0.5rem;
   font-size: clamp(0.7rem, 2vw, 1.2rem);
 }
 #tonBtn {
   background: none;
   border: none;
   font-size: 2em;
   font-family: inherit;
   cursor: pointer;
   margin-top: -1rem;
 }

</style> </head>

<body>

PUNKTE: 0 | LEBEN: 3 | TON: <button id="tonBtn">🔊</button>

 <canvas id="c"></canvas>
 

← → Pfeiltasten / Maus · Leertaste / Klick für Start

<script> document.addEventListener('DOMContentLoaded', function () {

 const canvas = document.getElementById('c');
 const ctx = canvas.getContext('2d');
 // --- Ton ---
 let audioCtx = null;
 let soundOn = true;
 document.getElementById('tonBtn').addEventListener('click', () => {
   soundOn = !soundOn;
   document.getElementById('tonBtn').textContent = soundOn ? '🔊' : '🔇';
 });
 function getAudioCtx() {
   if (!audioCtx) audioCtx = new AudioContext();
   return audioCtx;
 }
 function beep(freq = 440, dur = 0.05) {
   if (!soundOn) return;
   const ac = getAudioCtx();
   const osc = ac.createOscillator();
   const gain = ac.createGain();
   osc.connect(gain);
   gain.connect(ac.destination);
   osc.frequency.value = freq;
   gain.gain.setValueAtTime(0.2, ac.currentTime);
   gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + dur);
   osc.start();
   osc.stop(ac.currentTime + dur);
 }
 // --- Canvas-Grösse responsiv bestimmen, nur beim Laden ---
 const SEITENVERHAELTNIS = 4 / 3;
 const dpr = window.devicePixelRatio || 1;
 function canvasGroesseSetzen() {
   const cssBreite = Math.min(canvas.parentElement.clientWidth - 16, 800);
   const cssHoehe  = Math.round(cssBreite / SEITENVERHAELTNIS);
   canvas.style.width  = cssBreite + 'px';
   canvas.style.height = cssHoehe  + 'px';
   canvas.width  = Math.round(cssBreite * dpr);
   canvas.height = Math.round(cssHoehe  * dpr);
   ctx.scale(dpr, dpr);
 }
 canvasGroesseSetzen();
 // Logische Spielgrösse (CSS-Pixel, unabhängig von dpr)
 const W = canvas.width  / dpr;
 const H = canvas.height / dpr;
 // --- Konfiguration ---
 const KONFIG = {
   paddle: {
     breite:       Math.round(W * 0.15),
     hoehe:        Math.round(H * 0.018),
     y:            H - H * 0.08,
     geschwindigkeit: W * 0.012,
   },
   ball: {
     radius:       Math.round(W * 0.012),
     geschwindigkeit: Math.hypot(W * 0.006, H * 0.011),
   },
   steine: {
     spalten:      10,
     reihen:       6,
     abstand:      Math.round(W * 0.007),
     get breite()  { return Math.round((W - this.abstand) / this.spalten - this.abstand); },
     hoehe:        Math.round(H * 0.038),
     get offsetX() { return (W - this.spalten * (this.breite + this.abstand) + this.abstand) / 2; },
     offsetY:      Math.round(H * 0.1),
     farben:       ['#e74c3c','#e67e22','#f1c40f','#2ecc71','#3498db','#9b59b6'],
   },
   schrift: {
     gross: Math.min(Math.round(W * 0.15), 80),
     klein: Math.round(W * 0.03),
   },
 };
 // --- Zustand ---
 const state = { score: 0, lives: 3, paused: false, pauseMsg:  };
 let bricks = [], ball = {}, paddle = {};
 let rafId = null;
 const keys = {};
 function pause(msg) {
   state.paused  = true;
   state.pauseMsg = msg;
 }
 // --- Init ---
 function init() {
   if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }
   Object.assign(state, { score: 0, lives: 3, paused: false, pauseMsg:  });
   const { paddle: P, steine: S } = KONFIG;
   paddle = { x: W/2 - P.breite/2, y: P.y };
   bricks = [];
   for (let r = 0; r < S.reihen; r++)
     for (let c = 0; c < S.spalten; c++)
       bricks.push({ x: S.offsetX + c*(S.breite+S.abstand), y: S.offsetY + r*(S.hoehe+S.abstand), alive: true, color: S.farben[r] });
   resetBall();
   updateInfo();
   rafId = requestAnimationFrame(loop);
 }
 function resetBall() {
   const { paddle: P, ball: B } = KONFIG;
   ball = { x: W/2, y: P.y - B.radius - 2, vx: B.geschwindigkeit * 0.5, vy: -B.geschwindigkeit, launched: false };
 }
 // --- Game Loop ---
 function loop() {
   update();
   draw();
   if (state.paused) {
     showMessage(state.pauseMsg, 'Punkte: ' + state.score);
     rafId = null;
   } else {
     rafId = requestAnimationFrame(loop);
   }
 }
 // --- Steuerung ---
 window.addEventListener('keydown', e => {
   keys[e.key] = true;
   if (e.key === ' ') {
     getAudioCtx();
     if (state.paused) init();
     else ball.launched = true;
   }
 });
 window.addEventListener('keyup', e => { keys[e.key] = false; });
 canvas.addEventListener('mousemove', e => {
   const r = canvas.getBoundingClientRect();
   const scale = W / r.width;
   paddle.x = (e.clientX - r.left) * scale - KONFIG.paddle.breite / 2;
 }, { passive: true });
 canvas.addEventListener('touchmove', e => {
   e.preventDefault();
   const r = canvas.getBoundingClientRect();
   const scale = W / r.width;
   paddle.x = (e.touches[0].clientX - r.left) * scale - KONFIG.paddle.breite / 2;
 }, { passive: false });
 canvas.addEventListener('click', () => {
   getAudioCtx();
   if (state.paused) init();
   else ball.launched = true;
 });
 // --- Update ---
 function update() {
   const { paddle: P, ball: B, steine: S } = KONFIG;
   if (keys['ArrowLeft'])  paddle.x -= P.geschwindigkeit;
   if (keys['ArrowRight']) paddle.x += P.geschwindigkeit;
   paddle.x = Math.max(0, Math.min(W - P.breite, paddle.x));
   if (!ball.launched) { ball.x = paddle.x + P.breite/2; return; }
   ball.x += ball.vx;
   ball.y += ball.vy;
   // Wände (Richtung mit Math.abs garantieren, dann Position korrigieren)
   if (ball.x - B.radius < 0) {
     ball.vx = Math.abs(ball.vx);
     ball.x  = B.radius;
     beep(300);
   } else if (ball.x + B.radius > W) {
     ball.vx = -Math.abs(ball.vx);
     ball.x  = W - B.radius;
     beep(300);
   }
   if (ball.y - B.radius < 0) {
     ball.vy = Math.abs(ball.vy);
     ball.y  = B.radius;
     beep(300);
   }
   // Paddle-Kollision
   if (ball.vy > 0
     && ball.y + B.radius >= paddle.y
     && ball.y + B.radius <= paddle.y + P.hoehe
     && ball.x >= paddle.x
     && ball.x <= paddle.x + P.breite) {
     const angle = (ball.x - (paddle.x + P.breite/2)) / (P.breite/2) * (Math.PI / 3);
     ball.vx = B.geschwindigkeit * Math.sin(angle);
     ball.vy = -Math.abs(B.geschwindigkeit * Math.cos(angle));
     ball.y  = paddle.y - B.radius;                           // rauskorrigieren
     beep(480);
   }
   // Ball verloren
   if (ball.y > H) {
     state.lives--;
     updateInfo();
     if (state.lives <= 0) { pause('GAME OVER'); return; }
     resetBall();
   }
   // Block-Kollision
   for (const b of bricks) {
     if (!b.alive) continue;
     if (ball.x + B.radius > b.x && ball.x - B.radius < b.x + S.breite &&
         ball.y + B.radius > b.y && ball.y - B.radius < b.y + S.hoehe) {
       b.alive = false;
       state.score++;
       updateInfo();
       ball.vy *= -1;
       beep(660);
       break;
     }
   }
   if (bricks.every(b => !b.alive)) pause('GEWONNEN!');
 }
 function updateInfo() {
   document.getElementById('infoText').textContent = `PUNKTE: ${state.score} | LEBEN: ${state.lives} | TON: `;
 }
 // --- Zeichnen ---
 function draw() {
   const { paddle: P, ball: B, steine: S, schrift: F } = KONFIG;
   ctx.clearRect(0, 0, W, H);
   ctx.save();
   ctx.globalAlpha  = 0.15;
   ctx.fillStyle    = '#fff';
   ctx.font         = `bold ${Math.round(W * 0.18)}px monospace`;
   ctx.textAlign    = 'center';
   ctx.textBaseline = 'middle';
   ctx.fillText('BREAKOUT', W/2, H * 0.83);
   ctx.restore();
   for (const b of bricks) {
     if (!b.alive) continue;
     ctx.fillStyle = b.color;
     ctx.fillRect(b.x, b.y, S.breite, S.hoehe);
   }
   ctx.fillStyle = '#fff';
   ctx.fillRect(paddle.x, paddle.y, P.breite, P.hoehe);
   ctx.beginPath();
   ctx.arc(ball.x, ball.y, B.radius, 0, Math.PI * 2);
   ctx.fillStyle = '#fff';
   ctx.fill();
 }
 // --- Meldung im Canvas ---
 function showMessage(msg, sub) {
   const { schrift: F } = KONFIG;
   ctx.save();
   ctx.textBaseline = 'alphabetic';
   ctx.fillStyle    = 'rgba(0,0,0,0.7)';
   ctx.fillRect(0, 0, W, H);
   ctx.textAlign    = 'center';
   ctx.fillStyle    = '#fff';
   ctx.font         = `bold ${F.gross}px monospace`;
   ctx.fillText(msg, W/2, H/2 - F.gross * 0.5);
   ctx.font         = `${F.klein}px monospace`;
   ctx.fillStyle    = '#aaa';
   ctx.fillText(sub, W/2, H/2 + F.klein * 1.2);
   ctx.restore();
 }
 init();

}); </script>

</body> </html>