Introduction: Intelligent Arduino Uno & Mega Tic Tac Toe (Noughts and Crosses)

I decided to create the classic game of Noughts and Crosses (or Tic Tac Toe, as it's also known) on the Arduino after being asked by my daughter if we could build a game together. Here is a video her playing the final version on the Arduino Mega. The second video is me demonstrating the Arduino Uno version. I included some artificial intelligence (rather than just random placement) for the Arduino so that it tries to win, tries to stop you from winning if it can't, and only then places randomly.

Ask any questions in the comments and I'll do my best to answer. Enjoy!

UPDATE: I've successfully ported this to the Arduino Uno by Charlieplexing all 20 LEDs and creating a button matrix based on resistance. I've included notes in this Instructable so you can try both.

Step 1: Hardware

The hardware setup is actually quite simple but there are a lot of wires so it looks quite messy.

I used the following for the Arduino Mega version:

  • 1x Arduino (or Genuino) Mega
  • 1x very large breadboard
  • 1x half size breadboard
  • 10x green LEDs
  • 10x red LEDs
  • 10x push buttons
  • 1x LCD display
  • 20x 220Ω resistors
  • 10x 10kΩ resistors
  • 1x 10kΩ potentiometer (variable resistor)
  • Lots of jumper cables and wires

I had to use the Arduino Mega (rather than my Uno) because of the number of pins required for all the LEDs, buttons and LCD display. Of course, you can remove the LCD display and the two LEDs which announce the winner if you want but you would still need 28 pins!

If you want to try the version for the Arduino Uno, you will need:

  • 1x Arduino (or Genuino) Uno
  • 1x very large breadboard
  • 10x green LEDs
  • 10x red LEDs
  • 10x push buttons
  • 3x 1kΩ resistors
  • 2x 4.7kΩ resistors
  • 1x 10kΩ resistor
  • Jumper cables and wires

Step 2: The Breadboard

For the Arduino Mega

You can of course reduce the layout so that it fits on a smaller board. I used my large breadboard purely for ergonomic reasons so that my kids wouldn't knock LEDs or resistors when trying to press the buttons.

We basically have 9 green LEDs which represent the human player's pieces (noughts, I guess) and 9 red LEDs which represent the Arduino's pieces (the crosses). On the Fritzing diagram I've actually used bi-colour LEDs so feel free to adapt as you want. Each LED has its anode (+ or long leg) connected to a pin on the Arduino, and the cathode (- or short leg) connected through a 220Ω resistor to ground. On the Fritzing diagram, I've noted how the "numbering" of the buttons and LEDs correspond to the arrays in the code. The Fritzing diagram is rotated 90° clock-wise compared to the photos.

9 momentary push buttons allow the human to place their pieces on the board. A final push button allows the game to be reset to start play again. Each button has one terminal connected to +5V and the other terminal connected through a 10KΩ resistor to ground. The terminal connected to ground is also connected to a pin on the Arduino.

The small breadboard holds the LCD which just gives textual feedback ("Your turn...", "My turn...", "I won!!!", etc.) and two LEDs - red and green - or a bi-colour LED to show the winner. The potentiometer allows the contrast of the LCD to be adjusted. The LEDs are connected exactly as the LEDs on the large board and the LCD is wired up as per usual (there are a million tutorials explaining how to do this on the web).

For the Arduino Uno

It's a similar setup to the Mega but naturally we have far less pins at our disposal. To reduce the number of pins needed, I created a button matrix using resistors going into a single analogue pin on the Uno. I followed this very good tutorial and have included the diagram from that page above. I removed the final row of buttons. I had to play around and experiment to work out the voltages that were being passed to the analogue pin on the Arduino and noted them down for each button.

The LEDs proved tricky. I decided to Charlieplex all of them which allows us to control 20 LEDs from only 5 digital (PWM) pins. I followed this tutorial and used this library to make my life easier. The wiring took a while to work out so pay particular attention to making sure you get it right on the breadboard. Follow the Fritzing diagram to the letter or you'll end up in a pickle. It's very difficult to fault-find when Charlieplexing so, again, pay attention to the diagram. I started from scratch at least 10 times! I know I should have included some more resistors between the Arduino Uno and the LEDs but I couldn't be bothered.

Compared with the Arduino Mega version, I've removed the LCD, so the winner is announced simply by using two different colour LEDs. Both light if it's a draw.

Step 3: Gameplay

Again, gameplay is really simple. The first time the system is initialised it does a bit of flashing. The human player goes first and selects a square to place their piece but pressing the corresponding button. The green LED for that square lights up.

Next, it's the Arduino's turn. It places a piece on the board by lighting up the red LED corresponding to that square. The square is chosen in the following order:

  1. Place a piece to win.
  2. Place a piece to stop the human from winning.
  3. Choose a random free square.

It's then the human player's turn again.

Between each go, the system checks to see if anyone has won by analysing the board. If someone has won, they are notified on the LCD (on the Arduino Mega version) and the corresponding win LED lights.

If all the squares are filled and no winner is found, a draw is called.

The game is restarted by pressing the final push button on the board.

Step 4: The Code

The full code is below. However, pay particular attention to the constants declared at the top of the code. For the Arduino Mega version, check the pin definitions for the red and green LEDs, the buttons, the red and green win LEDs, the reset button. Most of them have been defined as 3D arrays and I've laid the code out to correspond to the LEDs and buttons as shown in the photos. Top left button on the photo corresponds to the first element in the array. We then work across to the right, down a row, across again, down a row, and across again. Remember, the Fritzing diagram is turned 90° clockwise compared to the photos - but I've noted the "numbering" of the LEDs and buttons on the diagram.

For the Arduino Uno version, check the Charlieplex pins at the top of the code, the analogue pin ("button") and the reset button pin. You shouldn't have any problem with the LED numbering and it can be left as-is. If for some strange reason, the button matrix isn't working, you'll need to do your own troubleshooting to check the voltage values that are coming into the analogue pin on each button press and adjust the "resButtons" array accordingly.

Don't forget, for the Arduino Uno version, you'll need the Charlieplex library attached as a file below the code. Unzip it and place it in the libraries folder of your Arduino IDE (you can search how to on the web if you don't know).

The intelligence bit is nothing more than the Arduino checking each column, row and diagonal to see if there are already two red LEDs lit and a spare space to place a piece. This would mean the Arduino could win and this takes priority. If the Arduino can't find such a line, it will then do the same again but this time looking for two green LEDs and a spare space. This means the human could win and the Arduino will therefore place a piece to block the human. Finally, if it can't win or block, it will pick a place at random by choosing one of the free spaces on the board.

I've commented the code as much as possible to help you understand what's happening. I know the code could be a lot more efficient. I think it's not bad for a first attempt though. Good luck!

For the Arduino Mega: (Arduino Uno version is below)

// TIC TAC TOE for Arduino Mega
// by Nick Harvey


// Include any libraries needed
#include <liquidcrystal.h> // For the LCD


// Define pins
const int green[3][3] = { // Green is the player
  {30, 31, 32},
  {33, 34, 35},
  {36, 37, 38}
};
const int red[3][3] = { // Red is the Arduino
  {40, 41, 42},
  {43, 44, 45},
  {46, 47, 48}
};
const int button[3][3] = { // Buttons to choose position
  {2, 3, 4},
  {5, 6, 7},
  {8, 9, 10}
};


const int greenWin = 50; // Lights if the player wins
const int redWin = 51; // Lights if the Arduino wins
const int resetButton = 11; // Button to start a new game


const int win[8][3][3] = { // This 4D array defines all possible winning combinations
  {
    {1, 1, 1},
    {0, 0, 0},
    {0, 0, 0}
  },
  {
    {0, 0, 0},
    {1, 1, 1},
    {0, 0, 0}
  },
  {
    {0, 0, 0},
    {0, 0, 0},
    {1, 1, 1}
  },
  {
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0}
  },
  {
    {0, 1, 0},
    {0, 1, 0},
    {0, 1, 0}
  },
  {
    {0, 0, 1},
    {0, 0, 1},
    {0, 0, 1}
  },
  {
    {1, 0, 0},
    {0, 1, 0},
    {0, 0, 1}
  },
  {
    {0, 0, 1},
    {0, 1, 0},
    {1, 0, 0}
  }
};


LiquidCrystal lcd(A4, A5, A3, A2, A1, A0); // Pins used for the LCD display (standard Arduino LCD library)


// Global variables
int gamePlay[3][3] = { // Holds the current game
  {0, 0, 0},
  {0, 0, 0},
  {0, 0, 0}
};

int squaresLeft = 9; // The number of free squares left on the board
int played = 0; // Has the Arduino played or not


// Global constants
const int startupFlashSpeed = 100; // The time in milliseconds the LEDs light for on startup
const int arduinoDelay = 3000; // How long the Arduino waits before playing (simulates thought)


void setup() {
  // put your setup code here, to run once:
  // Start serial comms
  Serial.begin(9600);


  // Initialise LCD
  lcd.begin(16, 2);
  // Define green and red pins as outputs
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      pinMode(green[i][j], OUTPUT);
      pinMode(red[i][j], OUTPUT);
    }
  }


  // Define green and red win lights as outputs
  pinMode(greenWin, OUTPUT);
  pinMode(redWin, OUTPUT);
  // Define buttons as inputs
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      pinMode(button[i][j], INPUT);
    }
  }


  //Define reset button as input
  pinMode(resetButton, INPUT);


  initialise();


  // Do startup flash
  startupFlash();
}


