Tic Tac Toe mit 2,4“ Touchdisplay

Als Hobbybastler und Vater will man nicht immer nur Nützliches mit seinem Elektronikequipment bauen, sondern will auch ein bisschen Spaß haben bzw. die staunenden Gesichter der Kinder sehen. Warum also nicht das Hobby mal mit Spaß verbinden und eine Runde Tic Tac Toe gegen einen Arduino spielen? Damit kann man sich und die Kinder dann eine ganze Weile beschäftigen. Mehr als ein 2,4“ TFT LCD Touchdisplay und ein Arduino Uno mit Stromversorgung sind dafür nicht nötig.

Der Ablauf

Die Spielregeln sind relativ einfach, eine Reihe von drei X- oder O-Zeichen verhelfen einem zum Sieg, interessanter jedoch ist, wie das Programm später funktionieren soll.

Beim ersten Start der Software werden Sie mit dem Startscreen, siehe Abbildung 1, begrüßt.

Abbildung 1: Startscreen Tic Tac Toe


Sie haben dann die Wahl zwischen einem einfachen „Easy“ oder einem schweren „Hard“ Spiel gegen den Arduino. Bei dem einfachen Spiel starten Sie mit dem ersten Zug und der Arduino wählt zufällig aus, wo sein nächster Zug sein wird. Primär geht es im einfachen Spiel dem Arduino nicht darum, zu gewinnen oder Sie am Sieg zu hindern.

Im schweren Spiel hingegen darf der Arduino beginnen und wird versuchen, sie vom Gewinnen abzuhalten und gleichzeitig versuchen, so schnell wie möglich selbst zu gewinnen. Der Knackpunkt beim ersten Zug vom Arduino in der schweren Variante besteht darin, dass nicht immer die Mitte, was einen strategischen Vorteil bietet, sondern ab und zu auch mal eine zufällige Position ausgewählt wird.

Nach jedem Zug prüft der Arduino, ob es schon einen Sieger gibt oder die möglichen neun Züge verspielt wurden. Ist das Spiel vorbei, wird geprüft, wer gewonnen hat, und der entsprechende EndScreen wird angezeigt. Um Schummeln zu vermeiden, kann immer nur ein leeres Feld ausgewählt werden, wie es auch bei der Papiervariante der Fall ist.

Benötigte Hardware

Die Hardware für diesen kleinen Zeitvertreib können Sie bei AZ-Delivery oder aber bei Amazon bestellen.

Anzahl Bauteil Anmerkung
1 Arduino Uno
1 2,4“ TFT LCD Touchdisplay
Tabelle 1: Benötigte Hardware für das Projekt


Generell können Sie auch ein anderes Touchdisplay benutzen, jedoch müssen Sie dann auf das Pinout des eingesetzten Touchdisplays achten und ggf. den Quellcode modifizieren.

Bevor Sie anfangen, stecken Sie das 2,4“ TFT LCD Touchdisplay auf den Arduino Uno.

Benötigte Software

Die benötigte Software für dieses Projekt ist auch überschaubar:

  • Arduino IDE , hier am besten die aktuellste Version herunterladen
  • Die Bibliothek MCUFriend_kbv mit allen Abhängigkeiten, die Sie über die Bibliotheksverwaltung installieren müssen.

Wie das geht, zeigen wir u.a. hier

Kalibrierungsdaten vom Display ermitteln

Bevor Sie nun den Quellcode für das Spiel übernehmen, müssen Sie noch eine Displaykalibrierung durchführen, damit die Positionsangaben beim Berühren vom Display auch korrekt sind. Damit Sie nun nicht lange suchen müssen, hat der Autor der MCUFriend_kbv-Bibliothek ein entsprechendes Beispiel mit dem Namen „TouchScreen_Calibr_native“ bereitgestellt, siehe Abbildung 2.

Wichtig ist, dass Sie bitte den Seriellen Monitor der Arduino IDE starten, da Sie hier Werte kopieren müssen.

Abbildung 2: Arduino Beispiel Touch-Kalibrierung öffnen


Folgen Sie den Anweisungen auf dem Touchdisplay, drücken und halten Sie die angezeigten Positionsmarker, die weiß markiert sind. Haben Sie alle Positionsmarken durch, wird Ihnen auf dem Touchdisplay die Kalibrierung vom Display ausgegeben, siehe Abbildung 3. Hier wird unterschieden zwischen Hochkant „Portrait“ und Querformat „Landscape“.

