Commodore blog

In der Commodore Plus/4 Ungarn Facebook-Gruppe habe ich bereits kurz erwähnt, dass ich an einem Port von The Pit für den Plus/4 zu arbeiten begonnen habe.

Als Einleitung zitiere ich das dort Geschriebene:

Wofür brauche ich die Töne, die ich im früheren Beitrag erwähnt habe? Für einen meiner großen Favoriten, der meine erste Begegnung mit Videospielen war, wenn man den Videoton Fernseh-Tennis nicht mitzählt.

Mitte der achtziger Jahre gab es im Offiziersklub der Militärsiedlung (!!!) aus irgendeinem Grund einen Münz-Arcade-Automaten, auf dem The Pit lief. Wir warfen unzählige Kossuth-Fünf-Fillér-Stücke hinein, bis wir herausfanden, dass der Schlüssel zum "Saal", den man extra anfordern musste, wenn man spielen wollte, auch die Kasse des Automaten öffnete.

Natürlich war es größtenteils nur so lange interessant, bis man keinen eigenen Computer zu Hause hatte, denn das sich ständig wiederholende, wenn auch mit der Zeit schneller werdende Spielgeschehen war bei weitem nicht so spannend wie das Warten vor dem Kassettenrekorder auf das Laden des aktuellen Spiels.

Jedenfalls hat es bei mir einen tiefen Eindruck hinterlassen, und ich erinnerte mich wieder daran, als ich vor kurzem entdeckte, dass Doug Turner, der Schöpfer von Icicle Works (und anderen großartigen Bergbau-Spielen) vor etwa fünf Jahren mit einem neuen Release herauskam: mit The Pit.


Was übrigens ein ganz anderes Spiel ist, mit ganz anderem technischem Know-how und Niveau, aber dennoch gibt es eine direkte Parallele. Dougs Inspiration, sein eigenes Icicle Works, ist eine frei interpretierte Boulder Dash-Klon in Weihnachts-Winter-Thematik. Die Inspiration für Boulder Dash war laut Aussage des Schöpfers Peter Liepa ein BASIC-Programm von Chris Gray, das für die heutige Zeit nicht erhalten geblieben ist. Chris' Programm war jedoch auch keine originelle Idee – es entstand in einer Spielhalle, während er an einem Arcade-Automaten spielte. Dieser Automat war The Pit.

Versteht ihr die Analogie? Wir sehen das Untergrundabenteuer im Querschnitt wie in einem Ameisenbau, Steine fallen auf unseren Kopf, und so weiter, und so fort...

Was für ein schöner Kreis, nicht wahr?

So hat mich The Pit dazu angeregt, den Plus/4-Port von The Pit zu erstellen. Keine wirklich menschen- und technikherausfordernde Aufgabe, aber angesichts der Zeit, die ich für so etwas habe, und wie sehr ich mich wieder ins Codieren einarbeiten muss, ist es für mich die perfekte Aufgabe.

Ich habe auch drei Videos angehängt, falls jemand das Spiel nicht kennt. Das erste ist die originale Arcade-Version, das zweite der C64-Port, und das dritte der zukünftige Plus/4-Port. Letzteres ist nur ein Vorgeschmack, denn die Fortsetzung ist noch sehr holprig.

 

 

 

 

Es veranschaulicht jedoch gut, dass das Einfügen von Digitaltönen eine starke Übertreibung wäre (es sei denn, wir digitalisieren eine Rechteckwelle, aber wozu, nicht wahr), und auch Soundeffekte brauchen nicht zu viele, deshalb zerbreche ich mir noch den Kopf darüber (im kurzen Ausschnitt spielt TEDZakker).

Noch ein Grund, warum ich nach einer effizienten Lösung suche: Ich möchte bei 16K bleiben. Die originale C64-Version war auf Cartridge, die geknackte Version läuft auch an der Stelle der Cartridge, passt bequem in 16K, mit viel freiem Platz.

Oh, und wie wird der Titel lauten? Natürlich The Pit, denn das ist auch der Originaltitel, aber ich werde mir etwas einfallen lassen, um es zu unterscheiden. Zum Beispiel The Pit Arcade oder ähnliches.


