pad.software
Kontakt

Blog · Spiele

Block Invaders: Blockgrafik-Shooter wie auf dem PET

Der Blockgrafik-Shooter vom Commodore PET als Browser-Nachbau, dazu die Erinnerung an die Computerabteilung im Kaufhof am Stachus.

5 Min. LesezeitAuf LinkedIn teilen

  • typescript
  • canvas
  • svelte
  • spiel

Ende der Siebziger, Anfang der Achtziger stand in der Computerabteilung im Kaufhof am Stachus ein Commodore PET. Die Abteilung war der Treffpunkt für alle, die einen Computer wollten und keinen hatten: Man stand herum, tippte BASIC-Zeilen ab und schaute den anderen über die Schulter. Auf dem PET lief ein Spiel, das ich dort zum ersten Mal gesehen habe. Reihen grüner Blöcke schieben sich seitwärts über den Bildschirm, rucken an der Wand eine Zeile tiefer, unten wehrt sich eine Kanone. Das Bemerkenswerte daran: Der PET konnte gar keine Pixel zeichnen, nur Zeichen. Trotzdem glitt der Block weich. Wie das geht, ist der Inhalt dieses Beitrags. Vorher das Spiel:

BLOCK INVADERS — 40×25 ZEICHEN

WELLE 1

Der Gegnerblock wandert pixelweise — nur die Randzellen ändern die Füllstufe.

Pfeiltasten bewegen die Kanone, die Leertaste feuert; dafür vorher das Spielfeld anklicken. Auf dem Telefon übernehmen die Tasten unter dem Feld. Alles läuft lokal im Browser, einen Server gibt es nicht.

Ein Bildschirm ohne Pixel

Der PET hatte keinen Bitmap-Modus. Sein Bildschirm war ein Raster aus 40 × 25 Zeichen zu je 8 × 8 Pixeln, und das einzige, was ein Programm tun konnte, war Zeichencodes in den Bildschirmspeicher schreiben: 1000 Bytes ab Adresse $8000, ein Byte pro Zelle. Weiche horizontale Bewegung dürfte es unter diesen Bedingungen nicht geben.

Der Trick des Originals waren Balkenzeichen: Glyphen mit 1 bis 8 gefüllten Spalten, einmal von links und einmal von rechts gefüllt. Ein Rechteck, das um ein Pixel wandert, ändert damit nur die Füllstufe seiner beiden Randzellen. Alle acht Schritte kippt es eine ganze Zelle weiter.

Der Nachbau übernimmt nicht nur den Trick, sondern die Beschränkung gleich mit. Gerendert wird über einen Framebuffer aus Zeichencodes, ein Uint8Array mit 1000 Einträgen, und die einzige Zeichenoperation für Bewegtes ist ein horizontaler Balkenlauf:

// Horizontaler Pixel-Lauf [x1, x2) in Zeichenzeile `zeile`, aufgelöst in
// Teilbalken: Randzellen tragen die Füllstufe, dazwischen Vollblöcke.
balken(zeile: number, x1: number, x2: number): void {
  if (x2 <= x1) return;
  const erste = Math.floor(x1 / ZELLE);
  const letzte = Math.floor((x2 - 1) / ZELLE);
  if (erste === letzte) {
    // Lauf innerhalb einer Zelle — kommt nur vor, wenn er einen Zellenrand
    // berührt: Nichts Bewegliches ist schmaler als 8 px. Genau wie auf dem
    // PET, dessen Zeichensatz freie Rechtecke im Zellinneren nicht hergab.
    if (x1 % ZELLE === 0) this.set(erste, zeile, linksGefuellt(x2 - erste * ZELLE));
    else this.set(erste, zeile, rechtsGefuellt(ZELLE - (x1 % ZELLE)));
    return;
  }
  this.set(erste, zeile, rechtsGefuellt(ZELLE - (((x1 % ZELLE) + ZELLE) % ZELLE)));
  for (let z = erste + 1; z < letzte; z++) this.set(z, zeile, VOLL);
  this.set(letzte, zeile, linksGefuellt(x2 - letzte * ZELLE));
}

Mehr Freiheit hat die Spiellogik nicht, und genau das ist der Punkt. Die ruckartige Vertikale, bei der der Gegnerblock eine ganze Zeile auf einmal fällt, muss nicht simuliert werden. Sie folgt aus der Architektur.

61 Zeichen, alle selbst gezeichnet