Für das Spiel brauchen Sie die Daten für die seitliche „Landscape“ Ausrichtung.

 Abbildung 3: Kalibrierung Touchdisplay durchführen


Diese Werte müssen Sie nun nicht abschreiben, sondern können die benötigten Werte aus dem Seriellen Monitor kopieren, siehe Abbildung 4.

Abbildung 4: Kalibrierungsausgabe im Seriellen Monitor


Wichtig dabei sind die beiden Zeilen unter „*** COPY_PASTE from Serial Terminal“ und die beiden Zeilen unter „LANDSCAPE CALIBRATION“. Die ersten Werte sind das Pinout, um das Touchdisplay anzusteuern, die unteren Werte das Mapping, um die korrekte Pixelposition vom Display zu erhalten.

Was ist Mapping?

Bevor Sie lange googlen, was „Mapping“ bedeutet, möchte ich Ihnen diese Frage gerne anhand von unserem Touchdisplay erläutern. Sie erhalten beim Drücken auf das Touchdisplay Informationsdaten über die Position und den Druck. Diese Werte spiegeln aber nicht die korrekten Pixelkoordinaten des Displays wider, sondern die Widerstandswerte vom Display.

Genau an dieser Stelle kommt das Mapping zum Einsatz. Die ermittelten Grenzwerte werden genutzt, um die X- und Y-Pixelkoordinaten vom Display zu errechnen. Die genaue Funktion von map können Sie auch in der Referenz von Arduino unter https://www.arduino.cc/reference/de/language/functions/math/map/ nachlesen.

Anpassung im Quellcode

Wenn Sie den Quellcode vom Spiel öffnen, finden Sie in Zeile 19 den Kommentar „// COPY-PASTE from Serial Terminal TouchScreen_Calibr_native“. Die beiden Zeilen dahinter müssen nun von Ihnen durch Ihre kopierten Werte hinter „*** COPY_PASTE from Serial Terminal“ der Kalibrierung ersetzt werden. Damit wird das Touchdisplay korrekt angesteuert. Sollten Sie das oben genannte Touchdisplay nutzen, so müssen Sie an dieser Stelle erst einmal nichts verändern müssen.

In der Funktion bool Touch_getXY(void), siehe Zeile 38 im Quellcode, finden Sie den Kommentar „//Update mapping from calibration“. Direkt danach sehen Sie das Mapping für die X- und Y-Koordinate vom Touchdisplay. Ersetzen Sie an dieser Stelle bitte die vorhandenen Werte mit Ihren kopierten Werten vom Seriellen Monitor der „LANDSCAPE CALIBRATION“, wobei Sie nur die Werte für LEFT, RT, TOP, BOT innerhalb der Klammern ersetzen sollten und nicht den gesamten Text, siehe Abbildung 5.

Abbildung 5: Kalibrierungsdaten für Mapping übernehmen


Der Quellcode

Da der Quellcode über 600 Zeichen hat und Sie zusätzlich noch eine weitere Datei für das Generieren vom X- und O- Symbol benötigen, soll an dieser Stelle nur der Ablauf erläutert werden. Den Quellcode können sie unter https://github.com/M3taKn1ght/Blog-Repo/tree/master/TicTacToe downloaden.

Am Anfang vom Quellcode wird ein Objekt der Klasse TouchScreen erzeugt, siehe Zeile 22 im Quellcode. An dieser Stelle werden die Pins für Kommunikation und der Erfassung der analogen Werte vom Touchdisplay übergeben.

In der Setup-Funktion werden der Serielle Monitor und das Display gestartet, gleichzeitig aber auch der Startscreen samt Buttons erzeugt. Interessant sind dabei die Zeilen 86 und 88, in denen das Aussehen der beiden Buttons vergeben wird, siehe Abbildung 6.

Abbildung 6: Initialisierung der beiden Buttons


Die drei angegebenen Farben definieren sich wie folgt. Die erste Farbe ist die Rahmenfarbe, die zweite Farbe ist die Füllfarbe und die letzte Farbe ist die Textfarbe. Hier können Sie gerne ein eigenes Design kreieren.

In der loop-Funktion wird überwacht, ob der Spieler eine Eingabe auf dem Display macht und an welcher Position. Dabei wird die Funktion Touch_getXY() aufgerufen, welche die entsprechenden Koordinaten über das Mapping ermittelt und in die globalen Variablen pixel_x und pixel_y schreibt, siehe Code 1.