Obwohl ich relativ wenig Zeit dafür habe (wie es bei Erwachsenen üblich ist), schreitet das Projekt dennoch gut voran und wird voraussichtlich in absehbarer Zeit fertig sein.

Der Grund, warum daraus ein Blogbeitrag geworden ist, ist, dass aufgrund der relativen Einfachheit relativ viele spezielle, zu lösende Probleme aufgetaucht sind, auch wenn ich einen Teil davon selbst verursacht habe.

Erstens: Das originale C64-Programm (zumindest das, was zu mir gekommen ist) wurde von einer Cartridge geknackt und passt in 16K, wobei noch reichlich Platz übrig ist. Daher wurde eines der Ziele, dass die Plus/4-Version auch in 16K passt und sogar auf dem C16 läuft (die beiden sind nicht dasselbe!).

Zweitens: Im Original gibt es eine Menge Sprites (vom Hintergrund unabhängige, hardwarebasiert laufende Elemente wie der Spieler, die Feinde usw...), diese können auf dem Plus/4 nur softwaremäßig gelöst werden, was sehr ressourcenintensiv ist. Das charakteristische Merkmal des Spiels ist jedoch, dass die meisten davon klein sind (1x1 Zeichen) und relativ selten auf den Hintergrund oder aufeinander treffen, so dass das Sprite-Masking (das "Kämmen" von Sprite und Hintergrund) fast vollständig weggelassen werden kann.

Daher stand von Anfang an fest, dass die Sprites wie folgt funktionieren werden:

1. Spieler: 1x1 Zeichen Figur, die theoretisch auf einer 2x2 Zeichen großen Sprite-Maske erscheinen könnte. Wie könnten wir das vereinfachen?
- Die pixelweise Bewegung bleibt, aber anhalten kann er nur innerhalb der Zeichengrenzen. Das erleichtert das Spiel ein wenig, im Original war es nämlich besonders fies, die Pixelgrenzen zu treffen.
- Wenn der Spieler sich im Sand bewegt, gräbt er sich immer ein zeichengroßes Gebiet vor sich frei – daher bewegt er sich immer auf leerem, bereits ausgegrabenem Boden. Daher trifft er nicht auf den Hintergrund, es muss nicht maskiert werden.
- Wenn er einen Schatz aufhebt, ist wieder keine Maskierung nötig – denn er hebt ihn auf, womit auch hier nur leerer Platz zurückbleibt.
- Wenn er mit einem Feind kollidiert, muss nicht maskiert werden, denn TOD! TOD! TOD!, was im Original-Spiel eine seltsame, verhedderte Animation hat. Dies kann aus festen Phasen erzeugt werden, und zwar innerhalb der Zeichengrenzen.
- Mit dem Projektil muss er nicht maskieren, denn er schießt es vor sich, und es entfernt sich von ihm.
- Wenn der Felsen herunterfällt, ist die Situation dieselbe wie mit dem Feind, es gibt nichts Extra zu tun.
- Unser Spieler bewegt sich außerdem nicht diagonal, sondern nur vertikal ODER horizontal, gleichzeitig beides ist nicht möglich.

Aufgrund des Obigen vereinfacht sich die Bewegung und Darstellung des Spielers erheblich. Praktisch muss er sich vor leerem Raum bewegen, er könnte sogar zeichenweise erscheinen, aber dann wäre seine Bewegung sehr ruckelig. Für die pixelweise Bewegung entstand folgende Lösung:
- Wenn der Spieler innerhalb der Zeichengrenzen ist, setzen wir ihn einfach als ein Zeichen aus, in das wir die aktuelle Animationsphase kopieren.
- Wenn er sich seitwärts bewegt, erhält er von der aktuellen Position aus LINKS nein, RECHTS ein zusätzliches Zeichen, in diesen beiden schieben wir ihn seitwärts um 1-7 Pixel, der 8. erfolgt bereits ein Zeichen weiter.
- Bei Auf-Ab-Bewegung geschieht dasselbe, aber von der aktuellen Position aus eine Zeile tiefer erscheint das zweite Zeichen.
- Den Hintergrund überschreiben wir damit nicht, denn vor der Bewegung prüfen wir, was in den vier möglichen Richtungen zu finden ist, und lassen nur weiter, wenn leerer Platz da ist. Wenn in einer Richtung Sand das Hindernis ist, aber der Spieler sich bewegen würde, graben wir einmal – danach wird auch dort leerer Platz sein, er kann weitergehen.
- Da das Spielfeld außer dem Eingang von unpassierbaren Hindernissen umgeben ist, ist aufgrund des Vorherigen keine Überprüfung der minimalen und maximalen begehbaren Position nötig, denn die Hindernisse halten den Spieler auf, bevor er den begehbaren Bereich verlässt. Der Eingang (und der Ausgang neben dem Panzer) wird übrigens von $00-Zeichen umgeben, die genauso leer sind wie die begehbaren $20-Zeichen, blockieren aber die Weiterbewegung des Spielers.

