Back
Featured image of post Lab 4 | Multiplayer Game mit dem ESP

Lab 4 | Multiplayer Game mit dem ESP

Disclamer

Wir werden dieses Projekt bei der Abschlusspräsentation vorstellen, dort bekommt man dann auch einen besseren Eindruck vom Spiel.

Vorbereitung

Bei mir lief die Einrichtung der ESPs, anders als bei manchen anderen Kommilitonen, sehr flüssig ab. Über die Tutorials, auf die in der Laboranleitung verwiesen wurde, konnte ich alles schnell zum Laufen bringen.

Einzige Hürde war die Wahl des richtigen USB-Kabels, damit auch Daten übertragen werden können. Ich habe noch ein 2m langes USB-Kabel, von dem ich weiß, dass es dafür geeignet ist. Der Upload ist aber regelmäßig abgebrochen. Nachdem ich die Uploadrate reduziert habe (bei dem langen Kabel kam es Wohl zu Datenverlust) lief der Upload problemlos. Den ESP 8266 hab ich mit dem Beispielskript Blink und Scan WiFi getestet. Da der ESP 32 keine interne Led besitzt, habe ich hier nur den Sketch Scan WiFi getestet.

Die Einrichtung von nkrog lief ebenso problemlos ab. Die vom ESP gehostete Website (wie im Tutorial beschrieben) konnte ich sowohl über die lokale IP, als auch über die nkrog Website aufrufen.

Die Vorbereitung ist also abgeschlossen.

Multiplayer Game

Bei diesem Lab sollte in einem Team ein kleines Multiplayer-Spiel entwickelt werden. Benni, Simon und ich sind uns schnell einig gewesen, dass der Controller auf jeden Fall genutzt werden sollte. Nach ein wenig Überlegung hatten wir die Idee, mit der LED-Matrix ein 3-Personen 4-Gewinnt-Spiel zu entwickeln.

 
 
 

Leider ist in dem Set kein Treiberboard für die LED Matrix dabeigewesen. Die Matrix selbst zu verkabeln macht wenig Spaß und erfordert viele freie Pins. Der ESP32, für den wir uns zunächst entschieden hatten, ermöglicht den betrieb der LED-Matrix also nicht.

Als Alternative haben wir uns überlegt, den kleinen Bildschirm am ESP32 für eine Spielfeldanzeige zu nutzen. Jedoch ist der Bildschirm sehr klein und nur schwarz-weiß. Bei 3 Personen wäre eine farbliche Anzeige sehr sinnvoll.

Deshalb nutzen wir nun eine Website zur Darstellung des Spielfeldes. Ein Server, der den Spielstand und die Logik verwaltet, soll lokal bei einem Spieler auf dem PC gehostet werden. Der Server wird über ngrok veröffentlicht.

Im Team haben wir nun die Aufgaben verteilt:

  • Simon kümmert sich um das Front End
  • Benni kümmert sich um das Back End
  • Ich kümmere mich um den ESP, der den Joystick auslesen soll und die Daten an den Server verschickt sowie um eine Python-Klasse die erkennt, ob jemand gewonnen hat.

Auslesen vom Joystick-Daten mit dem ESP32

Zunächst wollten wir den ESP32 nutzen, da Benni und Simon beide Probleme mit dem ESP8266 hatten. Bei diesem ist jedoch der einzig freie Analog-Pin bei WLAN-Nutzung nicht verfügbar. Da wir aber sowohl die Controller-Daten auslesen als auch die Daten an den Server schicken müssen, haben wir uns doch für den ESP8266 entschieden.

Auslesen vom Joystick-Daten mit dem ESP8266

Wir nutzen nur eine Achse des Controllers um die Spalte, in der der 4-Gewinnt-Stein eingeworfen werden soll, gewählt werden kann. Der Controller ist letztlich ein einfacher Spannungsteiler, da in Ruheposition 2,5V und in den ausgeschlagenen Positionen 0V bzw. 5V abfallen. Falls der Controller einen Ausschlag erkennt, wird eine Zahl erhöht bzw. verringert. Diese Zahl stellt die gewählte Spalte dar. Drückt der Spieler nun auf den Controllerknopf, wird die Zahl an den Server übermittelt.

