#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal_I2C.h>
// -------------------- Hardware --------------------
#define PIN 2
#define NUM_RINGS 10
#define LEDS_PER_RING 32
#define TOTAL_LEDS (NUM_RINGS * LEDS_PER_RING)
Adafruit_NeoPixel strip(TOTAL_LEDS, PIN, NEO_GRB + NEO_KHZ800);
// Button-Pins (INPUT_PULLUP: gedrueckt=LOW, losgelassen=HIGH)
int buttons[9] = {3,4,5,6,7,8,9,10,11};
int resetButton = 12;
//LCD-Display
//LiquidCrystal_I2C lcd(0x27, 16, 2);
// -------------------- Mapping --------------------
// mapBtnToCell[i] = welche Spielfeld-Zelle (0..8) wird vom physischen Taster i (buttons[i]) ausgeloest?
// Standard: Identität. Nach Kalibrierung werden hier passende Werte eingetragen.
int mapBtnToCell[9] = { 0,1,2, 3,4,5, 6,7,8 };
// -------------------- Spiel-Status --------------------
int board[9] = {0,0,0,0,0,0,0,0,0}; // 0=leer, 1=Gruen, 2=Rot
int currentPlayer = 1; // Start: Gruen
bool gameOver = false;
int lastWinPattern = -1; // -1 = keines
// -------------------- Farben --------------------
uint32_t colorGreen, colorRed, colorOff, colorDraw, colorCalib;
// -------------------- Entprellung --------------------
const unsigned long DEBOUNCE_MS = 30;
bool lastStableState[9];
bool lastReadState[9];
unsigned long lastChangeTime[9];
// -------------------- Prototypen --------------------
void makeMove(int cell);
void setRingColor(int ring, uint32_t color);
int checkWinner();
bool isDraw();
void showWinner(int player);
void showDraw();
void resetGame();
void highlightWinningLine(int patternIndex, uint32_t color, uint8_t repeats, uint16_t onMs, uint16_t offMs);
void printBoard();
void enterCalibration();
int waitForAnyButtonPress();
// -------------------- Setup --------------------
void setup() {
Serial.begin(115200);
delay(50);
Serial.println(F("\n[TicTacToe] Start"));
//lcd.init(); //initialize the lcd
//lcd.backlight(); //open the backlight
//lcd.setCursor(0, 0);
//lcd.print(F("Gruen spielt"));
strip.begin();
strip.show();
colorGreen = strip.Color(0,255,0);
colorRed = strip.Color(255,0,0);
colorOff = strip.Color(0,0,0);
colorDraw = strip.Color(255,255,255);
colorCalib = strip.Color(0,0,255); // Blau fuer Kalibrier-Hinweis
pinMode(resetButton, INPUT_PULLUP);
for (int i=0; i<9; i++) {
pinMode(buttons[i], INPUT_PULLUP);
lastStableState[i] = true; // ungedrueckt = HIGH = true
lastReadState[i] = true;
lastChangeTime[i] = 0;
}
printBoard();
}
// -------------------- Loop --------------------
void loop() {
// --- Reset-Button: Kurz vs. Lang (>=5s) ---
static unsigned long resetPressStart = 0;
static bool longCalibStarted = false;
//Spieler anzeige
if (digitalRead(resetButton) == LOW) {
if (resetPressStart == 0) {
resetPressStart = millis();
longCalibStarted = false;
}
// Langer Druck: Kalibrierung starten (nur einmal)
if (!gameOver && !longCalibStarted && (millis() - resetPressStart >= 5000UL)) {
Serial.println(F("[Calib] Reset >=5s gedrueckt -> Kalibrierung starten"));
longCalibStarted = true;
enterCalibration();
// Nach Kalibrierung: als "Losgelassen" behandeln
resetPressStart = 0;
return; // Loop abbrechen, damit keine Inputs direkt danach verarbeitet werden
}
} else {
// Loslassen
if (resetPressStart != 0 && !longCalibStarted) {
// Das war ein kurzer Druck -> normales Reset
Serial.println(F("[Btn] Kurz-Reset -> resetGame()"));
resetGame();
delay(200);
}
resetPressStart = 0;
longCalibStarted = false;
}
// Während Animation gesperrt
if (gameOver) return;
// Entprellter Tastenscan: Reagiere auf PRESS (HIGH -> LOW)
for (int i=0; i<9; i++) {
bool rawHigh = (digitalRead(buttons[i]) == HIGH); // HIGH=losgelassen, LOW=gedrueckt
if (rawHigh != lastReadState[i]) {
lastReadState[i] = rawHigh;
lastChangeTime[i] = millis();
}
if ((millis() - lastChangeTime[i]) >= DEBOUNCE_MS) {
if (lastStableState[i] != rawHigh) {
bool previous = lastStableState[i];
lastStableState[i] = rawHigh;
// PRESS erkannt: previous=true (HIGH), rawHigh=false (LOW)
if (previous == true && rawHigh == false) {
int cell = mapBtnToCell[i];
Serial.print(F("[Btn] PRESS auf Taster i=")); Serial.print(i);
Serial.print(F(" -> Zelle ")); Serial.println(cell);
if (board[cell] == 0) {
makeMove(cell);
} else {
Serial.println(F(" -> Feld bereits belegt, ignoriert."));
}
}
}
}
}
}
// -------------------- Spiellogik --------------------
void makeMove(int cell) {
board[cell] = currentPlayer;
// LED fuer diese Zelle
if (currentPlayer == 1) {
setRingColor(cell, colorGreen);
} else {
setRingColor(cell, colorRed);
}
Serial.print(F("[Move] Spieler "));
Serial.print(currentPlayer == 1 ? F("GRUEN") : F("ROT"));
Serial.print(F(" setzt Zelle ")); Serial.println(cell);
printBoard();
// Gewinner pruefen (vor Spielerwechsel!)
int winner = checkWinner(); // setzt lastWinPattern
if (winner != 0) {
Serial.print(F("[Win] Spieler "));
Serial.print(winner == 1 ? F("GRUEN") : F("ROT"));
Serial.print(F(" gewinnt! (Pattern "));
Serial.print(lastWinPattern);
Serial.println(F(")"));
//lcd.setCursor(0, 0);
//lcd.print(winner == 1 ? F("Gruen gewinnt!") : F("Rot gewinnt!"));
showWinner(winner); // animiert und resettet
return;
}
// Unentschieden?
if (isDraw()) {
Serial.println(F("[Draw] Unentschieden erkannt."));
//lcd.setCursor(0, 0);
//lcd.print(F("Unentschieden!"));
showDraw(); // animiert und resettet
return;
}
// Spielerwechsel
currentPlayer = (currentPlayer == 1) ? 2 : 1;
if (currentPlayer == 1) {
setRingColor(9, colorGreen);
} else {
setRingColor(9, colorRed);
}
Serial.print(F("[Next] Naechster Spieler: "));
Serial.println(currentPlayer == 1 ? F("GRUEN") : F("ROT"));
//lcd.setCursor(0, 0);
//lcd.print(currentPlayer == 1 ? F("Gruen spielt") : F("Rot spielt"));
}
// -------------------- LEDs --------------------
void setRingColor(int ring, uint32_t color) {
int startLED = ring * LEDS_PER_RING;
for (int i=0; i<LEDS_PER_RING; i++) {
strip.setPixelColor(startLED + i, color);
}
strip.show();
}
// -------------------- Gewinn-Check --------------------
int checkWinner() {
// Pattern 5 = {2,5,8} -> rechte Spalte / rechte Vertikale
static const int wins[8][3] = {
{0,1,2}, {3,4,5}, {6,7,8}, // Reihen
{0,5,6}, {1,4,7}, {2,3,8}, // Spalten
{0,4,8}, {2,4,6} // Diagonalen
};
lastWinPattern = -1;
for (int i=0; i<8; i++) {
int a = wins[i][0];
int b = wins[i][1];
int c = wins[i][2];
if (board[a] != 0 &&
board[a] == board[b] &&
board[b] == board[c]) {
lastWinPattern = i;
return board[a]; // 1=Gruen, 2=Rot
}
}
return 0;
}
// -------------------- Unentschieden --------------------
bool isDraw() {
for (int i=0; i<9; i++) {
if (board[i] == 0) return false;
}
return true;
}
// -------------------- Animationen --------------------
void showWinner(int player) {
gameOver = true;
uint32_t color = (player == 1) ? colorGreen : colorRed;
// Gewinnlinie 1x highlighten (hilft auch beim Debuggen)
if (lastWinPattern >= 0) {
highlightWinningLine(lastWinPattern, color, 1, 600, 150);
}
// Dann gesamtes Feld blinken (6x ~3s)
for (int j=0; j<6; j++) {
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, color);
strip.show();
delay(300);
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, 0);
strip.show();
delay(300);
}
resetGame(); // Automatischer Reset direkt nach der Animation
}
void showDraw() {
gameOver = true;
for (int j=0; j<5; j++) {
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, colorDraw);
strip.show();
delay(300);
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, 0);
strip.show();
delay(300);
}
resetGame(); // Automatischer Reset direkt nach der Animation
}
void highlightWinningLine(int patternIndex, uint32_t color, uint8_t repeats, uint16_t onMs, uint16_t offMs) {
static const int wins[8][3] = {
{0,1,2}, {3,4,5}, {6,7,8},
{0,3,6}, {1,4,7}, {2,5,8},
{0,4,8}, {2,4,6}
};
for (uint8_t r=0; r<repeats; r++) {
// An
for (int k=0; k<3; k++) setRingColor(wins[patternIndex][k], color);
delay(onMs);
// Aus
for (int k=0; k<3; k++) setRingColor(wins[patternIndex][k], colorOff);
delay(offMs);
}
}
// -------------------- Reset --------------------
void resetGame() {
for (int i=0; i<9; i++) board[i] = 0;
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, 0);
strip.show();
currentPlayer = 1;
gameOver = false;
lastWinPattern = -1;
//lcd.setCursor(0, 0);
//lcd.print(F("Gruen spielt"));
Serial.println(F("[Reset] Neues Spiel. Start: GRUEN"));
printBoard();
}
// -------------------- Kalibrierung --------------------
void enterCalibration() {
Serial.println(F("[Calib] *** Kalibrierung gestartet ***"));
Serial.println(F("[Calib] Es wird immer eine Zelle beleuchtet."));
Serial.println(F("[Calib] Druecke den PHYSISCHEN Taster, der diese Zelle ausloest."));
Serial.println(F("[Calib] Reihenfolge: 0..8 (links->rechts, oben->unten)"));
// Alles aus
for (int i=0; i<TOTAL_LEDS; i++) strip.setPixelColor(i, 0);
strip.show();
delay(150);
// Fuer jede Zelle 0..8 den passenden Button erfassen
for (int cell=0; cell<9; cell++) {
// Zielzelle blau anzeigen
setRingColor(cell, colorCalib);
Serial.print(F("[Calib] Zelle ")); Serial.print(cell);
Serial.println(F(" leuchtet -> Bitte passenden Taster druecken..."));
int btnIndex = waitForAnyButtonPress();
// btnIndex -> cell
mapBtnToCell[btnIndex] = cell;
// Bestaetigen (kurz gruen)
setRingColor(cell, colorGreen);
delay(300);
setRingColor(cell, colorOff);
delay(150);
}
// Mapping ausgeben (zum Copy&Paste)
Serial.println(F("[Calib] Fertig. mapBtnToCell lautet:"));
Serial.print(F("int mapBtnToCell[9] = { "));
for (int i=0; i<9; i++) {
Serial.print(mapBtnToCell[i]);
if (i<8) Serial.print(", ");
}
Serial.println(F(" };"));
Serial.println(F("[Calib] Diese Zeile kannst du oben in den Code uebernehmen."));
Serial.println(F("[Calib] *** Kalibrierung beendet ***"));
// Nach der Kalibrierung am besten einmal resetten:
resetGame();
}
int waitForAnyButtonPress() {
// Warte auf stabile PRESS-Flanke eines beliebigen Tasters
while (true) {
for (int i=0; i<9; i++) {
if (digitalRead(buttons[i]) == LOW) { // gedrueckt
// einfaches Debounce
delay(20);
if (digitalRead(buttons[i]) == LOW) {
// warte auf Loslassen, damit nur 1x gezaehlt wird
while (digitalRead(buttons[i]) == LOW) { delay(5); }
Serial.print(F(" -> erkannt: Taster i=")); Serial.println(i);
return i;
}
}
}
}
}
// -------------------- Debug --------------------
void printBoard() {
Serial.println(F("Board:"));
for (int r=0; r<3; r++) {
Serial.print(F(" "));
for (int c=0; c<3; c++) {
Serial.print(board[r*3 + c]); Serial.print(' ');
}
Serial.println();
}
}