Ein besonderer Bonus ist die Gestaltung des Spielfelds, weshalb die rechte Seite des Bildschirms für den Spieler nicht begehbar ist. Eine kleine Erklärung dazu:

Der Bildschirm des Commodore Plus/4 ist 25 Zeilen mal 40 Zeichen groß, in Pixeln 320x200. Die vertikale Position (0-199, oder hexadezimal $00-$C7) kann in einem Byte beschrieben werden, die horizontale jedoch nicht, ihr Wert (wenn der gesamte Bildschirm begehbar wäre) könnte $0000-$013F sein, was bereits nur in zwei Bytes gespeichert werden kann. Selbst wenn die Sprite-Position sowieso nicht $013F / $C7 sein kann, denn dann wäre nur noch ein Pixel in der unteren linken Ecke sichtbar.

Nun, bei The Pit gibt es dieses Problem nicht, unter Verwendung des originalen Levels kann der Spieler (und somit natürlich auch die Feinde), einschließlich seiner Größe, maximal bis zur Position $D0 / $B8 gehen. Das kann bereits in einem Byte gespeichert werden. Dieser Unterschied scheint nicht signifikant zu sein, aber so sparen wir mit dem kürzeren Programm auch Speicher und Prozessorzeit durch das unnötige Rechnen mit +1 Byte.

2. Feinde: Fast alles, was für den Spieler gilt, trifft auch auf sie zu. Plus Bonus, dass sie sich sehr selten treffen, damit muss man sich nicht näher befassen. Wenn sie aufeinander treffen, überschreiben sie sich einfach, was im Spiel-Eifer nicht auffallen und somit nicht störend sein wird.

3. Projektile: Es gibt zwei Arten von Geschossen, die des Spielers und die des Panzers. Letztere werden einfache zeichenbasierte Darstellungen sein, brauchen nicht mehr. Die des Spielers werden auch nicht komplizierter, denn sie bewegen sich vor leerem Hintergrund, bei Kollision mit einem Hindernis verschwinden sie (= verschwinden einfach), bei Kollision mit einem Feind verschwinden sie zusammen mit diesem.

4. Bomben im unteren Höhlenbereich: Ihre Darstellung ähnelt der der Geschosse, außerdem bewegen sie sich nur in eine Richtung, sind also noch einfacher zu handhaben.

5. Monster im Säurebecken, Panzer, UFO: haben keine wirkliche Rolle im Spiel. Alle drei können mit einfacher zeichenbasierter Geschichte auf minimaler Fläche sich bewegend dargestellt werden. Einzig der Panzer bekommt eine halb phasenverschobene zeichenbasierte Animation aufgrund der relativ langen, geraden zurückgelegten Strecke.

Um in 16K zu passen, musste ich keine allzu großen Anstrengungen unternehmen, da das Spiel selbst nicht allzu kompliziert ist. 

  • Statt des gesamten verfügbaren Zeichensatzes verwende ich nur die Hälfte, das reicht völlig für die Bildschirmdarstellung.

 

Der Zeichensatz von The Pit
Der Zeichensatz von The Pit im aktuellen Stand. Dies ist nicht der endgültige, die Animationsphasen werden noch entfernt.

