Creating Your First Game: Tic-Tac-Toe with Pygame!

Welcome, future game developers! Have you ever wanted to create your own game? It might sound like a big challenge, but with the right tools and a step-by-step approach, it’s totally achievable. Today, we’re going to dive into the exciting world of game development using a friendly Python library called Pygame. Our mission? To build a classic Tic-Tac-Toe game!

This tutorial is designed for absolute beginners. We’ll break down every step, explain technical terms, and make sure you have fun along the way.

What is Pygame?

Imagine you have a magic toolbox specifically designed for building computer games. That’s essentially what Pygame is!

  • Pygame: A set of Python modules designed for writing video games. It provides functionalities for graphics, sounds, input (like keyboard and mouse), and more. It makes it much easier to create games without having to worry about the very low-level details of how a computer draws images or plays sounds.

Think of it as giving you the paintbrush, canvas, and special effects machine so you can focus on creating your masterpiece, rather than building the tools themselves.

Setting Up Your Environment

Before we can start coding, we need to make sure your computer is ready.

1. Install Python

If you don’t have Python installed, head over to the official Python website (python.org) and download the latest stable version. Follow the installation instructions. Make sure to check the box that says “Add Python to PATH” during installation – this makes it easier to run Python from your command line.

  • Python: A popular, easy-to-learn programming language known for its readability and versatility.

2. Install Pygame

Once Python is ready, open your command prompt (on Windows) or terminal (on macOS/Linux). You can usually find it by searching for “cmd” or “terminal”. Then, type the following command and press Enter:

pip install pygame
  • pip: Python’s package installer. It’s a command-line tool that allows you to easily install and manage additional libraries (like Pygame) for Python.

If everything goes well, you’ll see messages indicating that Pygame has been successfully installed.

Game Design: Understanding Tic-Tac-Toe

Before we write any code, let’s quickly review the rules of Tic-Tac-Toe and how we’ll represent them in our game:

  • The Board: A 3×3 grid.
  • Players: Two players, traditionally “X” and “O”.
  • Turns: Players take turns placing their mark in an empty square.
  • Winning: A player wins by getting three of their marks in a row, column, or diagonal.
  • Draw: If all 9 squares are filled and no one has won, the game is a draw.

In our code, we’ll represent the board as a 2-dimensional list (a list of lists). Each “cell” in this list will hold a value indicating if it’s empty, “X”, or “O”.

  • 2-dimensional list (or 2D array/list of lists): Imagine a spreadsheet or a grid. A 2D list is a way to store data in rows and columns. For our Tic-Tac-Toe board, board[0][0] would be the top-left square, board[0][1] the top-middle, and so on.

Building Our Tic-Tac-Toe Game – Step by Step

Let’s start coding! Open your favorite text editor (like VS Code, Sublime Text, or even Notepad) and save the file as tic_tac_toe.py.

1. Import Pygame and Initialize

Every Pygame project starts with these lines. We import the pygame library and then initialize all its modules.

import pygame
import sys # Used for exiting the program

pygame.init()
  • pygame.init(): This function prepares Pygame for use. It initializes all the modules required for Pygame to work, such as those for graphics, sound, and input.

2. Set Up the Game Window

We need a window for our game to appear in. We’ll define its size and title.

WIDTH, HEIGHT = 600, 600
LINE_WIDTH = 15
BOARD_ROWS = 3
BOARD_COLS = 3
SQUARE_SIZE = WIDTH // BOARD_COLS # Each square will be 200x200 pixels

RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (180, 180, 180)
LINE_COLOR = BLACK
BG_COLOR = WHITE
X_COLOR = RED
O_COLOR = BLUE

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Tic-Tac-Toe!")
screen.fill(BG_COLOR)
  • RGB (Red, Green, Blue): A way to define colors by specifying the intensity of red, green, and blue light, ranging from 0 (no intensity) to 255 (full intensity). For example, (255, 0, 0) is pure red, (0, 0, 0) is black, and (255, 255, 255) is white.
  • pygame.display.set_mode(): Creates the game window (or “surface”).
  • pygame.display.set_caption(): Sets the title that appears in the window’s title bar.
  • screen.fill(): Fills the entire window with a specified color.

3. Game Variables