Der Code ist in folgende Funktionen aufgeteilt:

  • setup: Im Setup verbindet sich der ESP mit dem Router und ruft die Funktion update_website auf. Durch diesen erstmaligen Aufruf wird dem Server mitgeteilt, dass der Spieler am Spiel teilnehmen will.
  • loop: Im Loop werden die Funktionen read_values und check_thresholds nacheinander aufgerufen sowie für 20ms gewartet. Hierdurch wird der Button entprellt.
  • read_values: Die Spannung der Controller-Position und die des Buttons werden gemessen.
  • check_thresholds: Die eben aufgenommenen Messwerte werden verarbeitet. Hierzu sind Schwellwerte definiert. Übersteigt ein Messwert einen Schwellwert wird, eine Aktion ausgelöst. Diese Aktionen sind in den Funktionen go_left, go_right und button_action definiert.
  • go_left: Die Spaltennummer wird um 1 vermindert.
  • go_right: Die Spaltennummer wird um 1 erhöht.
  • button_action: Die Funktion update_wabsite wird aufgerufen.
  • update_website: Eine Verbindung zum Server wird aufgebaut. Die Spaltennummer sowie die Information, ob es sich um den initialen Aufruf handelt werden dem Server als JSON übermittelt. Anschließend wird der Response-Code und die Response-Message in den seriellen Monitor gedruckt.
  • print_position: Die Position wird in den seriellen Monitor gedruckt. Diese Funktion diente vor allem zum Debuggen

Der Code auf dem ESP sieht wie folgt aus:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>

#define joyPin A0
#define butPin 0

int joy = 0;
bool but = false;
bool moved = false;
bool pressed = false;

int selection = 0;
const int selection_len = 10;

const char* ssid = "SSID";  
const char* password = "PASSWORD";
const char* serverName = "SERVER";

WiFiServer server(80);
String header;

void setup() {
  Serial.begin(115200);

  Serial.println("");
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP()); 

  Serial.println("Connecting to host: ");
  update_website(1);
  Serial.println("");
}

void loop() {
  read_values();
  check_thresholds();
  
  delay(20);
}

void read_values() {
  joy = analogRead(joyPin);
  but = !digitalRead(butPin);
}

void check_thresholds() {
  int centered = 535;
  int bound = 100;
  int upper = centered + bound;
  int lower = centered - bound;

  if (joy > upper && moved == false) {
    moved = true;
    go_left();
  }

  if (joy < lower && moved == false) {
    moved = true;
    go_right();
  }
  
  if (joy < upper && joy > lower) {
    moved = false;
  }


  if (but == true && pressed == false) {
    pressed = true;
    button_action();
  }

  if (but == false && pressed == true){
    pressed = false;
  }
}

void go_left() {
  if (selection > 0) {
    selection = selection - 1;
  }
  update_website(0);
  print_position();
}

void go_right() {
  if (selection < selection_len - 1) {
    selection = selection + 1;
  }
  update_website(0);
  print_position();
}

void button_action() {
  update_website(0);
  Serial.println("BUTTON");
}

void update_website(int lobby) {
  if(WiFi.status()== WL_CONNECTED){
      HTTPClient http;
      
      http.begin(serverName);

      http.addHeader("Content-Type", "application/json");
      int httpResponseCode = http.POST("{\"position\":\"" + String(selection) + "\",\"button\":\"" + String(but) + "\",\"lobby\":\"" + lobby + "\"}");
     
      Serial.print("HTTP Response code: ");
      Serial.print(httpResponseCode);
      Serial.print(" | ");

      if (httpResponseCode < 500){
        String payload = http.getString();
        if (payload.length()  > 0){
          Serial.print("Payload: ");
          Serial.print(payload);    
        }
      }
      
      Serial.println("");
      http.end();
  }
}

void print_position() {
  for (int i=0; i < selection_len; i++) {
    if (selection == i) {
      Serial.print(1);
    }
    else{
      Serial.print(0);
    }
  }
  Serial.println("");
}

 

Python-Klasse zur Erkennung ob jemand gewonnen hat

Damit die Arbeitsteilung fair blieb war es außerdem meine Aufgabe, eine Python-Klasse zur Sieg-Erkennung zu schrieben. Das Spielfeld wird als Matrix interpretiert. Die Zahlen stehen für den zugehörigen Spieler. Hier sieht man ein vereinfachtes Beispiel:

