[Python] 4. Advanced Tic-Tac-Toe Game Ver.4 (with Classes!)
Great! Now that you’re ready to introduce object-oriented programming (OOP) into your Tic-Tac-Toe game, it’s the perfect opportunity to learn about classes and inheritance in Python. We’re going to enhance the game by creating a Player
class and then two subclasses: HumanPlayer
and RandomPlayer
. This approach will help you understand how to create reusable code and apply the principles of OOP, such as inheritance and polymorphism.
Building a Tic-Tac-Toe Game in Python Using Classes
In this tutorial, we’re going to take our Tic-Tac-Toe game to the next level by applying object-oriented programming concepts. We’ll create player objects that interact with the game board, and each type of player will have its own behavior. By using classes, we can make the code cleaner, more scalable, and easier to maintain.
So, let’s break it down and dive into the world of classes in Python!
Step 1: Understanding Classes and Inheritance
Before we dive into the code, let’s quickly review some OOP concepts:
- Classes: A class is like a blueprint for creating objects (instances). In our case, we’ll have a
Player
class that defines common behaviors for all players. - Inheritance: Subclasses can inherit properties and methods from a parent class. For example,
HumanPlayer
andRandomPlayer
will inherit from thePlayer
class. - Polymorphism: This allows different objects to be treated as instances of the same class. In our game, both human and random players will be able to take a turn, but they’ll do it differently.
Step 2: Creating the Player
Class
Let’s start by defining a parent class called Player
. This class will be a template for both human and random players. We’ll also give it a method called get_move()
that will be overridden by subclasses to define how each player chooses their move.
class Player:
def __init__(self, marker):
self.marker = marker
def get_move(self, board):
pass # This will be overridden by subclasses
__init__
method: This is the constructor method, and it will set the player’s marker (X
orO
).get_move()
method: This method will be defined differently depending on the type of player (human or random).
Step 3: Defining the HumanPlayer
Class
Now, let’s create the HumanPlayer
class, which inherits from Player
. The human player will be prompted to input their move manually.
class HumanPlayer(Player):
def get_move(self, board):
while True:
move = input(f"Player {self.marker}, choose a position (1-9): ")
if move in board:
return int(move) - 1
else:
print("Invalid move. Try again.")
Here’s how it works:
- Inheritance: The
HumanPlayer
class inherits fromPlayer
, meaning it automatically gets all the methods and properties of the parent class. get_move()
: This method asks the human player to choose a position, validates the input, and returns the selected index on the board.
Step 4: Creating the RandomPlayer
Class
Next, let’s add a RandomPlayer
class, which will choose a move randomly from the available positions on the board. This is great for testing the game or when you want to add AI later.
import random
class RandomPlayer(Player):
def get_move(self, board):
available_moves = [i for i, spot in enumerate(board) if spot.isdigit()]
move = random.choice(available_moves)
print(f"Random player {self.marker} chooses position {move + 1}")
return move
get_move()
: Instead of prompting the user for input, the random player selects a move from the available spots on the board using Python’srandom.choice()
.
Step 5: Updating the Game Logic to Use Players
Now that we have the player classes, we can update the main game logic to handle these player objects. Let’s rewrite the tic_tac_toe()
function to integrate human and random players.
def display_board(board):
print("\n" + board[0] + " | " + board[1] + " | " + board[2])
print("--+---+--")
print(board[3] + " | " + board[4] + " | " + board[5])
print("--+---+--")
print(board[6] + " | " + board[7] + " | " + board[8])
def check_winner(board):
winning_combinations = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
]
for combo in winning_combinations:
if board[combo[0]] == board[combo[1]] == board[combo[2]]:
return True
return False
def check_draw(turns):
return turns == 9
def tic_tac_toe():
board = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
player1 = HumanPlayer("X")
player2 = RandomPlayer("O")
players = [player1, player2]
turns = 0
game_over = False
while not game_over:
current_player = players[turns % 2]
display_board(board)
move = current_player.get_move(board)
board[move] = current_player.marker
turns += 1
if check_winner(board):
display_board(board)
print(f"\nPlayer {current_player.marker} wins!")
game_over = True
elif check_draw(turns):
display_board(board)
print("\nIt's a draw!")
game_over = True
Key Changes:
- Player Objects: We create instances of
HumanPlayer
andRandomPlayer
, each with its own marker (“X” and “O”). - Alternating Turns: We use the
players
list andturns % 2
to alternate between the two players. - Player Actions: The
get_move()
method is called on each player’s turn, making the code flexible and reusable. If you want to add more types of players in the future (like a smarter AI), you just need to create a new subclass!
Full Code
Here’s the complete code with object-oriented design and player classes:
import random
class Player:
def __init__(self, marker):
self.marker = marker
def get_move(self, board):
pass
class HumanPlayer(Player):
def get_move(self, board):
while True:
move = input(f"Player {self.marker}, choose a position (1-9): ")
if move in board:
return int(move) - 1
else:
print("Invalid move. Try again.")
class RandomPlayer(Player):
def get_move(self, board):
available_moves = [i for i, spot in enumerate(board) if spot.isdigit()]
move = random.choice(available_moves)
print(f"Random player {self.marker} chooses position {move + 1}")
return move
def display_board(board):
print("\n" + board[0] + " | " + board[1] + " | " + board[2])
print("--+---+--")
print(board[3] + " | " + board[4] + " | " + board[5])
print("--+---+--")
print(board[6] + " | " + board[7] + " | " + board[8])
def check_winner(board):
winning_combinations = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
]
for combo in winning_combinations:
if board[combo[0]] == board[combo[1]] == board[combo[2]]:
return True
return False
def check_draw(turns):
return turns == 9
def tic_tac_toe():
board = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
player1 = HumanPlayer("X")
player2 = RandomPlayer("O")
players = [player1, player2]
turns = 0
game_over = False
while not game_over:
current_player = players[turns % 2]
display_board(board)
move = current_player.get_move(board)
board[move] = current_player.marker
turns += 1
if check_winner(board):
display_board(board)
print(f"\nPlayer {current_player.marker} wins!")
game_over = True
elif check_draw(turns):
display_board(board)
print("\nIt's a draw!")
game_over = True
##