[Python] 5. Advanced Tic-Tac-Toe Game Ver.5 (with more Classes!)
Fantastic! Now it’s time to fully transition your Tic-Tac-Toe game into a completely object-oriented design by adding a Board
class and a Game
class. This will make your code more organized, modular, and easier to expand in the future. Let’s go step by step and refactor the game using these new classes.
Building an Object-Oriented Tic-Tac-Toe Game in Python
Welcome back! In this tutorial, we’ll complete the transition of our Tic-Tac-Toe game to an object-oriented approach by adding Board
and Game
classes. This will give us a clean, structured program that’s easy to maintain and extend. We’ve already worked with player classes, so now we’re going to add even more layers of OOP goodness.
Let’s break it down!
Step 1: Creating the Board
Class
First, we’ll define the Board
class. The board is responsible for maintaining the game state, displaying itself, and checking for winners or draws.
What the Board
Class Will Do:
- Store the current state of the game board.
- Display the board to the players.
- Check for a winner or a draw.
Here’s how we can implement it:
class Board:
def __init__(self):
self.board = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
def display(self):
print("\n" + self.board[0] + " | " + self.board[1] + " | " + self.board[2])
print("--+---+--")
print(self.board[3] + " | " + self.board[4] + " | " + self.board[5])
print("--+---+--")
print(self.board[6] + " | " + self.board[7] + " | " + self.board[8])
def update(self, position, marker):
self.board[position] = marker
def is_winner(self, marker):
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 self.board[combo[0]] == self.board[combo[1]] == self.board[combo[2]] == marker:
return True
return False
def is_draw(self):
return all(spot in ["X", "O"] for spot in self.board)
Explanation:
__init__()
: Initializes the board as a list of strings, each representing a position from 1 to 9.display()
: Prints the current state of the board.update()
: Updates the board when a player makes a move by placing their marker in the correct position.is_winner()
: Checks whether the given marker (X or O) has a winning combination on the board.is_draw()
: Checks whether all spots are filled without a winner, resulting in a draw.
Step 2: Creating the Game
Class
Now, we’ll create a Game
class that will handle the flow of the game. This class will manage turns, check for a winner, and coordinate interactions between players and the board.
What the Game
Class Will Do:
- Manage the game loop.
- Alternate turns between the players.
- Determine when the game ends (either win or draw).
Here’s the implementation:
class Game:
def __init__(self, player1, player2):
self.board = Board() # Create a new board
self.players = [player1, player2]
self.turns = 0
self.current_player = self.players[self.turns % 2]
def play(self):
game_over = False
while not game_over:
self.board.display() # Display the board
print(f"It's {self.current_player.marker}'s turn.")
move = self.current_player.get_move(self.board.board) # Get the move from the current player
self.board.update(move, self.current_player.marker) # Update the board with the move
self.turns += 1
if self.board.is_winner(self.current_player.marker):
self.board.display()
print(f"\nPlayer {self.current_player.marker} wins!")
game_over = True
elif self.board.is_draw():
self.board.display()
print("\nIt's a draw!")
game_over = True
else:
self.current_player = self.players[self.turns % 2] # Switch player for the next turn
Explanation:
__init__()
: The game initializes with two players and a freshBoard
object. It tracks whose turn it is and alternates players.play()
: This method contains the game loop. It displays the board, gets the current player’s move, updates the board, checks for a winner or a draw, and alternates turns until the game ends.
Step 3: Refactoring the Player
, HumanPlayer
, and RandomPlayer
Classes
Now that we have the Board
and Game
classes in place, we can easily integrate the player classes we’ve already built.
Here’s a quick recap of the player classes with minor updates:
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
These classes remain the same, with the get_move()
method responsible for determining how each player makes their move. The Game
class calls this method to get the player’s choice during each turn.
Step 4: Running the Game
Now that we have all our pieces in place (pun intended), we can create a simple script to run the game.
def main():
player1 = HumanPlayer("X")
player2 = RandomPlayer("O")
game = Game(player1, player2)
game.play()
if __name__ == "__main__":
main()
How It Works:
- We create two player objects:
player1
is aHumanPlayer
andplayer2
is aRandomPlayer
. - The
Game
class is initialized with these two players. - The
game.play()
method starts the game loop, alternating turns until there’s a winner or a draw.
Full Code with OOP
Here’s the complete code with all the classes:
```python 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
class Board: def init(self): self.board = [“1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”]
def display(self):
print("\n" + self.board[0] + " | " + self.board[1] + " | " + self.board[2])
print("--+---+--")
print(self.board[3] + " | " + self.board[4] + " | " + self.board[5])
print("--+---+--")
print(self.board[6] + " | " + self.board[7] + " | " + self.board[8])
def update(self, position, marker):
self.board[position] = marker
def is_winner(self, marker):
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 self.board[combo[0]] == self.board[combo[1]] == self.board[combo[2]] == marker:
return True
return False
def is_draw(self):
return all(spot in ["X", "O"]