You are here

Ada Looks Good, Now Program a Game Without Knowing Anything (FOSDEM 2022)

Artikel: 

Dieser Artikel ist eine textliche Ausarbeitung meines FOSDEM 2022 Vortrags "Ada Looks Good, Now Program a Game Without Knowing Anything".

 

Einleitung:

 

Entdeckt habe ich Ada Anfang 2019, wie genau weiß ich allerdings nicht mehr, aber ich mochte die Sprache direkt, anders als diverse andere Programmiersprachen die ich in der Vergangenheit angeschaut oder versucht habe zu lernen. Einige der Sachen die mir gefielen waren beispielsweise eine lesbare Syntax, brauchbare Fehlermeldungen und nicht jeder Müll einfach durch kompiliert wird.

Also fing ich an die Sprache zu lernen mit Hilfe des Tutorials von Wikibooks und Rosetta Code und nachdem ich einige der Grundlagen verstanden habe, habe ich natürlich sofort angefangen ein Spiel zu programmieren. Das Ergebnis ist genau auf dem Niveau das man erwartet, wenn jemand ein Rollenspiel programmiert der wenig Ahnung von der verwendeten Programmiersprache oder von Spieleentwicklung generell hat.

Zeit mich wirklich intensiver mit Ada zu beschäftigen war dann Anfang 2020, wo ich dann auch anfing ein neues Spiel zu entwickeln. Im September 2020 hatte es dann die Form eines frühen Civilization Klons angenommen und nach mehr als einem Jahr Entwicklung ist es inzwischen fast schon ein richtiges Spiel mit eigenen Ideen. Da ich fast die gesamte Entwicklung auf YouTube und Twitch livestreame, wurde ich irgendwann „entdeckt“ und gebeten einen Vortrag auf der FOSDEM 2022 zu halten.

Da ich aber keine Ahnung von gar nichts habe, habe ich einfach über ein paar grundlegende Dinge und Probleme in Ada geredet, die mir während meiner Spieleentwicklung aufgefallen sind. Denn manchmal wirkt es so als wäre Ada gar nicht primär für die Spieleprogrammierung entwickelt worden.

 

Allgemeines:

 

Erst einmal möchte ich klarstellen dass sämtliche Aussagen sich auf die Spieleentwicklung mit Ada 2012 unter einem halbwegs modernen Computersystem beziehen und es wahrscheinlich zu deutlichen Abweichung kommt, wenn ihr für alte oder embedded Systeme entwickelt (oder ihr einfach wisst was ihr tut). Dann fangen wir mal mit ein paar grundlegenden Empfehlungen an.

Ada bietet diverse zusätzliche validity Checks (-gnatVx), welche man alle aktivieren sollte. Das gleiche gilt für die zusätzlichen Warnungen, mit Ausnahme von -gnatw.y, welches nur Informationen liefert warum ein Package einen Body benötigen, eventuell bei sehr komplexen Packages sinnvoll. Außerdem sollte man alle Warnungen auch als Fehler behandeln lassen. Das alles klingt am Anfang vielleicht ein wenig übertrieben, aber wenn man erst einmal verstanden hat was man tun muss um die dadurch auftretenden Meldungen direkt zu vermeiden kostet es kaum mehr Zeit und man kann Probleme deutlich früher erkennen. Wodurch es dann möglich ist entsprechende Fehler in fünf Minuten Arbeit zu beheben und ihn nicht eine Zeit lang mit schleppt und dann drei Tage braucht um alles anzupassen. Was mir natürlich noch nie passiert ist.

Außerdem gibt es auch noch diverse style checks die man sich auch mal anschauen sollte. Unter anderem sollte man darüber nachdenken die Verschachtelungstiefe zu beschränken, dann bloß weil Ada Dinge wie verschachtelte Packages erlaubt muss man es damit nicht übertreiben. Anzumerken ist auch das Ada bei Namen eine Länge von 200 Zeichen garantiert, auch das sollte man für einen besser verständlichen Code benutzen, man hat keinen Vorteil wenn man alle Namen auf drei Zeichen beschränkt.

 

String Handling:

 

Wie bei vielen Programmen braucht man auch in Spielen Text und deswegen ist String Handling eine wichtige Sache. Glücklicherweise verfügt Ada über eine Vielzahl an verschiedenen Stringarten, wovon man aber bis auf drei Varianten alle anderen praktisch ignorieren kann. Diese drei Arten sind Unbounded_Wide_Wide_String, Wide_Wide_String und String.

Da man in Spielen die Texte oftmals in mehreren Sprachen vorliegen hat und/oder man Modding durch die Spieler ermöglichen möchte ist die genaue Textlänge eine Unbekannte. Die einfachste Lösung für dieses Problem ist deshalb ein Unbounded_String. Außerdem nutzen andere Sprachen oftmals Zeichen außerhalb des ASCII Zeichensatzes, beispielsweise enthält die deutsche Sprache die Sonderzeichen ä, ö, ü und ß, ein Problem das sich durch die Verwendung der Wide_Wide_Version einfach lösen lässt. Natürlich könnte man auch eine andere Stringvariante verwenden und die Länge des Strings dynamisch anpassen oder die UTF8-Version des Standardstrings verwenden. Das ist aber nicht nur ein deutlich größerer Aufwand um Text zu speichern, auch ergeben sich dadurch keine nennenswerte Vorteile. Sicherlich benötigt ein Unbounded_Wide_Wide_String mehr Speicher als ein normaler String, was allerdings höchstens theoretisch relevant ist, da dieser Verbrauch im Vergleich zu anderen Teilen, beispielsweise der Karte der Spielwelt, völlig im Hintergrundrauschen untergeht. Außerdem kann ein Unbounded_String auch ohne weitere Anpassungen in records verwendet werden.