We need to keep track of the game’s state: the board, whose turn it is, and if the game is over.

board = [['', '', ''],
         ['', '', ''],
         ['', '', '']]

player = 1 # 1 for Player X, 2 for Player O
game_over = False
winner = None

4. Drawing the Tic-Tac-Toe Board

Let’s draw the lines that form our 3×3 grid.

def draw_board():
    # Horizontal lines
    pygame.draw.line(screen, LINE_COLOR, (0, SQUARE_SIZE), (WIDTH, SQUARE_SIZE), LINE_WIDTH)
    pygame.draw.line(screen, LINE_COLOR, (0, 2 * SQUARE_SIZE), (WIDTH, 2 * SQUARE_SIZE), LINE_WIDTH)
    # Vertical lines
    pygame.draw.line(screen, LINE_COLOR, (SQUARE_SIZE, 0), (SQUARE_SIZE, HEIGHT), LINE_WIDTH)
    pygame.draw.line(screen, LINE_COLOR, (2 * SQUARE_SIZE, 0), (2 * SQUARE_SIZE, HEIGHT), LINE_WIDTH)

draw_board() # Draw the board initially
  • pygame.draw.line(): Draws a straight line on the screen. It takes the surface to draw on, the color, the starting point (x,y), the ending point (x,y), and the line thickness.

5. Drawing X’s and O’s

We need functions to draw the player marks on the board.

def draw_marks():
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            if board[row][col] == 'X':
                # Draw X
                # Line 1: top-left to bottom-right
                pygame.draw.line(screen, X_COLOR,
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.15, row * SQUARE_SIZE + SQUARE_SIZE * 0.15),
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.85, row * SQUARE_SIZE + SQUARE_SIZE * 0.85),
                                 LINE_WIDTH)
                # Line 2: top-right to bottom-left
                pygame.draw.line(screen, X_COLOR,
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.85, row * SQUARE_SIZE + SQUARE_SIZE * 0.15),
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.15, row * SQUARE_SIZE + SQUARE_SIZE * 0.85),
                                 LINE_WIDTH)
            elif board[row][col] == 'O':
                # Draw O
                center_x = col * SQUARE_SIZE + SQUARE_SIZE // 2
                center_y = row * SQUARE_SIZE + SQUARE_SIZE // 2
                radius = SQUARE_SIZE // 2 * 0.35 # Make it a bit smaller than the square
                pygame.draw.circle(screen, O_COLOR, (center_x, center_y), radius, LINE_WIDTH)
  • pygame.draw.circle(): Draws a circle. Takes the surface, color, center point (x,y), radius, and line thickness.

6. Handling Player Clicks and Updating the Board

When a player clicks, we need to convert the mouse click position into a board row and column, then place their mark.

def mark_square(row, col, player):
    if player == 1:
        board[row][col] = 'X'
    else:
        board[row][col] = 'O'

def available_square(row, col):
    return board[row][col] == ''

7. Checking for Win or Draw

This is the core game logic to determine if someone has won or if it’s a draw.

def check_win(player_mark):
    # Check horizontal win
    for row in range(BOARD_ROWS):
        if board[row][0] == player_mark and board[row][1] == player_mark and board[row][2] == player_mark:
            return True
    # Check vertical win
    for col in range(BOARD_COLS):
        if board[0][col] == player_mark and board[1][col] == player_mark and board[2][col] == player_mark:
            return True
    # Check ascending diagonal win
    if board[2][0] == player_mark and board[1][1] == player_mark and board[0][2] == player_mark:
        return True
    # Check descending diagonal win
    if board[0][0] == player_mark and board[1][1] == player_mark and board[2][2] == player_mark:
        return True
    return False

def check_draw():
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            if board[row][col] == '':
                return False # There's an empty square, so it's not a draw yet
    return True # All squares are filled, and no winner, so it's a draw

8. Displaying Game Messages and Reset

We’ll add a simple way to show who won or if it’s a draw, and a function to reset the game.

def display_message(message):
    font = pygame.font.Font(None, 80) # None uses default font, 80 is size
    text = font.render(message, True, BLACK) # True for anti-aliasing (smoother edges)
    text_rect = text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    screen.blit(text, text_rect)

