Tic Tac Toe mit 2,4“ Touchdisplay

As a hobbyist and father, you don't always want to build something useful with your electronics equipment, but you also want to have a bit of fun or see the amazed faces of your children. So why not combine your hobby with fun and play a round of Tic Tac Toe against an Arduino? You can keep yourself and the kids busy for quite a while. All you need is a 2.4" TFT LCD touch display and an Arduino Uno with power supply.

The process

The game rules are relatively simple, a number of three X or O characters help to victory, more interesting, however, how the program should work later.

At the first start of the software you will be welcomed with the startscreen, see Figure 1.

Figure 1: Startscreen Tic Tac Toe


You then have the choice between a simple "Easy" or a hard "Hard" game against the Arduino. In the easy game, you start with the first move and the Arduino randomly chooses where its next move will be. Primarily, in the easy game the Arduino is not concerned with winning or preventing you from winning.

In the hard game, however, the Arduino gets to start and will try to keep them from winning while trying to win as quickly as possible itself. The crux of the Arduino's first move in the hard variant is that it doesn't always pick the center, which gives it a strategic advantage, but occasionally picks a random position.

After each move, the Arduino checks whether there is already a winner or whether the possible nine moves have been gambled away. When the game is over, the Arduino checks who has won and displays the corresponding EndScreen. To avoid cheating, only one empty field can be selected at a time, as is the case with the paper version.

Required hardware

The hardware for this little pastime can be ordered at AZ-Delivery or at Amazon.

Number Component Annotation
1 Arduino Uno
1 2.4 "TFT LCD TouchDisplay
Table 1: Hardware required for the project


In general, you can also use a different touch display, but then you must pay attention to the pinout of the touchdisplay used and, if necessary, modify the source code.

Before you start, plug the 2.4 "TFT LCD TouchDisplay on the Arduino Uno.

Required software

The software required for this project is also manageable:

  • Arduino IDE , here best download the latest version
  • The library Mcufriend_kbv With all the dependencies you need to install via library management.

How that works, we show u.a. here

Determine calibration data from display

Before you now take over the source code for the game, you must still carry out a display calibration, so that the position indications are also correct when touching from the display. So that you don't have to search for a long time, the author of the MCUFriend_kbv library has provided a corresponding example with the name "TouchScreen_Calibr_native", see figure 2.

It is important that you please start the Serial Monitor of the Arduino IDE, because you have to copy values here.

Figure 2: Arduino Example Touch Calibration Open


Follow the instructions on the touch display, press and hold the displayed position markers, which are marked in white. Once you have passed all the position markers, the calibration of the display is shown on the touch display, see Figure 3. A distinction is made here between portrait and landscape format.

For the game, you need the data for the lateral "Landscape" orientation.

Figure 3: Perform Touch Display Calibration


You do not have to write off these values, but can copy the required values ​​from the serial monitor, see Figure 4.

Figure 4: Calibration output in the serial monitor


Important are the two lines under "*** Copy_paste from Serial Terminal" and the two lines under "Landscape Calibration". The first values ​​are the pinout to control the touch display, the lower values ​​the mapping to obtain the correct pixel position from the display.

What is mapping?

Before you spend a long time googling what "mapping" means, I would like to explain this question to you using our touch display. When you press the touch display, you receive information data about the position and the pressure. However, these values do not reflect the correct pixel coordinates of the display, but the resistance values from the display.

This is exactly where the mapping is used. The determined limit values ​​are used to calculate the X and Y pixel coordinates from the display. The exact function of MAP can also be found in the reference of Arduino under https://www.arduino.cc/reference/de/language/functions/math/map/ Read.

Source code customization

If you open the source code from the game, you will find the comment "// COPY-PASTE from Serial Terminal TouchScreen_Calibr_native" in line 19. The two lines behind it must now be replaced by you with your copied values behind "*** COPY_PASTE from Serial Terminal" of the calibration. Now the touch display is controlled correctly. If you use the touch display mentioned above, you do not have to change anything at this point.