0 0 0 0 0
0 0 0 1 0
3 2 1 3 0
2 1 2 3 0
1 2 1 3 0

Der Spieler 1 hat auf der Diagonalen vier Steine und hat damit gewonnen. Zur leichteren Erkennung wird pro Spieler eine Matrix erstellt und normiert:

Spieler 1:

0 0 0 0 0
0 0 0 1 0
0 0 1 0 0
0 1 0 0 0
1 0 1 0 0

Spieler 2:

0 0 0 0 0
0 0 0 0 0
0 1 0 0 0
1 0 1 0 0
0 1 0 0 0

Spieler 3:

0 0 0 0 0
0 0 0 0 0
1 0 0 1 0
0 0 0 1 0
0 0 0 1 0

Nun können in jeder Spielermatrix Sieges-Muster gesucht werden. Ein paar verschiedene Muster sind hier dargestellt:

1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
0 0 0 0 0
1 1 1 1 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 0

Bei einem 10 x 10 Spielfeld gibt es mehrere Hundert Muster. Wenn Eine Spielermatrix mit einem Muster übereinstimmt hat dieser gewonnen. Das Python-Skript sieht wie folgt aus:

import numpy as np

class Victory:
    def __init__(self, player_num, field_size):
        self.field_size = field_size
        self.player_num = player_num
        m_hori = self._make_m_hori()
        m_vert = self._make_m_vert()
        m_left = self._make_m_left()
        m_right = self._make_m_reight()
        self.patterns = m_hori + m_vert + m_left + m_right

    def _make_m_hori(self):
        v = np.array([1,1,1,1])
        ms = []
        for i in range(self.field_size[0]):
            for k in range(self.field_size[1]-3):
                m = self._get_empty_m()
                m[i,k:k+4] = v
                ms.append(m)
        return ms

    def _make_m_vert(self):
        v = np.array([1,1,1,1]).reshape(1,-1)
        ms = []
        for i in range(self.field_size[0]-3):
            for k in range(self.field_size[1]):
                m = self._get_empty_m()
                m[i:i+4,k] = v
                ms.append(m)
        return ms

    def _make_m_left(self):
        v = np.eye(4)
        ms = []
        for i in range(self.field_size[0]-3):
            for k in range(self.field_size[1]-3):
                m = self._get_empty_m()
                m[i:i+4,k:k+4] = v
                ms.append(m)
        return ms

    def _make_m_reight(self):
        v = np.fliplr(np.eye(4))
        ms = []
        for i in range(self.field_size[0]-3):
            for k in range(self.field_size[1]-3):
                m = self._get_empty_m()
                m[i:i+4,k:k+4] = v
                ms.append(m)
        return ms

    def _get_empty_m(self):
        field = [[0 for _ in range(self.field_size[0])] for _ in range(self.field_size[1])]
        return np.array(field)

    def _split(self, field):
        ms = []
        for i in range(self.player_num):
            controll_m = (i+1) * np.ones(self.field_size)
            m = (controll_m == field).astype(int)
            ms.append(m)
        return ms

    def check(self, field):
        field = np.array(field).reshape(self.field_size)
        players_ms = self._split(field)

        player_num = 0
        for players_m in players_ms:
            player_num = player_num + 1
            for pattern in self.patterns:
                m = (players_m==1)&(players_m == pattern)
                if np.count_nonzero(m) == 4:
                    return player_num
        return 0


if __name__ == "__main__":
    # Beispiel Spielfeld
    fieldsize = (6,6)
    fd = [[0 for _ in range(fieldsize[0])] for _ in range(fieldsize[1])]
    fd[1][1:5] = [1,2,3,4]
    fd[2][1:5] = [1,0,3,4]
    fd[3][1:5] = [1,2,1,4]
    fd[4][1:5] = [1,2,3,1]

    # Flatten
    ff = []
    for i in fd:
        for k in i:
            ff.append(k)

    # Victory API
    player_num = 3
    vic = Victory(player_num, fieldsize)
    zahl = vic.check(ff) # 0 == Keiner hat gewonnen, 1/2/3 Spieler hat gewonnen
    print(zahl)
Built with Hugo
Theme Stack designed by Jimmy