Gelegentlich benötigt man, vor allem wenn man mit der Standard- oder externen Bibliotheken interagieren will, einen String welcher nicht Unbounded ist. In diesen Fällen verwendet man, wenn möglich, am besten Wide_Wide_String. Dadurch muss man sich keine Sorgen um eventuelle Probleme bei Texten mit Sonderzeichen machen und durch die Verwendung von Ada.Strings.Wide_Wide_Unbounded.To_Wide_Wide_String und Ada.Strings.Wide_Wide_Unbounded.To_Unbounded_Wide_Wide_String ist eine einfach Umwandlungsoption verfügbar. Auch hier gilt dass der theoretisch sparbare Speicher irrelevant ist und es nur einen Haufen Extraarbeit erzeugt.

Zuletzt gibt es noch den ganz normalen String, welcher vor allem dann wichtig ist wenn man mit einer Bibliothek interagiert die nur String verwendet. Ein Beispiel ist hier die Standardbibliothek, welche mit Ada.Directories zwar seit Ada 2005 eine Möglichkeit bietet Verzeichnisse und Dateinamen einzulesen, aber ausschließlich in einen String.

 

Standardbibliothek:

 

Was uns direkt zu einem der Probleme mit der Ada Standardbibliothek bringt, Ada.Directories verwendet nur String. Zwar wurde in Ada 2005 auch Wide_Wide_ für vollständige UTF8 Unterstützung eingeführt und von vielen Packages gibt es auch eine entsprechende Wide_Wide_Variante, allerdings nicht für Ada.Directories. Daran hat auch die Überarbeitung in Ada 2012 nichts geändert. Dies muss man bei der Benennung von Dateien und Ordner berücksichtigen und entsprechend zusätzliche Konvertierungen einbauen.

Auch für Ada.Float_Text_IO existiert keine Wide_Wide_Version und wenn meine eine braucht muss man selbst eine abgeleitetes Package auf Basis von Ada.Wide_Wide_Text_IO erstellen. Kein unüberwindbares Hindernis, aber teilweise recht inkonsistent, vor allem da ein Integer_Wide_Wide_Text_IO existiert.

Ein weiteres Problem ist die oftmals nicht vorhandene Kommentierung bei gleicher Benennung verschiedener Prozeduren oder Funktionen. Ja, Ada erlaubt mehrere Prozeduren/Funktionen mit dem gleichen Namen in der gleichen Datei und das auch dann noch wenn die Übergabeparameter gleich lauten, so lange sich die Datentypen noch unterscheiden. 5000 Prozeduren mit dem Name Put zu erstellen und diese dann ohne jegliche Kommentierung in 300 Dateien zu verteilen ist allerdings wenig nutzerfreundlich. Wenn man Ada gerade lernt und man auf der Suche nach der Lösung eines Problems die Lösung „dafür benutzt man einfach Put“ findet, ist das schon ein wenig schwierig herauszufinden welches Put denn gemeint ist. Das gilt natürlich nicht nur für Put, auch Get existiert reichlich und das trotz der in Ada möglichen Namenslänge von 200 Zeichen.

 

Kleinigkeiten:

 

Zuletzt noch ein paar Kleinigkeiten. Der String den (Wide_Wide_)Image zurück gibt hat bei negativen Zahlen als erstes Zeichen ein Minus, bei positiven ein Leerzeichen, eventuell kann einem dies die Textpositionierung zerhacken. (Wide_Wide_)Value ermöglicht es einem einen String in eine Zahl umzuwandeln, prüft aber nicht ob der String ausschließlich aus Zahlen besteht. Dies muss man selbst prüfen, denn wenn der String ein Zeichen enthält welches keine Nummer ist kommt es zu einem Programmfehler. Wandelt man Float in einen String um, dann ist dieser in der wissenschaftlichen Ansicht 1.00E0001. Möchte man es als normale Kommazahl haben dann muss man das entsprechend anpassen, dafür gibt es aber in der Standardbibliothek ein Put, viel Spaß dabei das Richtige zu finden. Nur ein Witz, was ihr sucht ist in Ada.Wide_Wide_Text_IO.Float_IO und heißt:

procedure Put

(To   : out Wide_Wide_String;

Item : Num;

Aft  : Field := Default_Aft;

Exp  : Field := Default_Exp);

Dann noch das Einlesen/Schreiben von Daten. Die einfachsten Lösungen sind Text_IO für Text und Stream_IO für alles Andere. Das ist einfach und auch wenn andere Varianten eventuell weniger Festplattenspeicher brauchen ist es die Extraarbeit nicht wert, da die Grafiken, Sounds und Musiken  sowieso mehr Platz verbrauchen werden.

 

Zukunft:

 

Das war dann in etwa die Zusammenfassung meines FOSDEM Beitrags und ich gehe jetzt weiter an meinem Spiel arbeiten. Ich hoffe ja immer noch es in der Zukunft mal erfolgreich verkaufen zu können, die Anzahl kommerzieller Spiele welche in Ada geschrieben sind, ist schließlich deutlich zu niedrig. Außerdem seid ihr natürlich gerne zu meinen Livestreams eingeladen, neben deutsch spreche ich auch englisch und solltet ihr mich dennoch nicht verstehen dann keine Sorge, ich verstehe mich meistens auch nicht.