Python for Humans · Draft v0.1 · Oct 28, 2026 · Project · First recurring-project draft.

Tic-Tac-Toe: Meet Your Opponent

Course project · After The Word-Prediction Generator

You should already know
Three in a Row and The Word-Prediction Generator, including random.choice().
You'll build
A tic-tac-toe game you can play on your own.
You'll be able to
Collect legal choices and replace human input with a computer decision.
You need
This page. Each example runs in its own code box; nothing to install.

Our two-player game has all its rules. There's just one practical problem: sometimes nobody else wants to play.

Let's give O to the computer. You stay X and move first. The computer will choose randomly, but it must follow exactly the same rules as you.

Allow about 15–20 minutes. We'll keep the existing board and win checker.

Start with Two Players

Run the complete game below once. It is the finished Three in a Row program, ready to change.

def show_board(board):
    print(f"{board[0]} | {board[1]} | {board[2]}")
    print("--+---+--")
    print(f"{board[3]} | {board[4]} | {board[5]}")
    print("--+---+--")
    print(f"{board[6]} | {board[7]} | {board[8]}")


def make_move(board, square, player):
    if square in ['1', '2', '3', '4', '5', '6', '7', '8', '9']:
        position = int(square) - 1
        if board[position] in ['X', 'O']:
            print("That square is taken. Try another one.")
            return False
        board[position] = player
        return True
    print("Please choose a number from 1 to 9.")
    return False


def check_winner(board, player):
    lines = [
        [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 line in lines:
        matches = 0
        for position in line:
            if board[position] == player:
                matches = matches + 1
        if matches == 3:
            return True
    return False


board = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
player = 'X'
moves = 0

while moves < 9:
    show_board(board)
    square = input(f"{player}, choose a square (1-9, or quit): ")
    if square == 'quit':
        break
    if make_move(board, square, player):
        moves = moves + 1
        if check_winner(board, player):
            show_board(board)
            print(f"{player} wins!")
            break
        elif moves == 9:
            show_board(board)
            print("It's a draw!")
        else:
            if player == 'X':
                player = 'O'
            else:
                player = 'X'

print("Thanks for playing!")

Collect the Available Squares

The numbered labels give us a useful clue. An unoccupied square still contains its original digit string. A taken square contains X or O.

Write available_squares(board) to start an empty choices list, visit each value in the board, and append it if it's one of the nine digit strings. Return the choices after the loop.

Here's a small example to compare with your helper. It runs on its own:

def available_squares(board):
    choices = []
    for square in board:
        if square in ['1', '2', '3', '4', '5', '6', '7', '8', '9']:
            choices.append(square)
    return choices

board = ['X', '2', '3', '4', 'O', '6', '7', '8', '9']
print(available_squares(board))

Predict the result before running it. Then change another square to O and check that its label disappears from the choices.

The function builds a new list. It doesn't remove anything from the board.

Give O a Turn

In your starter:

  1. Add import random at the top and your available_squares() definition beside the other helpers.
  2. Inside the game loop, keep the input() question when player == 'X'.
  3. In the other branch, set square = random.choice(available_squares(board)) and display the computer's choice.

Keep the call to make_move() after both branches. It now receives either a human answer or a computer choice. Winning, counting, and switching don't need a second implementation.

Only ask the computer to choose while the game is still going. random.choice() needs a nonempty list. Our loop stops on a full board, and a winning move leaves it immediately.

Play the Computer

Here's a complete version if you'd like to compare:

import random

def show_board(board):
    print(f"{board[0]} | {board[1]} | {board[2]}")
    print("--+---+--")
    print(f"{board[3]} | {board[4]} | {board[5]}")
    print("--+---+--")
    print(f"{board[6]} | {board[7]} | {board[8]}")


def make_move(board, square, player):
    if square in ['1', '2', '3', '4', '5', '6', '7', '8', '9']:
        position = int(square) - 1
        if board[position] in ['X', 'O']:
            print("That square is taken. Try another one.")
            return False
        board[position] = player
        return True
    print("Please choose a number from 1 to 9.")
    return False


def check_winner(board, player):
    lines = [
        [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 line in lines:
        matches = 0
        for position in line:
            if board[position] == player:
                matches = matches + 1
        if matches == 3:
            return True
    return False


def available_squares(board):
    choices = []
    for square in board:
        if square in ['1', '2', '3', '4', '5', '6', '7', '8', '9']:
            choices.append(square)
    return choices


board = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
player = 'X'
moves = 0

while moves < 9:
    show_board(board)
    if player == 'X':
        square = input("X, choose a square (1-9, or quit): ")
    else:
        square = random.choice(available_squares(board))
        print("Computer chooses", square)
    if square == 'quit':
        break
    if make_move(board, square, player):
        moves = moves + 1
        if check_winner(board, player):
            show_board(board)
            print(f"{player} wins!")
            break
        elif moves == 9:
            show_board(board)
            print("It's a draw!")
        else:
            if player == 'X':
                player = 'O'
            else:
                player = 'X'

print("Thanks for playing!")

Play a few games. Try an occupied square on your turn: the computer shouldn't get an extra turn. Try hello, too. Finally, type quit and check that no more moves happen.

The computer may miss an obvious win or let you finish a row. It knows what's legal, but it doesn't yet know what's useful.

Try One Change

Choose a bonus:

A random opponent is already a complete game. After Testing, the optional Can You Beat It? project will give it a plan: win if it can, block if it must, then choose among what's left.

Write one sentence about why the computer chooses from available squares rather than from all nine labels.