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

Beispiel:Mini-space-invaders.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

<!DOCTYPE html> <html lang="de"> <head>

 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>mini space invaders</title>
 <style>
   :root {
     font-size: 100%;
   }
   body {
     background-color: #0a0a1a;
     display: flex;
     flex-direction: column;
     align-items: center;
     justify-content: center;
     min-height: 95vh;
     font-family: 'Courier New', Courier, monospace;
     font-size: 1rem;
     color: #e0e0ff;
     user-select: none;
   }
   #spielflaeche {
     border: 0.125em solid #334;
     box-shadow: 0 0 2em #1a1a5588, 0 0 4em #0a0a3388;
     display: block;
     background-color: #05050f;
     cursor: none;
   }
   #steuerung {
     font-size: .9em;
     color: #aabbdd;
   }
 </style>

</head> <body>

 <canvas id="spielflaeche" width="480" height="560"></canvas>

← → bewegen | Leertaste schießen | Enter Neustart

 <script>
 document.addEventListener('DOMContentLoaded', function () {
   // 1. INITIALISIERUNG
   // ===============================================
   const leinwand = document.getElementById('spielflaeche');
   const ctx = leinwand.getContext('2d');
   const BREITE = leinwand.width;
   const HOEHE  = leinwand.height;


   // 2. SPIELKONSTANTEN
   // ===============================================
   const SPIELER_GESCHWINDIGKEIT = 4;
   const SPIELER_BREITE          = 36;
   const SPIELER_HOEHE           = 22;
   const LASER_GESCHWINDIGKEIT   = 7;
   const LASER_BREITE            = 3;
   const LASER_HOEHE             = 14;
   const LASER_COOLDOWN_MS       = 200;
   const ALIEN_BREITE            = 30;
   const ALIEN_HOEHE             = 20;
   const ALIEN_SPALTEN           = 10;
   const ALIEN_REIHEN            = 4;
   const ALIEN_ABSTAND_X         = 46;
   const ALIEN_ABSTAND_Y         = 40;
   const ALIEN_START_X           = 28;
   const ALIEN_START_Y           = 60;
   const ALIEN_BOMBE_CHANCE      = 0.0008;
   const ALIEN_GESCHWINDIGKEIT_X = 0.8;
   const ALIEN_ABSTIEG           = 16;
   const BOMBE_GESCHWINDIGKEIT   = 3;
   const PUNKTE_PRO_ALIEN        = 10;
   const HUD_RAND_X       = 12;
   const HUD_ZEILE_Y      = 24;
   const HUD_TRENNLINIE_Y = 34;
   const BLOCK_BREITE  = 48;
   const BLOCK_HOEHE   = 20;
   const BLOCK_ANZAHL  = 3;
   const BLOCK_Y       = HOEHE - 110;
   const BLOCK_TREFFER = 3;
   const BLOCK_FARBEN = { 3: '#44cc66', 2: '#ccaa22', 1: '#cc4422' };
   const SCHRIFT_HUD   = '16px "Courier New", Courier, monospace';
   const SCHRIFT_KLEIN = '15px "Courier New", Courier, monospace';
   const SCHRIFT_TITEL = 'bold 32px "Courier New", Courier, monospace';
   const SCHRIFT_START = 'bold 36px "Courier New", Courier, monospace';
   const SCHRIFT_EMOJI = '80px monospace';


   // 3. SPIELZUSTAND – veränderliche Daten
   // ===============================================
   let zustand = erstelleNeuesSpiel();
   let rafId   = null;
   function erstelleNeuesSpiel() {
     return {
       aktiv:          false,
       spielVorbei:    false,
       gewonnen:       false,
       punkte:         0,
       leben:          3,
       spieler: {
         x:             BREITE / 2 - SPIELER_BREITE / 2,
         y:             HOEHE - 50,
         breite:        SPIELER_BREITE,
         hoehe:         SPIELER_HOEHE,
         links:         false,
         rechts:        false,
         letzterSchuss: 0,
       },
       laser:          [],
       bomben:         [],
       aliens:         [],
       bloecke:        (() => {
         const bloecke = [];
         const abstand = BREITE / (BLOCK_ANZAHL + 1);
         for (let i = 0; i < BLOCK_ANZAHL; i++) {
           bloecke.push({
             x:       Math.round(abstand * (i + 1) - BLOCK_BREITE / 2),
             y:       BLOCK_Y,
             breite:  BLOCK_BREITE,
             hoehe:   BLOCK_HOEHE,
             treffer: BLOCK_TREFFER,
           });
         }
         return bloecke;
       })(),
       alienRichtung:  1,
       sterne:         erzeugeSterne(80),
     };
   }


   // 4. ALIEN-FORMATION aufbauen
   // ===============================================
   function initAliens() {
     const reihenFarben = ['#ff6b6b', '#ffaa55', '#ffee55', '#55ffaa'];
     zustand.aliens = [];
     for (let reihe = 0; reihe < ALIEN_REIHEN; reihe++) {
       for (let spalte = 0; spalte < ALIEN_SPALTEN; spalte++) {
         zustand.aliens.push({
           x:      ALIEN_START_X + spalte * ALIEN_ABSTAND_X,
           y:      ALIEN_START_Y + reihe  * ALIEN_ABSTAND_Y,
           breite: ALIEN_BREITE,
           hoehe:  ALIEN_HOEHE,
           lebt:   true,
           farbe:  reihenFarben[reihe],
         });
       }
     }
   }


   // 5. HILFSFUNKTIONEN
   // ===============================================
   function erzeugeSterne(anzahl) {
     return Array.from({ length: anzahl }, () => ({
       x:          Math.random() * BREITE,
       y:          Math.random() * HOEHE,
       helligkeit: 0.3 + Math.random() * 0.7,
     }));
   }
   function kollidieren(a, b) {
     return (
       a.x            < b.x + b.breite &&
       a.x + a.breite > b.x            &&
       a.y            < b.y + b.hoehe  &&
       a.y + a.hoehe  > b.y
     );
   }
   const jetztMs = () => performance.now();


   // 6. EINGABE – Tastaturereignisse
   // ===============================================
   document.addEventListener('keydown', (ereignis) => {
     switch (ereignis.code) {
       case 'ArrowLeft':
       case 'KeyA':
         zustand.spieler.links = true; break;
       case 'ArrowRight':
       case 'KeyD':
         zustand.spieler.rechts = true; break;
       case 'Space':
         ereignis.preventDefault();
         if (!zustand.aktiv && !zustand.spielVorbei && !zustand.gewonnen) spielStarten();
         else if (zustand.aktiv) schiessen();
         break;
       case 'Enter':
         if (zustand.spielVorbei || zustand.gewonnen) spielNeustart();
         else if (!zustand.aktiv) spielStarten();
         break;
     }
   });
   document.addEventListener('keyup', (ereignis) => {
     if (ereignis.code === 'ArrowLeft'  || ereignis.code === 'KeyA') zustand.spieler.links  = false;
     if (ereignis.code === 'ArrowRight' || ereignis.code === 'KeyD') zustand.spieler.rechts = false;
   });


   // 7. SPIELAKTIONEN
   // ===============================================
   function spielStarten() {
     if (rafId !== null) return;
     initAliens();
     zustand.aktiv = true;
     rafId = requestAnimationFrame(spielSchleife);
   }
   function spielNeustart() {
     if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }
     zustand = erstelleNeuesSpiel();
     startbildschirmZeichnen();
   }
   function schiessen() {
     const jetzt = jetztMs();
     const sp    = zustand.spieler;
     if (jetzt - sp.letzterSchuss >= LASER_COOLDOWN_MS) {
       zustand.laser.push({
         x:      sp.x + sp.breite / 2 - LASER_BREITE / 2,
         y:      sp.y,
         breite: LASER_BREITE,
         hoehe:  LASER_HOEHE,
       });
       sp.letzterSchuss = jetzt;
     }
   }


   // 8. SPIELSCHLEIFE – Herzstück
   // ===============================================
   function spielSchleife() {
     if (!zustand.aktiv) { rafId = null; return; }
     aktualisieren();
     zeichnen();
     rafId = requestAnimationFrame(spielSchleife);
   }


   // 9. UPDATE – gesamte Spiellogik pro Frame
   // ===============================================
   function aktualisieren() {
     const sp = zustand.spieler;
     if (sp.links  && sp.x > 0)                  sp.x -= SPIELER_GESCHWINDIGKEIT;
     if (sp.rechts && sp.x < BREITE - sp.breite) sp.x += SPIELER_GESCHWINDIGKEIT;
     for (let i = zustand.laser.length - 1; i >= 0; i--) {
       zustand.laser[i].y -= LASER_GESCHWINDIGKEIT;
       if (zustand.laser[i].y + LASER_HOEHE < 0) zustand.laser.splice(i, 1);
     }
     aliensBewegen();
     alienBombenWerfen();
     bombenBewegen();
     kollisionenPruefen();
     if (zustand.aliens.every(a => !a.lebt)) {
       zustand.aktiv    = false;
       zustand.gewonnen = true;
     }
   }
   function aliensBewegen() {
     const lebendeAliens = zustand.aliens.filter(a => a.lebt);
     if (lebendeAliens.length === 0) return;
     const tempo = ALIEN_GESCHWINDIGKEIT_X *
       (1 + (1 - lebendeAliens.length / (ALIEN_REIHEN * ALIEN_SPALTEN)));
     let linksRand  = Infinity;
     let rechtsRand = -Infinity;
     for (const a of lebendeAliens) {
       if (a.x < linksRand)              linksRand  = a.x;
       if (a.x + a.breite > rechtsRand)  rechtsRand = a.x + a.breite;
     }
     let richtungsWechsel = false;
     if (rechtsRand >= BREITE && zustand.alienRichtung > 0) richtungsWechsel = true;
     if (linksRand  <= 0     && zustand.alienRichtung < 0) richtungsWechsel = true;
     if (richtungsWechsel) {
       zustand.alienRichtung *= -1;
       zustand.aliens.forEach(a => { if (a.lebt) a.y += ALIEN_ABSTIEG; });
     } else {
       zustand.aliens.forEach(a => { if (a.lebt) a.x += tempo * zustand.alienRichtung; });
     }
     zustand.aliens.forEach(a => {
       if (a.lebt && a.y + a.hoehe >= zustand.spieler.y) {
         zustand.aktiv       = false;
         zustand.spielVorbei = true;
       }
     });
   }
   function alienBombenWerfen() {
     const untersteAliens = [];
     for (let spalte = 0; spalte < ALIEN_SPALTEN; spalte++) {
       const dieseSpalte = zustand.aliens.filter(
         a => a.lebt && Math.floor((a.x - ALIEN_START_X) / ALIEN_ABSTAND_X + 0.5) === spalte
       );
       if (dieseSpalte.length > 0) {
         untersteAliens.push(dieseSpalte.reduce((max, a) => a.y > max.y ? a : max));
       }
     }
     untersteAliens.forEach(alien => {
       if (Math.random() < ALIEN_BOMBE_CHANCE) {
         zustand.bomben.push({
           x:      alien.x + alien.breite / 2 - 2,
           y:      alien.y + alien.hoehe,
           breite: 4,
           hoehe:  10,
         });
       }
     });
   }
   function bombenBewegen() {
     for (let i = zustand.bomben.length - 1; i >= 0; i--) {
       zustand.bomben[i].y += BOMBE_GESCHWINDIGKEIT;
       if (zustand.bomben[i].y > HOEHE) zustand.bomben.splice(i, 1);
     }
   }
   // Prüft alle relevanten Kollisionen:
   function kollisionenPruefen() {
     const sp = zustand.spieler;
     // 1. Laser ↔ Alien
     for (let li = zustand.laser.length - 1; li >= 0; li--) {
       for (let ai = 0; ai < zustand.aliens.length; ai++) {
         const alien = zustand.aliens[ai];
         if (!alien.lebt) continue;
         if (kollidieren(zustand.laser[li], alien)) {
           alien.lebt = false;
           zustand.laser.splice(li, 1);
           zustand.punkte += PUNKTE_PRO_ALIEN;
           break;
         }
       }
     }
     // 2. Laser ↔ Block
     for (let li = zustand.laser.length - 1; li >= 0; li--) {
       for (const block of zustand.bloecke) {
         if (block.treffer <= 0) continue;
         if (kollidieren(zustand.laser[li], block)) {
           block.treffer--;
           zustand.laser.splice(li, 1);
           break;
         }
       }
     }
     // 3. Bombe ↔ Block
     for (let bi = zustand.bomben.length - 1; bi >= 0; bi--) {
       for (const block of zustand.bloecke) {
         if (block.treffer <= 0) continue;
         if (kollidieren(zustand.bomben[bi], block)) {
           block.treffer--;
           zustand.bomben.splice(bi, 1);
           break;
         }
       }
     }
     // 4. Bombe ↔ Spieler
     for (let bi = zustand.bomben.length - 1; bi >= 0; bi--) {
       if (kollidieren(zustand.bomben[bi], sp)) {
         zustand.bomben.splice(bi, 1);
         zustand.leben--;
         if (zustand.leben <= 0) {
           zustand.aktiv       = false;
           zustand.spielVorbei = true;
         }
       }
     }
   }


   // 10. ZEICHENFUNKTIONEN
   // ===============================================
   function zeichnen() {
     ctx.clearRect(0, 0, BREITE, HOEHE);
     sterneFeldZeichnen();
     bloeckeZeichnen();
     spielerZeichnen();
     lasersZeichnen();
     aliensZeichnen();
     bombenZeichnen();
     hudZeichnen();
     if      (zustand.spielVorbei) spielEndeOverlay(false);
     else if (zustand.gewonnen)    spielEndeOverlay(true);
   }
   function sterneFeldZeichnen() {
     zustand.sterne.forEach(stern => {
       ctx.globalAlpha = stern.helligkeit;
       ctx.fillStyle   = '#ffffff';
       ctx.fillRect(stern.x, stern.y, 1.5, 1.5);
     });
     ctx.globalAlpha = 1;
   }
   function bloeckeZeichnen() {
     zustand.bloecke.forEach(block => {
       if (block.treffer <= 0) return;
       ctx.fillStyle = BLOCK_FARBEN[block.treffer];
       ctx.fillRect(block.x, block.y, block.breite, block.hoehe);
       if (block.treffer < BLOCK_TREFFER) {
         ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
         ctx.lineWidth   = 2;
         const risse = BLOCK_TREFFER - block.treffer;
         for (let r = 0; r < risse; r++) {
           const rissY = block.y + Math.round(block.hoehe * (r + 1) / (risse + 1));
           ctx.beginPath();
           ctx.moveTo(block.x,                rissY);
           ctx.lineTo(block.x + block.breite, rissY);
           ctx.stroke();
         }
       }
     });
   }
   function spielerZeichnen() {
     const sp = zustand.spieler;
     ctx.fillStyle = '#4af';
     ctx.fillRect(sp.x + 12, sp.y +  4, 12, 14);
     ctx.fillStyle = '#9ef';
     ctx.fillRect(sp.x + 15, sp.y,       6,  8);
     ctx.fillStyle = '#4af';
     ctx.fillRect(sp.x,      sp.y + 10, 14,  8);
     ctx.fillRect(sp.x + 22, sp.y + 10, 14,  8);
     ctx.fillStyle = '#06f';
     ctx.fillRect(sp.x + 14, sp.y + 18,  8,  4);
   }
   function lasersZeichnen() {
     zustand.laser.forEach(l => {
       ctx.fillStyle   = '#aaf';
       ctx.globalAlpha = 0.3;
       ctx.fillRect(l.x - 2, l.y, LASER_BREITE + 4, LASER_HOEHE);
       ctx.globalAlpha = 1;
       ctx.fillStyle   = '#fff';
       ctx.fillRect(l.x, l.y, LASER_BREITE, LASER_HOEHE);
     });
   }
   function aliensZeichnen() {
     zustand.aliens.forEach(alien => {
       if (!alien.lebt) return;
       const x = alien.x;
       const y = alien.y;
       ctx.fillStyle = alien.farbe;
       ctx.fillRect(x +  6, y,      18, 4);
       ctx.fillRect(x +  2, y +  4, 26, 8);
       ctx.fillRect(x +  6, y + 12, 18, 6);
       ctx.fillStyle = '#fff';
       ctx.fillRect(x +  8, y + 5,  4, 4);
       ctx.fillRect(x + 18, y + 5,  4, 4);
       ctx.fillStyle = alien.farbe;
       ctx.fillRect(x +  4, y - 4, 4, 5);
       ctx.fillRect(x + 22, y - 4, 4, 5);
       ctx.fillRect(x +  4, y + 18, 5, 4);
       ctx.fillRect(x + 21, y + 18, 5, 4);
     });
   }
   function bombenZeichnen() {
     zustand.bomben.forEach(b => {
       ctx.fillStyle   = '#f55';
       ctx.fillRect(b.x, b.y, b.breite, b.hoehe);
       ctx.fillStyle   = '#f884';
       ctx.globalAlpha = 0.4;
       ctx.fillRect(b.x - 2, b.y - 2, b.breite + 4, b.hoehe + 4);
       ctx.globalAlpha = 1;
     });
   }
   function hudZeichnen() {
     ctx.font      = SCHRIFT_HUD;
     ctx.fillStyle = '#ffffff';
     ctx.fillText(`PUNKTE: ${zustand.punkte}`, HUD_RAND_X, HUD_ZEILE_Y);
     ctx.fillStyle = '#ff7777';
     ctx.textAlign = 'right';
     ctx.fillText(`LEBEN: ${'♥ '.repeat(zustand.leben).trim()}`, BREITE - HUD_RAND_X, HUD_ZEILE_Y);
     ctx.textAlign = 'left';
     ctx.strokeStyle = '#445';
     ctx.lineWidth   = 1;
     ctx.beginPath();
     ctx.moveTo(0,      HUD_TRENNLINIE_Y);
     ctx.lineTo(BREITE, HUD_TRENNLINIE_Y);
     ctx.stroke();
   }
   function spielEndeOverlay(gewonnen) {
     const mitteX = BREITE / 2;
     const mitteY = HOEHE  / 2;
     ctx.fillStyle = 'rgba(0, 0, 20, 0.80)';
     ctx.fillRect(0, 0, BREITE, HOEHE);
     ctx.textAlign = 'center';
     ctx.font      = SCHRIFT_TITEL;
     ctx.fillStyle = gewonnen ? '#66ffaa' : '#ff6666';
     ctx.fillText(gewonnen ? '⚡ gewonnen!' : '✕ game over', mitteX, mitteY - 30);
     ctx.font      = SCHRIFT_HUD;
     ctx.fillStyle = '#ffffff';
     ctx.fillText(`Punkte: ${zustand.punkte}`, mitteX, mitteY + 12);
     ctx.font      = SCHRIFT_KLEIN;
     ctx.fillStyle = '#aabbcc';
     ctx.fillText('[ Enter ] nochmal spielen', mitteX, mitteY + 46);
     ctx.textAlign = 'left';
   }
   function startbildschirmZeichnen() {
     ctx.clearRect(0, 0, BREITE, HOEHE);
     sterneFeldZeichnen();
     const mitteX = BREITE / 2;
     const mitteY = HOEHE  / 2;
     ctx.textAlign = 'center';
     ctx.font      = SCHRIFT_START;
     ctx.fillStyle = '#7fdfff';
     ctx.fillText('mini space invaders', mitteX, mitteY - 80);
     ctx.font = SCHRIFT_EMOJI;
     ctx.fillText('👾', mitteX, mitteY + 10);
     ctx.font      = SCHRIFT_HUD;
     ctx.fillStyle = '#ccddef';
     ctx.fillText('← → bewegen  |  Leertaste schießen', mitteX, mitteY + 60);
     ctx.font      = SCHRIFT_KLEIN;
     ctx.fillStyle = '#66ffaa';
     ctx.fillText('[ Enter oder Leertaste ] starten', mitteX, mitteY + 90);
     ctx.textAlign = 'left';
   }


   // 11. SPIELSTART
   // ===============================================
   startbildschirmZeichnen();
 });
 </script>

</body> </html>