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.
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 😎.