Tic-Tac-Toe Game with AI Mode

Classic 3x3 Tic-Tac-Toe game supporting 2-Player local mode and Single Player vs smart AI with winning combination highlights and streaks.

Difficulty: Beginner Est. Time: 25 mins Vanilla JS (ES6+) Includes HTML, CSS, JS & README (.ZIP)
Live UI Runner

Tic-Tac-Toe Game with AI Mode

live-sandbox://tic-tac-toe

1. Project Overview & Features

The Tic-Tac-Toe Game with AI Mode is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.

Key Features Implemented:

  • Interactive 3x3 clickable game board
  • Switch between 2-Player mode and Single Player vs Computer AI
  • Winning cell highlight animations with strike line
  • Scoreboard tracking X wins, O wins, and Draws

2. Step-by-Step Code Walkthrough

A Step 1: Semantic HTML Structure

We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.

HTML (index.html)
<div class="tictactoe-card">
  <div class="game-header">
    <h2>Tic-Tac-Toe</h2>
    <div class="mode-select">
      <button class="mode-btn active" id="mode-p2">2-Player</button>
      <button class="mode-btn" id="mode-ai">vs AI</button>
    </div>
  </div>

  <div class="scoreboard">
    <div class="score-box x-box">
      <span>PLAYER X</span>
      <p id="score-x">0</p>
    </div>
    <div class="score-box tie-box">
      <span>DRAWS</span>
      <p id="score-draw">0</p>
    </div>
    <div class="score-box o-box">
      <span>PLAYER O</span>
      <p id="score-o">0</p>
    </div>
  </div>

  <div class="turn-indicator" id="status-text">Player X's Turn</div>

  <div class="board-grid" id="board">
    <div class="cell" data-idx="0"></div>
    <div class="cell" data-idx="1"></div>
    <div class="cell" data-idx="2"></div>
    <div class="cell" data-idx="3"></div>
    <div class="cell" data-idx="4"></div>
    <div class="cell" data-idx="5"></div>
    <div class="cell" data-idx="6"></div>
    <div class="cell" data-idx="7"></div>
    <div class="cell" data-idx="8"></div>
  </div>

  <button id="reset-game-btn" class="btn-reset-game"><i class="fas fa-redo"></i> Restart Match</button>
</div>

B Step 2: Styling & Responsive CSS

Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.