In the function bool Touch_getXY(void), see line 38 in the source code, you will find the comment "//Update mapping from calibration". Directly after that you see the mapping for the X- and Y-coordinate from the touch display. At this point please replace the existing values with your copied values from the serial monitor of the "LANDSCAPE CALIBRATION", where you should only replace the values for LEFT, RT, TOP, BOT inside the brackets and not the whole text, see figure 5.

Figure 5: Apply calibration data for mapping


The source code

Since the source code has over 600 characters and you also need another file for the generation of the X and O symbol, only the procedure will be explained here. You can download the source code under https://github.com/M3taKn1ght/Blog-Repo/tree/master/TicTacToe Download.

At the beginning of the source code an object of the TouchScreen class is created, see line 22 in the source code. At this point the pins for communication and the acquisition of analog values from the touch display are passed.

In the setup function the serial monitor and the display are started, but at the same time the start screen including buttons is created. Interesting are the lines 86 and 88, in which the appearance of the two buttons is assigned, see Figure 6.

Figure 6: Initialization of the two buttons


The three specified colors are defined as follows. The first color is the frame color, the second color is the fill color and the last color is the text color. You are welcome to create your own design here.

In the loop function it is monitored if the player makes an input on the display and at which position. Thereby the function Touch_getXY() is called, which determines the corresponding coordinates via the mapping and writes them into the global variables pixel_x and pixel_y, see 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 function of the program

If the input should be on one of the two buttons "Easy" and "Hard", the game field is generated via the function ResetGame() and the two buttons for easy or difficult game are deactivated, see Figure 7.

Figure 7: Play field in the simple white


Here, too, you have the option of giving the frame and all vertical and horizontal lines their own color in the ResetGame() function. How about a colorful game field, for example, see Figure 8?

Figure 8: Colorful playing field


In addition to generating the playing field, the playgame (BOOL) function is called. This feature gives you the Boolflag, whether the player wants to play a simple or heavy game and goes through the appropriate loop condition. So that the source code does not always have to jump through the LOOP function in the PLAYGAME function, a DO-While loop is used. This has condition that no winner has yet been established and not more than 9 trains, see 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
{
Moveearduino (False);
Printboard ();
Checkwinner ();
  }
}
else // hard-mode
{
if (iMoves% 2 == 1)
{
Moveearduino (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);
DrawamendScreen ();
}
Else IF (Iwinner == 2)
{
Serial.Println ("Human Wins");
Delay (3000);
DrawamendScreen ();
}
Else
{
Serial.Println ("Draw");
Delay (3000);
DrawamendScreen ();
  }
}

Code 2: Procedure of PlayGame (BOOL)

 
The scheme is almost identical in both modes in this loop:

  1. Play train Human / Arduino Check for feasibility
  2. Play the turn on the display
  3. Output on the serial monitor of the field occupancy
  4. Check if there is a winner or not

For the input from the human, the mapping from the touch display is used again in the movePlayer() function. Also a do-while loop waits until the player has made a valid move, see 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 (ISSETMARK [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 (ISSETMARK [8] == 0)
{
Serial.Println ("Player Move: 8");
IsetMark [8] = 2;
bvalidmove = true;
Drawplayermove (8);
}
}
}
} While (! bvalidmove);
}

Code 3: Function MovePlayer ()

With the function moveArduino(bool) the boolflag of the function passes the game mode, easy or hard game. In easy play, a random numeric value from 0 to 8 is generated and checked to see if this playfield has been played before. If this is the case, the do-while loop is run until a random number is generated that has not yet been selected on the game field, see code 4.

With the play option heavy, several things happen at once. First, the first move belongs to the Arduino, which decides by means of a modulo whether position 4, i.e. center, or a random field is chosen directly by the Arduino, see code 4 in the source code. For all further moves it is always checked if the player has to be prevented from winning or if the Arduino itself can win.

