Tic tac toe game in javascript with bot.

Learn how to create a tic tac toe game in JavaScript with an AI bot. It is a common frontend interview question that you can expect in the machine coding rounds.

Tic Tac Toe is a dual player game in which each player mark their option (X, O) on a 3 x 3 board in each turn and one who get 3 of them in line horizontally, vertically, or diagonally wins.

Tic Tac Toe game in javascript

By building this you will learn

  • Layout: How to layout element with HTML to create a 3 x 3 board.
  • Styling: Create 1 : 1 aspect ratio grids.
  • Bot:- How to create a bot to play a game along with.

The best thing you will learn in it is how to use the minimax algorithm of game theory to build an intelligent bot which is impossible to beat.

I guess you have good idea about what are we going to build, so let’s start creating it.

Creating the layout of the Tic Tac Toe board

The board is 3 x 3 grid along with an radio button to choose the players option.

I have separated the layout in three different sections.

Section to select the players option

For this we will be using a radio button.

<!-- Choose Option -->
<section class="option-selection-area">
    <h1>Choose Your Option</h1>
    <div class="field-group">
        <input type="radio" name="player-option" value="1" checked/>
        <label for="player-option">X</label>
    </div>
    <div class="field-group">
        <input type="radio" name="player-option" value="-1" />
        <label for="player-option">O</label>
    </div>
</section>

3 x 3 Board

I have used to 9 div’s to create the layout of the board which will be later aligned with CSS. I also given custom attributes to these elements to represent their position in a 3 x 3 array.

This way it will be easy for me to select and update them, but you can use either a canvas or whatever you prefer.

<!-- Board -->
<section class="game-area">
    <div class="row" data-column="0" data-row="0">
        <div><span></span></div>
    </div>
    <div class="row" data-column="1" data-row="0">
        <div><span></span></div>
    </div>
    <div class="row" data-column="2" data-row="0">
        <div><span></span></div>
    </div>
    <div class="row" data-column="0" data-row="1">
        <div><span></span></div>
    </div>
    <div class="row" data-column="1" data-row="1">
        <div><span></span></div>
    </div>
    <div class="row" data-column="2" data-row="1">
        <div><span></span></div>
    </div>
    <div class="row" data-column="0" data-row="2">
        <div><span></span></div>
    </div>
    <div class="row" data-column="1" data-row="2">
        <div><span></span></div>
    </div>
    <div class="row" data-column="2" data-row="2">
        <div><span><span></span></div>
    </div>
</section>

Result area

<!-- Result dashboard -->
<section class="result"></section>

I have wrapped all these sections inside a main wrapper to keep them together.

<main class="wrapper">
  <!-- All sections will be here -->
</main>

Styling the board

I have used flex box to align the elements.

To create a 1:1 element which has equal height and width, we give the padding-bottom:100% to the parent and make it relative and then keep the child absolute to the parent.

.wrapper {
  width: 350px;
  margin: 0 auto;
}

.option-selection-area {
  display: flex;
  justify-content: space-evenly;
  align-items: center;
  flex-wrap: wrap;
}

.option-selection-area > h1 {
  flex: 1 1 100%;
  text-align: center;
  font-size: 2em;
}

.field-group {
  display: inline-flex;
  align-items: center;
  font-size: 3em;
}

input[type="radio"] {
  width: 30px;
  height: 30px;
}

.game-area {
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-around;
  flex-wrap: wrap;
}

.row {
  flex: 0 1 calc(33% - 10px);
  border: 1px solid;
  width: calc(33% - 10px);
  margin-bottom: 10px;
}

/* Create 1:1 by giving padding-bottom */
.row > div {
  width: 100%;
  padding-bottom: 100%;
  position: relative;
}

.row > div > span {
  font-size: 8em;
  position: absolute;
  width: 100%;
  height: 100%;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-transform: uppercase;
}

.result {
  text-align: center;
  font-size: 2em;
  color: green;
}

Adding life to tic tac toe game with javascript and also creating bot.

First, we will handle player’s action that when he clicks on any grid on the board we mark his option.

Select and store all the elements so that we can use them whenever possible.

//Array to track the board
const board = [
  ["", "", ""],
  ["", "", ""],
  ["", "", ""],
];

//Options available
const options = document.querySelectorAll("[name='player-option']");
//Board
const rows = document.querySelectorAll(".row");
//Result area
const result = document.querySelector(".result");

//Players option
let ai = "O";
let human = "X";

Then listen to the click event on the board add the mark the option. When ever an option is marked on any particular grid in the board we do the same in the array which we have created for tracking.

We will use this array for calculating the result and deciding the bot move.

//Handle the player click on grid
rows.forEach((e) => {
  //Get the grand child span
  const span = e.children[0].children[0];

  e.addEventListener("click", (f) => {
    //Get which grid is clicked
    const dataRow = +e.getAttribute("data-row");
    const dataColumn = +e.getAttribute("data-column");
    
    //If the grid is not marked
    if (board[dataRow][dataColumn] === "") {
      //Player move
      span.innerHTML = human;
      board[dataRow][dataColumn] = human;
    }
  });
});