Der Zeichensatz stammt nicht aus dem Commodore-ROM (das wäre rechtlich heikel), sondern ist als Bitmaps im Code definiert: acht Bytes pro Glyphe, ein Byte pro Pixelzeile. Aus fünf Familien setzt sich alles zusammen:

  • Teilbalken von links und von rechts, für Gegner und Kanonenbasis.
  • Striche: die Kanone des Originals trug einen ein Pixel breiten Strich nach oben, und es gab acht Zeichen mit dem Strich in je einer anderen Spalte. So gleitet der Turm pixelweise mit, und der Schuss ist dasselbe Zeichen in Bewegung.
  • Viertelblöcke für die Bunker. Treffer tragen sie quadrantenweise ab, Schüsse von unten, Bomben von oben. Eine Maske mit vier Bits pro Zelle, fünfzehn Glyphen.
  • Ein 5×7-Font für die oberste Zeile: links SCORE, rechts MEN, wie im Original. Zehn Ziffern und genau die sieben Buchstaben, die diese zwei Wörter brauchen. Das Bonusobjekt läuft über genau diese Zeile hinweg und frisst die Buchstaben im Vorbeiflug: Zeichen-RAM kennt keine Ebenen, eine Zelle gehört dem, der zuletzt schreibt.
  • Explosionen: kleines x, großes X, Funkenstern. Ein Treffer stellt für einen Wimpernschlag x X x an die Einschlagstelle, flackernd. Mehr Pyrotechnik gibt ein Zeichensatz nicht her, und mehr braucht es nicht.

Die Balken- und Strichfamilien sind je eine Schleife:

for (let n = 1; n <= 8; n++) {
  definiere(n, Array(8).fill((0xff << (8 - n)) & 0xff));
}
for (let spalte = 0; spalte < 8; spalte++) {
  definiere(STRICH_BASIS + spalte, Array(8).fill(0x80 >> spalte));
}

Was auffällt: Es gibt keine Zeichen für ein Rechteck, das frei im Inneren einer Zelle schwebt. Auf dem PET gab es die auch nicht, und das setzt der Spielwelt eine harte Untergrenze. Die Gegner sind nach Reihen gestaffelt, unten 11 Pixel breit, jede Reihe darüber einen Pixel schmaler, und bei 8 Pixeln ist Schluss: Ein schmaleres Rechteck könnte komplett zwischen zwei Zellrändern liegen, und dafür fehlt schlicht die Glyphe. Die Mindestbreite ist keine Design-Entscheidung, sondern eine Eigenschaft des Zeichensatzes.

Die Gegner selbst sind schlichte gefüllte Rechtecke, wie in der PET-Fassung. Auch das hat einen rechtlichen Grund: Die bekannten Alien-Silhouetten sind tabu, und der Name des Automaten-Vorbilds fällt in diesem Beitrag deshalb nicht.

Timing wie 1979

Die Schleife läuft mit festen 60 Schritten pro Sekunde, per requestAnimationFrame und Akkumulator. Kein Delta-Time-Smoothing, keine Interpolation: Die Gegner rücken alle N Frames einen Pixel weiter, und N sinkt mit der Zahl der Überlebenden. Der letzte Gegner rast, weil die Maschine pro Takt weniger zu tun hat. Beim Original war das ein Nebeneffekt der Rechenzeit; hier ist es eine nachgebaute Tabelle, aber dieselbe Kurve. Kollisionen rechnen trotzdem auf Pixelebene, nicht auf Zellebene. Sonst fühlen sich Treffer falsch an.

Vom Spiel zum Messbalken

Das Spiel am Stachus hat bei mir keinen Highscore-Ehrgeiz ausgelöst, sondern eine Idee. Dieselben Teilbalken, die die Gegner weich über den Bildschirm schoben, taugten für waagrechte Balkendiagramme: Messwerte, die sich in Echtzeit bewegen, auf einer Maschine ohne Grafikmodus. BASIC war dafür viel zu langsam. Schnell genug war nur 6502-Maschinensprache, die direkt in den Bildschirmspeicher ab $8000 schrieb.

Flackerfrei wurde es erst, wenn man synchron zum Strahlrücklauf schrieb. In meiner Erinnerung war dafür der NMI zuständig; die PET-Unterlagen sagen, das Rücklauf-Signal hing am PIA und feuerte den 60-Hz-IRQ, und wer außerhalb des Rücklaufs in den Bildschirmspeicher schrieb, bekam Schnee ins Bild. Die Erinnerung an das Flackern stimmt also, die an den Interrupt-Pin nicht. Fünfundvierzig Jahre sind ein langes Cache-Fenster.

Geblieben ist der Kern: Ein Spielemechanismus, der eine Visualisierungstechnik war. Wer heute in diesem Nachbau den Gegnerblock gleiten sieht, sieht exakt die Technik, mit der damals meine Messbalken bei Prüfstandssteuerungen flüssig wurden.

Wer das Original sehen will: in diesem Video läuft es ab Minute 1:50 auf einem echten PET 2001. Das Video darf den Namen sagen.

Lieber direkt fragen?

Antwort innerhalb eines Werktages.