void initialise() {
  // Prepare the board for a game
  disp("TIC TAC TOE");


  // Set green and red LEDs off
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      digitalWrite(green[i][j], LOW);
      digitalWrite(red[i][j], LOW);
      gamePlay[i][j] = 0;
    }
  }


  // Set win LEDs off
  digitalWrite(greenWin, LOW);
  digitalWrite(redWin, LOW);


  // Reset variables
  squaresLeft = 9;


  // Tell the player it's their turn
  disp("Your turn...");
}


void loop() {
  // put your main code here, to run repeatedly:
  // Wait for an input and call the buttonPress routine if pressed
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if ((digitalRead(button[i][j]) == HIGH) && (gamePlay[i][j] == 0)) {
        buttonPress(i, j); // Pass the x and y of the button pressed
        break;
      }
    }
  }
}


void buttonPress(int i, int j) {
  // Button pressed, light the green LED and note the square as taken
  Serial.print("Button press "); Serial.print(i); Serial.println(j);
  digitalWrite(green[i][j], HIGH);
  gamePlay[i][j] = 1; // Note the square played
  squaresLeft -= 1; // Update number of squares left
  printGame(); // Print the game to serial monitor
  checkGame(); // Check for a winner
  arduinosTurn(); // Arduino's turn
}


void arduinosTurn() {
  // Arduino takes a turn
  Serial.println("Arduino's turn");
  disp("My turn...");
  checkPossiblities(); // Check to see if a winning move can be played
  if (played == 0) {
    checkBlockers(); // If no winning move played, check to see if we can block a win
  }
  if (played == 0) {
    randomPlay(); // Otherwise, pick a random square
  }
  squaresLeft -= 1; // Update number of squares left
  played = 0; // Reset if played or not
  printGame(); // Print the games to serial monitor
  checkGame(); // Check for a winner
  disp("Your turn..."); // Tell the player it's their turn
}


