Der PortaBrick Arcade ist eine vollfunktionsfähige mobile Arcade-Spielkonsole, die vollständig und zu 100% pur aus LEGO® gebaut wurde. Das Projekt ist aus einem Universitätsprojekt hervorgegangen und steht nun als Produktidee auf LEGO® Ideas zur Abstimmung.
Gestern hat es endlich geklappt: Eines meiner MOCs wurde auf LEGO Ideas zugelassen! Mein erstes Ideas-Projekt läuft! Zeit, den PortaBrick Arcade auch hier vorzustellen.
Der PortaBrick Arcade ist eine vollfunktionsfähige mobile Arcade-Spielkonsole, die vollständig aus LEGO® gebaut wurde. Der PortaBrick Arcade besteht aus 432 Bauteilen und wiegt inklusive Akku rund 520 Gramm. Die Software basiert auf dem Pybricks-Projekt.
Die Spielkonsole verfügt über zwei große mechanische Eingabeknöpfe, die auf Drucksensoren basieren, und für die Spielsteuerung verwendet werden. Die vier Tasten der zentralen Einheit, die auch den Mikroprozessor und den Akku beherbergt, kommen bei der Menüsteuerung zum Einsatz. Der PortaBrick Arcade besitzt zwei Displays. Das obere Display kann Millionen von Farben anzeigen und wird aus vier einzelnen Modulen á drei mal drei Pixel gebildet. Die Gesamtauflösung beträgt entsprechend sechs mal sechs Pixel. Das untere Display ist in der Zentraleinheit integriert und kann fünf mal fünf Pixel monochrom darstellen. Auf der Rückseite des Geräts befindet sich eine Klappe, die Zugang zum Micro-USB- Anschluss für Firmware-Updates und zum Aufladen des Akkus gewährleistet.
Die Software des PortaBrick Arcade umfasst die Menüs und deren Ablaufsteuerung, einen Treiber für das obere Display, eine Pixel- Bild-Bibliothek sowie zwei Spiele. Dabei handelt es sich um Umsetzungen der Arcade-Klassiker Snake und Pong. Grundsätzlich kann der PortaBrick Arcade um beliebige Spiele erweitert werden, so lange diese keine höhere Auflösung voraussetzen.
Die Software habe ich in MicroPython geschrieben. Die einzelnen Bestandteile sind in Klassen strukturiert. Den Code habe ich – wo es angebracht war – objektorientiert geschrieben. Die übrigen Bestandteile sowohl der Spiele wie auch der Menüführung habe ich prozedural umgesetzt. Um die Auswahl der Spiele zu erweitern, können Programmier_innen auf die Klassen zugreifen. Alle Code-Bestandteile sind umfangreich dokumentiert. Den gesamten Quellcode des PortaBrick Arcade sowie des Pixel Art Painters habe ich in einem GitHub-Repository unter der MIT-Lizenz veröffentlicht.
Bei der Entwicklung des PortaBrick Arcade gab es zwei Herausforderungen: Erstens brauchte ich einen Treiber, um das Gesamtdisplay aus vier einzelnen ColorLightMatrix-Modulen anzusteuern. Zweitens zeigte sich während der Entwicklung, dass ich eine Art von Multitasking beziehungsweise Multithreading benötige.
Multi-Matrix-Treiber
Die erste Herausforderung im Projekt war die Entwicklung eines Treibers, der es ermöglicht, ein Display anzusteuern, das aus einer beliebigen Anzahl an ColorLightMatrix-Modulen gebildet wird. Die Schwierigkeit war hierbei, gegebene Koordinaten so zu übersetzen, dass das richtige Matrix-Modul angesprochen wird. In ersten Ansatz nutzte ich hierfür Wenn-Dann-Abfragen, die je nach gegebener Displaygröße die x- und y-Koordinate umrechnete. Allerdings war diese Lösung zu unflexibel. Für jede Konfiguration des Displays hätte ich neue Umrechnungsfunktionen erstellen und dem Treiber hinzufügen müssen. Daher entscheid ich mich, eine mathematische Lösung zu finden.
Insgesamt benötigte ich drei Gleichungen um die drei unbekannten „Neues x“, Neues y“ und „Modul-ID“ zu berechnen. In einer Kombination aus Ausprobieren und Aufstellen von Gleichungen gelangte ich nach einiger Zeit zu einem funktionierenden Ergebnis. Die Gleichungen lauten:
(1) x_new = x mod 3
(2) y_new = y mod 3
(3) module_id = ⎣y/3⎦* x_max +⎣x/3⎦
Mit Hilfe der drei Gleichungen ist es nun möglich, ein beliebiges Display aus 3×3-Pixel-Modulen anzusteuern. Nachdem ich eine Lösung für die Pixel-genaue Berechnung gefunden hatte, entwickelte ich nach und nach die Funktionen zur Anzeigen der Inhalte. Aktuell können folgende Funktionen mit dem Treiber genutzt werden:
- Ansteuerung eines beliebigen Pixels in einer beliebigen Farbe
- Ausschalten eines beliebigen Pixels
- Anzeige einer Pixel-Grafik in einer beliebigen Farbe
- Ausschalten des gesamten Displays
Multi Threading
Der MicroPython-Implementierung in Pybricks fehlt (auf dem LEGO Spike PrimeHub) die Möglichkeit in Threads zu arbeiten, also Instruktionen gleichzeitig beziehungsweise parallel abzuarbeiten. Das führte in meiner Umsetzung der Spiele dazu, dass der Status der Eingabeknöpfe nur einmal je Schleife ausgelesen wurde. In der Pause zwischen den Programmdurchläufen können so keine Eingaben ausgelesen werden. Spielende hatten deswegen den Eindruck, die Spielkonsole reagiere stark verzögert beziehungsweise gar nicht auf Eingaben. Man musste exakt den Moment des Auslesens abpassen, um eine Reaktion auf den Displays zu sehen.
Eine Lösung fand ich nach Recherche in der Nutzung von sogenannten Generatoren. Dabei griff ich auf ein Beispiel aus der Pybricks-Community zurück. Generatoren in Python sind spezielle Funktionen. Im Gegensatz zu herkömmlichen Funktionen, die einen Wert zurückgeben und den Funktionsaufruf beenden, können Generatoren mehrere Werte nacheinander liefern, dann anhalten und zu einem späteren Zeitpunkt wieder aufgenommen werden, um den nächsten Wert zu generieren.
Generatoren werden in Python mit dem Schlüsselwort „yield“ definiert, anstelle des „return“- Statements, das in herkömmlichen Funktionen verwendet wird. Das „yield“-Statement gibt einen Wert zurück und friert den aktuellen Zustand der Funktion ein. Bei jedem Aufruf des Generators wird der nächste Wert generiert, und der Generator läuft bis zum nächsten „yield“-Statement. Auf diese Weise können Generatoren Schritt für Schritt Werte produzieren, ohne den gesamten Speicherbedarf auf einmal zu beanspruchen.
In den Spielen des PortaBrick Arcade verwende ich das Generator-Prinzip für eine nicht- blockierende Warte-Funktion. Nicht blockierend bedeutet, dass nur ein einzelner Funktionsaufruf wartet und der restliche Programmablauf ungehindert fortgeführt werden kann. Pro Spiel kommt diese Art des nicht blockierenden Wartens genau einmal vor. Ich verwende die Funktion, um die Spielgeschwindigkeit zu steuern, indem ich die Kernfunktion des jeweiligen Spiels bremse. Der restlichen Funktionen wie Erkennung der Eingaben oder das Zeichnen des Bildschirminhalts laufen ungebremst mit der Ausführungsgeschwindigkeit des Mikroprozessors ab. Per Variable kann die Spielgeschwindigkeit festgelegt werden. Auf diese Art ist es möglich, die Eingaben per Taster jederzeit zu erkennen und zu verarbeiten.
Neben der Multi-Thread-Programmierung habe ich weitere Individuelle Lösungen verwendet. So änderte ich das Verhalten des großen Zentral-Knopfes. In Pybricks wie auch in der LEGO Spike Prime Firmware dient dieser zum Ein- und Ausschalten, der Bestätigung des gewählten Programms und zum Abbruch des laufenden Programms. Die Abbruch-Funktion habe ich auf den mit dem Bluetooth-Symbol gekennzeichneten Knopf rechts oben gelegt. So konnte ich den Zentral-Knopf in die Menüsteuerung einbinden. Er dient jetzt zur Bestätigung der Eingaben in den Dialogen des Menüs.
Weiterentwicklung
Unabhängig vom Ausgang der Abstimmung auf LEGO® Ideas habe ich folgende Punkte für die Zukunft der Spielkonsole geplant:
- Überarbeitung des Hauptmenüs
- weiteren Spiele ähnlich Tetris, Space Invaders oder Pac Man
- drahtlose Verbindung zwischen zwei PortaBrick Arcade für Multiplayer-Spiele
- Erweiterung des PortaBrick Arcade zu einer stationären Arcade-Maschine
- Portierung auf LEGO® natives Python, wie in der Spike-App verwendet