//Check in "main-menu" if human select a game
void loop() {
unsigned long currentMillis = millis();
if(currentMillis - previousMillis >= interval )
{
previousMillis = currentMillis;
if(iEnableButtons)
{
bPressed = Touch_getXY();
if(bPressed)
Serial.println("X: " + String(pixel_x) + " Y: " + String(pixel_y));
btnEasy.press(bPressed && btnEasy.contains(pixel_x,pixel_y));
if (btnEasy.justPressed()){
//btnEasy.drawButton(true);
Serial.println("Easy game");
ResetGame();
playGame(false); //Easy Mode
}
btnHard.press(bPressed && btnHard.contains(pixel_x,pixel_y));
if (btnHard.justPressed()){
//btnHard.drawButton(true);
Serial.println("Hard game");
ResetGame();
playGame(true); //Hard Mode
      }
    }
  }
}

 Code 1: Loop-Funktion des Programms


Sollte die Eingabe auf einem der beiden Buttons „Easy“ und „Hard“ sein, so werden das Spielfeld über die Funktion ResetGame() generiert und die beiden Buttons für einfaches oder schwieriges Spiel deaktiviert, siehe Abbildung 7.

Abbildung 7: Spielfeld im simplen weiß


Auch hier haben Sie die Möglichkeit, in der Funktion ResetGame() dem Rahmen und allen vertikalen und horizontalen Linien eine eigene Farbe zu geben. Wie wäre es denn z.B. mit einem bunten Spielfeld, siehe Abbildung 8?

Abbildung 8: Buntes Spielfeld


Neben dem Generieren des Spielfelds wird auch die Funktion playGame(bool) aufgerufen. Diese Funktion bekommt über das Boolflag mitgegeben, ob der Spieler ein einfaches oder schweres Spiel spielen möchte und durchläuft die entsprechende Schleifenbedingung. Damit vom Quellcode nicht immer über die loop-Funktion in die Funktion playGame gesprungen werden muss, wird eine Do-While-Schleife genutzt. Diese hat zur Bedingung, dass noch kein Gewinner feststeht und nicht mehr als 9 Züge gemacht wurde, siehe Code 2.

//Start a loop to play game
void playGame(bool bHardMode)
{
do
{
Serial.println("MOVE: " + String(iMoves)); //Print move
if(!bHardMode) //Easy-Mode
{
if(iMoves % 2 == 1)
{
movePlayer();
printBoard();
checkWinner();
}
else
{
moveArduino(false);
printBoard();
checkWinner();
  }
}
else //Hard-Mode
{
if(iMoves % 2 == 1)
{
moveArduino(true);
printBoard();
checkWinner();
}
else
{
movePlayer();
printBoard();
checkWinner();
  }
}
iMoves++;
}
while(iWinner == 0 && iMoves < 10);
//End of game, cause somebody win or no moves left
if(iWinner == 1)
{
Serial.println("CPU WINS");
delay(3000);
drawGameEndScreen();
}
else if(iWinner == 2)
{
Serial.println("HUMAN WINS");
delay(3000);
drawGameEndScreen();
}
else
{
Serial.println("DRAW");
delay(3000);
drawGameEndScreen();
  }
}

 Code 2: Ablauf von playGame(bool)

 
Das Schema ist in dieser Schleife bei beiden Modi fast identisch:

  1. Spielzug Mensch / Arduino auf Durchführbarkeit prüfen
  2. Spielzug auf dem Display darstellen
  3. Ausgabe auf dem Seriellen Monitor der Spielfeldbelegung
  4. Prüfen, ob es einen Gewinner gibt oder nicht

Bei der Eingabe vom Menschen kommt in der Funktion movePlayer() wieder das Mapping vom Touchdisplay zum Einsatz. Ebenfalls wird über eine Do-While-Schleife gewartet, bis der Spieler einen gültigen Zug gemacht hat, siehe Code 3.

 //Get input from player and check if move is possible