void checkPossiblities() {
  // Check all rows, then columns, then diagonals to see if there are two reds lit and a free square to make a line of three
  Serial.println("Checking possibilities to win...");
  disp("Can I win?");
  int poss = 0; // Used to count if possible - if it gets to 2 then its a possiblity
  int x = 0; // The X position to play
  int y = 0; // The Y position to play
  int space = 0; // Is there a free square or not to play
  // Check rows
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 2) {
        poss += 1; // Square is red. Increment the possiblity counter
      }
      if (gamePlay[i][j] == 0) {
        space = 1; // Square is empty. Note this and the position
        x = i;
        y = j;
      }
      if ((poss == 2) && (space == 1)) { // 2 red squares and a free square
        Serial.print("Found an obvious row! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }


  // Check columns - same as for rows but the "for" loops have been reversed to go to columns
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[j][i] == 2) {
        poss += 1;
      }
      if (gamePlay[j][i] == 0) {
        space = 1;
        x = j;
        y = i;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) { // This time also check if we've already played
        Serial.print("Found an obvious column! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    // Reset variables
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }


  // Check crosses - as for rows and columns but "for" loops changed
  // Check diagonal top left to bottom right
  for (int i = 0; i < 3; i++) {
    if (gamePlay[i][i] == 2) {
      poss += 1;
    }
    if (gamePlay[i][i] == 0) {
      space = 1;
      x = i;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an obvious cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
  }
  // Reset variables
  poss = 0;
  x = 0;
  y = 0;
  space = 0;


  // Check diagonal top right to bottom left
  int row = 0; // Used to count up the rows
  for (int i = 2; i >= 0; i--) { // We count DOWN the columns
    if (gamePlay[row][i] == 2) {
      poss += 1;
    }
    if (gamePlay[row][i] == 0) {
      space = 1;
      x = row;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an obvious cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
    row += 1; // Increment the row counter
  }
  // Reset variables
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
}


void checkBlockers() {
  // As for checkPossibilites() but this time checking the players squares for places to block a line of three
  Serial.println("Checking possibilities to block...");
  disp("Can I block?");
  int poss = 0;
  int x = 0;
  int y = 0;
  int space = 0;


  // Check rows
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 1) {
        poss += 1;
      }
      if (gamePlay[i][j] == 0) {
        space = 1;
        x = i;
        y = j;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) {
        Serial.print("Found an blocker row! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }


  // Check columns
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[j][i] == 1) {
        poss += 1;
      }
      if (gamePlay[j][i] == 0) {
        space = 1;
        x = j;
        y = i;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) {
        Serial.print("Found an blocker column! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }


  // Check crosses
  for (int i = 0; i < 3; i++) {
    if (gamePlay[i][i] == 1) {
      poss += 1;
    }
    if (gamePlay[i][i] == 0) {
      space = 1;
      x = i;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an blocker cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
  }
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
  int row = 0;
  for (int i = 2; i >= 0; i--) {
    if (gamePlay[row][i] == 1) {
      poss += 1;
    }
    if (gamePlay[row][i] == 0) {
      space = 1;
      x = row;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an blocker cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
    row += 1;
  }
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
}


void randomPlay() {
  // No win or block to play... Let's just pick a square at random
  Serial.println("Choosing randomly...");
  int choice = random(1, squaresLeft); // We pick a number from 0 to the number of squares left on the board
  Serial.print("Arduino chooses "); Serial.println(choice);
  int pos = 1; // Stores the free square we're currently on
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 0) { // Check to see if square empty
        if (pos == choice) { // Play the empty square that corresponds to the random number
          playPoss(i, j);
        }
        pos += 1; // Increment the free square counter
      }
    }
  }
}


void playPoss(int x, int y) {
  // Simulate thought and then play the chosen square
  disp("Hmmm...");
  delay(arduinoDelay);
  disp("OK");
  digitalWrite(red[x][y], HIGH);
  gamePlay[x][y] = 2; // Update the game play array
  played = 1; // Note that we've played
}


void checkGame() {
  // Check the game for a winner


  // Check if the player has won
  Serial.println("Checking for a winner");
  disp("Checking...");
  int player = 1;
  int winner = 0;
  for (int i = 0; i < 8; i++) { // We cycle through all winning combinations in the 4D array and check if they correspond to the current game
    //Check game
    int winCheck = 0;
    for (int j = 0; j < 3; j++) {
      for (int k = 0; k < 3; k++) {
        if ((win[i][j][k] == 1) && (gamePlay[j][k] == player)) {
          winCheck += 1;
        }
      }
    }
    if (winCheck == 3) {
      winner = 1;
      Serial.print("Player won game "); Serial.println(i);
      endGame(1);
    }
  }


  // Do the same for to check if the Arduino has won
  player = 2;
  winner = 0;
  for (int i = 0; i < 8; i++) {
    //Check game
    int winCheck = 0;
    for (int j = 0; j < 3; j++) {
      for (int k = 0; k < 3; k++) {
        if ((win[i][j][k] == 1) && (gamePlay[j][k] == player)) {
          winCheck += 1;
        }
      }
    }
    if (winCheck == 3) {
      winner = 1;
      Serial.print("Arduino won game "); Serial.println(i);
      endGame(2);
    }
  }
  if (squaresLeft == -1) {
    endGame(0);
  }
}


void printGame() {
  // Prints the game to the serial monitor
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      Serial.print(gamePlay[i][j]); Serial.print(" ");
    }
    Serial.println("");
  }
  Serial.print(squaresLeft); Serial.println(" squares left");
}


void endGame(int winner) {
  // Is called when a winner is found
  switch (winner) {
    case 0:
      Serial.println("It's a draw");
      digitalWrite(greenWin, HIGH);
      digitalWrite(redWin, HIGH);
      disp("It's a draw!");
      break;
    case 1:
      Serial.println("Player wins");
      digitalWrite(greenWin, HIGH);
      disp("You win!!!");
      break;
    case 2:
      Serial.println("Arduino wins");
      digitalWrite(redWin, HIGH);
      disp("I win!!!");
      break;
  }
  lcd.setCursor(0, 1);
  lcd.print("Press reset...");
  while (digitalRead(resetButton) == LOW) {
  }
  initialise();
}


void disp(String message) {
  // Used to quickly display a message on the LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(message);
}


void startupFlash() {
  // Flash at the start
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      digitalWrite(green[i][j], HIGH);
      digitalWrite(greenWin, HIGH);
      delay(startupFlashSpeed);
      digitalWrite(green[i][j], LOW);
      digitalWrite(greenWin, LOW);
      digitalWrite(red[i][j], HIGH);
      digitalWrite(redWin, HIGH);
      delay(startupFlashSpeed);
      digitalWrite(red[i][j], LOW);
      digitalWrite(redWin, LOW);
    }
  }
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      digitalWrite(green[j][i], HIGH);
      delay(startupFlashSpeed);
      digitalWrite(green[j][i], LOW);
    }
  }
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      digitalWrite(red[i][j], HIGH);
      delay(startupFlashSpeed);
      digitalWrite(red[i][j], LOW);
    }
  }
  for (int i = 0; i < 3; i++) {
    digitalWrite(red[i][i], HIGH);
    delay(startupFlashSpeed);
    digitalWrite(red[i][i], LOW);
  }
  int row = 0;
  for (int i = 2; i >= 0; i--) {
    digitalWrite(green[row][i], HIGH);
    delay(startupFlashSpeed);
    digitalWrite(green[row][i], LOW);
    row += 1;
  }
}

