Skip to content

Technik Blog

Programmieren | Arduino | ESP32 | MicroPython | Python | Raspberry Pi | Raspberry Pi Pico

Menu
  • Smarthome
  • Arduino
  • ESP32 & Co.
  • Raspberry Pi & Pico
  • Solo Mining
  • Über mich
  • Deutsch
  • English
Menu

Tic-Tac-Toe Spaß auf dem ESP32: Ein Touchscreen-Spielprojekt

Posted on 17. April 202422. April 2024 by Stefan Draeger

Wie du ein Tic-Tac-Toe Spiel am ESP32 mit Touchscreen programmierst, erfährst du hier in meiner Schritt-für-Schritt-Anleitung. Ich nehme dich mit und zeige dir detaliert wie du dieses kleine Spiel in der Arduino IDE mit C/C++ programmierst. Es ist einfacher als du denkst.

Tic-Tac-Toe Spaß auf dem ESP32: Ein Touchscreen-Spielprojekt
Dieses Video auf YouTube ansehen.

Bei Tic-Tac-Toe handelt es sich um ein einfaches Spiel für zwei Spieler, das auf einem 3×3-Raster gespielt wird. Jeder Spieler wählt abwechselnd ein Feld aus und versucht, eine Reihe von drei seiner eigenen Symbole horizontal, vertikal oder diagonal zu bilden. Auf dem ESP32 mit dem 2,8″ TFT Touchdisplay wird das Spiel grafisch dargestellt, und die Spieler können ihre Züge durch Berühren der entsprechenden Felder tätigen. Das Programm überprüft kontinuierlich, ob ein Spieler gewonnen hat oder das Spiel unentschieden ist, und gibt entsprechende Rückmeldungen aus. Es ist ein unterhaltsames und interaktives Projekt, das die Funktionalitäten des ESP32 und des Touchscreens demonstriert.

Inhaltsverzeichnis

  • Benötigte Ressourcen für dieses Spiel
  • Programmieren von Tic-Tac-Toe am ESP32 mit der Arduino IDE

Benötigte Ressourcen für dieses Spiel

Wenn du das kleine Spiel nachprogrammieren möchtest, dann benötigst du:

  • ein ESP32 Development Board*,
    • einen Stift (für den Touchscreen),
    • ein Datenkabel,
  • die Arduino IDE 2.3.x

Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!

Programmieren von Tic-Tac-Toe am ESP32 mit der Arduino IDE

Das fertige Spiel kannst du dir entweder nachfolgend als ZIP-Datei oder von meinem GitHub Repository StefanDraeger/ESP32_Development_Board_ESP32-2432S028R herunterladen. (neben vielen weiteren Beispielen zum ESP32 Development Board)

Spiel – Tic-Tac-Toe für das ESP32 Development BoardHerunterladen

Im nachfolgenden YouTube-Video demonstriere ich dir das kleine Programm mit jeweils den 2 verschiedenen Möglichkeiten das der Spieler 0 oder X gewinnt und einmal unentschieden ist.

Demonstration - Tic-Tac-Toe auf dem ESP32 Development Board
Dieses Video auf YouTube ansehen.

Hier nun der Quellcode mit entsprechenden Kommentaren:

/*
  Einbinden der Bibliothek zum kommunizieren
  mit dem Display.
*/
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include "lgfx_ESP32_2432S028.h"

//Größe der Zeichenfläche definieren
#define MAX_X 319
#define MAX_Y 239

//Felder für die ermittelte Position
//bei einem klick
uint16_t x = 0, y = 0;


//Instanz des Displays
static LGFX lcd;

//definieren eines Feldes
struct Field {
  int32_t x;        //X Koordinate
  int32_t y;        //Y Koordinate
  bool isSelected;  //bereits belegt?
  char symbol;      //Symbol O oder X, default -
};

//Spielfeld, 3x3 Felder groß
Field playground[3][3] = {
  { { 0, 0, false, '-' }, { 0, 0, false, '-' }, { 0, 0, false, '-' } },
  { { 0, 0, false, '-' }, { 0, 0, false, '-' }, { 0, 0, false, '-' } },
  { { 0, 0, false, '-' }, { 0, 0, false, '-' }, { 0, 0, false, '-' } }
};

//Feld für den aktuellen Spieler
bool currentPlayerX = true;
//Feld ob das Spiel beendet ist
bool gameOver = false;

//Abmaße einer Spielfläche
const int32_t DIMENSION = 50;
//Abstände zwischen den Feldern
const int32_t PADDING = 10;

void setup(void) {
  //beginn der seriellen Kommunikation mit
  //115200 Baud
  Serial.begin(115200);

  //beginn der Kommunikation mit dem Display
  lcd.init();
  //drehen des Displays
  lcd.setRotation(1);
  //füllen des Displays mit der Farbe Schwarz
  lcd.fillScreen(TFT_BLACK);
  //eine kleine Pause von 100ms.
  delay(100);
  //zeichnen des Hintergrundes inkl. dem befüllen
  //der Spielfläche mit den Koordinaten der Felder
  drawBackground();
}

void loop() {
  //Wenn ein Touchaktion ausgeführt / erkannt wurde,
  //dann liefert die Funktion getTouch den Wert 1 und
  //befüllt die übergebenen Parametern mit den Koordinaten
  if (lcd.getTouch(&x, &y) == 1) {

    //Wenn die ermittelte X oder Y Positon außerhalb der Range ist,
    //dann soll die Funktion hier verlassen werden.
    if (x > MAX_X || y > MAX_Y) {
      return;
    }

    //Wenn das Spiel nicht beendet ist, dann...
    if (!gameOver) {
      //umkehren des Wertes des Feldes currentPlayerX
      //aus true wird false und andersherum
      currentPlayerX = !currentPlayerX;

      //Offset für das Symbol der Spieler
      int32_t offset = ((DIMENSION / 2) / 2) + 3;

      //For-Schleife über die Zeilen
      for (int row = 1; row < 4; row++) {
        //For-Schleife über die Spalten
        for (int col = 1; col < 4; col++) {
          //entnehmen eines Feldes aus dem Spielfeld
          Field& field = playground[row - 1][col - 1];
          //Wenn das Feld nicht belegt ist und die geklickte Koordinate
          //innerhalb dieses Feldes ist, dann...
          if (field.isSelected == false && checkCoord(field, x, y)) {
            //Feld als belegt markieren
            field.isSelected = true;
            //setzen der Schritgröße auf 4
            lcd.setFont(&fonts::Font4);
            //Schriftfarbe setzen,
            //Schriftfarbe - schwarz,
            //Hintergrund - grau
            lcd.setTextColor(TFT_BLACK, TFT_LIGHTGREY);
            //Symbol setzen
            char symbol = currentPlayerX ? 'X' : 'O';
            //platzieren des Cursors an die X/Y Koordinate mit offset
            lcd.setCursor(field.x + offset, field.y + offset);
            //schreiben des Symbols
            lcd.print(String(symbol));
            //schreiben des Symbols auf das Feld
            field.symbol = symbol;
            //eine Pause von 250ms.
            delay(250);

            //prüfen ob das Spiel beendet ist,
            //zunächst wird geprüft ob der aktuelle Spieler gewonnen hat
            if (checkWin(symbol)) {
              //Anzeigen der Meldung das der Spieler gewonnen hat.
              showMessage("Spieler " + String(symbol) + " hat gewonnen!", 20);
              //Feld gameOver auf true setzen und damit wird im
              //nächsten Schritt der Else-Zweig betreten.
              gameOver = true;
              //eine Pause von 2 Sekunden.
              delay(2000);
            } else {
              //Wenn der aktuelle Spieler nicht gewonnen hat, dann wird geprüft
              //ob ggf. ein unentschieden besteht.
              //Im ersten Schritt wird geprüft ob noch Felder mit einem - existieren,
              //(alternativ könnte man auch das Feld isSelected wählen)
              bool playgroundContainsDash = false;
              for (int row = 1; row < 4; row++) {
                for (int col = 1; col < 4; col++) {
                  Field& field = playground[row - 1][col - 1];
                  if (field.symbol == '-') {
                    playgroundContainsDash = true;
                  }
                }
              }
              //Wenn alle Felder belegt sind, dann...
              if (!playgroundContainsDash) {
                //prüfen ob der Spieler O oder X gewonnen hat.
                //(Hier sollte eigentlich immer False als ergebnis kommen.)
                if (!checkWin('O') || !checkWin('X')) {
                  //Anzeigen der Meldung "Unentschieden"
                  showMessage("Unentschieden!", 55);
                  //das Spiel beenden
                  gameOver = true;
                }
              }
            }
          }
        }
      }
    } else {
      //Wenn das Spiel beendet ist, und der Spieler auf das Display klickt,
      //dann soll ein neues gestartet werden.
      gameOver = false;
      //das Array zurücksetzen
      initPlayground();
      //zeichnen der Spielfläche
      drawBackground();
    }
  }
  //eine Pause von 50ms.
  delay(50);
}

//Funktion zum zeichnen der Spielfläche
void drawBackground() {
  // füllen des Displays mit der Farbe Schwarz
  lcd.fillScreen(TFT_BLACK);
  //Schriftgröße 4
  lcd.setFont(&fonts::Font4);
  //Schriftfarbe setzen
  //Schriftfarbe - grau,
  //Hintergrundfarbe - schwarz
  lcd.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
  //Text an der Koordinate schreiben
  lcd.drawString("Tic-Tac-Toe", 75, 25);

  //Zeichnen der Felder
  for (int row = 1; row < 4; row++) {
    for (int col = 1; col < 4; col++) {
      drawFilledRectangle(row, col);
    }
  }
}

//Zeichnet ein gefülltet Rechteck
void drawFilledRectangle(int32_t row, int32_t col) {
  //berechnen der Position der Felder aus den Daten
  //Zeile & Spalte
  int32_t x = (DIMENSION + PADDING) * col;
  int32_t y = (DIMENSION + PADDING) * row;

  //Zeichnen des gefüllten Rechtecks
  lcd.fillRect(x, y, DIMENSION, DIMENSION, TFT_LIGHTGREY);

  //Speichern der Koordinaten an dem Feld
  Field& field = playground[row - 1][col - 1];
  field.x = x;
  field.y = y;
}

//Funktion zum prüfen ob die geklickte Koordinate innerhalb des Feldes liegt
bool checkCoord(Field field, int x, int y) {
  return (x >= field.x && x <= (field.x + DIMENSION) && y >= field.y && y <= (field.y + DIMENSION));
}

//Funktion zum prüfen ob der Spieler mit dem Symbol "player" gewonnen hat.
bool checkWin(char player) {
  // Überprüfen auf horizontale Gewinnmöglichkeiten
  for (int row = 0; row < 4; row++) {
    if (playground[row][0].symbol == player && playground[row][1].symbol == player && playground[row][2].symbol == player)
      return true;
  }

  // Überprüfen auf vertikale Gewinnmöglichkeiten
  for (int col = 0; col < 4; col++) {
    if (playground[0][col].symbol == player && playground[1][col].symbol == player && playground[2][col].symbol == player)
      return true;
  }

  // Überprüfen auf diagonale Gewinnmöglichkeiten
  if (playground[0][0].symbol == player && playground[1][1].symbol == player && playground[2][2].symbol == player)
    return true;
  if (playground[0][2].symbol == player && playground[1][1].symbol == player && playground[2][0].symbol == player)
    return true;

  // Wenn keine Gewinnmöglichkeiten gefunden wurden
  return false;
}

//Funktion zum initialisieren des Arrays.
//Diese Funktion wird aufgerufen wenn
//das Spiel beendet ist und neugestartet wird.
void initPlayground() {
  for (int row = 1; row < 4; row++) {
    for (int col = 1; col < 4; col++) {
      Field& field = playground[row - 1][col - 1];
      field.x = 0;
      field.y = 0;
      field.isSelected = false;
      field.symbol = '-';
    }
  }
}

//Funktion zum anzeigen einer Meldung auf dem Display.
//Da die Texte unterschiedlich lang sind, wird zusätzlich 
//noch die X Koordinate als Parameter übergeben.
void showMessage(String msg, int32_t x) {
  lcd.setFont(&fonts::Font4);
  lcd.setTextColor(TFT_BLACK, TFT_ORANGE);
  lcd.setCursor(x, 120);
  lcd.print(msg);
}

3 thoughts on “Tic-Tac-Toe Spaß auf dem ESP32: Ein Touchscreen-Spielprojekt”

  1. Martin sagt:
    17. April 2024 um 23:01 Uhr

    Cooles Projekt, danke!

    Antworten
  2. karl-Heinz Hempel sagt:
    12. August 2024 um 23:03 Uhr

    geht das auch mit dem Arduino

    Antworten
    1. Stefan Draeger sagt:
      13. August 2024 um 08:14 Uhr

      Hi, klaro geht das auch.
      Du benötigst lediglich das Display welches auch einzeln verfügbar ist.
      Der Anschluss an den Arduino ist jetzt nicht so einfach.

      Gruß, Stefan

      Antworten

Schreibe einen Kommentar Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden?
Support-Ticket erstellen

Newsletter abonnieren

Bleib auf dem Laufenden: Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32 und mehr – direkt in dein Postfach.

Jetzt Newsletter abonnieren

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee.
So hilfst du mit, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

draeger-it.blog auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kategorien

Tools

  • Unix-Zeitstempel-Rechner
  • ASCII Tabelle
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner
  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix – Generator

Links

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com

Stefan Draeger
Königsberger Str. 13
38364 Schöningen

Tel.: 01778501273
E-Mail: info@draeger-it.blog

Folge mir auf

  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
©2025 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
Optionen verwalten Dienste verwalten Verwalten von {vendor_count}-Lieferanten Lese mehr über diese Zwecke
Einstellungen anzeigen
{title} {title} {title}