Intro to Classes & Constructors


This lesson is based directly on the class-based Tic-tac-toe implementation in 2025-08-18-tictactoe-code.ipynb. All code cells below are taken from that notebook (unchanged or only lightly formatted) so you can study how class, __init__, methods, and object collaboration work in a real example. This will be a comprehensive guide about class methods, and how they can be applied into your code projects!

What you’ll learn

How a class works

A class is a blueprint for creating objects (instances). It groups data (attributes) and behavior (methods) that belong together. Think of a class like a blueprint for a car; each car you create from that blueprint is an instance with its own state (color, mileage). init is a special method Python calls automatically when you create (instantiate) an object. Purpose: initialize the instance’s attributes (its state). Signature pattern: def init(self, …) — self is the new instance being created.

class Player:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol

Board class — encapsulates grid and rules

Below is the Board implementation copied from the notebook. It contains display helpers, move validation, undo, and win detection.

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("\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")

    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("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

    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
        ]
        for combo in win_combinations:
            if (self.grid[combo[0]] == symbol and
                self.grid[combo[1]] == symbol and
                self.grid[combo[2]] == symbol):
                return True
        return False


class TicTacToe:
    def __init__(self, player1, player2):
        self.board = Board()
        self.players = [player1, player2]
        self.current_player = player1

    def switch_player(self):
        self.current_player = (
            self.players[1] if self.current_player == self.players[0] else self.players[0]
        )

    def play(self):
        print("Welcome to Tic-Tac-Toe!")
        print(f"{self.players[0].name} is '{self.players[0].symbol}'")
        print(f"{self.players[1].name} is '{self.players[1].symbol}'")
        print("Players take turns choosing a position (1–9).\n")

        self.board.display_reference()
        self.board.display()

        while True:
            try:
                move = int(input(f"{self.current_player.name} ({self.current_player.symbol}), enter your move (1-9): "))
            except ValueError:
                print("Invalid input. Please enter a number from 1 to 9.")
                continue

            if not self.board.make_move(move, self.current_player.symbol):
                continue

            self.board.display()

            if self.board.check_winner(self.current_player.symbol):
                print(f"{self.current_player.name} ({self.current_player.symbol}) wins!")
                break

            if self.board.is_full():
                print("It's a tie!")
                break

            self.switch_player()


if __name__ == "__main__":
    player1 = Player("Player 1", "X")
    player2 = Player("Player 2", "O")
    game = TicTacToe(player1, player2)
    game.play()

AIPlayer subclass (optional)

The original notebook includes an AIPlayer subclass; include it here for completeness—it’s an extension of Player that provides smarter get_move logic.

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

Simulated game demo

Use the code below to run a short simulated game or to drive the full interactive loop if you have a terminal. This demo is taken from the notebook and shows how classes collaborate.

# Simulated game demo
print("Simulated Tic-Tac-Toe Game")

# Create players
player1 = Player("Alice", "X")
player2 = Player("Bob", "O")

# Create a game instance
game = TicTacToe(player1, player2)

# Simulate a few moves
game.board.display_reference()
game.board.display()

# Player 1 makes a move
game.board.make_move(1, player1.symbol)
game.board.display()

# Player 2 makes a move
game.board.make_move(5, player2.symbol)
game.board.display()

# Player 1 makes another move
game.board.make_move(2, player1.symbol)
game.board.display()

# Player 2 makes another move
game.board.make_move(9, player2.symbol)
game.board.display()

# Player 1 makes the winning move
game.board.make_move(3, player1.symbol)
game.board.display()

# Check for a winner
if game.board.check_winner(player1.symbol):
    print(f"{player1.name} ({player1.symbol}) wins!")
elif game.board.check_winner(player2.symbol):
    print(f"{player2.name} ({player2.symbol}) wins!")
elif game.board.is_full():
    print("It's a tie!")
else:
    print("The game is still ongoing.")