def reset_game():
    global board, game_over, player, winner
    board = [['', '', ''],
             ['', '', ''],
             ['', '', '']]
    player = 1
    game_over = False
    winner = None
    screen.fill(BG_COLOR) # Clear the screen
    draw_board() # Redraw empty board
  • pygame.font.Font(): Creates a font object, which we use to render text.
  • font.render(): Creates an image (surface) of the text.
  • text.get_rect(): Gets a rectangular area covering the text, useful for positioning.
  • screen.blit(): Draws one image (surface) onto another (our screen surface).

9. The Main Game Loop

This is the heart of any Pygame game. It’s a while loop that keeps running until you quit the game. Inside this loop, we handle events (like mouse clicks), update the game state, and redraw everything.

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            sys.exit() # Exit the program cleanly

        if event.type == pygame.MOUSEBUTTONDOWN and not game_over:
            mouse_x, mouse_y = event.pos # Get mouse click coordinates

            # Determine which square was clicked
            clicked_col = mouse_x // SQUARE_SIZE
            clicked_row = mouse_y // SQUARE_SIZE

            if available_square(clicked_row, clicked_col):
                mark_square(clicked_row, clicked_col, player)
                draw_marks() # Redraw marks after placing one

                if check_win('X') or check_win('O'):
                    game_over = True
                    winner = 'X' if player == 1 else 'O'
                    display_message(f"Player {winner} Wins!")
                elif check_draw():
                    game_over = True
                    display_message("It's a Draw!")
                else:
                    player = 2 if player == 1 else 1 # Switch player

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r and game_over: # Press 'R' to reset
                reset_game()


    # Update the full display Surface to the screen
    pygame.display.update()

pygame.quit() # Uninitialize Pygame modules
  • Game Loop: The core engine of a game. It repeatedly performs three main tasks:
    1. Process Input: Checks for user actions (mouse clicks, keyboard presses).
    2. Update Game State: Changes game variables based on input or game logic (e.g., move a character, update scores).
    3. Render Graphics: Draws everything on the screen to show the updated game state.
  • pygame.event.get(): Retrieves all events that have occurred since the last call.
  • event.type == pygame.QUIT: This event occurs when the user clicks the ‘X’ button to close the window.
  • event.type == pygame.MOUSEBUTTONDOWN: This event occurs when a mouse button is pressed down.
  • pygame.display.update(): This command takes everything we’ve drawn onto the screen surface and makes it visible to the player. It “flips” the display buffer.
  • pygame.quit(): Cleans up Pygame modules. It’s good practice to call this before your program ends.

Putting It All Together (Complete Code)

Here’s the entire code for your simple Tic-Tac-Toe game. You can copy and paste this into your tic_tac_toe.py file.

import pygame
import sys

pygame.init()

WIDTH, HEIGHT = 600, 600
LINE_WIDTH = 15
BOARD_ROWS = 3
BOARD_COLS = 3
SQUARE_SIZE = WIDTH // BOARD_COLS # Each square will be 200x200 pixels

RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (180, 180, 180)
LINE_COLOR = BLACK
BG_COLOR = WHITE
X_COLOR = RED
O_COLOR = BLUE

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Tic-Tac-Toe!")
screen.fill(BG_COLOR)

board = [['', '', ''],
         ['', '', ''],
         ['', '', '']]

player = 1 # 1 for Player X, 2 for Player O
game_over = False
winner = None

def draw_board():
    # Horizontal lines
    pygame.draw.line(screen, LINE_COLOR, (0, SQUARE_SIZE), (WIDTH, SQUARE_SIZE), LINE_WIDTH)
    pygame.draw.line(screen, LINE_COLOR, (0, 2 * SQUARE_SIZE), (WIDTH, 2 * SQUARE_SIZE), LINE_WIDTH)
    # Vertical lines
    pygame.draw.line(screen, LINE_COLOR, (SQUARE_SIZE, 0), (SQUARE_SIZE, HEIGHT), LINE_WIDTH)
    pygame.draw.line(screen, LINE_COLOR, (2 * SQUARE_SIZE, 0), (2 * SQUARE_SIZE, HEIGHT), LINE_WIDTH)