CSS (style.css)
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
  background: #0f172a;
  color: #f8fafc;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.tictactoe-card {
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 24px;
  padding: 28px;
  width: 100%;
  max-width: 400px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
  text-align: center;
}
.game-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 18px;
}
.game-header h2 {
  font-size: 1.3rem;
  color: #f43f5e;
}
.mode-select {
  display: flex;
  background: #0f172a;
  padding: 3px;
  border-radius: 8px;
}
.mode-btn {
  background: transparent;
  border: none;
  color: #94a3b8;
  padding: 4px 10px;
  border-radius: 6px;
  font-size: 0.75rem;
  cursor: pointer;
  font-weight: 600;
}
.mode-btn.active {
  background: #334155;
  color: #f8fafc;
}
.scoreboard {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}
.score-box {
  flex: 1;
  background: #0f172a;
  padding: 10px;
  border-radius: 10px;
  border: 1px solid #334155;
}
.score-box span {
  font-size: 0.68rem;
  color: #94a3b8;
  font-weight: 700;
}
.score-box p {
  font-size: 1.3rem;
  font-weight: 800;
  margin-top: 2px;
}
.x-box p { color: #38bdf8; }
.o-box p { color: #f43f5e; }
.tie-box p { color: #facc15; }
.turn-indicator {
  font-size: 1rem;
  font-weight: 700;
  color: #38bdf8;
  margin-bottom: 18px;
}
.board-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 20px;
}
.cell {
  background: #0f172a;
  border: 2px solid #334155;
  border-radius: 12px;
  height: 90px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.5rem;
  font-weight: 800;
  cursor: pointer;
  transition: all 0.2s;
  user-select: none;
}
.cell:hover:empty {
  background: #1e293b;
  border-color: #64748b;
}
.cell.x { color: #38bdf8; }
.cell.o { color: #f43f5e; }
.cell.win {
  background: rgba(16, 185, 129, 0.2);
  border-color: #10b981;
}
.btn-reset-game {
  width: 100%;
  background: #334155;
  color: #f8fafc;
  border: none;
  padding: 12px;
  border-radius: 10px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.2s;
}
.btn-reset-game:hover {
  background: #475569;
}

C Step 3: JavaScript Logic & Event Handling

Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:

JavaScript (script.js)
let board = ['', '', '', '', '', '', '', '', ''];
let currentPlayer = 'X';
let isGameActive = true;
let isAiMode = false;
let scores = { X: 0, O: 0, D: 0 };

const statusText = document.getElementById('status-text');
const cells = document.querySelectorAll('.cell');
const scoreX = document.getElementById('score-x');
const scoreO = document.getElementById('score-o');
const scoreDraw = document.getElementById('score-draw');
const resetBtn = document.getElementById('reset-game-btn');
const modeP2 = document.getElementById('mode-p2');
const modeAi = document.getElementById('mode-ai');

const winningConditions = [
  [0, 1, 2], [3, 4, 5], [6, 7, 8],
  [0, 3, 6], [1, 4, 7], [2, 5, 8],
  [0, 4, 8], [2, 4, 6]
];

function handleCellClick(e) {
  const cell = e.target;
  const idx = parseInt(cell.dataset.idx);

  if (board[idx] !== '' || !isGameActive) return;

  makeMove(idx, currentPlayer);

  if (isGameActive && isAiMode && currentPlayer === 'O') {
    setTimeout(makeAiMove, 350);
  }
}

function makeMove(idx, player) {
  board[idx] = player;
  cells[idx].textContent = player;
  cells[idx].classList.add(player.toLowerCase());

  checkWinner();
}

function checkWinner() {
  let roundWon = false;
  let winCombo = null;

  for (let condition of winningConditions) {
    let [a, b, c] = condition;
    if (board[a] && board[a] === board[b] && board[a] === board[c]) {
      roundWon = true;
      winCombo = condition;
      break;
    }
  }

  if (roundWon) {
    statusText.textContent = `Player ${currentPlayer} Wins! πŸŽ‰`;
    winCombo.forEach(idx => cells[idx].classList.add('win'));
    scores[currentPlayer]++;
    updateScores();
    isGameActive = false;
    return;
  }

  if (!board.includes('')) {
    statusText.textContent = "It's a Draw! 🀝";
    scores.D++;
    updateScores();
    isGameActive = false;
    return;
  }

  currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
  statusText.textContent = `Player ${currentPlayer}'s Turn`;
  statusText.style.color = currentPlayer === 'X' ? '#38bdf8' : '#f43f5e';
}

function makeAiMove() {
  if (!isGameActive) return;
  // Simple AI: Find empty cells and pick
  const emptyIndices = board.map((val, idx) => val === '' ? idx : null).filter(val => val !== null);
  if (emptyIndices.length === 0) return;
  
  const randomMove = emptyIndices[Math.floor(Math.random() * emptyIndices.length)];
  makeMove(randomMove, 'O');
}

function updateScores() {
  scoreX.textContent = scores.X;
  scoreO.textContent = scores.O;
  scoreDraw.textContent = scores.D;
}

function resetGame() {
  board = ['', '', '', '', '', '', '', '', ''];
  currentPlayer = 'X';
  isGameActive = true;
  statusText.textContent = "Player X's Turn";
  statusText.style.color = '#38bdf8';
  cells.forEach(cell => {
    cell.textContent = '';
    cell.className = 'cell';
  });
}

modeP2.addEventListener('click', () => {
  isAiMode = false;
  modeP2.classList.add('active');
  modeAi.classList.remove('active');
  resetGame();
});

modeAi.addEventListener('click', () => {
  isAiMode = true;
  modeAi.classList.add('active');
  modeP2.classList.remove('active');
  resetGame();
});

cells.forEach(cell => cell.addEventListener('click', handleCellClick));
resetBtn.addEventListener('click', resetGame);

3. Core JavaScript Concepts You Learned

2D Array Grid Representation

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Winning Line Algorithms

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Game State Management

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Simple AI Simulation

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

4. How to Run Locally on Your Computer

  1. Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save tic-tac-toe-project.zip.
  2. Extract the archive: Unzip the downloaded file. Inside you will find separate index.html, style.css, script.js, index-standalone.html, and a comprehensive README.md.
  3. Open in any browser: Simply double-click index.html (or index-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge.
  4. Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click index.html and select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!

Bonus Challenges for Learners

Ready to level up? Try adding these extra features on your own:

  • Add sound effects or audio feedback for user button clicks.
  • Add custom theme color customizer with CSS variables.
  • Export the results to PDF or copy as a formatted text summary.

Other Beginner Projects You Might Like

Beginner20 mins
Games

Rock Paper Scissors Game

Interactive Rock Paper Scissors battle with animated shake countdown, score history, streaks, and computer AI logic.

Math.random() DecisionsConditional Win/Loss LogicCSS Keyframe Triggers+1
Beginner30 mins
Games

Memory Card Matching Game

A 4x4 card flipping memory game with 3D flip animations, move counters, timer, match pair checks, and celebration popups.

Fisher-Yates ShuffleCSS 3D perspective & rotateYsetTimeout Board Lock+1