Creating tic tac toe bot using minimax algorithm of game theory

The minimax algorithm works by choosing the best move based on certain values and we mark the bot option for the grid where the value is greatest.

It works on minimizing the loss rather than trying to win in each turn.

You can read more about the minimax algorithm.

//Bot move
const bestMove = () => {
  // AI to make its turn
  let bestScore = -Infinity;
  let move;
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      // Is the spot available?
      if (board[i][j] == "") {
        board[i][j] = ai;
        let score = minimax(board, 0, false);
        board[i][j] = "";
        if (score > bestScore) {
          bestScore = score;
          move = { i, j };
        }
      }
    }
  }

  return move;
};

//Calculate where next move should take place
const minimax = (board, depth, isMaximizing) => {
  //Check the winner and return the score
  let result = checkWinner();
  if (result !== null) {
    return scores[result];
  }

  if (isMaximizing) {
    let bestScore = -Infinity;
    for (let i = 0; i < 3; i++) {
      for (let j = 0; j < 3; j++) {
        // Is the spot available?
        if (board[i][j] == "") {
          board[i][j] = ai;
          let score = minimax(board, depth + 1, false);
          board[i][j] = "";
          bestScore = Math.max(score, bestScore);
        }
      }
    }
    return bestScore;
  } else {
    let bestScore = Infinity;
    for (let i = 0; i < 3; i++) {
      for (let j = 0; j < 3; j++) {
        // Is the spot available?
        if (board[i][j] == "") {
          board[i][j] = human;
          let score = minimax(board, depth + 1, true);
          board[i][j] = "";
          bestScore = Math.min(score, bestScore);
        }
      }
    }
    return bestScore;
  }
};

Now if you see the minimax function it checks the score and returns the result. So we need to assign certain scores to each player to make this algorithm work.

As user can select different options we will have to update the score whenever the options changes to make sure that bot is working properly.

//Score to decide the next move of bot
const scores = {
  X: 10,
  O: -10,
  tie: 0,
};

//Function to update selected option and setting the score for bot
const updateSelector = (value) => {
  if (value === "1") {
    human = "X";
    ai = "O";
  } else {
    human = "O";
    ai = "X";
  }

  //Update the score based on selector
  scores[human] = -10;
  scores[ai] = 10;
};

//Update player option initally
let start = options[0].value;
updateSelector(start);

//Update player option on option change
options.forEach((e) => {
  e.addEventListener("change", (f) => {
    const { value } = f.target;
    updateSelector(value);
  });
});

This updates the options for default selector as well as when option changes.

Another function that is pending is the checkWinner() which calculates who won. It checks if any of the line horizontally, vertically, and diagonally is filled in the array and if yes by who's marker.

If no one wins then there is a tie.

//Check all the values are equal
const equals3 = (a, b, c) => {
  return a == b && b == c && a != "";
};

//Check match winner
const checkWinner = () => {
  let winner = null;

  // horizontal
  for (let i = 0; i < 3; i++) {
    if (equals3(board[i][0], board[i][1], board[i][2])) {
      winner = board[i][0];
    }
  }

  // Vertical
  for (let i = 0; i < 3; i++) {
    if (equals3(board[0][i], board[1][i], board[2][i])) {
      winner = board[0][i];
    }
  }

  // Diagonal
  if (equals3(board[0][0], board[1][1], board[2][2])) {
    winner = board[0][0];
  }
  if (equals3(board[2][0], board[1][1], board[0][2])) {
    winner = board[2][0];
  }

  //Are still moves left
  let openSpots = 0;
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      if (board[i][j] == "") {
        openSpots++;
      }
    }
  }

  //Return winner
  if (winner == null && openSpots == 0) {
    return "tie";
  } else {
    return winner;
  }
};

Now call this bot to make move once the player has marked his option and check the result after it.

//Handle the player click on grid
rows.forEach((e) => {
  //Get the grand child span
  const span = e.children[0].children[0];

  e.addEventListener("click", (f) => {
    //Get which grid is clicked
    const dataRow = +e.getAttribute("data-row");
    const dataColumn = +e.getAttribute("data-column");

    //If the grid is not marked
    if (board[dataRow][dataColumn] === "") {
      //Player move
      span.innerHTML = human;
      board[dataRow][dataColumn] = human;

      //Bot move
      const botMove = bestMove();

      //If bot can make move then update the board
      if (botMove) {
        board[botMove.i][botMove.j] = ai;
        const botPlace = document.querySelector(
          `[data-row='${botMove.i}'][data-column='${botMove.j}'] span`
        );

        botPlace.innerHTML = ai;
      }

      //Get match's result and show it on the dash board
      const outcome = checkWinner();
      if (outcome) {
        if (outcome === "tie") {
          result.innerHTML = outcome;
        } else {
          result.innerHTML = `${outcome} wins`;
        }
      }
    }
  });
});

After each move it gets the bot move and marks it marker on the board as well as update it the array which we are using for tracking.

OOLA!. You just learned to create your own to play with in a tic tac toe game 😎.