Skip to main content

TicTacToe Class

The TicTacToe class manages the game board state, move validation, and win condition checking for a 3x3 Tic-Tac-Toe game.

Constructor

__init__()

Initializes a new Tic-Tac-Toe game with an empty board. Returns: None
def __init__(self) -> None:
    self.board = [" " for _ in range(9)]  # 3x3 board
    self.current_winner = None  # Keep track of winner!
Attributes:
  • board: List of 9 strings representing the game board (indices 0-8)
  • current_winner: String (‘X’ or ‘O’) if there’s a winner, otherwise None

Methods

Displays the current state of the game board in a formatted grid. Parameters: None Returns: None (prints to console)
def print_board(self):
    for row in [self.board[i*3 : (i+1)*3] for i in range(3)]:
        print("| " + " | ".join(row) + " |")
This method prints the board with the current moves (‘X’, ‘O’, or empty spaces).

Displays a reference board showing the position numbers (0-8) for each square. Parameters: None Returns: None (prints to console)
@staticmethod
def print_board_nums():
    # 0 | 1 | 2 etc (tells us what number corresponds to what box)
    number_board = [[str(i) for i in range(j*3, (j+1)*3)] for j in range(3)]
    for row in number_board:
        print("| " + " | ".join(row) + " |")
This static method helps players understand which number corresponds to which board position.

available_moves()

Returns a list of all empty positions on the board. Parameters: None Returns: list[int] - List of indices (0-8) where moves can be made
def available_moves(self):
    return [i for i, spot in enumerate(self.board) if spot == ' ']
Example:
game = TicTacToe()
moves = game.available_moves()
# Returns: [0, 1, 2, 3, 4, 5, 6, 7, 8] for an empty board

empty_squares()

Checks if there are any empty squares remaining on the board. Parameters: None Returns: bool - True if there are empty squares, False otherwise
def empty_squares(self):
    return " " in self.board

num_empty_squares()

Counts the number of empty squares on the board. Parameters: None Returns: int - Number of empty squares (0-9)
def num_empty_squares(self):
    return self.board.count(" ")

make_move(square, letter)

Attempts to place a letter (‘X’ or ‘O’) at the specified square.
square
int
required
The board position (0-8) where the move should be made
letter
str
required
The player’s letter (‘X’ or ‘O’)
Returns: bool - True if the move was successful, False if the square is occupied
def make_move(self, square, letter):
    # If valid move, then make the move (assign square to letter)
    # then return True
    # If invalid, return False
    if self.board[square] == " ":
        self.board[square] = letter
        if self.winner(square, letter):
            self.current_winner = letter
        return True
    return False
This method automatically checks for a winner after each move and updates current_winner if applicable.

winner(square, letter)

Checks if the move at the specified square creates a winning condition.
square
int
required
The board position (0-8) where the last move was made
letter
str
required
The player’s letter (‘X’ or ‘O’) to check for a win
Returns: bool - True if the move creates three in a row, False otherwise
def winner(self, square, letter):
    # Winner if 3 in a row anywhere

    # Check row
    row_ind = square // 3
    row = self.board[row_ind*3 : (row_ind + 1) * 3]
    if all([spot == letter for spot in row]):
        return True

    # Check column
    col_ind = square % 3
    column = [self.board[col_ind+i*3] for i in range(3)]
    if all([spot == letter for spot in column]):
        return True

    # Check diagonals
    # But only if the square is an even number (2, 4, 6, 8)
    if square % 2 == 0:
        diagonal1 = [self.board[i] for i in [0, 4, 8]]  # Left to rigth diagonal
        if all([spot == letter for spot in diagonal1]):
            return True
        diagonal2 = [self.board[i] for i in [2, 4, 6]]  # Rigth to left diagonal
        if all([spot == letter for spot in diagonal2]):
            return True

    # If all of these fall
    return False
The method checks three possible win conditions:
  1. Row: All three squares in the same row match the letter
  2. Column: All three squares in the same column match the letter
  3. Diagonal: All three squares in either diagonal match the letter (only checked for even-numbered squares: 0, 2, 4, 6, 8)

play() Function

The main game loop that manages turn-taking and game flow.
game
TicTacToe
required
An instance of the TicTacToe class
x_player
Player
required
Player instance for ‘X’ (can be HumanPlayer, RandomComputerPlayer, or GeniusComputerPlayer)
o_player
Player
required
Player instance for ‘O’ (can be HumanPlayer, RandomComputerPlayer, or GeniusComputerPlayer)
print_game
bool
default:"True"
Whether to print the game board and moves to the console
Returns: str | None - The winning letter (‘X’ or ‘O’) if there’s a winner, None for a tie
def play(game, x_player, o_player, print_game=True):
    if print_game:
        game.print_board_nums()

    letter = 'X'  # Starting letter
    # Iterate while the game stil has empty squares
    while game.empty_squares():
        # Get the move from the appropiate player
        if letter == 'O':
            square = o_player.get_move(game)
        else:
            square = x_player.get_move(game)

        # Let's make a move!
        if game.make_move(square, letter):
            if print_game:
                print(letter + f" makes a move to square {square}")
                game.print_board()
                print("")

            if game.current_winner:
                if print_game:
                    print(letter + " wins!")
                return letter

            # After we made our move, we need to alternate letter
            letter = 'O' if letter == 'X' else 'X'  # Switches player

        # Tiny break
        time.sleep(0.8)

    if print_game:
        print("It's a tie!")

Usage Example

from game import TicTacToe, play
from player import HumanPlayer, GeniusComputerPlayer

x_player = HumanPlayer('X')
o_player = GeniusComputerPlayer('O')

ttt = TicTacToe()
winner = play(ttt, x_player, o_player)
The play() function includes a 0.8 second delay between moves for better user experience.

Build docs developers (and LLMs) love