Tic Tac Toe

Portable Electronic Game Machine

Liam Davies

Issue 29, December 2019

This article includes additional downloadable resources.
Please log in to access.

Log in

Build your own handheld Tic-Tac-Toe gaming console and play against your mates! Comes complete with a 9V battery and 3D-printed enclosure so you can bring the fun anywhere!

BUILD TIME: 5 Hours (Not including 3d Printing Time)

Tic Tac Toe - The timeless game played by thousands of bored kids (and adults too, we won’t judge) all over the world. Don’t you think it needs a bit of a modern update? In this project, we’ll make a fully portable Tic-Tac-Toe handheld console, complete with rainbow lights and a 3D-printed enclosure.

Our electronic Tic-Tac-Toe game has exactly the same rules as normal Tic-Tac-Toe, except with colours of RGB lights rather than naughts and crosses. We’ve even included a choose-your-character system for both Player 1 and Player 2 to choose their favourite colours to play with!


There are four main components that drive our Tic-Tac-Toe gaming board: The ATmega328P, the RGB LEDs, the pushbuttons and the IO extender. In our main build, we’ll mount all of the components onto two prototyping PCBs, stacked on top of each other with some PCB standoffs.


Arduinos are great. They have just about as much functionality as any electronics enthusiast might need for any project, and much, much more. But for a project like this, where space is limited, we really don’t need all the features a normal Arduino board has. Features like USB-to-serial converters and USB ports just take up room in a packed project. Instead, we’ll be essentially making our own Arduino compatible Uno board, which will only have the features we need. 

ATmega328P IC with Arduino Uno bootloader. IMAGE CREDIT: Jaycar

