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:
- Add
import randomat the top and youravailable_squares()definition beside the other helpers. - Inside the game loop, keep the
input()question whenplayer == 'X'. - 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:
- Have the computer announce its move in the voice of the castle's dragon.
- Before choosing, display the list of available squares. Check that every computer choice came from that list, then remove the extra display when you're happy.
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.