Tic-Tac-Toe AI with Minimax Algorithm in JavaScript
Tic-Tac-Toe AI with Minimax Algorithm in JavaScript
In this article, we will implement a simple Tic-Tac-Toe game where an AI opponent uses the Minimax Algorithm to play optimally against a human player.
Definition:
Minimax is a recursive algorithm to minimize the possible loss in a worst-case scenario, or equivalently, to maximize the minimum gain.
Idea:
- One player tries to maximize their score (MAX player).
- The other tries to minimize the score (MIN player).
Each player assumes that the opponent plays optimally.
📌 How it Works?
The algorithm builds a game tree of possible moves:
- Starting from the current state, generate all possible moves.
- Recursively simulate each move and its consequences down to a terminal state (win, lose, draw, or depth limit).
- Assign a score to each terminal state.
- scores:
- MAX player chooses the move with the highest score.
- MIN player chooses the move with the lowest score.
Initial Setup and Constants
const PLAYER = "X";
const AI = "O";
const PLAYER_SVG = "❌";
const AI_SVG = "â•";
let board = [
["", "", ""],
["", "", ""],
["", "", ""]
];
let currentPlayer = PLAYER;
let flag = false;
const resetBtn = document.querySelector(".restartBtn");
const aiStartsBtn = document.querySelector(".aiStarts");
resetBtn.addEventListener("click", () => resetGame());
aiStartsBtn.addEventListener("click", () => toggleAIStart());
Utility Functions
Check if More Moves Are Available
function isMoreMovesAvailable(board) {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === "") {
return true;
}
}
}
return false;
}
Toggle AI Starting Player
function toggleAIStart() {
aiStartsBtn.disabled = true;
flag = !flag;
startGame(flag);
}
Check Equality for Winning Conditions
function isEqual(a, b, c) {
return a === b && b === c && a !== "";
}
Game Over Detection
function gameOver(board) {
let winner = null;
// Check rows
for (let i = 0; i < 3; i++) {
if (isEqual(board[i][0], board[i][1], board[i][2])) {
winner = board[i][0];
}
}
// Check columns
for (let i = 0; i < 3; i++) {
if (isEqual(board[0][i], board[1][i], board[2][i])) {
winner = board[0][i];
}
}
// Check diagonals
if (isEqual(board[0][0], board[1][1], board[2][2])) {
winner = board[0][0];
}
if (isEqual(board[2][0], board[1][1], board[0][2])) {
winner = board[2][0];
}
if (winner === null && !isMoreMovesAvailable(board)) {
return "tie";
} else {
return winner;
}
}
Game Evaluation Function
Assign scores to game outcomes to guide the minimax algorithm:
function gameEval(board) {
if (gameOver(board) === PLAYER) return 10;
else if (gameOver(board) === AI) return -10;
else return 0;
}
Minimax Algorithm Implementation
function minimax(board, depth, isMax) {
const result = gameOver(board);
if (result !== null) return gameEval(board);
if (isMax) {
let bestScore = -Infinity;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === "") {
board[i][j] = PLAYER;
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++) {
if (board[i][j] === "") {
board[i][j] = AI;
let score = minimax(board, depth - 1, true);
board[i][j] = "";
bestScore = Math.min(score, bestScore);
}
}
}
return bestScore;
}
}
Handling Player Turn
Add click event listeners to cells and update the board state:
function playerTurn(board) {
const cells = document.querySelectorAll(".cell");
cells.forEach((cell) => {
const row = parseInt(cell.id[0]);
const col = parseInt(cell.id[1]);
cell.addEventListener("click", () => {
if (board[row][col] === "") {
cell.innerHTML = PLAYER_SVG;
board[row][col] = PLAYER;
const result = gameOver(board);
if (result) {
endGame(result);
} else {
currentPlayer = AI;
aiTurn(board);
}
}
});
});
}
Handling AI Turn
Use the minimax algorithm to choose the best move:
function aiTurn(board) {
let bestScore = Infinity;
let bestMove = null;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === "") {
board[i][j] = AI;
const score = minimax(board, 3, true);
board[i][j] = "";
if (score < bestScore) {
bestScore = score;
bestMove = { i, j };
}
}
}
}
const cell = document.getElementById(`${bestMove.i}${bestMove.j}`);
cell.innerHTML = AI_SVG;
board[bestMove.i][bestMove.j] = AI;
const result = gameOver(board);
if (result) endGame(result);
}
Ending the Game
Disable all cells and show the result:
function endGame(winner) {
const score = document.querySelector(".score");
const cells = document.querySelectorAll(".cell");
cells.forEach((cell) => {
cell.disabled = true;
});
if (winner === "tie") {
score.textContent = "Tie game!";
} else {
score.textContent = `${winner} is the winner!`;
}
}
Resetting the Game
Clear the board and reset UI:
function resetGame() {
const cells = document.querySelectorAll(".cell");
const score = document.querySelector(".score");
cells.forEach((cell) => {
cell.disabled = false;
cell.innerHTML = "";
});
score.textContent = "";
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
board[i][j] = "";
}
}
flag = false;
aiStartsBtn.disabled = false;
}
Starting the Game
function startGame(flag) {
if (flag) {
playerTurn(board);
aiTurn(board);
} else {
playerTurn(board);
}
}
// Start game initially
startGame();