At the core of this project, we’ll be using the ATmega328P, which many of you will recognise as the microprocessor behind the Arduino Uno. All we need to add is: 

  • A 16MHz quartz crystal for the microprocessor clock 
  • A pullup resistor with button for the Reset pin 
  • A stable 5V power source & reference voltage 
  • When you source your ATmega328P, make sure it has the Arduino bootloader preinstalled.


    For the LEDs, we’re using nine WS2811 5mm round LEDs. These WS2811 LEDs are commonly seen in addressable RGB light strips, where they’re in a flat package rather than a round package.

    Normal common-anode RGB LEDs (shown here) have their anodes (+) connected together to the positive voltage rail. With these, it’s your responsibility to connect the cathodes (-) to ground through a resistor to light it up. By changing the resistance, and therefore current flow, you can vary the intensity of that light channel. Although simple, when driving more than a couple of these it starts to get very cluttered very quickly: Each LED needs a connection to the positive rail, and three wires for each of the colour channels.

    Enter, the WS2811. It still has four leads on its base, but you’ll notice the way we interface with the LEDs is totally different. WS2811 LEDs are designed to be chained together, meaning each LED connects to the next. This means there is one lead for inputting data to the LED, and one lead for outputting data to the next LED. The other two leads are for 5V and ground. It also means that we need just one pin for controlling the entire array of LEDs from a microcontroller. Awesome! 


    IMAGE CREDIT: Adafruit Industries

    We’re using the MCP23017 I/O extender for the ATmega328P to add an extra 16 digital GPIO pins so we can read the button states on the game board. By hooking up pins 12 and 13 on the extender to the Arduino’s SDA and SCL pins, we use just two GPIO pins on the Arduino! 

    It’s worth noting this I/O extender isn’t strictly necessary. The ATmega328P has 20 digital I/O pins (including the 6 analogue pins, A0-A5), and we only need 9 to read the inputs from all of the buttons on the Tic-Tac-Toe grid. However, we want to demonstrate how to use an I/O extender for any project, or even an upsized version of this one. The MCP23017 has a cool feature of being able to use up to eight extenders on the same two control pins (SDA and SCL). It has three ‘channel set’ pins, named A0-A2. Depending on the binary encoding of these pins which are either tied to the positive voltage rail or ground, you can set the I2C address of each chip. All you must do then is hook the SDA and SCL lines up to all your I/O extenders. If you have eight of these extenders, you can use up to a whopping 128 GPIO pins with just two GPIO pins on the Arduino!

    The circuit configuration for hooking up four MCP23017’s is shown here. Notice the chip number (The last three bits of the I2C address) is the binary encoding of states of the A pins on each chip. For example, the second chip has only A0 connected to 5V and A1/A2 are connected to ground in order to represent “001” in binary. After connecting the SDA and SCL pins from the Arduino to all chips, we can use a total of 64 GPIO pins with the GPAx and GPBx pins!

    As we said, we’re including this I/O extender mainly to demonstrate how to use it in your own projects. If you don’t want to use one of these extenders, just hook up the buttons directly to the digital pins on your ATmega and change the code accordingly. Easy peasy!

    Whenever dealing with any type of switch with microcontrollers, it’s super important – and often forgotten by newbies – to add pull-up or pull-down resistors to the inputs. When the switch is closed, (pressed in the case of a Normally Open pushbutton) there’s no trouble: the microcontroller simply reads what digital state the button is connected to. However, when the switch is open (i.e. the pin isn’t connected to anything), who knows what the microcontroller will read? The pin will happily fluctuate to whatever value it wants. The purpose of a pullup or pulldown resistor is to set the value of the pin when it would otherwise be in a floating state, either pulling it to VCC (pullup), or ground (pulldown).

    Although the MCP23017 does include 100kΩ pullup resistors internally, we did some research and found that some enthusiasts had trouble getting these to work reliably when reading digital inputs. Even the chip’s datasheet states that the pull-up resistors are weak! We are going to add some 4.7kΩ pulldown resistors to our buttons, although any decently high value of resistor will work, preferably in the thousands of ohms. We ran out of 4.7kΩ resistors in our main build and used 10kΩ instead.

    The Prototype:

    Our fundamental prototype build for this project will be essentially testing the core functionality of the project on a smaller scale. We’ll only use three LEDs and three buttons to make sure the IC’s we’re using work the way we want them to. We’ll also hook up the 9V battery and voltage regulator to make sure the LEDs and ATmega are running properly.

    Note that while this build can be accomplished on one breadboard, we used two to help space everything out. We used solid-core breadboard wire to make this breadboard prototype to keep everything clean, however, you could use regular jumper wires just as easily to make the whole prototyping process faster.

    Parts Required:JaycarAltronicsCore Electronics
    1 x ATmega328P with Bootloader *ZZ8727Z5126000-ATMEGA328+UNO+CAPS
    1 x 16MHz Oscillator Crystal *Included with part aboveV1289AIncluded with part above
    2 x 22pf Ceramic Capacitors *RC5316R2814Included with part above
    2 x 10µF Electrolytic Capacitor^RE6070R4767CE05274
    1 x 220Ω Resistor*RR0556R0229FIT0119
    1 x 10kΩ Resistor*RR0596R0058FIT0119
    3 x 4.7kΩ Resistors*RR0588R7574COM-10969
    1 x MCP23017 I2C 16-bit IO Extender--ADA732
    1 x 7805 5V Voltage RegulatorZV1505Z0505ADA2164
    1 x 3mm Round Red LEDZD0100Z0004CE05103
    10 x 5mm WS2812B RGB LEDs*--POLOLU-2535
    1 x 1N4004 Diode*ZR1004Z0109COM-14884
    3 x SPST PCB Mount Tactile Switch SquareSP0608S1135-
    1 x 9V Battery Clip to 2.1mm Barrel Connector--PRT-09518
    1 x 9V BatterySB2423S4931CE05337
    2 x 28-pin IC SocketsPI6466P0571PRT-07942
    1 x 2.1 DC Breadboard Barrel Jack Adapter #--PRT-10811
    1 x 3.5mm Micro SPST Tactile SwitchSP0601S1120FIT0179
    1 x FTDI USB to Serial ConverterXC4464Z6225DFR0065

    Parts Required:

    * Quantity required, may only be sold in packs. Breadboard and prototyping hardware required.

    ^ These capacitors are for smoothing the output of the 7805 regulator. Feel free to substitute for something similar.

    # It's super important that these are breadboard compatible, not PCB-compatible, otherwise you won't be able to fit them into the breadboard

    Note: Depending on where you get your ATMega328P, 16MHz crystal and 22pF capacitors, some suppliers may include them in a bundle.


    We recommend starting with the power electronics on the breadboard for this project, it’ll make spacing out everything else easier.

    We first added the 2.1mm barrel jack adapter and, through a 1N4004 diode to prevent reverse polarity problems, hooked up to the input pin of the 5V voltage regulator. After bridging the ground pin of the barrel jack and the voltage regulator to ground, we added a wire running to the red power line of the breadboard. 

    By the way, if you’re sourcing power from something that could have voltage fluctuations, add capacitors to the voltage regulator (both on the input and output side) that will help smooth out any spikes or drops. A significant change in voltage that makes it to the ATmega could cause a reset, and at worst, destroy the chip completely. 


    Since we’re using a 9V battery for this project, and aren’t expecting the voltage to fluctuate significantly, we added a 10µF electrolytic across the output and ground of the regulator just to be safe. 

    Check everything is working as it should by connecting the 9V battery to the barrel jack and measuring the voltage across the positive and negative line of the breadboard, it should be very close to 5V – perfect! 


    The next step is to hook up our ATmega328P. We’re using the Duinotech clone from Jaycar, which includes a super handy sticky label on the top, labelling all the pins to simplify connecting everything up. Most pins on the ATmega are pretty self-explanatory: Tie the ground pins to ground, tie VCC, AVCC and AREF to 5V. We also added another 10µF capacitor between VCC and ground. Don’t forget to add a 10kΩ pullup resistor on the reset pin and tactile pushbutton when pushed, will reset the Arduino. 

    To add the 16MHz quartz crystal that oscillates as the clock of the ATmega, just connect it between the X1 and X2 pins, and add a 22pF ceramic capacitor from each pin to ground. Note that neither the crystal nor capacitors are polarised. 

    We also recommend adding the Arduino’s power LED to the breadboard so we can tell that everything is working properly. Just hook up any LED to pin D13 and tie it to ground with a 200Ω (or something similar) resistor. 

    In the code section of this build, we’ll talk about how you upload code to an Arduino without a USB port. 


    The next step is to add the MCP23017 which will add an extra 16 GPIO pins to our Arduino at the cost of only two pins for I2C communication. After placing it on the breadboard, connect the GND, VCC and Reset pins. We also connected A0, A1 and A2 to ground which marks this extender chip as being the first chip. If you are using more, tie the consecutive chip pins to VCC or ground for the binary encoding of that chip number as we discussed in the ‘How It Works’ section. 

    After all the power pins are hooked up, we need to connect the actual inputs we want to use on the extender. The MCP23017 has two sets of eight pins for GPIO, GPA0 to GPA7 and GPB0 to GPB7. In our fundamental build, we’re only using the three pins GPA0, GPA1 and GPA2. One of these each will connect to one side of our buttons.


    We’re adding only three buttons for the fundamental build just to make sure everything is working as it should be. To add them to the breadboard, orient the long side of the buttons across the breadboard. The buttons work by connecting the two long sides of the button’s pins together when pressed, so connect one side to VCC, and the other to the I/O extender pin and ground through a 10kohm resistor. This is a pulldown resistor, ensuring that the I/O extender reads the pin state as low unless connected directly to VCC in which case it reads the pin as high. 


    As we discussed earlier, the WS2811 LEDs are fairly simple to hook up. They need just one GPIO pin and then they can be daisy chained to control multiple LEDs at the same time. Reading from left to right with the flat edge of the LED on the right, the pin order is Data In, Ground, 5V and Data Out. After placing the LEDs in a row along the breadboard, we connected the first LEDs Data In pin to D12 on the ATmega. The Data Out pin can then be connected to the next LED’s Data In, and so forth. Don’t forget to connect all LEDs to both VCC and Ground!


    The Arduino code for the fundamental build won’t have any functionality for the Tic Tac Toe game itself, rather we’ll just be testing both the buttons and the LEDs work as intended. The code is available to download from our website.

    #include <FastLED.h>
    #define NUM_LEDS 3
    #include <Wire.h>
    #include "Adafruit_MCP23017.h"
    CRGBArray<NUM_LEDS> leds;
    Adafruit_MCP23017 mcp;
    void setup() {
      FastLED.addLeds<NEOPIXEL,12>(leds, NUM_LEDS);
    void loop(){
      for(int i = 0; i < 3; i++) {   
        leds[i] = CRGB(mcp.digitalRead(0) * 255, mcp.digitalRead(1) * 255, mcp.digitalRead(2) * 255);;

    We’re using two libraries for this project, the Adafruit MCP23017 library to control the extender GPIO pins, and the FastLED library to control the WS2811 LEDs. To install them, navigate to Sketch > Libraries > Manage Libraries in the Arduino IDE and search and download them.

    The setup function is very simple, we are just initialising both libraries for use - note that the FastLED library also needs the digital pin we are controlling the LEDs on. The loop function lets us test all buttons and LEDs by lighting up the colour channels on the LEDs in accordance with what buttons are pressed - one button controls the red channel, one controls the green channel, and so forth.

    Uploading code to a breadboard Arduino is a little tricky. To make this work, we grabbed a FTDI micro-USB programmer that connects to the TX and RX pins on the ATmega. This converts USB data received into serial data that can write to the ATmega.

    Note: The 328 can be programmed by plugging in to an Arduino board, via a USB breakout or by linking the Arduino board USB controller to the 328 Tx/Rx pins and rails. Break Out Boards can be used or an AVR programmer used.

    Some of these USB-to-serial converters also include a DTR pin, which can be connected to the reset pin of the Arduino via a 0.1uF electrolytic capacitor. The purpose of the pin is to automatically reset the Arduino during the upload process so the user doesn’t have to do it. We had some trouble getting this to work, as the Arduino IDE would compile the code and then freeze when uploading it. If you encounter this problem, try using a different sized capacitor or tying the RX and TX lines to VCC via 10kΩ resistor. 

    Nonetheless, we still couldn’t get it to upload using the tutorials we found online for using a USB-to-serial converter with a DTR pin. We ended up disconnecting the DTR pin entirely, and following this process for uploading a program (this is also the process for uploading code if you don’t have a DTR pin): 

    • Disconnect the USB-to-serial converter from the computer - cut off power. 
    • Hold the reset button connected to the ATmega. 
    • Reconnect the power and upload the code via the Arduino IDE. 
    • When the IDE displays it is uploading, release the reset button.

    The IDE should now upload the code to the Arduino and, when it’s done, the status LED on pin D13 should blink rapidly. 


    The main issues we were looking for when testing our breadboard was any power or circuit problems. After checking the circuit and connecting a multimeter across 5V and ground to verify there’s no shorts, it’s time to connect the circuit to the 9V battery! The LED connected to D13 lights up, and the buttons and RGB LEDs should work as expected.


    The most common area for troubles to arise in this build is the circuitry surrounding the ATmega. Check the 5V and GND pins are connected correctly and add a decoupling capacitor between 5V and ground in case your power supply voltage is fluctuating. Then, check the crystal and ceramic capacitors are connected to pins X1 and X2 on the Arduino. We had trouble getting the code to upload, so definitely add the debugging LED to pin D13 so you can tell what it’s doing and follow the programming steps.

    Check that the input power diode oriented correctly, and make sure the 7805 is correctly inserted into the breadboard.

    If you experience problems with the RGB LEDs, check the LED data pin is connected to the correct ATmega pin. Then, check the 5V and GND pins are connected correctly, and the flat side is facing the same way as the other LEDs so the data signal transfers between them.

    If a pushbutton isn’t working, check the pulldown resistor and 5V wire is in the correct order (you might be accidentally pulling up the signal). Put a multimeter into continuity mode and verify that depressing the button shorts both sides of pins together, and make sure you’re using an appropriate value for the pullup resistors. If the value is too high, the input pins on the MCP23017 may not be able to read the tiny amount of current. The resistors should be in the 1000's of ohms.

    The Main Build:

    The wiring for the main build is virtually identical to the fundamental build, with the exception of nine LEDs and nine buttons to add into the mix. We’re also adding a buzzer to add an audible cue to game actions. We aren’t going to go over the intricacies of the circuitry in this build, as we’ve already gone over the technical side in the fundamental prototype. Nonetheless, we’ve included a full circuit schematic if you want to refer to when building it. Let’s get to it!

    Additional Required:JaycarAltronicsCore Electronics
    6 x SPST PCB Mount Tactile Switch Square (9 in total required)SP0608S1135COM-09190
    9 x 10kΩ Resistors (10 in total required)RR0596R0058FIT0119
    4 x M3 x 4mm Threaded Brass Inserts*--ADA4255
    4 x M3 x 10mm PCB standoffs*HP0900H1216003-STOFFHEX_10MM
    8 x M3 x 10mm/5mm Screws*HP0404H3120AFIT0270
    2 x 7cm x 9cm Prototyping PerfBoards*HP9550-ADA2670
    1 x PC Mount Piezo BuzzerAB3459S6104ADA1536
    Hookup WireWH3032-PRT-11367

    Additional Required:

    * Quantity required, may only be sold in packs. Breadboard and prototyping hardware required.


    The control board contains basically everything except the LEDs and buttons. That means the ATmega, the I/O extender board, and the power electronics are all kept together. This control board is the trickiest part of the project, so if you get stuck, take a look at the schematic.

    The first thing we did was set up the power electronics. Positioning is quite important when beginning a soldered prototype, because once you’ve started, it’s going to be quite an arduous task moving everything around again. We added the barrel jack to begin with. Always remember that many connector, headers and jacks, the male side that plugs into the board will have some overhang, extending from the side of the board. We positioned the barrel jack adapter a couple of centimetres into the board to allow space for the male jack in the enclosure. (We had some problems with the jack later!)

    We then soldered up the 7805 voltage regulator to the pins on the barrel jack adapter. Don’t forget the diode between the unregulated voltage rail and the Vin pin on the regulator – this will give us reverse polarity protection so we won’t accidentally destroy the circuit. Later on, we’ll add a 10µF capacitor between 5V and ground of the regulator to smooth out the output. We’re not doing it now so we don’t have to put stress on the capacitor when we flip over the circuit board to solder other components.

    We then soldered in the two 28-pin IC sockets which will give us a guide to which pins connect where to the ATmega microcontroller and the I/O extender. It’s also a good idea not to solder all the pins on the sockets in all at once, as you might need to move it. Instead, solder one pin on each corner of the socket, which will loosely hold it in place until you’re confident it’s in a good position.

    As we detailed in the prototype build, it’s more or less a matter of connecting 5V and ground to the pins of the ICs. We soldered the ATmega’s supporting circuitry in first, including the crystal, capacitors and red status LED. Don’t forget to insert the ATmega chip into the socket the right way! We also added four male headers connected to 5V, ground, TX and RX for programming the board.

    At this point, you can connect the control board to the USB-to-Serial converter. Make sure that the RX and TX pins on the converter are connected to the opposite pin on the Arduino – i.e. RX to TX, TX to RX. You can then upload the default Blink sketch and, if everything is hooked up correctly, the red debug LED should blink!

    When connecting up the I/O extender, there’s a couple of ground and 5V lines to connect. Connect the SDA and SCL lines of the extender to the ATmega and remember to pull the A0, A1 and A2 pins to ground which sets the chips address to 000.

    We can then begin adding the wires that will lead to the button board, which we’ll make next. You’ll need a 5V and Ground wire, which we’ve colour-coded red and black, and a white wire connected to pin D12 of the Arduino which will control the daisy-chained LEDs. For the buttons, we’re running one yellow wire from each pin of the IO extender to each one of the buttons. To simplify the circuitry on the button board, we also added 10kΩ pulldown resistors directly to the extender’s inputs. (Note that on the schematic, the 10kΩ resistors look as if they are placed close to the physical buttons. We placed them in a nine-long row directly in front of the MCP23017 in order to save space on the button board.)


    The button board is where we’ll be putting the – you guessed it – buttons! The addressable LEDs will be mounted close to the buttons, so when we get to 3D printing the enclosure, the light from the LEDs will shine through the white button covers to get an awesome glowing effect. Although this board is easier to solder together than the control board, take your time! An LED soldered in around the wrong way will be the last thing you want to discover after connecting everything up.

    The first step is to align the buttons on the 9cm x 7cm perfboard such that it’s an evenly spaced 3x3 grid. We found leaving four holes between each button seemed to space it out quite well. After that, we added the nine WS2812 5mm LEDs, placed directly next to the buttons. Make sure that the flat edge of all LEDs face to the next LED in the chain. We then linked them all together with some white solid-core wire, snaking through all nine LEDs.

    After the white data wires are complete, add the power wires. The second pin of each LED is for 5V, and the third pin is for ground. While you’re at it, connect 5V to one side of each button. This will help us later when we connect the nine yellow button wires that will read the 5V when the button is pressed.

    We had some trouble connecting ground for one row of the LEDs on the top side of the board, so we ended up adding some solid-core black wire on the underside. Remember, you can refer to the main build schematic to make sure the LEDs and buttons are wired properly. It’s a good idea to quickly run through the circuit with a multimeter to make sure there aren’t any shorts and all the LEDs are properly connected to the power rails.

    To finish off the electronics for this build, simply join up the wires hanging from the control board to the button board. Connect the 5V (red) and ground (black) to the rails on the board, and connect the LED data wire (white) to the first LED’s Data In pin. Then, just hook up the nine yellow wires to the opposite signal side of the buttons. When the circuit is running and a button is pressed, the button will send a 5V signal through its yellow wire to the I/O extender.

    To get the boards ready for the enclosure, we added a zip tie and some hot glue to the wires between the control board and the button board. This ensures the wires won’t get in the way when we insert the button covers into the top lid. We also carefully bent the LEDs down flat with the buttons, which makes sure they won’t interfere with the button covers.


    It’s time to write the code for the final build! “But DIYODE!” we hear you ask, “Why are you writing the code before you’ve done the 3D printing!?” - Because our Arduino doesn’t have a USB port, we won’t be able to access the programming headers once it’s in the enclosure. We can also test our game logic is bug-free to make sure it works properly before packing all the electronics in. Let’s have a quick rundown of what we want our program to do:

    1. Wait for any button to be pressed, which will start the program and light up the LEDs. Since our TicTacToe game board doesn’t have an on-off switch (Whoops!), if we leave the LEDs on all the time the 9V battery will run flat.
    2. When the game starts, ask each player what colour they want to be in the game – display nine colours on the buttons and set the player colour to whichever one they press.
    3. Start the game. The players take turns of pressing squares, which is then lit up to their colour.
    4. Once a winning combination is detected, flash all squares the winning colour and go back into standby mode – turn the LEDs off.

    Let’s get started! We aren’t going to show all 220 lines of code for this project, but we’ll look at some of the most important parts of it. You can download all code from the project files.


    #include <FastLED.h>
    #define NUM_LEDS 9
    #include <Wire.h>
    #include "Adafruit_MCP23017.h"
    #include <avr/wdt.h>
    CRGBArray<NUM_LEDS> leds;
    Adafruit_MCP23017 mcp;
    int board[3][3] = {{0,0,0},
    int playerColours[] = {0,32,64,96,128,160,192,224,255};
    bool player1 = true;
    int brightness = 160;
    int player1Hue = 0;
    int player2Hue = 0;

    This code defines the variables and objects for our program. You’ll notice we’re using the FastLED library for controlling our LEDs and the Adafruit_MCP23017 library for controlling our buttons through the GPIO extender. Our “board” array defines a 3x3 array that lets us keep track of which player has pressed what. Zero means the square is empty; if a player presses a button, their player number is set to that square. The player1 variable is then toggled from False to True or vice versa, depending on which player is next.

    At the beginning of the game, we show a selection of nine different colours that the player can choose from to act as their square colour. The “playerColours” array defines a list of hues the players can choose from, scattered throughout the colour spectrum. They’re then assigned to the player1Hue and player2Hue variables.

    Our brightness variable defines how bright the WS2811 LEDs light up, on a scale of 0-255. If the brightness is too low, you won’t be able to see the game in daylight, and if the brightness is too high, it will drain our 9V battery much more quickly! We found 150-160 is a good value and prevents the LEDs from drawing too much current.


    The code is a little lengthy to publish here, but check it out in the digital resources.

    There are a number of functions that this function calls, so take a look at the project files if you to want to have a look at how they work.

    The loop revolves around iterating over all the buttons and checking if any are pressed. If a button is pressed, and that grid square is empty, the square will be assigned to that player. When assigned, the square lights up that players colour.

    After a player has chosen a square, the code checks to whether any player has won or if there is a tie. You can look at the CheckWinState code below to see how it does this. It can return 1 or 2 if a certain player has won, 3 if the game is a tie or otherwise 0 if the game should continue. If the game continues, the players swap and the loop continues. If the game ends, an image is flashed - the colour of the player who won, or a white letter “T” if the game is a tie.


    The win check code is too long to show here. You can download it from our website.

    This code is for determining the state of the game – whether a certain player has won, or the game is a tie. This code is slightly inefficient in order to make it clearer – we’ve defined quite a few variables so it’s easier to follow along. We refer to the “board” variable several times throughout this method, which is a 2-dimensional array containing which squares are empty or assigned to players. For example, calling the following would assign the middle square to Player 1:

    board[1][1] = 1;

    Easy, right? The first case we check is horizontal wins, which is defined as when three squares in a horizontal row are assigned to one player. We iterate through each row and check if either player 1 or player 2 owns it, and then returns 1 or 2 if we find a player has won. Checking for vertical wins is very similar.

    We then check for diagonal wins, of which there are two cases: Top left to bottom right, and top right to bottom left, checking if either player has won on either.

    If no players have won, we check whether the board is filled to result in a tie. We set our “tie” variable to true by default, and then while we’re iterating through the board, if we find any empty squares, we set the variable to false. It’s important that we check for a tie after we check for wins, as the board could be filled because a player just won!

    Finally, if none of those cases happened, we return 0 to let the loop function know the game is free to continue.


    Similar to the Fundamental build, connect the 5V, ground and RX/TX pins on the USB-to-Serial converter to the header pins on the control board. Follow the procedure we used for uploading code in the fundamental build, holding the reset button on the board while you do it – and voila, the game is good to go!

    It’s a good idea to playtest the game before we put it into the enclosure, because the last thing you want to do is disassemble the entire project just to change a teeny-tiny bug in your code.


    The 3D-Printed enclosure for this project comes in three parts: the top lid, the enclosure, and the nine pushbutton covers. The .stl files are available on our website.

    It’s worth noting that the enclosure we designed is specifically made for the 7cm x 9cm prototyping boards that we used for the control and button boards. Due to the rather poor machining of the prototyping boards, the four corner holes aren’t quite square, and so the screw holes on the enclosure can’t be either if we want a good fit. We designed our 3D printed file to support our particular prototyping board, however, we’ve included a file called MainBody_Exact.stl that has the exact measurements the prototyping board should ideally have.

    We printed both the top lid and enclosure in lime green PLA plastic on our Flashforge Finder. They came out quite well, just remember to give the prints a quick clean up – removing any stringing, clearing screw holes with a hand drill, etc.

    We’ve included a STL file for the button covers, which you can either print one-at-a-time or all together in the same print. You need to print nine, all of which can be printed with the top side facing down. Each should take around 15 minutes to print. White or translucent (“neutral” colour) PLA plastic works well for these buttons, which lets the LED light shine through. Be aware that the infill type you choose will be visible when the LEDs light up – in our case, small hexagons.


    If you’ve ever tried to print an object with some screw thread made from plastic, chances are it either didn’t work or degraded very quickly! The accuracy of 3D-printing and the durability of PLA/ABS simply can’t produce a good quality screw thread.

    So how do we connect two 3D-printed objects together with screws? We could use nuts, but they’re ugly and is rather inconvenient where you don’t want the screw thread to show through on the other side of the object.

    IMAGE CREDIT: Core Electronics

    Brass inserts are a great way to add high-quality screw-in points for your project. If you haven’t seen them before, they’re essentially screw thread adapters for 3D-prints. On the inside of the inserts is a standard screw thread, and on the outside is knurled rings.

    We’ve added four small holes in the top lid of the project, which are slightly smaller than the diameter of the brass inserts. By pushing the inserts into the holes with a hot soldering iron, the inserts conduct the heat into the plastic of the 3D print. This then slightly melts the plastic, allowing the insert to slide into the plastic and cool in place. The insert, if pushed in correctly, will provide a firm hold due to the knurled texture on the outside.

    After we added our inserts, we found that the lid didn’t align very well- there was quite a bit of ugly overhang that definitely isn’t welcome on our final build. After we heated up the inserts again with our soldering iron, we moved them until the lid mounts properly.

    If you’d like to avoid the problems we had inserting them, you can buy brass insert tips for your soldering iron that hold the insert straight while pushing into plastic – definitely a necessity if you find yourself using them a lot.


    To throw the whole build together, we’ll first need to insert the control board into the enclosure. Insert four M3 x 10mm screws into the bottom of the enclosure, which should slot neatly into the control board. After that, we added 10mm PCB standoffs to the remaining screw thread, which allows us to screw in the button board over the top. If the screws are too long and hit the other screws on the bottom, grind them down with a rotary tool or use some 5mm ones instead.

    Even though the screws were the right length, we still had problems getting the button board to sit properly. The buzzer on the control board was slightly too tall, which means the button board area above it was slightly lifted, and thus the lid also wasn’t closing properly. We put a rotary sanding bit onto our trusty Dremel and sanded the offending buttons down, which reduces their height and allows there to be enough space between it and the top lid for them to be pressed comfortably.

    We then realised the 9V barrel connector and jack were sticking out too far, pressing against the wall of the enclosure and preventing the boards sitting properly. We ended up having to make some last-minute modifications to the control board, by de-soldering the jack and soldering the cables of the 9V battery clip directly to the board.

    If you haven’t already, slot in the 9V battery and connect the clip terminals. We managed to jam it in between some cables which comfortably hold it in place, but if your battery wants to move around, put a dab of hot glue onto it to secure it into the enclosure.

    The only thing left to do for the build is to close everything up! To prevent the button covers falling out when you screw on the lid, leave it upside down on your table and lie the enclosure face-down over the top of it. Then, insert four 10mm screws to the outer holes and lock them into the brass insert thread.


    Now comes the fun part! Our testing process was, of course, super scientific, and involved competing against family and friends to have some Tic-Tac-Toe fun and get their opinions. The project seems to hold up well to some throwing around, and we found we could leave the 9V battery connected to the board for about a week before it goes flat. In hindsight, we probably should have remembered to add an On/Off switch and an easy way to get access to the programming pins on the Arduino.

    Overall, the project looks and works great! The 3D prints came out surprisingly high-quality on the entry-level printer we used for the project; the layer lines are hardly visible. The RGB LEDs are quite bright indoors (although they are sometimes difficult to see in direct sunlight) and the feel of the buttons is satisfyingly tactile. Thankfully, we didn’t find any bugs in the game; although players can accidentally choose similar colours, which makes it difficult to see who is who when playing.


    We think there’s a wide variety of extra minigames you can write code for and program it onto the board. 

    How about a 4-player wack-a-mole game, where each player has a colour and must press any buttons that illuminate that colour. How long will it be before someone gets too competitive and presses someone else’s colour by accident? 

    You could even program the board to play a rudimentary roulette game! After the centre button is pressed, the lights around the outside illuminate in a spinning circular pattern until it lands on one of the eight squares. Place bets that it’s going to land on your square! 

    Even though there are 9 LEDs, it could become a DICE by using only those that are needed for the patterns. What about adding a Bluetooth or WiFi shield module to the ATmega and program it to interface with your computer, phone or tablet? Smart home control comes to mind – you have nine different buttons to activate or toggle power to appliances around the house! You could even build two of these game boards and add multiplayer compatibility over WiFi!


    Our electronic Tic-Tac-Tie gameboard has been designed so that anybody can play!

    When the 9V battery is connected to the game board, just press any button to start.

    1. One white light will flash, which means it’s Player 1’s turn to play. The game board will light up nine different colours, simply press on the colour to select it as your player colour.
    2. After Player 1 has selected their colour, two lights will flash and the game will let Player 2 select their colour.
    3. The game begins! Player 1 is first; press any button to illuminate it your colour. Player 2 can then take their turn, pressing any unoccupied square. Try to select squares to outplay your opponent.
    4. After any player has placed three squares in a row, they win the game! The game board will flash the winning players colour.
    5. If no-one wins, the game board will flash a white “T”.
    6. The game board goes back into standby mode, press any button to play again!
    Liam Davies

    Liam Davies

    17-year old high school student.