Maker.io main logo

How to Build an Arduino-based Electronic Tic-Tac-Toe Mini-Game

2021-12-28 | By Maker.io Staff

License: See Original Project

Previously, we looked at how you can build various electronic mini-games (such as an Arduino-based reaction tester and a rock-paper-scissors robot). Later, I discussed how to assemble a simple device that simulates multiple dice commonly used in various board games (like Dungeons and Dragons). This article discusses an electronic tic-tac-toe mini-game you can effortlessly build at home. The project is suitable for beginners, and you can either assemble it on a solderless breadboard or use a prototyping perfboard for a more professional-looking end result. I’ll go over the schematic diagram, the software, and a few tips and tricks that will help you assemble the project.

Finished_1

This image shows the finished project after the players made a few moves.

Bill of Materials

Part/Pcs./Where to buy

Note that you can use any Arduino or other development board for this project. However, I tested it using the Arduino Nano 33 IoT and an Arduino UNO, so they are guaranteed to work in this project.

Components_2

You’ll only need a handful of readily-available components to build this project.

Schematic Diagram

The schematic diagram might look complicated at first because of the LED grid. However, if you take a closer look at the drawing, you’ll see that only three connections are going from the Arduino to the LEDs. I connected the first LED’s DIN pin in each row to a digital pin on the Arduino. Then, I used the DOUT and DIN pins of the LEDs in a row to daisy-chain them together. This way, the Arduino can address each row individually. You could also create a single long chain of LEDs that spans over multiple rows of the grid. However, that approach makes building the finished device and writing the firmware more complicated. Therefore, I decided to sacrifice two digital pins of the Arduino to make the overall project easier to understand and build.

Schemeit_3

The schematic diagram for this project. Scheme-It link

Note how the LED’s voltage input is labeled +5V. Regardless, I connected the LEDs to the 3.3V pin of the Arduino. I’ve done it this way because the LEDs expect the data input (DIN) to be a specified voltage above the supply voltage. The Arduino Nano 33 IoT can only output 3.3V over its data pins, and the LEDs would often run into errors when I used the Arduino’s 5V pin to supply them with power.

If you use a 5V board, such as the Arduino UNO, make sure to connect the LED’s 5V pin to the Arduino’s 5V supply pin. I also recommend using a resistor on the data line going from the Arduino to the first LED and a smoothing capacitor at each LED’s supply pin for longer chains of LEDs. However, I omitted both in this project, as I split the array into three very short strips that work fine without these external components. Longer LED strips might run into issues if you omit these components.

Besides that, you must pay close attention to the polarity of the LEDs. Unlike regular LEDs, the devices I used in this project contain a small microcontroller. Reversing the polarity will most likely destroy the LED. The following image describes the pinout of each LED:

Led_4

Ensure that you don’t incorrectly connect the LEDs to prevent damaging them. Image source: https://learn.sparkfun.com/tutorials/ws2812-breakout-hookup-guide#addressable-through-hole-led

How the Game Works

I made this a two-player game for the sole reason of simplicity, as I’d like beginners to understand how the code works. A simple AI opponent would be too easy to beat, and thus, the game wouldn’t be fun. A more complex AI player algorithm would be too complicated to explain in a short article. Therefore, two human players make their moves in alternating turns. A green cursor indicates the currently selected cell on the tic-tac-toe playfield. Players can use the left button to move the cursor to the next cell. Once the cursor is over an empty field, players can use the second button to place their X or O in that cell. The LED in that cell then turns red or blue, respectively. The first player to light up a row of LEDs in the same color wins. In essence, the firmware loop method looks like this:

Diagram_5

This diagram outlines the main game loop of the tic-tac-toe firmware.

The Software Part of this Project

Once you understand the main game loop, read the firmware code. Before you can upload the program to the Arduino, install the Adafruit NeoPixel library using the IDE’s built-in library manager.

I’ll go over the essential parts of the firmware here. You can download the complete Arduino code at the end of this article. First, initialize the three lines of LEDs and all necessary GPIO pins:

Copy Code
unsigned playField[3][3] = {
{0, 0, 0},
{0, 0, 0},
{0, 0, 0}
};

/* Other variables omitted */

Adafruit_NeoPixel row1(3, LED_ROW_1, NEO_RGB + NEO_KHZ800);
Adafruit_NeoPixel row2(3, LED_ROW_2, NEO_RGB + NEO_KHZ800);
Adafruit_NeoPixel row3(3, LED_ROW_3, NEO_RGB + NEO_KHZ800);

/* helper functions omitted – see below */

void setup()
{
Serial.begin(9600);

pinMode(LED_ROW_1, OUTPUT);
pinMode(LED_ROW_2, OUTPUT);
pinMode(LED_ROW_3, OUTPUT);
pinMode(OK_BTN_PIN, INPUT);
pinMode(NEXT_BTN_PIN, INPUT);

row1.begin();
row2.begin();
row3.begin();
}

As you can see, the Arduino stores the game state in a two-dimensional unsigned integer array where zero indicates an empty cell, a one means that player one placed their X in that cell, and a two denotes that player two put their O in that cell.

The following helper method checks whether a player won the game. For that purpose, the function first iterates over each cell in a row, one row at a time. If a row contains three cells with the same value other than zero, that player won. So, for example, if a row contains three twos, player two wins. If the rows don't determine a winner, the helper function similarly checks the columns. If no column contains a winning combination, the method checks the two diagonals for three matching entries:

Copy Code
unsigned playerWon()
{
// Check each row for a winner
for(int i = 0; i < 3; i++)
{
unsigned firstCell = playField[i][0];

if(firstCell != 0 && firstCell == playField[i][1] && firstCell == playField[i][2])
return firstCell;
}

// Check each column for a winner
for(int i = 0; i < 3; i++)
{
unsigned firstCell = playField[0][i];

if(firstCell != 0 && firstCell == playField[1][i] && firstCell == playField[2][i])
return firstCell;
}

// Check diagonals
unsigned centerCell = playField[1][1];

if(centerCell == 0)
return 0;
else if(centerCell == playField[0][0] && centerCell == playField[2][2])
return centerCell;
else if(centerCell == playField[0][2] && centerCell == playField[2][0])
return centerCell;

return 0;
}

Lastly, the helper function returns the winner’s number or zero if no player won. The following important helper method turns on the LEDs on the playfield if a user makes an input:

Copy Code
void drawPlayField(void)
{
int r = 0;
int b = 0;
int g = 1;

row1.clear();
row2.clear();
row3.clear();

for(int y = 0; y < 3; y++)
{
for(int x = 0; x < 3; x++)
{
if(x == markedCellX && y == markedCellY)
{
r = 0; g = 1; b = 0;
}
else
{
unsigned value = playField[y][x];
r = (value == 1) ? 1 : 0;
b = (value == 2) ? 1 : 0;
g = 0;
}

if (y == 0)
row1.setPixelColor(2 - x, row1.Color(r, g, b));

if (y == 1)
row2.setPixelColor(2 - x, row2.Color(r, g, b));

if (y == 2)
row3.setPixelColor(2 - x, row3.Color(r, g, b));
}
}

row1.show();
row2.show();
row3.show();
}

This method first turns all LEDs off. It then iterates over each cell in the two-dimensional array. The program first checks whether it encountered the selected cell. If that’s the case, it makes the LED turn green, which indicates the cursor. If the cell doesn’t contain the cursor, the method checks the array value on that position. Then, the function sets the r, g, and b variables according to the array value. If the array contains a one, the program sets r to one. Otherwise, r is zero. Similarly, if the array contains a two, the program sets b to one. Otherwise, b is zero. Next, the program sends the color information to the LEDs before turning each row back on using the new color information.

These two helper methods contained most of the game logic. Therefore, the loop method is rather short, as it only calls the previously discussed functions:

Copy Code
void loop()
{
unsigned long currentMillis = millis();

if(currentMillis - lastMillis > UPDATE_THRESHOLD)
{
if(digitalRead(NEXT_BTN_PIN))
{
markedCellX += 1;

if(markedCellX >= 3)
{
markedCellY = (markedCellY + 1) % 3;
markedCellX = 0;
}
}
else if(digitalRead(OK_BTN_PIN))
{
if(playFieldAvailable(markedCellX, markedCellY))
{
setPlayField(markedCellX, markedCellY, activePlayer);
activePlayer = (activePlayer == 1) ? 2 : 1;
}
}

unsigned winner = playerWon();
if(winner != 0)
{
Serial.print("Player ");
Serial.print(winner);
Serial.println(" won the game! Resetting...");
resetGame();
}

lastMillis = currentMillis;
}

drawPlayField();
}

As you can see, the loop method first checks whether enough time has elapsed since it last detected user inputs. I have included this check as a debounce measure. If enough time has elapsed, the loop checks whether the player pressed the 'next' button. If that’s the case, the program moves the cursor to the next cell. If the program didn’t register a button press, the method checks whether the player pushed the 'place' button instead. If so, the loop checks whether the currently selected cell is free. If it is, the program places the player’s X or O in that field and switches to the other player. Regardless of whether a player pressed a button or not, the method checks whether one of them won the game before re-drawing the entire playfield.

Download the Firmware Code

You can download the complete Arduino sketch here.

Summary

Finished_6

 This is the finished product.

This article discussed how to build a simple two-player tic-tac-toe game at home using only a handful of cheap and readily-available components. I used an Arduino Nano 33 IoT, but you can use whichever Arduino board you like. The project uses three individual rows of addressable RGD LEDs. I split the grid into three rows to make the device easier to assemble. Doing so further reduced the firmware complexity. The firmware implements a simple game loop. The Arduino first responds to button presses. If a player presses the 'next' button, they can move the cursor over the playfield. When they press the 'place' button, the Arduino puts their X or O in the selected field. The first player who manages to have three Xs or Os in a row wins. 

 

制造商零件编号 ABX00032
ARDUINO NANO 33 IOT WITH HEADERS
Arduino
¥219.78
Details
制造商零件编号 COM-12986
ADDRESS LED DISCR SERIAL RGB 5PC
SparkFun Electronics
¥29.49
Details
制造商零件编号 MJTP1230
SWITCH TACTILE SPST-NO 0.05A 12V
APEM Inc.
¥1.79
Details
制造商零件编号 CF14JT2K20
RES 2.2K OHM 5% 1/4W AXIAL
Stackpole Electronics Inc
¥0.81
Details
制造商零件编号 FIT0203
BREADBOARD GENERAL PURPOSE PTH
DFRobot
¥23.61
Details
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum