//verwendete Bibliotheken:
//https://github.com/ThingPulse/esp8266-oled-ssd1306
//https://github.com/adafruit/Adafruit_NeoPixel

#define DEBUG 1

#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif

#include <Wire.h>
#include "SSD1306Wire.h"
#include <Adafruit_NeoPixel.h>

#define PIN            26
#define NUMPIXELS      1
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

SSD1306Wire display(0x3c, SDA, SCL);

//Portpins für Ein-/Ausgänge
const int Enc_A = 34, Enc_B = 35, Enc_Taster = 0;
const int LED_rot = 32, LED_gruen = 33, Taster_L = 2, Taster_R = 4;

//Globale Variablen
int Encoder, Status, auswahl, Spieler, game, aktiv, iX, iY, iComputer; // zur Kommunikation zw. ISR und loop
int felder [9];
const int16_t setx = display.getWidth() / 3;
const int16_t sety = display.getHeight() / 3;

//functions
void spielfeld(void);
void spiel(void);
void box(int16_t setx, int16_t sety);
void start(void);
void sieg(void);
void spieler1(int16_t setx, int16_t sety);
void spieler2(int16_t setx, int16_t sety);
void Pos(void);
void Auswertung();
void AuswertungL();
void AuswertungR();
void computer();
void gegenComputer(void);
void taktik(int x);
void fehler();

void spielfeld(void) {
  int16_t drittelx = display.getWidth() / 3;
  int16_t drittely = display.getHeight() / 3;

  display.drawLine(drittelx, display.getHeight() , drittelx, 0);
  display.drawLine(drittelx * 2, display.getHeight() , drittelx * 2, 0);
  display.drawLine(display.getWidth(), drittely, 0 , drittely);
  display.drawLine(display.getWidth(), drittely * 2, 0 , drittely * 2);

  display.display();
}

void spiel(void) {
  Pos();
  box(setx * iX, sety * iY);
  if (Spieler == 1 and auswahl == 1 and felder[Encoder] == 0) {
    spieler1(setx * iX , sety * iY);
    auswahl = 0;
    felder[Encoder] = 1;
    sieg();
    do {
      Encoder ++;
      fehler();
    } while (felder[Encoder] != 0);
    Pos();
    box(setx * iX, sety * iY);
    digitalWrite(LED_rot, HIGH);
    digitalWrite(LED_gruen, LOW);
  }
  else if (Spieler == 2 and auswahl == 1 and felder[Encoder] == 0) {
    spieler2(setx * iX , sety * iY);
    auswahl = 0;
    felder[Encoder] = 2;
    sieg();
    do {
      Encoder ++;
      fehler();
    } while (felder[Encoder] != 0);
    Pos();
    box(setx * iX, sety * iY);
    digitalWrite(LED_gruen, HIGH);
    digitalWrite(LED_rot, LOW);
  }
  else if (iComputer == 1 and Spieler == 2) {
    computer();
  }
}

void box(int16_t setx, int16_t sety) {
  setx = setx - (display.getWidth() / 3) / 2;
  sety = sety - (display.getHeight() / 3) / 2;
  display.setColor(WHITE); // alternate colors
  char* text[1];
  if (Spieler == 1) text[0] = "X";
  else text[0] = "O";
  display.drawString(setx - 3, sety - 6, text[0]);
  display.display();
  display.setColor(BLACK); // alternate colors
  display.drawString(setx - 3, sety - 6, text[0]);
  display.setColor(WHITE);
  delay(100);
  aktiv = 0;
}

void start(void) {
  display.setLogBuffer(5, 30);
  display.println("Tic Tac Toe");
  display.drawLogBuffer(0, 0);
  for (uint8_t i = 0; i < 101; i++) {
    display.drawProgressBar(0, display.getHeight() / 2 - 10, display.getWidth() - 1, 20, i);
    display.display();
    delay(15);
  }
  display.clear();
  debug("Start zu ende");
}

void sieg(void) {
  display.display();
  display.setLogBuffer(5, 30);
  aktiv = 0;
  if (felder[0] == 1 and felder [1] == 1 and felder [2] == 1 or
      felder[3] == 1 and felder [4] == 1 and felder [5] == 1 or
      felder[6] == 1 and felder [7] == 1 and felder [8] == 1 or
      felder[0] == 1 and felder [4] == 1 and felder [8] == 1 or
      felder[2] == 1 and felder [4] == 1 and felder [6] == 1 or
      felder[1] == 1 and felder [4] == 1 and felder [7] == 1 or
      felder[2] == 1 and felder [5] == 1 and felder [8] == 1 or
      felder[0] == 1 and felder [3] == 1 and felder [6] == 1) {
    delay(2000);
    display.clear();
    game = 0;
    const char* test[1] = {
      "Spieler X hat gewonnen!"
    };
    display.println(test[0]);
    display.drawLogBuffer(0, 0);
    display.display();
    pixels.setPixelColor(0, pixels.Color(100, 0, 0)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
    delay(4000);
    pixels.setPixelColor(0, pixels.Color(0, 0, 0)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
    display.clear();
  }
  else if (felder[0] == 2 and felder [1] == 2 and felder [2] == 2 or
           felder[3] == 2 and felder [4] == 2 and felder [5] == 2 or
           felder[6] == 2 and felder [7] == 2 and felder [8] == 2 or
           felder[0] == 2 and felder [4] == 2 and felder [8] == 2 or
           felder[2] == 2 and felder [4] == 2 and felder [6] == 2 or
           felder[1] == 2 and felder [4] == 2 and felder [7] == 2 or
           felder[2] == 2 and felder [5] == 2 and felder [8] == 2 or
           felder[0] == 2 and felder [3] == 2 and felder [6] == 2) {
    delay(2000);
    display.clear();
    game = 0;
    char* test[1];
    if (iComputer == 0) test[0] = { "Spieler O hat gewonnen!" };
    else test[0] = { "Computer hat gewonnen!" };
    display.println(test[0]);
    display.drawLogBuffer(0, 0);
    display.display();
    pixels.setPixelColor(0, pixels.Color(0, 100, 0)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
    delay(4000);
    pixels.setPixelColor(0, pixels.Color(0, 0, 0)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
    display.clear();
  }
  else if (felder[0] != 0 and felder[1] != 0 and felder[2] != 0 and
           felder[3] != 0 and felder[4] != 0 and felder[5] != 0 and
           felder[6] != 0 and felder[7] != 0 and felder[8] != 0) {
    felder[8] = 0;
    delay(2000);
    display.clear();
    game = 0;
    const char* test[1] = {
      "Unentschieden ..."
    };
    display.println(test[0]);
    display.drawLogBuffer(0, 0);
    display.display();
    delay(4000);
    display.clear();
  }
}

void spieler1(int16_t setx, int16_t sety) {
  setx = setx - (display.getWidth() / 3) / 2;
  sety = sety - (display.getHeight() / 3) / 2;

  int16_t x = (display.getWidth() / 3) / 4;
  int16_t y = (display.getHeight() / 3) / 4;

  display.drawLine(setx - x, sety - y, setx + x, sety + y);
  display.drawLine(setx + x, sety - y, setx - x, sety + y);
  Spieler = 2;
  display.display();
}

void spieler2(int16_t setx, int16_t sety) {
  setx = setx - (display.getWidth() / 3) / 2;
  sety = sety - (display.getHeight() / 3) / 2;

  display.drawCircleQuads(setx, sety, 8, 0b00001111);
  Spieler = 1;
  display.display();
}

void Pos(void) {
  switch ( Encoder )
  {
    case 0:
      iY = 1;
      iX = 1;
      break;
    case 1:
      iY = 1;
      iX = 2;
      break;
    case 2:
      iY = 1;
      iX = 3;
      break;
    case 3:
      iY = 2;
      iX = 1;
      break;
    case 4:
      iY = 2;
      iX = 2;
      break;
    case 5:
      iY = 2;
      iX = 3;
      break;
    case 6:
      iY = 3;
      iX = 1;
      break;
    case 7:
      iY = 3;
      iX = 2;
      break;
    case 8:
      iY = 3;
      iX = 3;
      break;
    default:
      iY = 0;
      iX = 0;
  }
}

void setup() {
  Serial.begin(115200); // Serielle Schnittstelle mit 115200Bit/s
  pinMode(LED_rot, OUTPUT); // die LEDs beginnen danach
  pinMode(LED_gruen, OUTPUT); // zu leuchten!
  Serial.print("LED Test");
  digitalWrite(LED_rot, LOW);
  digitalWrite(LED_gruen, LOW);
  delay(2000);
  digitalWrite(LED_rot, HIGH);
  digitalWrite(LED_gruen, HIGH);
  pinMode(Taster_L, INPUT_PULLUP); // Taster brauchen hier den
  pinMode(Taster_R, INPUT_PULLUP); // Pullup-Widerstand

  display.init();
  display.flipScreenVertically();
  display.setContrast(255);

  pinMode(Enc_A, INPUT); // externe Pullups auf der Platine
  pinMode(Enc_B, INPUT); // Spur B des Encoders
  pinMode(Enc_Taster, INPUT_PULLUP); // Taster des Encoders = Flash-Taster!

  attachInterrupt(digitalPinToInterrupt(Enc_A), Auswertung, RISING); // ISR void Auswertung(void)
  // use the input in polling-mode first. Attach interrupt after start of the game
  //attachInterrupt(digitalPinToInterrupt(Taster_L), AuswertungL, FALLING); // ISR void Auswertung(void)
  attachInterrupt(digitalPinToInterrupt(Taster_R), AuswertungR, FALLING); // ISR void Auswertung(void)
  Encoder = 1;
  Status = 0;
  Spieler = 1;
  game = 0;
  aktiv = 0;
  iComputer = 2;
  iX = 2;
  iY = 1;
  digitalWrite(LED_gruen, HIGH);
  digitalWrite(LED_rot, LOW);
}

void loop() {
  delay(10);
  if (game == 0) {
    for (int i = 0; i < 9; ++i) {
      felder[i] = 0;
    }
    start();
    if (iComputer == 2) {
      gegenComputer();
    }
    spielfeld();
    game = 1;
    box(display.getWidth() / 3 * iX, display.getHeight() / 3 * iY);
  }
  if (digitalRead(Enc_Taster) == false) {
    auswahl = 1;
    while (digitalRead(Enc_Taster) == false); // warte auf loslassen
  }
  if (Status == 1 or auswahl == 1 or iComputer == 1 and Spieler == 2) {
    spiel();
    Status = 0; // Signal wieder löschen
  }
  for (int i = 0; i < 9; ++i) {
    Serial.printf("%4d", felder[i]);
  }
  Serial.printf("\n");
}

//Einzelspieler (gegen Computer)
void gegenComputer(void) {
  //noInterrupts();
  const char* test[2] = {
    "Halte Taster 2 gedrückt,",  "für Einzelspieler."
  };
  display.println(test[0]);
  display.println(test[1]);
  display.drawLogBuffer(0, 0);
  display.display();
  delay(3000);
  if ( digitalRead(Taster_L) == false ) { // low-aktiv, daher false
    iComputer = 1;
  }
  else iComputer = 0;
  display.clear();
  //interrupts();
  attachInterrupt(digitalPinToInterrupt(Taster_L), AuswertungL, FALLING); // ISR void Auswertung(void)
}

void taktik(int x) {

  if (felder[0] == x and felder [1] == x and felder [2] == 0) {
    spieler2(setx * 3 , sety * 1);
    Encoder = 2;
    return;
  }
  if (felder[0] == x and felder [1] == 0 and felder [2] == x) {
    spieler2(setx * 2 , sety * 1);
    Encoder = 1;
    return;
  }
  if (felder[0] == 0 and felder [1] == x and felder [2] == x) {
    spieler2(setx * 1 , sety * 1);
    Encoder = 0;
    return;
  }
  if (felder[3] == x and felder [4] == x and felder [5] == 0) {
    spieler2(setx * 3 , sety * 2);
    Encoder = 5;
    return;
  }
  if (felder[3] == x and felder [4] == 0 and felder [5] == x) {
    spieler2(setx * 2 , sety * 2);
    Encoder = 4;
    return;
  }
  if (felder[3] == 0 and felder [4] == x and felder [5] == x) {
    spieler2(setx * 1 , sety * 2);
    Encoder = 3;
    return;
  }
  if (felder[6] == x and felder [7] == x and felder [8] == 0) {
    spieler2(setx * 3 , sety * 3);
    Encoder = 8;
    return;
  }
  if (felder[6] == x and felder [7] == 0 and felder [8] == x) {
    spieler2(setx * 2 , sety * 3);
    Encoder = 7;
    return;
  }
  if (felder[6] == 0 and felder [7] == x and felder [8] == x) {
    spieler2(setx * 1 , sety * 3);
    Encoder = 6;
    return;
  }
  if (felder[0] == x and felder [4] == x and felder [8] == 0) {
    spieler2(setx * 3 , sety * 3);
    Encoder = 8;
    return;
  }
  if (felder[0] == x and felder [4] == 0 and felder [8] == x) {
    spieler2(setx * 2 , sety * 2);
    Encoder = 4;
    return;
  }
  if (felder[0] == 0 and felder [4] == x and felder [8] == x) {
    spieler2(setx * 1 , sety * 1);
    Encoder = 0;
    return;
  }
  if (felder[2] == x and felder [4] == x and felder [6] == 0) {
    spieler2(setx * 1 , sety * 3);
    Encoder = 6;
    return;
  }
  if (felder[2] == x and felder [4] == 0 and felder [6] == x) {
    spieler2(setx * 2 , sety * 2);
    Encoder = 4;
    return;
  }
  if (felder[2] == 0 and felder [4] == x and felder [6] == x) {
    spieler2(setx * 3 , sety * 1);
    Encoder = 2;
    return;
  }
  if (felder[1] == x and felder [4] == x and felder [7] == 0) {
    spieler2(setx * 2 , sety * 3);
    Encoder = 7;
    return;
  }
  if (felder[1] == x and felder [4] == 0 and felder [7] == x) {
    spieler2(setx * 2 , sety * 2);
    Encoder = 4;
    return;
  }
  if (felder[1] == 0 and felder [4] == x and felder [7] == x) {
    spieler2(setx * 2 , sety * 1);
    Encoder = 1;
    return;
  }
  if (felder[2] == x and felder [5] == x and felder [8] == 0) {
    spieler2(setx * 3 , sety * 3);
    Encoder = 8;
    return;
  }
  if (felder[2] == x and felder [5] == 0 and felder [8] == x) {
    spieler2(setx * 3 , sety * 2);
    Encoder = 5;
    return;
  }
  if (felder[2] == 0 and felder [5] == x and felder [8] == x) {
    spieler2(setx * 3 , sety * 1);
    Encoder = 2;
    return;
  }
  if (felder[0] == x and felder [3] == x and felder [6] == 0) {
    spieler2(setx * 1 , sety * 3);
    Encoder = 6;
    return;
  }
  if (felder[0] == x and felder [3] == 0 and felder [6] == x) {
    spieler2(setx * 1 , sety * 2);
    Encoder = 3;
    return;
  }
  if (felder[0] == 0 and felder [3] == x and felder [6] == x) {
    spieler2(setx * 1 , sety * 1);
    Encoder = 0;
    return;
  }
}

void computer() {
  int i = 0;

  if (Spieler == 2) taktik(2);
  if (Spieler == 2) taktik(1);
  if (Spieler == 2) {
    srand(time(0));
    do {
      i = rand() % 9;
    } while (felder[i] != 0);
    Encoder = i;
    Pos();
    spieler2(setx * iX , sety * iY);
  }

  felder[Encoder] = 2;
  sieg();
  do {
    Encoder ++;
    fehler();
  } while (felder[Encoder] != 0);
  Pos();
  box(setx * iX, sety * iY);
  digitalWrite(LED_gruen, HIGH);
  digitalWrite(LED_rot, LOW);
}

//Interrupts
void Auswertung() {
  debug("Encoder-ISR");

  aktiv = ++aktiv;
  if (aktiv == 1) {
    if (digitalRead(Enc_B) == false) {
      do {
        Encoder ++;
        fehler();
      } while (felder[Encoder] != 0);
    }
    else {
      do {
        Encoder --;
        fehler();
      } while (felder[Encoder] != 0);
    }
    Status = 1; // Signal an loop geben
  }
    debug(Encoder);
}

void AuswertungL( ) {
  debug("Taster-ISR");
  aktiv = ++aktiv;
  if (aktiv == 1) {
    do {
      Encoder --;
      fehler();
    } while (felder[Encoder] != 0);
    Status = 1; // Signal an loop geben
  }
    debug(Encoder);
}

void AuswertungR( ) {
  debug("Taster2-ISR");
  aktiv = ++aktiv;
  if (aktiv == 1) {
    do {
      Encoder ++;
      fehler();
    } while (felder[Encoder] != 0);
    Status = 1; // Signal an loop geben
  }
    debug(Encoder);
}

void fehler() {
  if (Encoder < 0) {
    Encoder = 8;
  }
  else if (Encoder > 8) {
    Encoder = 0;
  }
}
Taster2
Taster4
LED32
LED33
ESP32 Schulboard mit LEDs und Tastern, I2C-Bus mit Standard-Belegung
SSD1306 Display
WS2812 an Pin26