Three Prompts: Impossible Tic Tac Toe

AI Assistant

Three Prompts: Impossible Tic Tac Toe

Impossible Tic Tac Toe game demonstration

Introduction

Tic-tac-toe is a classic game that most of us have played since childhood. While the game itself is simple, creating an AI opponent that never loses presents an interesting programming challenge. In this article, I'll walk through how I built an "Impossible Tic Tac Toe" game using React and the minimax algorithm—all guided by just three AI prompts.

What makes this project fascinating is how it transforms a simple game into a showcase of artificial intelligence concepts. The "impossible" AI uses the minimax algorithm to evaluate all possible moves and always choose the optimal one, making it unbeatable. You can either play against a friend or test your skills against this impossible opponent.

The Three Prompts

Here are the exact prompts that were used to create this project:

Prompt 1


@projects/impossible-tic-tac-toe 

I want you to take a look at the package.json, then the app files.

Then I want you to implement a super simple tic-tac-toe implementation. With no extra deps needed.

For now only get the game working. Prepare in the future to allow the person to play against an impossible AI.

Prompt 2


Fix the styles being all white  for the boxes and x and os. Same with the buttons for reset and history.

Make the history collapsed by default as to not shift the UI as more history comes in.

Then start the impossible AI logic. Make a button before starting to play against another player or the AI


Plan your changes before /execute . Keep it simple

Prompt 3

Now fix the impossible AI logic, so it starts playing right away once the user makes a turn. Don't make any UI changes just update the AI logic

Technologies Used

This project was built with simplicity in mind, using only essential technologies:

  • React 19.1.0: For building the user interface with functional components and hooks
  • Vite: As the build tool for fast development and optimized production builds
  • CSS: For styling the game board, buttons, and visual elements
  • No additional dependencies: The entire game, including the AI, was built using only React and its core features

Key Features and Functionality

1. Dual Game Modes

Players can choose between two modes:

  • Player vs Player: Classic two-player mode where players take turns
  • Player vs Impossible AI: Challenge yourself against an unbeatable AI opponent

2. Unbeatable AI

The heart of this project is the "impossible" AI that uses the minimax algorithm to:

  • Evaluate all possible future game states
  • Select moves that maximize its chances of winning
  • Consider the depth of moves to prefer quicker victories
  • Block any potential winning moves by the player

3. Interactive Game Board

The game features a clean, responsive board with:

  • Visual distinction between X (red) and O (blue) markers
  • Hover effects on empty squares
  • Clear status messages showing whose turn it is or the game result

4. Game History Navigation

Players can:

  • View a chronological history of all moves
  • Jump back to any previous game state
  • Collapse the history to keep the UI clean
  • Reset the game at any point

Implementation Details

The Minimax Algorithm

The core of the "impossible" AI is the minimax algorithm—a decision-making algorithm used in two-player games. Here's how it works in our tic-tac-toe implementation:

function minimax(squares, depth, isMaximizing) {
  // Terminal conditions: check for win/lose/draw
  const winner = calculateWinner(squares)
  
  // If X wins (player)
  if (winner === 'X') return -10 + depth
  
  // If O wins (AI)
  if (winner === 'O') return 10 - depth
  
  // If it's a draw
  if (!areMovesLeft(squares)) return 0
  
  // Recursive evaluation of all possible moves
  if (isMaximizing) {
    // AI's turn (maximizing)
    let bestScore = -Infinity
    for (let i = 0; i < squares.length; i++) {
      if (squares[i] === null) {
        squares[i] = 'O'
        const score = minimax(squares, depth + 1, false)
        squares[i] = null
        bestScore = Math.max(score, bestScore)
      }
    }
    return bestScore
  } else {
    // Player's turn (minimizing)
    let bestScore = Infinity
    for (let i = 0; i < squares.length; i++) {
      if (squares[i] === null) {
        squares[i] = 'X'
        const score = minimax(squares, depth + 1, true)
        squares[i] = null
        bestScore = Math.min(score, bestScore)
      }
    }
    return bestScore
  }
}

The algorithm recursively evaluates all possible game states, assigning scores:

  • +10 for AI wins (minus the depth to prefer quicker wins)
  • -10 for player wins (plus the depth to delay losses)
  • 0 for draws

React Component Structure

The game is built with three main components:

  1. Square: A simple button component representing each cell in the grid
  2. Board: Manages the 3×3 grid of squares and handles click events
  3. App: The main component that manages game state, history, and mode selection

Immediate AI Response

One interesting challenge was making the AI respond immediately after the player's move:

function handlePlay(nextSquares) {
  const winner = calculateWinner(nextSquares)
  const isDraw = nextSquares.every(square => square)
  const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]

  if (gameMode === 'ai' && xIsNext && !winner && !isDraw) {
    const aiMove = findBestMove(nextSquares)
    if (aiMove !== -1) {
      const aiSquares = nextSquares.slice()
      aiSquares[aiMove] = 'O'
      setHistory([...nextHistory, aiSquares])
      setCurrentMove(nextHistory.length)
    } else {
      setHistory(nextHistory)
      setCurrentMove(nextHistory.length - 1)
    }
  } else {
    setHistory(nextHistory)
    setCurrentMove(nextHistory.length - 1)
  }
}

This approach ensures that the AI makes its move within the same render cycle as the player's move, creating a seamless experience.

Challenges and Solutions

Challenge: UI Shifting with Game History

Problem: As the game progressed and more moves were recorded, the growing history list would shift the UI layout.

Solution: I implemented a collapsible history container that's hidden by default:

.history-container {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
  width: 100%;
}

.history-container.expanded {
  max-height: 300px;
  overflow-y: auto;
}

This approach keeps the UI stable while still allowing players to access the full history when needed.

Challenge: Making the AI Truly Unbeatable

Problem: Early implementations of the AI occasionally made suboptimal moves.

Solution: Refining the minimax algorithm to consider move depth when evaluating scores:

// If X wins (player)
if (winner === 'X') return -10 + depth
  
// If O wins (AI)
if (winner === 'O') return 10 - depth

This adjustment makes the AI prefer shorter paths to victory and longer paths to defeat, ensuring it always makes the optimal move.

Conclusion

Building the Impossible Tic Tac Toe game was an exercise in combining simple game mechanics with sophisticated AI concepts. The minimax algorithm demonstrates how even with perfect play from both sides, tic-tac-toe will always end in a draw—unless one player makes a mistake.

This project showcases how a classic game can be enhanced with modern web technologies and AI algorithms. While the game itself is straightforward, the implementation of the unbeatable AI provides insights into decision trees, recursive algorithms, and game theory.

Try it yourself by playing the game and see if you can find a way to beat the impossible AI (spoiler: you can't if you play perfectly too, but it's still fun to try!).

For those interested in game development or AI, this project demonstrates how relatively simple algorithms can create compelling interactive experiences. The complete source code is available to explore, modify, and learn from.

Links