Understanding the Flow of a Tic-Tac-Toe Game in JavaScript
A Tic-Tac-Toe game is a good project for understanding how JavaScript works with the DOM, event listeners, functions, conditions, and arrays. In this example, two players, X and O, take turns clicking on a 3×3 board. After every move, JavaScript checks whether a player has won or whether the game has ended in a tie.
Setting Up the Players
const players = ["X", "O"];
let currentPlayer = players[0];
The players array stores the two players. Since array indexing starts from 0, players[0] is "X".
Therefore, the game starts with:
currentPlayer = "X";
The currentPlayer variable keeps track of whose turn it is.
Getting the Game Squares
let cells = document.getElementsByClassName("square");
This gets all the HTML elements that have the class square.
For example, a Tic-Tac-Toe board can have nine squares:
0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8
The cells collection contains these nine squares so that JavaScript can work with them.
Defining the Winning Combinations
const winningCombinations = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
There are eight possible ways to win in Tic-Tac-Toe.
For example:
[0, 1, 2]
represents the first row, while:
[0, 3, 6]
represents the first column.
The diagonal is represented by:
[0, 4, 8]
These combinations are later used by the calculateWin() function.
Handling a Player's Click
The handleEvents() function is responsible for adding a click event to every square.
const handleEvents = () => {
for (let i of cells) {
i.addEventListener("click", () => {
// game logic
});
}
}
The for...of loop goes through every square in cells. An event listener is then attached to each square.
This means whenever a player clicks a square, the code inside the click event runs.
Checking Whether the Square Is Already Filled
The first thing the click event checks is whether the square already contains a player.
if (i.textContent === "X" || i.textContent === "O") {
return alert("This cell is already filled!");
}
If the square contains either "X" or "O", the function stops using return.
This prevents a player from replacing an existing move.
Placing the Player's Symbol
If the square is empty, the current player's symbol is placed inside it.
i.textContent = currentPlayer;
If it is X's turn:
currentPlayer = "X";
clicking a square changes it to:
X
After the move, the game needs to check whether X has won or whether the board is full.
Checking for a Winner
The code calls:
const iswin = calculateWin();
The calculateWin() function checks every winning combination.
for (let i of winningCombinations) {
const [a, b, c] = i;
Suppose the current combination is:
[0, 1, 2]
Array destructuring assigns:
a = 0;
b = 1;
c = 2;
The function then gets the three corresponding squares:
const square1 = document.getElementById(`square${a}`);
const square2 = document.getElementById(`square${b}`);
const square3 = document.getElementById(`square${c}`);
It checks whether all three contain the same player:
if (
square1.textContent &&
square1.textContent === square2.textContent &&
square1.textContent === square3.textContent
) {
return true;
}
The first condition:
square1.textContent
makes sure the square isn't empty.
The other two conditions check whether all three squares contain the same symbol.
If a winning combination is found, calculateWin() returns true. Otherwise, after checking all combinations, it returns false.
Checking for a Tie
After checking for a winner, the game checks whether the board is full.
const isTie = gameTie();
The gameTie() function goes through every square:
const gameTie = () => {
for (let i of cells) {
if (i.textContent === "") {
return false;
}
}
return true;
}
If even one square is empty, the game is not a tie, so it returns false.
If every square is filled, it returns true.
Handling the Winner or Tie
After placing a symbol, two checks are made:
if (iswin) {
return alert(`${currentPlayer} wins!`);
}
if (isTie) {
return alert("It's a tie!");
}
If iswin is true, the game displays the winner.
For example:
X wins!
If nobody has won but the board is full, the game displays:
It's a tie!
The return stops the rest of the click function from running.
Switching Between Players
If there is no winner and no tie, the game switches to the other player.
currentPlayer =
currentPlayer === players[0]
? players[1]
: players[0];
This uses the ternary operator.
It can be understood as:
if (currentPlayer === "X") {
currentPlayer = "O";
} else {
currentPlayer = "X";
}
So the flow becomes:
X's turn
↓
X clicks
↓
Check winner
↓
Check tie
↓
Switch to O
↓
O's turn
↓
O clicks
↓
Check winner
↓
Check tie
↓
Switch to X
Starting the Game
At the bottom of the code, we have:
document.addEventListener("DOMContentLoaded", handleEvents);
DOMContentLoaded runs when the HTML document has been loaded.
Only after the page has loaded does JavaScript call:
handleEvents();
This is important because handleEvents() needs to find the HTML elements containing the square class.
Complete Game Flow
The overall flow of the program is:
HTML loads
↓
DOMContentLoaded
↓
handleEvents()
↓
Add click events to all squares
↓
Player clicks a square
↓
Check if square is already filled
↓
Place X or O
↓
calculateWin()
↓
gameTie()
↓
Is there a winner?
├── Yes → Show winner → Stop
└── No
↓
Is it a tie?
├── Yes → Show tie → Stop
└── No
↓
Switch player
↓
Wait for next click
Restart Function
There is also a restartButton() function:
const restartButton = () => {
for (let i of cells) {
i.textContent = "";
}
}
It removes the contents of all squares, effectively clearing the board.
However, in the current code, this function is only defined and is not connected to a button or called anywhere. Also, if you use it as a real restart function, you would normally reset currentPlayer as well.
Top comments (0)