Editor

Tic-Tac-Toe in Python

Intermediate · Games · about 75 minutes to build · 123 lines

Two players take turns on one keyboard, or you play X against a computer that takes winning squares and blocks yours.

The program

Press Run and answer its questions in the box that appears under the code.

tic_tac_toe.py
"""Tic-tac-toe for two players on one keyboard, or one player against the computer."""
import random
EMPTY = " "
# The eight lines of three that win: rows, columns and diagonals (squares 0-8).
WIN_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),
]
CENTER = 4
CORNERS = (0, 2, 6, 8)
def show_board(board):
"""Print the board with the square numbers in the empty squares."""
print()
for row in range(3):
cells = []
for col in range(3):
index = row * 3 + col
cells.append(board[index] if board[index] != EMPTY else str(index + 1))
print(f" {cells[0]} | {cells[1]} | {cells[2]}")
if row < 2:
print("---+---+---")
def find_winner(board):
"""Return "X" or "O" if that mark has three in a line, otherwise None."""
for a, b, c in WIN_LINES:
if board[a] != EMPTY and board[a] == board[b] == board[c]:
return board[a]
return None
def free_squares(board):
"""Return the indexes of the empty squares."""
return [i for i, cell in enumerate(board) if cell == EMPTY]
def ask_move(board, mark):
"""Ask the player for a free square from 1 to 9 and return its index."""
while True:
text = input(f"Player {mark}, choose a square (1-9): ").strip()
if not text.isdecimal() or not 1 <= int(text) <= 9:
print(" Please type a number from 1 to 9.")
elif board[int(text) - 1] != EMPTY:
print(" That square is taken.")
else:
return int(text) - 1
def winning_square(board, mark):
"""Return a square that completes three in a line for mark, or None."""
for square in free_squares(board):
trial = board.copy()
trial[square] = mark
if find_winner(trial) == mark:
return square
return None
def computer_move(board, mark, opponent):
"""Pick a square: win if possible, else block, else center, corner, any."""
for target in (mark, opponent):
square = winning_square(board, target)
if square is not None:
return square
if board[CENTER] == EMPTY:
return CENTER
corners = [c for c in CORNERS if board[c] == EMPTY]
return random.choice(corners or free_squares(board))
def play_game(vs_computer):
"""Play one game; X always moves first."""
board = [EMPTY] * 9
mark = "X"
while True:
if vs_computer and mark == "O":
square = computer_move(board, "O", "X")
print(f" The computer takes square {square + 1}.")
else:
show_board(board)
square = ask_move(board, mark)
board[square] = mark
winner = find_winner(board)
if winner or EMPTY not in board:
show_board(board)
if winner and vs_computer:
print("\nYou win." if winner == "X" else "\nThe computer wins.")
elif winner:
print(f"\nPlayer {winner} wins.")
else:
print("\nIt's a draw.")
return
mark = "O" if mark == "X" else "X"
def main():
"""Show the menu until the player chooses to quit."""
print("=== Tic-Tac-Toe ===")
while True:
print("\n1. Two players")
print("2. Play against the computer")
print("3. Quit")
choice = input("Choose 1, 2 or 3: ").strip()
if choice == "1":
play_game(vs_computer=False)
elif choice == "2":
print("You are X and you go first.")
play_game(vs_computer=True)
elif choice == "3":
print("Goodbye.")
break
else:
print(" Please type 1, 2 or 3.")
if __name__ == "__main__":
main()

Open it in the full editor to save it, share it and keep building.

Sample run

What the program printed, and what was typed (in yellow).

=== Tic-Tac-Toe ===

1. Two players
2. Play against the computer
3. Quit
Choose 1, 2 or 3: 2
You are X and you go first.

 1 | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
Player X, choose a square (1-9): 1
  The computer takes square 5.

 X | 2 | 3
---+---+---
 4 | O | 6
---+---+---
 7 | 8 | 9
Player X, choose a square (1-9): 9
  The computer takes square 7.

 X | 2 | 3
---+---+---
 4 | O | 6
---+---+---
 O | 8 | X
Player X, choose a square (1-9): 5
  That square is taken.
Player X, choose a square (1-9): 3
  The computer takes square 2.

 X | O | X
---+---+---
 4 | O | 6
---+---+---
 O | 8 | X
Player X, choose a square (1-9): 6

 X | O | X
---+---+---
 4 | O | X
---+---+---
 O | 8 | X

You win.

1. Two players
2. Play against the computer
3. Quit
Choose 1, 2 or 3: 3
Goodbye.

How it is built

1. The board and the winning lines

The board is a list of nine strings, one per square, numbered 0 to 8 in the code and 1 to 9 for the player. WIN_LINES lists the eight rows, columns and diagonals, so finding a winner is one loop.

EMPTY = " "
# The eight lines of three that win: rows, columns and diagonals (squares 0-8).
WIN_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),
]
CENTER = 4
CORNERS = (0, 2, 6, 8)

2. Finding a winner

A line wins when its three squares hold the same mark and are not empty. The chained comparison board[a] == board[b] == board[c] checks all three at once.

def find_winner(board):
    """Return "X" or "O" if that mark has three in a line, otherwise None."""
    for a, b, c in WIN_LINES:
        if board[a] != EMPTY and board[a] == board[b] == board[c]:
            return board[a]
    return None

3. Looking one move ahead

For each free square, the function puts the mark on a copy of the board and asks find_winner whether that move wins. Working on board.copy() leaves the real board untouched.

def winning_square(board, mark):
    """Return a square that completes three in a line for mark, or None."""
    for square in free_squares(board):
        trial = board.copy()
        trial[square] = mark
        if find_winner(trial) == mark:
            return square
    return None

4. How the computer chooses

The computer looks for its own winning square first, then for the square that blocks the player, then takes the center, then a free corner. A player who takes two opposite corners can still beat it, which keeps the game fun.

def computer_move(board, mark, opponent):
    """Pick a square: win if possible, else block, else center, corner, any."""
    for target in (mark, opponent):
        square = winning_square(board, target)
        if square is not None:
            return square
    if board[CENTER] == EMPTY:
        return CENTER
    corners = [c for c in CORNERS if board[c] == EMPTY]
    return random.choice(corners or free_squares(board))

5. Taking turns

mark switches between X and O after every move, and the computer plays O when vs_computer is True. The game ends when someone has three in a line or no EMPTY square is left, which is a draw.

def play_game(vs_computer):
    """Play one game; X always moves first."""
    board = [EMPTY] * 9
    mark = "X"
    while True:
        if vs_computer and mark == "O":
            square = computer_move(board, "O", "X")
            print(f"  The computer takes square {square + 1}.")
        else:
            show_board(board)
            square = ask_move(board, mark)
        board[square] = mark

        winner = find_winner(board)
        if winner or EMPTY not in board:
            show_board(board)
            if winner and vs_computer:
                print("\nYou win." if winner == "X" else "\nThe computer wins.")
            elif winner:
                print(f"\nPlayer {winner} wins.")
            else:
                print("\nIt's a draw.")
            return
        mark = "O" if mark == "X" else "X"

Make it bigger

  • Let the player choose to go second, so the computer opens the game.
  • Make the computer unbeatable with the minimax algorithm, which plays out every possible game to the end.
  • Keep a running score of X wins, O wins and draws across games.
  • Grow the board to 4 x 4 with four in a row to win, building WIN_LINES with loops instead of by hand.

What it practises