🎮 Build Your First Python Game - Tic Tac Toe (Object-Oriented)
Categories: python games ai Breadcrumb: /games/tictactoeLearn Object-Oriented Programming by building a fun Tic Tac Toe game with AI in Python! Perfect for beginners.
- Why Use OOP for Tic-Tac-Toe?
- 🎭 Player Class
- 📋 Board Class
- 🎮 TicTacToe Class
Understanding Object-Oriented Programming- Why Use OOP for Tic-Tac-Toe?
- What just happened in the code above:
The Player Class - Simple but Essential- Board Initialization:
- 🎯 Notice the Method Responsibility
The Board Class - Where the Logic Lives- Board Initialization:
- Why this approach is brilliant:
Win Detection - The Smart Algorithm- 🎯 Key OOP Principle
The TicTacToe Class - The Game Controller
- Notice how the classes collaborate:
- What makes this good Object-Oriented design:
- 🚀 Challenge Yourself
Understanding Object-Oriented Programming
Try out the web version of the game before diving into the code!
Our Tic-Tac-Toe game is built using Object-Oriented Programming (OOP), which organizes code into classes and objects. Think of classes as blueprints and objects as the actual things built from those blueprints.
Why Use OOP for Tic-Tac-Toe?
- Organization: Each part of the game has its own responsibility
- Reusability: We can create multiple players or boards easily
- Maintainability: Changes to one class don't break others
- Real-world modeling: Code mirrors how we think about the game
🎭 Player Class
Represents each player
📋 Board Class
Manages the game grid
🎮 TicTacToe Class
Controls the entire game
classDiagram
class Player {
+name
+symbol
}
class Board {
+grid
+display()
+make_move()
+check_winner()
}
class TicTacToe {
+board
+players
+current_player
+switch_player()
}
TicTacToe "1" --> "1" Board
TicTacToe "2" --> "1..2" Player
class Player:
def __init__(self, name, symbol):
self.name = name
self.symbol = symbol
# Let's test it by creating some players
player1 = Player("Alice", "X")
player2 = Player("Bob", "O")
print(f"Player 1: {player1.name} uses symbol '{player1.symbol}'")
print(f"Player 2: {player2.name} uses symbol '{player2.symbol}'")
Player 1: Alice uses symbol 'X'
Player 2: Bob uses symbol 'O'
The Player Class - Simple but Essential
The Player class is our simplest class, but it demonstrates key OOP concepts perfectly.
What just happened in the code above:
- __init__ method: The "constructor" that runs when creating a new player
- self parameter: Refers to the specific player object being created
- Instance variables: name and symbol are stored with each player
- Encapsulation: Each player keeps track of their own data
class Board:
def __init__(self):
self.grid = [" "] * 9 # Creates 9 empty spaces
print("New board created!")
print(f"Grid contents: {self.grid}")
# Test creating a board
test_board = Board()
New board created!
Grid contents: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
The Board Class - Where the Logic Lives
The Board class is the heart of our game logic. It manages the 3x3 grid and all game rules.
Board Initialization:
- self.grid = [" "] * 9: Creates a list with 9 empty spaces
- Why 9 spaces? We represent the 3x3 grid as positions 0-8
- Position mapping: User enters 1-9, we convert to 0-8 internally
flowchart TD
A[Player chooses position] --> B[Board.make_move]
B --> C{Is position valid?}
C -- No --> D[Show error]
C -- Yes --> E{Is spot empty?}
E -- No --> D
E -- Yes --> F[Place symbol]
F --> G[Update board]
class Board:
def __init__(self):
self.grid = [" "] * 9
def display(self):
print("\n")
print(" " + self.grid[0] + " | " + self.grid[1] + " | " + self.grid[2])
print("---+---+---")
print(" " + self.grid[3] + " | " + self.grid[4] + " | " + self.grid[5])
print("---+---+---")
print(" " + self.grid[6] + " | " + self.grid[7] + " | " + self.grid[8])
print("\n")
def display_reference(self):
reference = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
print("Board positions:\n")
print(" " + reference[0] + " | " + reference[1] + " | " + reference[2])
print("---+---+---")
print(" " + reference[3] + " | " + reference[4] + " | " + reference[5])
print("---+---+---")
print(" " + reference[6] + " | " + reference[7] + " | " + reference[8])
print("\n")
# Test the display methods
board = Board()
print("This shows the position numbers:")
board.display_reference()
print("This shows the current game state:")
board.display()
This shows the position numbers:
Board positions:
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
This shows the current game state:
| |
---+---+---
| |
---+---+---
| |
🎯 Notice the Method Responsibility
The Board class knows how to display itself. We don't need external code to format the output - this is encapsulation in action!
class Board:
def __init__(self):
self.grid = [" "] * 9
def display(self):
print("\n")
print(" " + self.grid[0] + " | " + self.grid[1] + " | " + self.grid[2])
print("---+---+---")
print(" " + self.grid[3] + " | " + self.grid[4] + " | " + self.grid[5])
print("---+---+---")
print(" " + self.grid[6] + " | " + self.grid[7] + " | " + self.grid[8])
print("\n")
def is_full(self):
return " " not in self.grid
def make_move(self, position, symbol):
index = position - 1 # Convert 1-9 to 0-8
if index < 0 or index > 8:
print("Invalid position. Choose a number between 1 and 9.")
return False
if self.grid[index] != " ":
print("That spot is already taken. Try again.")
return False
self.grid[index] = symbol
return True
# Test the game logic
board = Board()
print("Testing valid move:")
result1 = board.make_move(5, "X") # Should work
print(f"Move successful: {result1}")
board.display()
print("Testing invalid move (same spot):")
result2 = board.make_move(5, "O") # Should fail
print(f"Move successful: {result2}")
print("Testing invalid position:")
result3 = board.make_move(10, "O") # Should fail
print(f"Move successful: {result3}")
Testing valid move:
Move successful: True
| |
---+---+---
| X |
---+---+---
| |
Testing invalid move (same spot):
That spot is already taken. Try again.
Move successful: False
Testing invalid position:
Invalid position. Choose a number between 1 and 9.
Move successful: False
Win Detection - The Smart Algorithm
The most complex part of our Board class is checking for winners. Let's break down this algorithm:
Why this approach is brilliant:
- Data-driven: All winning combinations are stored as data, not hardcoded logic
- Scalable: Easy to modify for different board sizes
- Readable: The winning patterns are clearly visible
- Efficient: Checks all possibilities in a simple loop
flowchart TD
A[After each move] --> B[Check all win combinations]
B --> C{Any combination matches player symbol?}
C -- Yes --> D[Declare winner]
C -- No --> E{Board full?}
E -- Yes --> F[Declare tie]
E -- No --> G[Continue game]
class Board:
def __init__(self):
self.grid = [" "] * 9
def display(self):
print("\n")
print(" " + self.grid[0] + " | " + self.grid[1] + " | " + self.grid[2])
print("---+---+---")
print(" " + self.grid[3] + " | " + self.grid[4] + " | " + self.grid[5])
print("---+---+---")
print(" " + self.grid[6] + " | " + self.grid[7] + " | " + self.grid[8])
print("\n")
def make_move(self, position, symbol):
index = position - 1
if 0 <= index <= 8 and self.grid[index] == " ":
self.grid[index] = symbol
return True
return False
def check_winner(self, symbol):
win_combinations = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], # Rows
[0, 3, 6], [1, 4, 7], [2, 5, 8], # Columns
[0, 4, 8], [2, 4, 6] # Diagonals
]
print(f"Checking for winner with symbol '{symbol}'")
print(f"Win combinations to check: {win_combinations}")
for combo in win_combinations:
if (self.grid[combo[0]] == symbol and
self.grid[combo[1]] == symbol and
self.grid[combo[2]] == symbol):
print(f"WINNER! Found winning combination: {combo}")
return True
print("No winner found")
return False
# Test win detection
board = Board()
print("Setting up a winning scenario...")
board.make_move(1, "X") # Top left
board.make_move(2, "X") # Top middle
board.make_move(3, "X") # Top right - should be a win!
board.display()
is_winner = board.check_winner("X")
print(f"Is X the winner? {is_winner}")
Setting up a winning scenario...
X | X | X
---+---+---
| |
---+---+---
| |
Checking for winner with symbol 'X'
Win combinations to check: [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]
WINNER! Found winning combination: [0, 1, 2]
Is X the winner? True
The TicTacToe Class - The Game Controller
The TicTacToe class orchestrates everything. It's the "conductor" of our game orchestra.
🎯 Key OOP Principle
Notice how each class has a single responsibility: Player stores player data, Board manages the grid, and TicTacToe controls game flow. This is called the Single Responsibility Principle!
import random
from colorama import Fore, Style, init
init(autoreset=True) # Initialize colorama
class Player:
def __init__(self, name, symbol, is_ai=False):
self.name = name
self.symbol = symbol
self.is_ai = is_ai
self.wins = 0
self.moves = []
def add_win(self):
self.wins += 1
print(f"{Fore.GREEN}🏆 {self.name} now has {self.wins} wins!{Style.RESET_ALL}")
def get_move(self, board):
if not self.is_ai:
return None
# AI Logic
return self._get_best_move(board)
def _get_best_move(self, board):
# First, try to win
for i in range(9):
if board.grid[i] == " ":
board.grid[i] = self.symbol
if board.check_winner(self.symbol):
board.grid[i] = " " # Reset the move
return i + 1
board.grid[i] = " " # Reset the move
# Then, block opponent's win
opponent_symbol = "O" if self.symbol == "X" else "X"
for i in range(9):
if board.grid[i] == " ":
board.grid[i] = opponent_symbol
if board.check_winner(opponent_symbol):
board.grid[i] = " " # Reset the move
return i + 1
board.grid[i] = " " # Reset the move
# Try to get center
if board.grid[4] == " ":
return 5
# Try corners
corners = [0, 2, 6, 8]
random.shuffle(corners)
for corner in corners:
if board.grid[corner] == " ":
return corner + 1
# Take any available space
for i in range(9):
if board.grid[i] == " ":
return i + 1
class Board:
def __init__(self):
self.grid = [" "] * 9
self.move_history = []
def display(self):
print("\n")
colors = {" ": Style.RESET_ALL, "X": Fore.RED, "O": Fore.BLUE}
for i in range(0, 9, 3):
print(" " + colors[self.grid[i]] + self.grid[i] + Style.RESET_ALL + " | " +
colors[self.grid[i+1]] + self.grid[i+1] + Style.RESET_ALL + " | " +
colors[self.grid[i+2]] + self.grid[i+2] + Style.RESET_ALL)
if i < 6:
print("---+---+---")
print("\n")
def display_reference(self):
print(f"{Fore.YELLOW}Board positions:{Style.RESET_ALL}\n")
reference = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
for i in range(0, 9, 3):
print(" " + reference[i] + " | " + reference[i+1] + " | " + reference[i+2])
if i < 6:
print("---+---+---")
print("\n")
def is_full(self):
return " " not in self.grid
def make_move(self, position, symbol):
index = position - 1
if index < 0 or index > 8:
print(f"{Fore.RED}Invalid position. Choose a number between 1 and 9.{Style.RESET_ALL}")
return False
if self.grid[index] != " ":
print(f"{Fore.RED}That spot is already taken. Try again.{Style.RESET_ALL}")
return False
self.grid[index] = symbol
self.move_history.append((position, symbol))
return True
def undo_last_move(self):
if self.move_history:
position, _ = self.move_history.pop()
self.grid[position - 1] = " "
return True
return False
def check_winner(self, symbol):
win_combinations = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], # Rows
[0, 3, 6], [1, 4, 7], [2, 5, 8], # Columns
[0, 4, 8], [2, 4, 6] # Diagonals
]
return any(all(self.grid[i] == symbol for i in combo) for combo in win_combinations)
class TicTacToe:
def __init__(self, player1, player2):
self.board = Board()
self.players = [player1, player2]
self.current_player = player1
self.game_history = []
self.moves_count = 0
def switch_player(self):
self.current_player = self.players[1] if self.current_player == self.players[0] else self.players[0]
if not self.current_player.is_ai:
print(f"{Fore.CYAN}Now it's {self.current_player.name}'s turn{Style.RESET_ALL}")
def play_turn(self):
self.moves_count += 1
if self.current_player.is_ai:
position = self.current_player.get_move(self.board)
print(f"{Fore.YELLOW}AI {self.current_player.name} chooses position {position}{Style.RESET_ALL}")
else:
try:
position = int(input(f"{self.current_player.name}, enter position (1-9) or 0 to undo: "))
if position == 0:
if self.board.undo_last_move():
self.switch_player()
self.moves_count -= 2
print(f"{Fore.YELLOW}Move undone!{Style.RESET_ALL}")
return True
except ValueError:
print(f"{Fore.RED}Please enter a number!{Style.RESET_ALL}")
return True
if self.board.make_move(position, self.current_player.symbol):
self.board.display()
if self.board.check_winner(self.current_player.symbol):
print(f"{Fore.GREEN}🎉 {self.current_player.name} WINS in {self.moves_count} moves!{Style.RESET_ALL}")
self.current_player.add_win()
self.game_history.append({
'winner': self.current_player.name,
'moves': self.moves_count
})
return False
if self.board.is_full():
print(f"{Fore.YELLOW}It's a tie!{Style.RESET_ALL}")
self.game_history.append({
'winner': 'Tie',
'moves': self.moves_count
})
return False
self.switch_player()
return True
return True
def display_stats(self):
print(f"\n{Fore.CYAN}=== Game Statistics ==={Style.RESET_ALL}")
for player in self.players:
print(f"{player.name}: {player.wins} wins")
print(f"Total games played: {len(self.game_history)}")
if self.game_history:
avg_moves = sum(game['moves'] for game in self.game_history) / len(self.game_history)
print(f"Average moves per game: {avg_moves:.1f}")
# Test the enhanced game with AI
player1 = Player("Human", "X")
player2 = Player("AI", "O", is_ai=True)
game = TicTacToe(player1, player2)
print(f"{Fore.GREEN}=== Welcome to Advanced Tic-Tac-Toe! ==={Style.RESET_ALL}")
game.board.display_reference()
while True:
game.board.display()
if not game.play_turn():
game.display_stats()
play_again = input(f"\n{Fore.YELLOW}Play again? (y/n): {Style.RESET_ALL}").lower()
if play_again != 'y':
break
game.board = Board()
game.moves_count = 0
print(f"\n{Fore.GREEN}=== New Game ==={Style.RESET_ALL}")
game.board.display_reference()
=== Welcome to Advanced Tic-Tac-Toe! ===
Board positions:
Board positions:
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
| |
---+---+---
| |
---+---+---
| |
---+---+--- | |
---+---+---
| |
| |
X | |
X | |
---+---+---
| |
---+---+---
| |
---+---+---
---+---+---
| |
| |
X | |
---+---+---
X | |
---+---+---
| |
| |
---+---+---
| ---+---+---
| |
AI AI chooses position 5
|
AI AI chooses position 5
X | |
X | |
---+---+---
| O |
---+---+---
---+---+---
| O |
---+---+---
| | |
|
Now it's Human's turn
Now it's Human's turn
X | |
---+---+---
X | |
---+---+---
| O | O |
|
---+---+---
| | ---+---+---
| |
X | | X
X | | X
---+---+---
| O | ---+---+---
| O |
---+---+---
|
---+---+---
| |
X | | X
|
X | | X
---+---+---
| O | ---+---+---
| O |
---+---+---
| |
---+---+---
| |
AI AI chooses position 2
AI AI chooses position 2
X | O | X
---+---+--- X | O | X
---+---+---
| O |
| O |
---+---+---
---+---+---
| | |
|
Now it's Human's turn
X | O | X
---+---+---
| ONow it's Human's turn
X | O | X
---+---+---
| O |
---+---+---
| |
---+---+---
| |
|
That spot is already taken. Try again.
X | OThat spot is already taken. Try again.
X | O | X
---+---+---
| | X
---+---+---
| O |
---+---+---
O |
---+---+---
| |
| |
That spot is already taken. Try again.
X | OThat spot is already taken. Try again.
X | O | X
---+---+---
| X
---+---+---
| O |
---+---+---
| O |
---+---+---
| |
| |
That spot is already taken. Try again.
X | That spot is already taken. Try again.
X | O | X
---+---+---
O | X
---+---+---
| O | O |
---+---+---
|
---+---+---
| | | |
That spot is already taken. Try again.
XThat spot is already taken. Try again.
X | O | X
---+---+---
| O | X
---+---+---
| O |
---+---+---
| O |
---+---+---
| |
| |
That spot is already taken. Try again.
X | OThat spot is already taken. Try again.
X | O | X
---+---+---
| | X
---+---+---
| O |
---+---+---
| | O |
---+---+---
| |
Please enter a number!
X | O | XPlease enter a number!
X | O | X
---+---+---
| O |
---+---+---
| O |
---+---+---
---+---+---
| |
| |
X | O | X
X | O | X
---+---+---
---+---+---
| | O |
---+---+---O |
---+---+---
|
| | X
| X
X |
X | O | X
O | X
---+---+---
| ---+---+---
| O | O |
---+---+---
---+---+---
| | X
| | X
AI AI chooses position 8
AI AI chooses position 8
X | O X | O | X
---+---+---
| X
---+---+---
| O | O |
---+---+---
|
---+---+---
| O | X
| O | X
🎉 AI WINS in 12 moves!
🏆 AI now has 1 wins!
=== Game Statistics ===
🎉 AI WINS in 12 moves!
🏆 AI now has 1 wins!
=== Game Statistics ===
Human: 0 wins
AI: 1 wins
Total games played: 1
Average moves per game: 12.0
Human: 0 wins
AI: 1 wins
Total games played: 1
Average moves per game: 12.0
class AIPlayer(Player):
def __init__(self, name, symbol):
super().__init__(name, symbol)
self.is_ai = True
def get_move(self, board):
"""
Implements the AI strategy in this order:
1. Win if possible
2. Block opponent's winning move
3. Take center
4. Take corner
5. Take any available space
"""
# Try to win
winning_move = self._find_winning_move(board, self.symbol)
if winning_move is not None:
return winning_move
# Block opponent's winning move
opponent_symbol = "O" if self.symbol == "X" else "X"
blocking_move = self._find_winning_move(board, opponent_symbol)
if blocking_move is not None:
return blocking_move
# Take center if available
if board.grid[4] == " ":
return 5
# Take a corner if available
corners = [0, 2, 6, 8]
import random
random.shuffle(corners) # Add randomness to corner selection
for corner in corners:
if board.grid[corner] == " ":
return corner + 1
# Take any available space
for i in range(9):
if board.grid[i] == " ":
return i + 1
def _find_winning_move(self, board, symbol):
"""Check all possible moves to find a winning move"""
for i in range(9):
if board.grid[i] == " ":
# Try move
board.grid[i] = symbol
if board.check_winner(symbol):
board.grid[i] = " " # Reset the move
return i + 1
board.grid[i] = " " # Reset the move
return None
Testing Class Collaboration
Let's see how all our classes work together in a simplified game scenario:
Notice how the classes collaborate:
- TicTacToe manages the overall game flow
- Board validates moves and checks for winners
- Player provides the data needed for moves
flowchart TD
A[Start Game] --> B[Current Player's Turn]
B --> C[Player chooses position]
C --> D[Board.make_move]
D --> E{Move valid?}
E -- No --> B
E -- Yes --> F[Board.display]
F --> G[Check winner]
G -- Winner --> H[End Game]
G -- No winner --> I{Board full?}
I -- Yes --> J[End Game=Tie]
I -- No --> K[Switch Player]
K --> B
# Let's simulate a quick game to see all classes working together
print("=== SIMULATED GAME DEMO ===")
player1 = Player("Alice", "X")
player2 = Player("Bob", "O")
game = TicTacToe(player1, player2)
# Simulate some moves
moves = [
(1, "Alice plays position 1"),
(5, "Bob plays position 5"),
(2, "Alice plays position 2"),
(6, "Bob plays position 6"),
(3, "Alice plays position 3 - should win!")
]
print(f"Starting game: {player1.name} vs {player2.name}")
game.board.display_reference()
game.board.display()
for position, description in moves:
print(f"\n--- {description} ---")
# Make the move
success = game.board.make_move(position, game.current_player.symbol)
if success:
game.board.display()
# Check for winner
if game.board.check_winner(game.current_player.symbol):
print(f"🎉 {game.current_player.name} ({game.current_player.symbol}) WINS!")
break
# Check for tie
if game.board.is_full():
print("It's a tie!")
break
# Switch players
game.switch_player()
else:
print("Move failed, trying next move...")
=== SIMULATED GAME DEMO ===
Starting game: Alice vs Bob
Board positions:
1 | 2 | 3
---+---+---
4 | 5 | 6
Starting game: Alice vs Bob
Board positions:
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+------+---+---
7 | 8 | 9
7 | 8 | 9
| | | |
---+---+---
| |
---+---+---
| |
---+---+---
---+---+---
| | |
|
--- Alice plays position 1 ---
--- Alice plays position 1 ---
X | |
---+---+---X | |
---+---+---
| | | |
---+---+---
| ---+---+---
| |
Now it's Bob's turn
--- Bob plays position 5 --- |
Now it's Bob's turn
--- Bob plays position 5 ---
X | |
---+---+---
| O | X | |
---+---+---
| O |
---+---+---
| |
---+---+---
| |
Now it's Alice's turn
--- Alice plays position 2 ---
Now it's Alice's turn
--- Alice plays position 2 ---
X | X |
X | X |
---+---+---
| O |
---+---+---
---+---+---
| O |
---+---+---
| |
Now it's Bob's turn
--- Bob plays position 6 ---
X
| |
Now it's Bob's turn
--- Bob plays position 6 ---
X | X |
---+---+---
| O | O
---+---+--- | X |
---+---+---
| O | O
---+---+---
| |
Now it's Alice's turn
--- Alice plays position 3 - should win! ---
| |
Now it's Alice's turn
--- Alice plays position 3 - should win! ---
X | X | X
---+---+---
| O | X | X | X
---+---+---
| O | O
---+---+---
O
---+---+---
| |
🎉 Alice (X) WINS!
| |
🎉 Alice (X) WINS!
Key Takeaways
What makes this good Object-Oriented design:
- Separation of concerns: Each class has one clear job
- Encapsulation: Data and methods that work on that data are grouped together
- Composition: Complex objects are built from simpler ones
- Maintainability: You can modify the Board display without touching game logic
- Reusability: The Player class could be used in other games
- Readability: The code structure mirrors how we think about the game
🚀 Challenge Yourself
Try adding new features like score tracking, different board sizes, or AI players. Notice how the OOP structure makes these additions much easier!
# Try modifying the code above! Here are some ideas:
# 1. Add a method to Player class to track wins
class EnhancedPlayer(Player):
def __init__(self, name, symbol):
super().__init__(name, symbol)
self.wins = 0
def add_win(self):
self.wins += 1
print(f"{self.name} now has {self.wins} wins!")
# Test it
enhanced_player = EnhancedPlayer("Charlie", "Z")
enhanced_player.add_win()
enhanced_player.add_win()
# 2. What other enhancements can you think of?
# - Add a reset method to Board?
# - Track the number of moves?
# - Add different difficulty levels?
print("Your turn to experiment! Try modifying the classes above.")
Charlie now has 1 wins!
Charlie now has 2 wins!
Your turn to experiment! Try modifying the classes above.
Charlie now has 2 wins!
Your turn to experiment! Try modifying the classes above.
# Let's play a game against the AI!
print("=== Playing against AI ===")
# Create players
human = Player("Human", "X")
ai = AIPlayer("AI", "O")
# Create game
game = TicTacToe(human, ai)
print("\nGame board positions:")
game.board.display_reference()
while True:
# Human's turn
while True:
try:
position = int(input("Enter your move (1-9): "))
if 1 <= position <= 9 and game.board.make_move(position, human.symbol):
break
print("Invalid move, try again.")
except ValueError:
print("Please enter a number between 1-9")
game.board.display()
# Check if human won
if game.board.check_winner(human.symbol):
print("🎉 You win!")
break
# Check for tie
if game.board.is_full():
print("It's a tie!")
break
# AI's turn
print("\nAI is thinking...")
ai_move = ai.get_move(game.board)
print(f"AI chooses position {ai_move}")
game.board.make_move(ai_move, ai.symbol)
game.board.display()
# Check if AI won
if game.board.check_winner(ai.symbol):
print("🤖 AI wins!")
break
# Check for tie again
if game.board.is_full():
print("It's a tie!")
break
# Ask to play again
play_again = input("\nPlay again? (y/n): ").lower()
if play_again == 'y':
print("\nStarting new game...")
=== Playing against AI ===
Game board positions:
Board positions:
Game board positions:
Board positions:
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
7 | 8 | 9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
Please enter a number between 1-9
X | |
---+---+---
X | |
---+---+---
| |
| |
---+---+---
---+---+---
| | | |
AI is thinking...
AI chooses position 5
AI is thinking...
AI chooses position 5
X | |
X | |
---+---+---
| O
---+---+---
| O |
---+---+---
|
---+---+---
| |
| |
X | | X
X | | X
---+---+---
| O |
---+---+---
| O |
---+---+---
|
---+---+---
| |
AI is thinking...
AI chooses position 2
|
AI is thinking...
AI chooses position 2
X | O | X | O | X
---+---+---
| O |
---+---+---X
---+---+---
| O |
---+---+---
| |
| |
That spot is already taken. Try again.
Invalid move, try again.
That spot is already taken. Try again.
Invalid move, try again.
X | O | X
X | O | X
---+---+---
| O | ---+---+---
| O |
---+---+---
X | |
---+---+---
X | |
AI is thinking...
AI chooses position 8
X |
AI is thinking...
AI chooses position 8
X | O | X
---+---+---
O | X
---+---+---
| O |
---+---+---
| O |
---+---+---
X | O |
🤖 AI wins!
X | O |
🤖 AI wins!