For the Arduino Uno:

<p>// TIC TAC TOE for Arduino Uno<br>// by Nick Harvey</p><p>#include <charlieplex.h>
byte pins[5] = {5, 6, 9, 10, 11};
Charlieplex charlie(pins, sizeof(pins));</charlieplex.h></p><p>// Define LEDs
const int green[3][3] = { // Green is the player
  {0, 8, 14},
  {2, 10, 16},
  {4, 12, 6}
};
const int red[3][3] = { // Red is the Arduino
  {1, 9, 15},
  {3, 11, 17},
  {5, 13, 7}
};
const int button = A0; // The analog pin the button matrix is connected to</p><p>const int resButtons[3][3] = { // The resistance thresholds for the buttons
  {800, 400, 200},
  {160, 140, 120},
  {90, 85, 70}
};</p><p>const int greenWin = 18; // Lights if the player wins
const int redWin = 19; // Lights if the Arduino wins
const int resetButton = 13; // Button to start a new game</p><p>const int win[8][3][3] = { // This 4D array defines all possible winning combinations
  {
    {1, 1, 1},
    {0, 0, 0},
    {0, 0, 0}
  },
  {
    {0, 0, 0},
    {1, 1, 1},
    {0, 0, 0}
  },
  {
    {0, 0, 0},
    {0, 0, 0},
    {1, 1, 1}
  },
  {
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0}
  },
  {
    {0, 1, 0},
    {0, 1, 0},
    {0, 1, 0}
  },
  {
    {0, 0, 1},
    {0, 0, 1},
    {0, 0, 1}
  },
  {
    {1, 0, 0},
    {0, 1, 0},
    {0, 0, 1}
  },
  {
    {0, 0, 1},
    {0, 1, 0},
    {1, 0, 0}
  }
};</p><p>// Global variables
int gamePlay[3][3] = { // Holds the current game
  {0, 0, 0},
  {0, 0, 0},
  {0, 0, 0}
};
int squaresLeft = 9; // The number of free squares left on the board
int played = 0; // Has the Arduino played or not</p><p>// Global constants
const int arduinoDelay = 3000; // How long the Arduino waits before playing (simulates thought)</p><p>void setup() {
  // put your setup code here, to run once:</p><p>  // Start serial comms
  Serial.begin(9600);</p><p>  // Define buttons as inputs
  pinMode(button, INPUT);</p><p>  //Define reset button as input
  pinMode(resetButton, INPUT);</p><p>  initialise();
}</p><p>void initialise() {
  // Prepare the board for a game
  Serial.println("Initialising...");
  // Set green and red LEDs off
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      charlie.setLed(green[i][j], false);
      charlie.setLed(red[i][j], false);
      gamePlay[i][j] = 0;
    }
  }
  // Set win LEDs off
  charlie.setLed(greenWin, false);
  charlie.setLed(redWin, false);</p><p>  // Reset variables
  squaresLeft = 9;
}
void loop() {
  // put your main code here, to run repeatedly:
  // Wait for an input and call the buttonPress routine if pressed
  int upper = 10000;
  if (analogRead(button) != 0) {
    int x;
    int y;
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        if (gamePlay[i][j] == 0) {
          if ((analogRead(button) > resButtons[i][j]) && (analogRead(button) < upper)) {
            buttonPress(i, j);
          }
        }
        upper = resButtons[i][j];
      }
    }
  }
  charlie.loop();
}</p><p>void buttonPress(int i, int j) {
  // Button pressed, light the green LED and note the square as taken
  Serial.print("Button press "); Serial.print(i); Serial.print(":"); Serial.println(j);
  charlie.setLed(green[i][j], true);
  gamePlay[i][j] = 1; // Note the square played
  squaresLeft -= 1; // Update number of squares left
  printGame(); // Print the game to serial monitor
  checkGame(); // Check for a winner
  arduinosTurn(); // Arduino's turn
}</p><p>void arduinosTurn() {
  // Arduino takes a turn
  Serial.println("Arduino's turn");
  checkPossiblities(); // Check to see if a winning move can be played
  if (played == 0) {
    checkBlockers(); // If no winning move played, check to see if we can block a win
  }
  if (played == 0) {
    randomPlay(); // Otherwise, pick a random square
  }
  squaresLeft -= 1; // Update number of squares left
  played = 0; // Reset if played or not
  printGame(); // Print the games to serial monitor
  checkGame(); // Check for a winner
}</p><p>void checkPossiblities() {
  // Check all rows, then columns, then diagonals to see if there are two reds lit and a free square to make a line of three
  Serial.println("Checking possibilities to win...");
  int poss = 0; // Used to count if possible - if it gets to 2 then its a possiblity
  int x = 0; // The X position to play
  int y = 0; // The Y position to play
  int space = 0; // Is there a free square or not to play
  // Check rows
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 2) {
        poss += 1; // Square is red. Increment the possiblity counter
      }
      if (gamePlay[i][j] == 0) {
        space = 1; // Square is empty. Note this and the position
        x = i;
        y = j;
      }
      if ((poss == 2) && (space == 1)) { // 2 red squares and a free square
        Serial.print("Found an obvious row! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }</p><p>  // Check columns - same as for rows but the "for" loops have been reversed to go to columns
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[j][i] == 2) {
        poss += 1;
      }
      if (gamePlay[j][i] == 0) {
        space = 1;
        x = j;
        y = i;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) { // This time also check if we've already played
        Serial.print("Found an obvious column! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    // Reset variables
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }</p><p>  // Check crosses - as for rows and columns but "for" loops changed
  // Check diagonal top left to bottom right
  for (int i = 0; i < 3; i++) {
    if (gamePlay[i][i] == 2) {
      poss += 1;
    }
    if (gamePlay[i][i] == 0) {
      space = 1;
      x = i;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an obvious cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
  }
  // Reset variables
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
  // Check diagonal top right to bottom left
  int row = 0; // Used to count up the rows
  for (int i = 2; i >= 0; i--) { // We count DOWN the columns
    if (gamePlay[row][i] == 2) {
      poss += 1;
    }
    if (gamePlay[row][i] == 0) {
      space = 1;
      x = row;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an obvious cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
    row += 1; // Increment the row counter
  }
  // Reset variables
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
}</p><p>void checkBlockers() {
  // As for checkPossibilites() but this time checking the players squares for places to block a line of three
  Serial.println("Checking possibilities to block...");
  int poss = 0;
  int x = 0;
  int y = 0;
  int space = 0;
  // Check rows
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 1) {
        poss += 1;
      }
      if (gamePlay[i][j] == 0) {
        space = 1;
        x = i;
        y = j;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) {
        Serial.print("Found an blocker row! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }</p><p>  // Check columns
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[j][i] == 1) {
        poss += 1;
      }
      if (gamePlay[j][i] == 0) {
        space = 1;
        x = j;
        y = i;
      }
      if ((poss == 2) && (space == 1) && (played == 0)) {
        Serial.print("Found an blocker column! "); Serial.print(x); Serial.println(y);
        playPoss(x, y);
      }
    }
    poss = 0;
    x = 0;
    y = 0;
    space = 0;
  }</p><p>  // Check crosses
  for (int i = 0; i < 3; i++) {
    if (gamePlay[i][i] == 1) {
      poss += 1;
    }
    if (gamePlay[i][i] == 0) {
      space = 1;
      x = i;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an blocker cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
  }
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
  int row = 0;
  for (int i = 2; i >= 0; i--) {
    if (gamePlay[row][i] == 1) {
      poss += 1;
    }
    if (gamePlay[row][i] == 0) {
      space = 1;
      x = row;
      y = i;
    }
    if ((poss == 2) && (space == 1) && (played == 0)) {
      Serial.print("Found an blocker cross! "); Serial.print(x); Serial.println(y);
      playPoss(x, y);
    }
    row += 1;
  }
  poss = 0;
  x = 0;
  y = 0;
  space = 0;
}</p><p>void randomPlay() {
  // No win or block to play... Let's just pick a square at random
  Serial.println("Choosing randomly...");
  int choice = random(1, squaresLeft); // We pick a number from 0 to the number of squares left on the board
  Serial.print("Arduino chooses "); Serial.println(choice);
  int pos = 1; // Stores the free square we're currently on
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      if (gamePlay[i][j] == 0) { // Check to see if square empty
        if (pos == choice) { // Play the empty square that corresponds to the random number
          playPoss(i, j);
        }
        pos += 1; // Increment the free square counter
      }
    }
  }
}</p><p>void playPoss(int x, int y) {
  // Simulate thought and then play the chosen square
  int delayStop = millis() + arduinoDelay;
  while (millis() < delayStop) {
    charlie.loop();
  }
  charlie.setLed(red[x][y], true);
  gamePlay[x][y] = 2; // Update the game play array
  played = 1; // Note that we've played
}</p><p>void checkGame() {
  // Check the game for a winner
  // Check if the player has won
  Serial.println("Checking for a winner");
  int player = 1;
  int winner = 0;
  for (int i = 0; i < 8; i++) { // We cycle through all winning combinations in the 4D array and check if they correspond to the current game
    //Check game
    int winCheck = 0;
    for (int j = 0; j < 3; j++) {
      for (int k = 0; k < 3; k++) {
        if ((win[i][j][k] == 1) && (gamePlay[j][k] == player)) {
          winCheck += 1;
        }
      }
    }
    if (winCheck == 3) {
      winner = 1;
      Serial.print("Player won game "); Serial.println(i);
      endGame(1);
    }
  }
  // Do the same for to check if the Arduino has won
  player = 2;
  winner = 0;
  for (int i = 0; i < 8; i++) {
    //Check game
    int winCheck = 0;
    for (int j = 0; j < 3; j++) {
      for (int k = 0; k < 3; k++) {
        if ((win[i][j][k] == 1) && (gamePlay[j][k] == player)) {
          winCheck += 1;
        }
      }
    }
    if (winCheck == 3) {
      winner = 1;
      Serial.print("Arduino won game "); Serial.println(i);
      endGame(2);
    }
  }
  if (squaresLeft == -1) {
    endGame(0);
  }
}</p><p>void printGame() {
  // Prints the game to the serial monitor
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      Serial.print(gamePlay[i][j]); Serial.print(" ");
    }
    Serial.println("");
  }
  Serial.print(squaresLeft); Serial.println(" squares left");
}</p><p>void endGame(int winner) {
  // Is called when a winner is found
  switch (winner) {
    case 0:
      Serial.println("It's a draw");
      charlie.setLed(greenWin, true);
      charlie.setLed(redWin, true);
      break;
    case 1:
      Serial.println("Player wins");
      charlie.setLed(greenWin, true);
      break;
    case 2:
      Serial.println("Arduino wins");
      charlie.setLed(redWin, true);
      break;
  }
  while (digitalRead(resetButton) == LOW) {
    charlie.loop();
  }
  initialise();
}</p>

Step 5: Next Steps / Improvements

For the future, I'm going to add a score keeper (showing the score on the LCD) so that we can play 10 games, for example, and the player with the highest score wins. I also want to add a switch (not a button) to allow two human players to play against each other. Finally, I'd like to replace the two colours of LED with some 8x8 LED matrices so that I really can show O and X. I need to buy a few more first though.