void movePlayer()
{
bool bValidMove = false;
Serial.println("Players move");
do
{
bPressed = false;
bPressed = Touch_getXY();
if(bPressed)
{
Serial.println("X: " + String(pixel_x) + " Y: " + String(pixel_y));
if((pixel_x<115)&& (pixel_y>=150)) //6
{
if(iSetMark[6]==0)
{
Serial.println("Player Move: 6");
iSetMark[6] = 2;
bValidMove = true;
drawPlayerMove(6);
Serial.println("Drawing player move");
}
}
else if((pixel_x>0 && pixel_x<115)&& (pixel_y<150 && pixel_y>80)) //3
{
if(iSetMark[3]==0)
{
Serial.println("Player Move: 3");
iSetMark[3] = 2;
bValidMove = true;
drawPlayerMove(3);
Serial.println("Drawing player move");
}
}
else if((pixel_x<125)&& (pixel_y<80)) //0
{
if(iSetMark[0]==0)
{
Serial.println("Player Move: 0");
iSetMark[0] = 2;
bValidMove = true;
drawPlayerMove(0);
}
}
else if((pixel_x>125 && pixel_x<=195)&& (pixel_y<80)) //1
{
if(iSetMark[1]==0)
{
Serial.println("Player Move: 1");
iSetMark[1] = 2;
bValidMove = true;
drawPlayerMove(1);
}
}
else if((pixel_x>195)&& (pixel_y<80)) //2
{
if(iSetMark[2]==0)
{
Serial.println("Player Move: 2");
iSetMark[2] = 2;
bValidMove = true;
drawPlayerMove(2);
}
}
else if((pixel_x>125 && pixel_x<=195)&& (pixel_y<150 && pixel_y>80)) //4
{
if(iSetMark[4]==0)
{
Serial.println("Player Move: 4");
iSetMark[4] = 2;
bValidMove = true;
drawPlayerMove(4);
}
}
else if((pixel_x>195)&& (pixel_y<150 && pixel_y>80)) //5
{
if(iSetMark[5]==0)
{
Serial.println("Player Move: 5");
iSetMark[5] = 2;
bValidMove = true;
drawPlayerMove(5);
}
}
else if((pixel_x>125 && pixel_x<=195)&& (pixel_y>150)) //7
{
if(iSetMark[7]==0)
{
Serial.println("Player Move: 7");
iSetMark[7] = 2;
bValidMove = true;
drawPlayerMove(7);
}
}
else if((pixel_x>195)&& (pixel_y>150)) //8
{
if(iSetMark[8]==0)
{
Serial.println("Player Move: 8");
iSetMark[8] = 2;
bValidMove = true;
drawPlayerMove(8);
}
}
}
}while(!bValidMove);
}

Code 3: Funktion movePlayer()

Bei der Funktion moveArduino(bool) übergibt das Boolflag der Funktion den Spielmodus, einfaches oder schweres Spiel. Beim einfachen Spiel wird ein zufälliger Zahlenwert von 0 bis 8 generiert und geprüft, ob dieses Spielfeld schon einmal gespielt wurde. Sollte das der Fall sein, so wird die Do-While-Schleife solange durchlaufen, bis eine Zufallszahl generiert wurde, die auf dem Spielfeld noch nicht gewählt wurde, siehe Code 4.

Bei der Spieloption schwer passieren gleich mehrere Dinge. Zunächst gehört der erste Spielzug dem Arduino, der mittels eines Modulo entscheidet, ob direkt die Position 4, also Mitte oder ein zufälliges Feld vom Arduino gewählt wird, siehe Code 4 im Quellcode. Bei allen weiteren Zügen wird immer geprüft, ob der Spieler am Gewinnen gehindert werden muss oder aber der Arduino selbst gewinnen kann.

 //Function to let Arduino make a move
