In dieser Anleitung wird gezeigt, wie du das berühmte Spiel “Pong” selber programmieren kannst. Zum Anzeigen des Spiels werden wir die LED-Matrix und für die Steuerung der Spieler den Beschleunigungssensor der senseBox MCU S2 verwenden. Außerdem wird das Kommunikationsprotokoll ESP-Now genutzt, um einen der Spieler drahtlos zu steuern.
Achtung: Dieses Projekt ist vergleichsweise schwer. Du solltest am besten schon Erfahrung mit Blockly und der senseBox haben.
In den nächsten Kapiteln wird erklärt, wie du Pong selber programmieren kannst. Falls du lieber vorgefertigten Code direkt auf deine senseBox(en) laden möchtest, springe zum Kapitel “Zusammenfassung”.
Aufbau
Verbinde die LED-Matrix mit einem QWIIC-Kabel und schließe sie an Port A der GPIO-Schnittstelle an. Wenn der du alleine spielen möchtest, ist das Alles was du brauchst.
Falls du zu zweit spielen möchtest, benötigst du noch eine zweite senseBox MCU S2.
Im Kapitel “MAC-Adresse herausfinden” wird optional ein OLED Display verwendet. Falls du einen OLED Display parat hast und ihn nutzen möchtest, schließe ihn an die zweite I2C Schnittstelle der senseBox MCU S2 mit der LED-Matrix an.
Programmierung: Einzelspieler
Zeichnen der Spieler
Initialisiere als Erstes die LED-Matrix im Setup() und erstelle zwei Variabeln. Diese Variabeln werden die Position der Spieler speichern. Du kannst sie als Zahl (int) oder auch als Kommazahl (float) definieren. Falls du später noch ein wenig mit den Positionen und Bewegungen der Spieler experimentieren möchtest (siehe Kapitel “Ausblick”) ist es sinnvoll sie als Kommazahlen zu definieren.
Erstelle als Nächstes eine neue Funktion (hier “draw” benannt). In dieser Funktion werden die Spieler (und später auch der Ball) gezeichnet. Füge zuerst viermal den Block “Pixel setzen” in die neue Funktion ein. Der Spieler soll in der Spalte 0 (ganz links) der LED-Matrix gezeichnet werden, weshalb die X-Koordinaten aller vier Blöcke auf 0 gesetzt werden. Die Y-Koordinate wird für das erste Pixel auf den Wert der Positionsvariable des jeweiligen Spielers gesetzt. Nachfolgende Pixel werden jeweils ein Feld weiter unten gezeichnet. Unser Spieler ist damit 4 Pixel hoch. Wähle eine Farbe deiner Wahl für den Spieler.
Für den zweiten Spieler fügst du nun vier weitere “Pixel setzen” Blöcke ein. Der Spieler soll sich auf der äußerst rechten Spalte des Spielfelds befinden, setze also für diese vier Blöcke auf die X Position 11.
Nachdem du beide Spieler hiermit gezeichnet hast, entferne an allen Blöcken außer dem Letzten das Häkchen an “Zeige sofort”. Wenn du dies nicht tust, flackert die LED-Matrix, da nach jedem Färben eines Pixels die ganze Matrix neu angezeigt wird.
Rufe anschließend die kreierte Funktion im Setup() auf.
Steuern der Spieler
Um einen Spieler zu bewegen, verwenden wir den eingebauten Beschleunigungssensor. Wir nutzen dafür Logikblöcke, um abzufragen, ob der Sensor aktuell entlang der x-Achse in eine Richtung geneigt ist. Misst der Sensor für X Werte unter -1 ist die senseBox eindeutig in eine Richtung geneigt; misst er mehr als positiv 1 ist sie eindeutig in die andere Richtung geneigt. Alles zwischen 1 und -1 interprieren wir als nicht geneigt und in diesem Fall wird der Spieler nicht bewegt. Ist die senseBox also eindeutig geneigt, erhöhen oder erniedrigen wir die Position eines Spielers um jeweils ein Pixel. Der Spieler sollte sich jedoch nicht aus der LED-Matrix heraus bewegen können. Aus diesem Grund beschränken wir die Position des Spielers zwischen 1 und 5. Alles in allem ergibt sich folgender Code:
Nachdem wir die Position des Spielers angepasst haben, zeichnen wir ihn neu, mit der zuvor definierten Funktion. Packe nun die beiden Logik Blöcke und den Funktionsaufruf in einen “Intervall” Block, damit sich der Spieler nicht zu schnell bewegen kann. Stelle als Zeit 100ms ein und füge jetzt alles in den Loop().
WICHTIG: Da sich die Position des Spielers nun ändern kann, füge einen Block “Matrix leeren” zu Beginn deiner Funktion zum Zeichen (hier “draw” benannt) ein, damit die alte Position des Spielers nicht mehr zu sehen ist.
Nun sollte dein gesamter Code wie folgt aussehen:
Ball zeichnen und bewegen
Definiere als erstes Variablen für den Ball, die seine Position und Bewegungsrichtung sowohl horizontal als auch vertikal definieren. In unserem Beispiel setzen wir die horizontale und vertikale Richtung auf -1, das heißt, der Ball wird sich zu Beginn nach schräg links oben bewegen.
Füge nun einen neuen Block “Pixel setzen” am Ende der Funktion zum Zeichnen ein (hier “draw” benannt), um den Ball abhängig von der aktuellen horizontalen (hier “ball_x”) und vertikalen Position (hier “ball_y”) zu zeichnen. Denke daran, dass nur der letzte “Pixel setzen” Block ein Häkchen an “Zeige sofort” benötigt.
Als Nächstes ändern wir die Position des Balles alle 200 Millisekunden um die jeweiligen aktuellen Bewegungsrichtungen. Dafür erstellen wir ein neues Intervall in der Endlosschleife() und addieren darin die horizontale Richtung auf die horizontale Position und die vertikale Richtung auf die vertikale Position. Rufe danach die Funktion zum Zeichnen auf, um die neue Position des Balles auf der LED-Matrix anzuzeigen. Beachte, dass das Intervall einen anderen Namen als das Intervall zur Bewegung der Spieler haben sollte.
Dein Code sollte nun wiefolgt aussehen:
Kollisionsprüfung
Sowohl an der oberen und unteren Kante der LED-Matrix als auch an den beiden Spieler soll der Ball “abprallen”. Dafür werden wir eine Funktion erstellen, die überprüft, ob der Ball momentan mit einer Kante oder einem Spieler kollidiert und gegebenenfalls die Richtung des Balles ändern.
Als Erstes werden wir uns um Kollisionen mit der oberen und unteren Kante kümmern. Erstelle also zu Beginn eine neue Funktion (hier nennen wir sie “check_position”). Mache nun eine Abfrage in der Funktion, ob die vertikale Position des Balles größer oder gleich 0 oder kleiner oder gleich 7 ist. Diese Abfrage wird zutreffen, sobald der Ball sich am äußersten Rand oder außerhalb des Bildschirms befindet. In diesem Fall muss dann die vertikale Richtung des Balles umgekehrt werden (indem wir das Vorzeichen ändern), um ihn vom Rand abprallen zu lassen.
Als Nächstes kümmern wir uns um die Überprüfung, ob der Ball mit einem Spieler kollidiert und an ihm abprallt. Wir beginnen mit der Überprüfung des linken Spielers. Erstelle dazu als Erstes eine neue Logik Abfrage, ob die horizontale Position des Spielers auf der äußerst linken Spalte des Spielfeldes befindet, also zwischen 1 und 0 liegt. Ist dies der Fall, überprüfe nun, ob sich die vertikale Position des Balles auf dem linken Spieler befindet. Da der Spieler vier Pixel groß ist, müssen wir dafür überprüfen, ob die vertikale Position größer oder gleich der Position des Spielers ist oder kleiner oder gleich der Position des Spielers plus 3 ist. Ist auch dies der Fall, kehren wir die horizontale Bewegungsrichtung des Balles um.
Die Überprüfung auf Kollision mit dem rechten Spieler funktioniert fast genauso, nur dass wir zuerst überprüfen, ob die horizontale Position des Balles zwischen 10 und 11 liegt und danach die vertikale Position nicht mit dem linken, sondern mit dem rechten Spieler abgleichen.
Packe die beiden Überprüfungen nun jeweils in eine Kammer eines “wenn mache sonst” Blockes. Mit diesem Block soll sichergestellt werden, dass der Ball vom linken Spieler nur nach rechts abprallen kann und vom rechten Spieler nur nach links. Dafür überprüfen wir, ob die aktuelle Bewegungsrichtung des Balles größer (nach rechts) oder kleiner als 0 ist (nach links). Tun wir dies nicht, kann es sein, dass der Ball hinter oder in einem Spieler stecken bleibt.
Zu diesem Zeitpunkt sollte deine Funktion nun wie folgt aussehen:
Nun können wir noch überprüfen, ob sich der Ball am rechten oder linken Rand aus dem Spielfeld bewegt hat. Wenn sich der Ball nach links aus dem Spielfeld bewegt hat, dann hat der linke Spieler verloren. Dafür überprüfen wir, ob die horizontale Position des Spielers kleiner als -1 ist. Falls dies zutrifft, kannst du den Block “Draw bitmap” nutzen, um einen traurigen Smiley anzeigen zu lassen. Nachdem der Smiley für eine Weile angezeigt wurde, setze die horizontale Position des Balles auf 8 um ihn wieder in der Mitte zu positionieren. Jetzt kann eine neue Runde Pong beginnen.
Analog dazu testen wir nun noch, ob sich der Ball nach rechts aus dem Spielfeld bewegt hat. Ist dies der Fall, hat der linke Spieler gewonnen und wir zeigen einen fröhlichen Smiley. Danach setzen wir den Ball wieder in die Mitte auf die horizontale Position 3.
Rufe die erstellte Funktion zur Kollisionsüberprüfung jedes Mal auf, nachdem der Ball sich bewegt hat.
Alles in allem ergibt sich folgender Code:
Damit kannst du bereits alleine Pong spielen. Du steuerst dabei den linken Spieler. Der rechte Spieler bewegt sich nicht, aber es ist trotzdem nicht immer einfach einen Punkt gegen ihn zu erzielen.
Falls du Pong lieber zu zweit spielen möchtest, wird dies im nächsten Kapitel erklärt.
Programmierung: Drahtlose Steuerung des zweiten Spielers
Um den zweiten Spieler zu steuern, nutzen wir eine weitere senseBox MCU S2. Diese sendet drahtlos, per ESP-Now, Befehle an die erste senseBox mit der LED-Matrix. Dafür müssen wir als Erstes die MAC-Adresse der ersten senseBox herausfinden. Da wir dafür ein neues Skript benötigen, gib deinem bisher erstellten Programmcode einen Namen und lade ihn mit dem Button “Projekt herunterladen” (fünfter Button von rechts). Nun kannst du mit dem ganz rechten Button “reset workspace” deine Programmierebene leeren.
MAC-Adresse herausfinden
Initialisiere als erstes ESP-Now im Setup(). Um sich nun die MAC-Adresse anzeigen zu lassen, kannst du zum Beispiel das OLED-Display der senseBox verwenden. Initialisiere dafür auch den Display im Setup() und zeige und schreibe dann die MAC-Adresse auf das Display.
Schließe nun das OLED-Display an die zweite I2C-Schnittstelle der senseBox an und führe den Code aus.
Falls du kein Display zur Hand hast, kannst du alternativ auch einfach die LED-Matrix verwenden, um dir die MAC-Adresse anzeigen zu lassen. Führe in diesem Fall folgenden Code auf der senseBox aus:
In jedem Fall solltest du dir nun die angezeigte MAC-Adresse notieren, da sie im nächsten Schritt verwendet wird.
Senden der Steuerungsbefehle
Leere erneut deine Programmierebene, um nun den Code für den zweiten Spieler zu erstellen.
Initialiere dann wieder ESP-Now und verbinde mit der MAC-Adresse, die du im vorherigen Schritt herausgefunden hast. Überprüfe jetzt in der Endlosschleife wieder die Neigung der senseBox, genauso wie du es auch zuvor bereits einmal programmiert hast. Anstatt nun jedoch direkt die Position des Spielers anzupassen, verschicken wir mit dem Block “Sende Nachricht an Empfänger” einen Befehl an die andere senseBox. Um mitzuteilen, dass sich der Spieler nach unten bewegen soll verschicken wir eine -1 und um mitzuteilen, dass er sich nach oben bewegen soll verschicken soll eine positive 1. Dein Code sollte nun wie folgt aussehen (abgesehen von der angegebene MAC-Adresse):
Lade diesen Code nun auf die zweite senseBox ohne LED-Matrix.
Empfangen und Umsetzen der Befehle
Öffne nun wieder den Programmcode, den du für die Programmierung des Einzelspielermodus erstellt und heruntergeladen hattest. Diesen werden wir jetzt anpassen, um den rechten Spieler entsprechend der empfangenen Befehle zu bewegen.
Füge im Setup wieder einen Block hinzu, um ESP-Now zu initialisieren. Setze danach einen Block “Wenn Nachricht “message” erhalten von ‘mac_address’“ am Ende des Setup() ein. Überprüfe nun, ob die empfangene Nachricht “message” einer “-1” oder einer “1” entspricht. Handelt es sich um eine “-1” dann addiere -1 auf die Position des zweiten Spielers und entsprechend eine 1 falls es sich um eine empfangene “1” handelt.
Insgesamt ergibt sich daraus folgender Code, welchen du nun auf die erste senseBox mit der LED-Matrix laden kannst:
Wenn du nun beide senseBoxen mit Strom versorgst, kannst du zu zweit Pong auf der LED-Matrix spielen.
Zusammenfassung
Wenn du Pong alleine spielen möchtest, dann lade einfach diesen Code auf eine senseBox MCU S2 mit der LED-Matrix.
Wenn du lieber zu zweit spielen möchtest, musst du als Erstes die MAC-Adresse der senseBox MCU S2 mit der LED-Matrix herausfinden. Lade dafür diesen Code auf die senseBox, um dir die Adresse auf der LED-Matrix anzeigen zu lassen, oder diesen Code, falls du dir die Adresse lieber auf dem OLED Display anzeigen lassen möchtest (schließe dafür den OLED Display an eine freie I2C Schnittstelle an). Notiere dir die angezeigte Adresse. Öffne danach diesen Code und trage die MAC-Adresse an allen drei Stellen ein. Lade ihn nun auf eine zweite senseBox MCU S2. Lade als letzten Schritt diesen Code auf die senseBox MCU S2 mit der LED-Matrix, von der du zuvor die MAC-Adresse herausgefunden hast.
Ausblick
Die hier vorgestellte Implementierung von Pong ist noch recht grundlegend und könnte noch verbessert oder erweitert werden.
Du könntest zum Beispiel versuchen einen Punktestand zu programmieren, der statt der traurigen und fröhlichen Smileys angezeigt wird. Nutze dafür zum Beispiel den Block “Zeige Text/Zahl” und erstelle zwei neue Variabeln, die angepasst werden, sobald ein Spieler einen Punkt erzielt. Du könntest auch eine maximale Punktzahl festlegen, bei der ein Spieler das Spiel endgültig gewonnen hat (z.B. wie beim Tennis oder Badminton).
Außerdem könntest du mit der Bewegungsrichtung und Geschwindigkeit des Balles experimentieren. Ändere dafür die Variablen, die wir hier im Code “ball_dir_x” und “ball_dir_y” benannt haben, oder die angegebenen Millisekunden in den Intervallen in der Endlosschleife. In der klassischen Implementierung von Pong hängt die Richtung, in der der Ball von einem Spieler abprallt, davon ab, an welcher Stelle der Ball auf den Spieler trifft. Trifft der Ball zum Beispiel mittig auf dem Spieler auf, so wird er mehr oder weniger horizontal zurückgeworfen, ohne sich schräg nach unten oder oben zu bewegen.
Auch die Größe der Spieler kann verändert werden, um das Spiel einfacher oder schwieriger zu machen. Um die Spieler zu verkleinern, entferne jeweils den letzten Block “Pixel setzen” des jeweiligen Spielers aus der Funktion zum Zeichnen (hier “draw” benannt). Beachte, dass du nun auch in der Funktion zur Überprüfung von Kollisionen (hier “check_position” benannt) die maximale Höhe des Spielers anpassen musst (beispielsweise von +3 auf +2).
Des Weiteren wäre es für die Handhabung der senseBoxen einfacher, wenn nicht eine senseBox sowohl das Steuern des Spielers und das Anzeigen der LED-Matrix übernehmen würde, sondern dies auf zwei senseBoxen aufgeteilt werden würde. Falls du noch eine weitere senseBox parat hast, könntest du versuchen den Code dementsprechend anzupassen.