(Für die Grafik habe ich im Grunde die C-64-Version als Basis genommen, aber einige Details aus der originalen Arcade-Version übernommen)

  • Die Töne sind auch in der originalen Arcade-Version nicht allzu kompliziert, können also recht einfach und sparsam erzeugt werden. Dabei war nach einigem Suchen Epy meine Hilfe. Sein einstimmiger Effekt-Player ist etwa 256 Byte, dazu kommen noch die Definitionen der Effekte, die ähnlich viel Platz benötigen werden, so dass der Sound nur etwa ein halbes Kilobyte Speicher belegt. Epys Player habe ich übrigens fast unverändert übernommen, nur so viel geändert, dass ein bestimmter Effekt geloopt (wiederholt) werden kann. So konnten der Panzer- und der UFO-Sound mit relativ wenig, sich wiederholenden Daten beschrieben werden.

 

Der Sound des UFOs
Der Sound des UFOs, 45 Byte

 

  • Der Spielbildschirm, also die Farb- und Zeichendaten, belegen insgesamt 2000 Byte Speicher. Darin gibt es relativ viele sich wiederholende Daten, so dass die Größe prinzipiell mit einer einfachen Byte-Kompression verringert werden könnte. Die dafür nötige Entpackroutine würde jedoch zusätzlichen Speicherplatz belegen, so dass die Verringerung nicht mehr so signifikant wäre. Vorläufig ist noch reichlich Platz, wenn er doch knapp werden sollte, komme ich darauf zurück.
  • Der Titelbildschirm jedoch, der theoretisch ebenfalls 2000 Byte Platz beanspruchen würde, ist nicht gespeichert. Da relativ wenig Inhalt darauf ist, wird er unter Verwendung der werkseitigen KERNAL-Routinen als Textinhalt ausgegeben. So belegt er nur $015C, also dezimal 348 Byte.
  • Die verschiedenen Zustände, Adressen, Timer enthaltenden Speicherbereiche wurden größtenteils teilweise auf die Zero-Page ($00-$FF) verlegt, teilweise auf den Bereich unter dem Bildschirmspeicher (unter $0800). Diese bringen nicht viel, aber da sie bei jedem Spielstart neu mit Daten gefüllt werden müssen, wäre es sinnlos, den "normalen" Speicher damit zu belasten, besonders keinen Sinn hätte es, sie in der fertigen Programmdatei zu speichern.
  • Die Abfrage von Tastatur und Joystick erfolgt aus einer Tabelle. 37 Byte Programm führen die Abfrage durch, und pro Taste sind 4-5 Byte die Tabellengröße, anhand derer wir wissen, welcher Tastenzustand abgefragt werden muss, und wenn gedrückt, was damit zu tun ist. Das bedeutet bei wenigen Tasten noch mehr Wachstum, aber wenn das Spiel sowohl mit Joystick als auch Tastatur steuerbar ist, plus Pause, Beenden und ähnliches, kann man damit ein paar Byte gewinnen.
  • Soweit möglich, verwende ich die werkseitigen KERNAL-Routinen. Bildschirmlöschen, Tastaturmatrix abfragen, auf den Bildschirm schreiben – diese einzeln bedeuten nicht immer einen ernsthaften Vorteil, aber bei systematischer Verwendung kann man ganz gut sparen.
  • Daraus ergibt sich auch, dass ich die ROMs nicht ausschalte, nicht einmal vorübergehend. Ich brauche den darunterliegenden RAM nicht, und so spare ich mir auch die STA $FF3F / STA $FF3E Drei-Byte-Befehle. In der Folge leite ich den Interrupt auch nicht an $FFFE/FF um, sondern an $0314/15, spare so auch das Stapeln und Zurücklesen der Register.
  • Obwohl wir die Position des Spielers in Pixeln speichern, wird öfter die zeichenweise und die pixelweise Position innerhalb des Zeichens benötigt. Nach der Bewegung berechnet eine separate Routine letztere einmal pro Bildschirmzyklus und speichert sie auch. So belege ich lieber noch vier Byte auf der Zero-Page, muss sie aber nicht während des Laufens immer wieder mit ANDs und Bitschieberechnungen berechnen.
  • Beim Schreiben der beweglichen Elemente auf den Bildschirm lade ich die Position des Elements in ein Zero-Page-Byte-Paar, und wenn es dort ist, verwende ich in derselben Runde dieses Byte-Paar auch zur Umgebungsprüfung (z.B. ob vor dem Spieler ein Hindernis ist oder ob der Feind den Spieler eingeholt hat). Damit kann die Neuberechnung der Bildschirmpositionen gespart werden. Kombiniert mit der vorherigen Idee kann man so viele Ressourcen sparen.
  • Es gibt unzählige kleine Lösungen für noch kleinere Byte-Einsparungen – ein Beispiel ist, dass wenn ich irgendwo ein Datum eingeben muss und es dafür in ein Register geladen habe, versuche ich, es auch für etwas anderes zu verwenden. Typisches Beispiel dafür ist, wenn ich den Bildschirm ausschalte, dann den Rahmen, Hintergrund auf schwarz stelle, dann mit Inhalt fülle, dann reicht ein einziges LDY #$00, denn das kann ich nacheinander in $FF06, $FF19, $FF15 schreiben, und mit demselben kann ich auch den Y-indizierten Zyklus starten, mit dem ich den Inhalt fülle. Diese Lösung funktioniert typischerweise nachträglich gut, denn im (fast) fertigen Programm ist so etwas leichter zu erkennen – aber vorausschauend kann man einen größeren Teil bereits in den frühen Phasen machen.
  • ...und eine der besten Möglichkeiten, Platz zu sparen, ist, dass ich anstelle des eingebauten Monitors in einem crossplatform-assembler arbeite. So muss ich nicht alles an gut merkbare, auf Null endende Adressen packen wie in klassischen Zeiten :)