void moveArduino(bool bHardMode)
{
bool bValidMove = false;
int iRandomMove;
Serial.println("Arduino move");
//Hard mode
if(bHardMode)
{
//First move Arduino draw direct to middle or sometimes other pos
if(iMoves == 1)
{
if(millis() % 8 == 0) //Thats why other position is possible
{
iRandomMove = random(9);
iSetMark[iRandomMove] = 1;
Serial.println("Arduino Move: " + String(iRandomMove));
drawArduinoMove(iRandomMove);
}
else
{
iSetMark[4] = 1;
Serial.println("Arduino Move: " + String(4));
drawArduinoMove(4);
}
}
else
{
int iNextMove = checkPlayerMove();
int iWinMove = checkPossibleWin();
Serial.println("Check player: " + String(iWinMove));
if(iWinMove >= 0)
{
delay(1000);
iSetMark[iWinMove] = 1;
Serial.println("Arduino Move: " + String(iWinMove));
drawArduinoMove(iWinMove);
}
else
{
if(iNextMove >= 0)
{
iSetMark[iNextMove] = 1;
Serial.println("Arduino Move: " + String(iNextMove));
drawArduinoMove(iNextMove);
}
else
{
do{
iRandomMove = random(9);
if(iSetMark[iRandomMove] == 0)
{
delay(1000);
iSetMark[iRandomMove] = 1;
Serial.println("Arduino Move: " + String(iRandomMove));
drawArduinoMove(iRandomMove);
bValidMove = true;
}
}while(!bValidMove);
}
}
} //else
} //if(bHardMode)
else //Easy Mode
{
do{
iRandomMove = random(9);
if(iSetMark[iRandomMove] == 0)
{
delay(1000);
iSetMark[iRandomMove] = 1;
Serial.println("Arduino Move: " + String(iRandomMove));
drawArduinoMove(iRandomMove);
bValidMove = true;
}
}while(!bValidMove);
}
}

Code 4: Funktion moveArduino (bool)

In beiden Fällen wird eine Zeichenfunktion, drawPlayerMove(int) bzw. drawArduinoMove(int), aufgerufen. Der Integer ist dabei die Spielfeldposition, die in eine X- und Y-Koordinate auf dem Display übersetzt werden muss. Die jeweilige Funktion ruft wiederum die Zeichenfunktion drawSign() auf, welche das X- bzw. O-Zeichen an die entsprechende Stelle im Spielfeld zeichnet.

Sie werden sich nun fragen, was das x_bitmap bzw. circle bei den beiden vorherig genannten Funktionen ist.

Hier kommt die Datei graphics.c zum Einsatz, in der für das X- bzw. O-Zeichen ein Array hinterlegt ist. Diese beiden Arrays werden in der Zeile 23 und 24 in der Variable circle und x_bitmap geladen, siehe Abbildung 9.

Abbildung 9: Array für die Zeichen laden


Das Schlagwort extern, verweist dabei auf eine externe C-Datei, die sich im gleichen Projektordner wie die *.ino-Datei befinden muss.

Danach wird das Zeichen in der Funktion drawSign() Bit für Bit ausgelesen und mit der angegebenen Farbe an der richtigen X- und Y-Koordinate vom Touchdisplay eingefügt.

Wollen Sie auch hier individuell sein, so können Sie in der Funktion drawPlayerMove(int) bzw. drawArduinoMove(int), siehe Zeile 563 bis 596 im Quellcode, entsprechend die Farbe vom Zeichen verändern.

Damit ist der größte Teil vom Programm erklärt.

Punkt 3 ist eine simple Ausgabe mittels For-Schleife auf dem Seriellen Monitor. Punkt 4 der obigen Liste, wird über die Funktion checkWinner() realisiert, die jede Kombination für eine Gewinnerreihe prüft, ob Sie oder der Arduino eine Reihe hat und entsprechend die globale Variable iWinner überschreibt.

Sobald iWinner überschrieben oder 9 Züge erreicht wurden, springt das Programm aus der Do-While-Schleife von playGame(bool) und verkündet mit der Funktion drawGameEndScreen() den Gewinner oder das Unentschieden (engl. draw), siehe Abbildung 10. Damit Sie vorher noch sehen, warum Sie oder der Arduino gewonnen haben, wird eine Verzögerung von 3 Sekunden angesetzt. Gleiches gilt auch bei einem Unentschieden.

Abbildung 10: Endscreen mit Gewinner


Mit dem GameEnd-Screen werden auch wieder die beiden Buttons zur Auswahl von einem leichten oder schweren Spiel eingeblendet.

Gerne können Sie auch nach Lust und Laune den Quellcode modifizieren. Denkbar wäre, dass der Schwierigkeitsgrad schwer ein bisschen einfacher wird, indem ab und zu der Arduino eher einen zufälligen Zug macht, anstatt immer zu versuchen Sie zu blocken. Hierzu müssten Sie ggf. wieder mit einem Modulo arbeiten.

Sie können aber auch einfach an den aufgezeigten Stellen die Farbe der Elemente verändern, um so einen individuellen Touch zu erhalten.

Ich wünsche Ihnen viel Spaß beim Nachbau.

Dieses und weitere Projekte finden sich auf GitHub unter https://github.com/M3taKn1ght/Blog-Repo

