Three Prompts: Impossible Tic Tac Toe
Three Prompts: Impossible Tic Tac Toe

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:
- Square: A simple button component representing each cell in the grid
- Board: Manages the 3×3 grid of squares and handles click events
- 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.