Der Großteil des Obigen spart natürlich nur minimal Platz, aber um zu verstehen, warum auch diese Krümel wichtig sind, blättern wir jetzt zurück zum oben scheinbar grundlos grün geschriebenen Text – dessen Größe beträgt etwa 4500 Zeichen, also etwa 4.4kB.

Dieser kleine grüne Textteil passt viermal hintereinander schon nicht mehr in 16K!

Noch eine Kleinigkeit zum Schluss: Während der Entwicklung verwende ich oft so eine Art Bauklotz-Debug-Methoden, die dann aus der endgültigen Version leicht-schnell entfernt werden können. Dabei ist der Bildschirm passiv, auf für den Programmablauf unwichtigen Bereichen können hervorragend Informationen angezeigt werden, die die Fehlersuche oder sogar den Entwicklungsprozess selbst unterstützen. Solche sind auch auf diesem Screenshot zu sehen:

The Pit Arcade Commodore Plus/4
"Debug-Modus" unter The Pit

Man sieht gut einerseits die klassische "Rasterzeit-Messung", die zu Beginn des Interrupts gestoppte und am Ende zurückgesetzte Rahmfarbe, andererseits unten auf dem Bildschirm in einer Reihe die pixelweisen X- und Y-Koordinaten des Spielers, die pixelweisen X- und Y-Koordinaten innerhalb des Zeichens, die zeichenweisen X- und Y-Koordinaten, den Grabezähler (0-7, bzw. entsprechend @-G), sowie die aktuelle Bewegungsrichtung (horizontal, vertikal oder – im vorliegenden Fall – keine).

In der High-Score-Liste stehen oben anstelle der Namen die Hindernisse in den vier Richtungen um den Spieler, darunter die aktuelle Bewegungsrichtung.

Außerdem ist hier im Debug-Modus das oben erwähnte leere, aber vom freien Bereich abweichende $00, also @-Zeichen, nicht leer, enthält ein einzelnes Pixel, um es vom wirklich leeren, begehbaren $20 (Leerzeichen)-Zeichen unterscheiden zu können. Deshalb sieht man zum Beispiel auch, dass vom Ausgang neben dem UFO der Spieler nur nach unten abfahren kann.

Darüber hinaus kann im Debug-Modus die Bewegung der Feinde, das Fallen der Felsen, das Schießen des Panzers usw. ausgeschaltet werden (und hier ist es ausgeschaltet). Das kann viel helfen, wenn man einen Fehler zurückverfolgen muss, dessen Quelle noch völlig im Dunkeln liegt.

Dieser ganze Debug-Modus kann vor dem Speichern der fertigen Version mit einigen ; auskommentiert werden, beim Speichern dann übersprungen werden. In unserem Fall – gerade wegen des 16K-Ziels liegt er ohnehin über $4000, stört also auch im verfügbaren Speicher nicht.

 

Nun, das war's erstmal, nächstes Mal – hoffe ich – melde ich mich mit dem fertigen The Pit.