MuestraPara arduinoProyectos para principiantes

6 comentarios

Jörn Weise

Jörn Weise

Hallo Herr Sternberg,
ich muss zugeben, ich habe bisher das Display nicht weiter verwendet, aber genau Ihre Anfrage beim Adventskalender 2020 umgesetzt. Gucken Sie doch mal unter https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/das-neunte-turchen
Mit dem neuem az-Wandmod (ich empfehle den mit dem 2,8"-Display) haben Sie gleich alle Bauteile und müssen nur noch den passenden Controller verbauen.
Gruß
Weise

Konrad Sternberg

Konrad Sternberg

Hallo Herr Weise,

Danke für dieses Tutorial. Meine Tochter ist hin und weg. Bisher hat Sie den Arduino Uno immer mit Schule und “lästig” zusammen gebracht.

Auch ich bin daran interessiert, wie man dieses Display an einen ESP8266 oder ESP32 anbringt. Wenn es da Neuigkeiten gibt bitte melden.

Danke

Konrad Sternberg

Jörn Weise

Jörn Weise

Hallo Thomas,
ja ich kann verstehen, dass Sie das etwas frustriert. Aktuell suche ich noch das Pinout vom Display, dann kann ich mal auf meiner GitHub-Seite eine angepasste Version für einen ESP32- NodeMCU machen. So wie ich das sehe, braucht es für den Touch zwei analoge Eingänge.
Wie gesagt, ich gucke es mir mal an und werde dann hier noch einmal berichten.
Gruß
Jörn Weise

Thomas

Thomas

Hallo,
versuche , das Ganze mit einem ESP32 laufen zu lassen. Schon beim Programm “TouchScreen_Calibr_native” klemmt es. Der erste Text wird angezeigt, aber ein “touch” funktioniert nicht. Wenn immerhin die Ausgabe auf dem Display funktioniert, dürfte ja nur noch irgendwas mit dem touch falsch beschaltet oder konfiguriert sein. Gibt es irgendwo ein Layout für die Beschaltung eines ESP32 mit dem Display? Oder ist in einem Headerfile noch etwas anders zu definieren?
Ich habe die Ausgaben jeder Art mit den Beispielen aus TFTeSPI mit dem 2,4"-Display (parallel) genutzt. In der zu konfigurierenden Datei user_setup.h ist so gut wie nichts in Richtung Touch anzupassen, was mich schon stutzig macht (bei Nutzung eines SPI-Display ist einiges zu finden).

Würde mich über ein paar Hinweise freuen…

Grüße
Thomas

Jörn Weise

Jörn Weise

Hallo Jochen,
die unten angegebenen Meldungen kommen, wenn Sie vergessen haben die graphics.c im selben Ordner wie die TicTacToe.ino zu kopieren. Einfach die fehlende graphics.c ins Projektverzeichnis von TicTacToe kopieren und das kompilieren sollte problemlos laufen.

Jochen

Jochen

Hallo, bin wie in der Anleitung beschrieben vorgegangen, mit Kalibrierung und Anpassung des Code.
Ich bekomme diese Fehlermeldungen. Können Sie helfen?
Vielen Danke und Gruß, Jochen

C:\Users\info\AppData\Local\Temp\cccxEXye.ltrans0.ltrans.o: In function `drawArduinoMove’:
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:601: undefined reference to `x_bitmap’
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:601: undefined reference to `x_bitmap’
C:\Users\info\AppData\Local\Temp\cccxEXye.ltrans0.ltrans.o: In function `drawPlayerMove’:
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:582: undefined reference to `circle’
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:582: undefined reference to `circle’
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:579: undefined reference to `circle’
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:579: undefined reference to `circle’
C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:576: undefined reference to `circle’
C:\Users\info\AppData\Local\Temp\cccxEXye.ltrans0.ltrans.o:C:\Users\info\Documents\Arduino\TicTacToe\TicTacToe/TicTacToe.ino:576: more undefined references to `circle’ follow
collect2.exe: error: ld returned 1 exit status
exit status 1
Fehler beim Kompilieren für das Board Arduino Uno.

Deja un comentario

Todos los comentarios son moderados antes de ser publicados

Artículos de blog

  1. Ahora instalamos el esp32 a través de la administración.
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. Transporte Aéreo - programación de ESP mediante redes locales inalámbricas