def draw_marks():
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            if board[row][col] == 'X':
                # Draw X
                # Line 1: top-left to bottom-right
                pygame.draw.line(screen, X_COLOR,
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.15, row * SQUARE_SIZE + SQUARE_SIZE * 0.15),
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.85, row * SQUARE_SIZE + SQUARE_SIZE * 0.85),
                                 LINE_WIDTH)
                # Line 2: top-right to bottom-left
                pygame.draw.line(screen, X_COLOR,
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.85, row * SQUARE_SIZE + SQUARE_SIZE * 0.15),
                                 (col * SQUARE_SIZE + SQUARE_SIZE * 0.15, row * SQUARE_SIZE + SQUARE_SIZE * 0.85),
                                 LINE_WIDTH)
            elif board[row][col] == 'O':
                # Draw O
                center_x = col * SQUARE_SIZE + SQUARE_SIZE // 2
                center_y = row * SQUARE_SIZE + SQUARE_SIZE // 2
                radius = SQUARE_SIZE // 2 * 0.35
                pygame.draw.circle(screen, O_COLOR, (center_x, center_y), radius, LINE_WIDTH)

def mark_square(row, col, player):
    if player == 1:
        board[row][col] = 'X'
    else:
        board[row][col] = 'O'

def available_square(row, col):
    return board[row][col] == ''

def check_win(player_mark):
    # Check horizontal win
    for row in range(BOARD_ROWS):
        if board[row][0] == player_mark and board[row][1] == player_mark and board[row][2] == player_mark:
            return True
    # Check vertical win
    for col in range(BOARD_COLS):
        if board[0][col] == player_mark and board[1][col] == player_mark and board[2][col] == player_mark:
            return True
    # Check ascending diagonal win
    if board[2][0] == player_mark and board[1][1] == player_mark and board[0][2] == player_mark:
        return True
    # Check descending diagonal win
    if board[0][0] == player_mark and board[1][1] == player_mark and board[2][2] == player_mark:
        return True
    return False

def check_draw():
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            if board[row][col] == '':
                return False
    return True

def display_message(message):
    font = pygame.font.Font(None, 80)
    text = font.render(message, True, BLACK)
    text_rect = text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    screen.blit(text, text_rect)

def reset_game():
    global board, game_over, player, winner
    board = [['', '', ''],
             ['', '', ''],
             ['', '', '']]
    player = 1
    game_over = False
    winner = None
    screen.fill(BG_COLOR)
    draw_board()

draw_board()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            sys.exit()

        if event.type == pygame.MOUSEBUTTONDOWN and not game_over:
            mouse_x, mouse_y = event.pos

            clicked_col = mouse_x // SQUARE_SIZE
            clicked_row = mouse_y // SQUARE_SIZE

            if available_square(clicked_row, clicked_col):
                mark_square(clicked_row, clicked_col, player)
                draw_marks()

                if check_win('X') or check_win('O'):
                    game_over = True
                    winner = 'X' if player == 1 else 'O'
                    # Clear any previous message before displaying new one
                    screen.fill(BG_COLOR)
                    draw_board()
                    draw_marks()
                    display_message(f"Player {winner} Wins!")
                elif check_draw():
                    game_over = True
                    screen.fill(BG_COLOR)
                    draw_board()
                    draw_marks()
                    display_message("It's a Draw!")
                else:
                    player = 2 if player == 1 else 1

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r and game_over: # Press 'R' to reset game
                reset_game()

    pygame.display.update()

pygame.quit()

How to Run Your Game

Save the code above as tic_tac_toe.py. Then, open your command prompt or terminal, navigate to the directory where you saved the file (using cd command), and run it using:

python tic_tac_toe.py

A new window should pop up, showing your Tic-Tac-Toe board! Click on the squares to play. If the game ends, press ‘R’ to reset.

Conclusion

Congratulations! You’ve just created your very first interactive game using Pygame. We covered setting up the environment, drawing graphics, handling user input, and implementing core game logic. This is a fantastic foundation for more complex projects.

Don’t stop here! Game development is a journey of continuous learning. Try to add more features to your game:

  • Display whose turn it is.
  • Make the winning line visible.
  • Add sound effects.
  • Create an AI opponent!

Have fun experimenting, and keep building!

Comments

Leave a Reply