In this tutorial, we will learn how to create a fun and engaging game of Tic-Tac-Toe using the popular JavaScript framework, React. With React, you can create a dynamic and interactive version of the game that allows players to enjoy a seamless gaming experience.
By the end of this tutorial, you will have a fully-functional Tic-Tac-Toe game that you can share with your friends and colleagues. So, get ready to dive into the world of React and start building your own Tic-Tac-Toe game today!
The first step would be setting up our React application and for that, you need to choose a development environment. For this project, we’ll use Vite, but feel free to choose another environment that suits your preferences.
If you don’t know how to set up React app using Vite Click here.
Now we are gonna create two jsx files called Board.jsx and Square.jsx in our src folder, and import Board.jsx in App.jsx.
In our project, all of the code will be contained within the Board.jsx and Square.jsx files. We are going to use tailwindcss here so you need to install the tailwindcss. If you don’t know how to install tailwindcss in your project so do visit the official website of tailwindcss :- https://tailwindcss.com/.
Game appearance
1. Creating UI
user interface (UI) is one of the main part of our game it will decide how our game is going to look and we will create half of our UI in Board.jsx.
To make a user interface, we’ll start by creating a container called a “div” in our Board.jsx and giving it an id, “Harry”. We will add some “CSS classes” to this container. Inside “Harry”, we’ll create another container with id “Venki” and add CSS classes to it as well. Inside “Venki”, we’ll create two more containers with id “Rahul” and “Rohit” and add tailwindcss classes to each of them.
Inside the div “Rahul” we will create a div called “Nikk” and a button called “button-1” with the text “new Game”. Within the div “Rohit” we will create three more divs and we will also add Tailwind CSS classes to the className attribute of each of these three new divs and for that we will create a variable called “borderRowClass”.
Now, this is how our Board.jsx is going to look :
import React from "react";
const Board = () => {
const borderRowClass =
"board-row board-row flex justify-evenly items-center";
return (
<div
id="Harry"
className=" flex items-center justify-center w-full h-screen text-white bg-slate-800 App"
>
<div id="Venki" className=" bg-white h-96 w-96">
<div id="Rahul" className=" flex justify-between">
<div id="Nikk" className="mb-2 text-xl">Player __ please move</div>
<button
id="button-1"
type="button"
className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
>
new Game
</button>
</div>
<div
id="Rohit"
className=" border-2 border-white border-solid border-container "
>
<div className={borderRowClass}></div>
<div className={borderRowClass}></div>
<div className={borderRowClass}></div>
</div>
</div>
</div>
);
};
export default Board;
We are going to work on “Square.jsx”. In this file, we will create a component called “Square” that represents a clickable square in the game. When we play the game, we will click on this square to make a move.
This is how our Square.jsx is going to look :
import React from "react";
const Square = (props) => {
return (
<div
onClick={props.onClick}
className="flex items-center justify-center w-full text-3xl border-2 border-white border-solid h-28 square "
>
{props.value}
</div>
);
};
export default Square;
It is a simple file that contains a function called “square”. This function uses “props” and some pre-made design styles called “CSS classes” in its code. The component in our file has a container called “div”, which has an “onClick” event. The value of this event is coming from “props”. When we click on this square, the action that happens is also coming from “props”.
Next, we will come back to the Board.jsx, and recall that we had created three divs inside the div “Rohit” now, we will add three Square components inside each div and import it. We have completed our first step of creating UI.
Now, this is how our UI is going to look :
2. Adding logic to display values (X/0)
To add logic to the game, we will start by creating two state variables called “arr” and “isXturn”. Where the initial value of “arr” will be `Array(9).fill(null)` which means an Array of 9 lengths with the each value of null.
Now, we will create a function called “handleClick” that takes an input parameter “index” and will assign it to the “onClick” event of each Square component.
This will ensure that whenever a player clicks on a square, the “handleClick” function will be called with the index of that square as a parameter, allowing us to keep track of which square was clicked and update the game board accordingly.
Next, We will assign the current value of each position in the game board array to the “value” prop of each Square component.
This will ensure that each square on the game board displays the current value at its corresponding position in the game board array, allowing players to see the current state of the game board.
Code :
const [arr, setArr] = useState(Array(9).fill(null));
const [isXturn, setIsXturn] = useState(true);
const handleClick = (index) => {};
// -----> your code
// <div className={borderRowClass}>
<Square onClick={() => handleClick(0)} value={arr[0]} />
<Square onClick={() => handleClick(1)} value={arr[1]} />
<Square onClick={() => handleClick(2)} value={arr[2]} />
// </div>
// <div className={borderRowClass}>
<Square onClick={() => handleClick(3)} value={arr[3]} />
<Square onClick={() => handleClick(4)} value={arr[4]} />
<Square onClick={() => handleClick(5)} value={arr[5]} />
// </div>
// <div className={borderRowClass}>
<Square onClick={() => handleClick(6)} value={arr[6]} />
<Square onClick={() => handleClick(7)} value={arr[7]} />
<Square onClick={() => handleClick(8)} value={arr[8]} />
// </div>
// ----> your code
Now we will move to the “handleClick” function, we will first update the value of the “isXturn” variable to the opposite of its current value using the “setIsXturn” function.
Then, we will create a new variable called “copyState” that will be a copy of our current game board state variable “arr”.
This will allow us to make changes to the copied version of the game board state without modifying the original state variable.
Next, we will determine whether the current player is X or O based on the value of the “isXturn” variable.
If “isXturn” is true, we will set the value of the clicked square to “X” in our “arr” game board state variable. If “isXturn” is false, we will set the value of the clicked square to “O” in the “arr” game board state variable.
After updating the value of the clicked square in the copied game board state variable, we will pass the copied game board state variable “copyState” to the “setArr” function to update the original game board state variable “arr”.
This will update the game board state with the latest move made by the player, allowing us to keep track of the game’s progress and determine the winner.
This will allow us to keep track of the player’s moves and update the game board state accordingly.
Code :
const handleClick = (index) => {
setIsXturn(!isXturn);
const copyState = [...arr];
copyState[index] = isXturn ? "x" : "0";
setArr(copyState);
};
This is how our game is going to work :
Do you see any Problem in there??
There is a problem with the game’s current functionality. If we click on the first square of the game board, it will display an “X” value. However, if we click on the same square again, it will change the value to “O”, which is not correct.
To fix the problem we will add an “if” statement to the “handleClick” function.
If the value of the clicked square is not null (i.e., it already contains an “X” or “O”), the function will simply return without making any changes to the game board state. This will prevent players from changing the value of a square that has already been clicked.
Code :
const handleClick = (index) => {
if (arr[index] !== null) {
return;
}
setIsXturn(!isXturn);
const copyState = [...arr];
copyState[index] = isXturn ? "x" : "0";
setArr(copyState);
};
Now, the problem should be fixed.
3. Whose Move and New Game
To display whose turn it is, we need to update the content of the <div>
with an id
of “Nikk”. We can achieve this by adding an if-else statement to the <div>
code. If the “isXturn
” variable is, we will display the letter “X”, and if it’s false
, we will display the number “0” on the screen.
Code :
<div id="Nikk" className="mb-2 text-xl">
Player {isXturn ? "X" : "0"} please move
</div>
To create a “New Game” functionality, we need to define a function called “handleNewGame” and we will pass it to the Button with id “button-1”.
When a player clicks on the “New Game” button, this function will be called, which will reset the game board by setting all the values in the arr
state to null. This will remove all “X” and “0” from the board, effectively starting a new game.
Code :
const handleNewGame = () => {
setArr(Array(9).fill(null));
};
<button
onClick={handleNewGame}
id="button-1"
// -----> your code >
new Game
</button>
4. Adding Winner Logic
To check who has won the game we need to define a function called “checkWinner”. First, we need to create a two-dimensional array that will store all possible winning combinations on the game board. This array will be used to check if any of the players has won the game.
Next, we will add a “for…of” loop, ( “for…of” loop provides an easier way to iterate over elements in an array or other iterable objects, such as strings or maps, without needing to use an index or counter variable. ) in which we will add all the logic of our winner.
In the “for...of
” loop, we will perform three checks to determine if a player has won the game. Firstly, we will check if the current element of the “arr
” array is not null.
Next, we will check if the first and second elements of the ‘arr
” array are equal, and then we will check if the first and third elements of the “arr
” array are equal. If all three conditions are true, we will return the value of that element, which could be “X” or “0”, indicating that the player has won the game.
If any of these conditions are not true, we will return false, indicating that the player has not won the game.
Code :
const checkWinner = () => {
const winnerlogic = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let logic of winnerlogic) {
const [a, b, c] = logic;
if (arr[a] !== null && arr[a] === arr[b] && arr[a] === arr[c]) {
return arr[a];
}
}
return false;
};
const isWinner = checkWinner();
Now, we will add a statement that if “isWinner” returns a value “X” or “0” so display it on the screen and also display a playAgain button and if it returns false so nothing will be change.
Code :
<div id="Venki" className=" w-96">
{isWinner ? (
<div className="flex flex-col justify-center space-y-4 items-centerlkjhgfdxz">
{" "}
<div className="text-4xl "> {isWinner} has won </div>
<div>
<button
id="button-2"
type="button"
className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
>
Play Again
</button>
</div>
</div>
) : (
<>
{" "}
<div id="Rahul" className=" flex justify-between">
<div id="Nikk" className="mb-2 text-xl">
Player {isXturn ? "X" : "0"} please move
</div>
<button
onClick={handleNewGame}
id="button-1"
type="button"
className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
>
new Game
</button>
</div>
<div
id="Rohit"
className="border-2 border-white border-solid border-container"
>
<div className={borderRowClass}>
<Square onClick={() => handleClick(0)} value={arr[0]} />
<Square onClick={() => handleClick(1)} value={arr[1]} />
<Square onClick={() => handleClick(2)} value={arr[2]} />
</div>
<div className={borderRowClass}>
<Square onClick={() => handleClick(3)} value={arr[3]} />
<Square onClick={() => handleClick(4)} value={arr[4]} />
<Square onClick={() => handleClick(5)} value={arr[5]} />
</div>
<div className={borderRowClass}>
<Square onClick={() => handleClick(6)} value={arr[6]} />
<Square onClick={() => handleClick(7)} value={arr[7]} />
<Square onClick={() => handleClick(8)} value={arr[8]} />
</div>
</div>
</>
)}
</div>
5. Play Again Button
To add play again functionality to our game we just need to do what we did previously in our New Game functionality, we just need to create a function called “handlePlayAgain” and pass it to the, which will reset the game board by setting all the values in the arr
state to null. This will remove all “X” and “0” from the board.
Code :
const handlePlayAgain = () => {
setArr(Array(9).fill(null));
};
<button
onClick={handlePlayAgain}
id="button-2"
type="button"
className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
>
Play Again
</button>
Now, our game will work properly.
I hope this tutorial helped you build your own Tic-Tac-Toe game.
ALSO SEE- MAKE SNAKE GAME IN JAVASCRIPT REACT