// 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);
ISSETMARK [Irandommove] = 1;
Serial.Println ("Arduino Move:" + String (Irandommove));
Drawawuinomove (Irandommove);
}
Else
{
IsetMark [4] = 1;
Serial.Println ("Arduino Move:" + String (4));
Drawawuinomove (4);
}
}
Else
{
int inextmove = checkplayermove ();
INT iWinmove = CheckPossibleWin ();
Serial.Println ("Check Player:" + String (Iwinmove));
if (iwinmove> = 0)
{
Delay (1000);
ISSETMARK [IWINMOVE] = 1;
Serial.Println ("Arduino Move:" + String (Iwinmove));
Drawawuinomove (Iwinmove);
}
Else
{
IF (inextmove> = 0)
{
ISSETMARK [INEXTMOVE] = 1;
Serial.Println ("Arduino Move:" + String (Inextmove));
Drawawuinomove (inextmove);
}
Else
{
do{
Irandommove = Random (9);
IF (ISSETMARK [Irandommove] == 0)
{
Delay (1000);
ISSETMARK [Irandommove] = 1;
Serial.Println ("Arduino Move:" + String (Irandommove));
Drawawuinomove (Irandommove);
bvalidmove = true;
}
} While (! bvalidmove);
}
}
} // else
} // if (bhardmode)
Else // Easy Mode
{
do{
Irandommove = Random (9);
IF (ISSETMARK [Irandommove] == 0)
{
Delay (1000);
ISSETMARK [Irandommove] = 1;
Serial.Println ("Arduino Move:" + String (Irandommove));
Drawawuinomove (Irandommove);
bvalidmove = true;
}
} While (! bvalidmove);
}
}

Code 4: Function MoveArduino (BOOL)

In both cases a drawing function, drawPlayerMove(int) or drawArduinoMove(int), is called. The integer here is the playfield position, which must be translated into an X and Y coordinate on the display. The respective function in turn calls the draw function drawSign(), which draws the X or O sign to the corresponding position on the playfield.

You will now ask yourself what the x_bitmap or circle is for the two previously mentioned functions.

Here the file graphics.c is used, in which an array is stored for the X or O character. These two arrays are loaded in lines 23 and 24 in the variable circle and x_bitmap, see Figure 9.

Figure 9: Loading the array for the characters


The keyword external refers to an external C file that must be located in the same project folder as the *.ino file.

Afterwards the sign is read bit by bit in the function drawSign() and inserted with the specified color at the correct X and Y coordinate from the touch display.

If you want to be individual, you can change the color of the sign in the function drawPlayerMove(int) or drawArduinoMove(int), see line 563 to 596 in the source code.

With this the biggest part of the program is explained.

Item 3 is a simple output to the serial monitor using a For loop. Point 4 of the above list, is realized via the function checkWinner(), which checks every combination for a winning row, if you or the Arduino has a row and overwrites the global variable iWinner accordingly.

As soon as iWinner is overwritten or 9 moves are reached, the program jumps out of the do-while loop of playGame(bool) and announces the winner or draw with the function drawGameEndScreen(), see Figure 10. To let you see why you or the Arduino won beforehand, a delay of 3 seconds is applied. The same applies to a draw.

Figure 10: Endscreen with winner


With the GameEnd screen, the two buttons for selecting an easy or hard game are also displayed again.

You can also modify the source code as you like. Conceivably, the difficulty level hard could be made a bit easier by having the Arduino make a random move every now and then rather than always trying to block you. For this you might have to work with a modulo again.

But they can also simply change the color of the elements to the locations shown in order to obtain an individual touch.

I wish you a lot of fun with replica.

This and other projects can be found on GitHub at https://github.com/m3token1ght/blog-repo

DisplaysFor arduinoProjects for beginners

7 comments

Guido Monstrey

Guido Monstrey

hi, can i have those projects in another language please? The german language is to difficult for me, sorry.
My language is dutch (i am from Belgium The Flanders).
In dutch or english it is oke.
Thank you
Guido

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.

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN