Creating a Tic Tac Toe game in Pygame is a great way to practice Python programming and understand game development basics.
This guide will walk you through creating a two-player Tic Tac Toe game with Pygame and then modifying it to include an AI opponent.
If you want a pure Python version with the command line, check out the previous article: https://developer-service.blog/building-an-ai-powered-tic-tac-toe-in-python/
You can also follow along with the video version of this post:
What is Pygame?
Pygame is a set of Python modules designed for writing video games.
It provides functionalities and tools to create games and multimedia applications. Pygame is built on top of the Simple DirectMedia Layer (SDL) library, which handles lower-level game-related tasks such as graphics, sound, and input.
Key Features of Pygame
- Cross-Platform: Pygame runs on nearly every operating system and hardware platform where Python is supported, including Windows, macOS, and Linux.
- 2D Graphics: Pygame provides easy-to-use methods for drawing shapes, rendering images, and handling animations.
- Sound and Music: It supports various audio formats and allows for the playback of sound effects and background music.
- Input Handling: Pygame can handle keyboard, mouse, and joystick input, making it suitable for creating interactive applications and games.
- Simple API: The API is designed to be easy to learn for beginners, with straightforward functions and a clear structure.
- Community and Resources: Pygame has a large community and plenty of tutorials, making it easier to find help and learn through examples.
Setting Up Your Environment
Before we begin, ensure you have Pygame installed. You can install it using pip:
pip install pygame
This is the only additional library needed to write our game.
Two-Player Tic Tac Toe
First, let's create a basic Tic Tac Toe game where two human players can play against each other.
We go step by step in building our game.
Step 1: Import Required Modules and Initialize Pygame
Start by importing Pygame and initializing it.
import pygame
import sys
pygame.init()
Step 2: Define Constants and Initialize the Screen
Set up the screen dimensions, colors, and other constants.
# Screen size
WIDTH, HEIGHT = 600, 600
LINE_WIDTH = 15
WIN_LINE_WIDTH = 15
# Color definitions
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
# Board size
BOARD_ROWS = 3
BOARD_COLS = 3
SQUARE_SIZE = WIDTH // BOARD_COLS
CIRCLE_RADIUS = SQUARE_SIZE // 3
CIRCLE_WIDTH = 15
CROSS_WIDTH = 25
SPACE = SQUARE_SIZE // 4
# Initialize screen
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Tic Tac Toe')
screen.fill(WHITE)
Here's a breakdown of what it does:
- Screen size: The variables
WIDTH
andHEIGHT
are set to 600, indicating that the game window will be a square of 600x600 pixels.LINE_WIDTH
andWIN_LINE_WIDTH
are set to 15. - Color definitions: The variables
WHITE
,BLACK
, andRED
are defined as tuples, each containing three values between 0 and 255. These are RGB values, a common way to define colors in programming. - Board size: The variables
BOARD_ROWS
andBOARD_COLS
are set to 3, indicating that the game board will be a 3x3 grid, as is standard for Tic Tac Toe.SQUARE_SIZE
is calculated by dividingWIDTH
byBOARD_COLS
, meaning each square on the board will be 200x200 pixels.CIRCLE_RADIUS
is set to one-third ofSQUARE_SIZE
, andCIRCLE_WIDTH
andCROSS_WIDTH
are set to 15 and 25 respectively.SPACE
is set to one-fourth ofSQUARE_SIZE
. - Initialize screen: The
pygame.display.set_mode()
function is used to create a game window of the size specified byWIDTH
andHEIGHT
. Thepygame.display.set_caption()
function is used to set the title of the game window to 'Tic Tac Toe'. Thescreen.fill(WHITE)
function is used to set the background color of the game window to white.
Step 3: Initialize the Game Board
Create a 2D list to represent the game board.
board = [[0 for _ in range(BOARD_COLS)] for _ in range(BOARD_ROWS)]
The variable board
is initialized as a list. The list comprehension [0 for _ in range(BOARD_COLS)]
creates a new list of zeros with a length equal to BOARD_COLS
, which is 3 in this case. The _
variable is a throwaway variable that is not used in the code.
The outer list comprehension [0 for _ in range(BOARD_ROWS)]
repeats the inner list comprehension BOARD_ROWS
number of times, which is also 3. The result is a 3x3 list of lists, where each inner list represents a row on the game board, and the zeros in the inner lists represent the individual squares on the board.
Step 4: Draw the Board
Create functions to draw the grid lines and the Xs and Os.
def draw_lines():
# Horizontal lines
pygame.draw.line(screen, BLACK, (0, SQUARE_SIZE), (WIDTH, SQUARE_SIZE), LINE_WIDTH)
pygame.draw.line(screen, BLACK, (0, 2 * SQUARE_SIZE), (WIDTH, 2 * SQUARE_SIZE), LINE_WIDTH)
# Vertical lines
pygame.draw.line(screen, BLACK, (SQUARE_SIZE, 0), (SQUARE_SIZE, HEIGHT), LINE_WIDTH)
pygame.draw.line(screen, BLACK, (2 * SQUARE_SIZE, 0), (2 * SQUARE_SIZE, HEIGHT), LINE_WIDTH)
def draw_figures():
for row in range(BOARD_ROWS):
for col in range(BOARD_COLS):
if board[row][col] == 1:
pygame.draw.circle(screen, BLACK, (
int(col * SQUARE_SIZE + SQUARE_SIZE // 2), int(row * SQUARE_SIZE + SQUARE_SIZE // 2)),
CIRCLE_RADIUS,
CIRCLE_WIDTH)
elif board[row][col] == 2:
pygame.draw.line(screen, BLACK, (col * SQUARE_SIZE + SPACE, row * SQUARE_SIZE + SQUARE_SIZE - SPACE),
(col * SQUARE_SIZE + SQUARE_SIZE - SPACE, row * SQUARE_SIZE + SPACE), CROSS_WIDTH)
pygame.draw.line(screen, BLACK, (col * SQUARE_SIZE + SPACE, row * SQUARE_SIZE + SPACE),
(col * SQUARE_SIZE + SQUARE_SIZE - SPACE, row * SQUARE_SIZE + SQUARE_SIZE - SPACE),
CROSS_WIDTH)
This code defines two functions, draw_lines()
and draw_figures()
, that are used to draw the game board and the X's and O's on the board.
draw_lines()
function:
This function is responsible for drawing the horizontal and vertical lines that make up the 3x3 grid of the Tic Tac Toe game board.
The pygame.draw.line()
function is used to draw each line. It takes five arguments: the first is the surface to draw on (in this case, the screen
), the second is the color of the line (BLACK
), the third and fourth are the starting and ending coordinates of the line, and the fifth is the width of the line (LINE_WIDTH
).
The function draws two horizontal lines and two vertical lines, with the coordinates calculated using the SQUARE_SIZE
variable, which is the size of each square on the board.
draw_figures()
function:
This function is responsible for drawing the X's and O's on the game board, based on the current state of the game.
The function uses a nested for loop to iterate over each square on the board, represented by the board
list. The board
list is a 3x3 list of lists, where each inner list represents a row on the board, and the zeros in the inner lists represent the individual squares on the board.
If the value of a square is 1, the function uses the pygame.draw.circle()
function to draw an O on the square. The function takes four arguments: the first is the surface to draw on (in this case, the screen
), the second is the color of the circle (BLACK
), the third is the center coordinates of the circle, and the fourth is the radius of the circle (CIRCLE_RADIUS
).
If the value of a square is 2, the function uses the pygame.draw.line()
function to draw an X on the square. The function draws two lines that intersect at the center of the square to form the X. The coordinates of the lines are calculated using the SQUARE_SIZE
and SPACE
variables.
Step 5: Handle Player Moves
Create functions to mark the squares and check if a player has won.
def mark_square(row, col, _player):
board[row][col] = _player
def available_square(row, col):
return board[row][col] == 0
def is_board_full():
for row in range(BOARD_ROWS):
for col in range(BOARD_COLS):
if board[row][col] == 0:
return False
return True
def check_win(_player):
# Vertical win
for col in range(BOARD_COLS):
if board[0][col] == _player and board[1][col] == _player and board[2][col] == _player:
print(f"Vertical win on column {col} by player {_player}")
draw_vertical_winning_line(col)
return True
# Horizontal win
for row in range(BOARD_ROWS):
if board[row][0] == _player and board[row][1] == _player and board[row][2] == _player:
print(f"Horizontal win on row {row} by player {_player}")
draw_horizontal_winning_line(row)
return True
# Ascending diagonal win
if board[2][0] == _player and board[1][1] == _player and board[0][2] == _player:
print(f"Ascending diagonal win by player {_player}")
draw_asc_diagonal()
return True
# Descending diagonal win
if board[0][0] == _player and board[1][1] == _player and board[2][2] == _player:
print(f"Descending diagonal win by player {_player}")
draw_desc_diagonal()
return True
return False
This code defines four functions, mark_square()
, available_square()
, is_board_full()
, and check_win()
, that are used to manage the state of the game board and to determine the winner of the game.
mark_square(row, col, _player)
function:
This function is responsible for marking a square on the game board with the current player's marker (X or O). It takes three arguments: row
and col
are the coordinates of the square to be marked, and _player
is the current player (1 for X and 2 for O).
The function assigns the value of _player
to the square at the specified coordinates in the board
list.
available_square(row, col)
function:
This function is responsible for determining whether a square on the game board is available to be marked or not. It takes two arguments: row
and col
are the coordinates of the square to be checked.
The function checks the value of the square at the specified coordinates in the board
list. If the value is 0, the square is available and the function returns True
. If the value is 1 or 2, the square is already marked and the function returns False
.
is_board_full()
function:
This function is responsible for determining whether the game board is full or not.
The function uses a nested for loop to iterate over each square on the board, represented by the board
list. If the value of a square is 0, the function returns False
immediately, indicating that the board is not full. If the loop completes without finding any squares with a value of 0, the function returns True
, indicating that the board is full.
check_win(_player)
function:
This function is responsible for determining whether the current player has won the game or not. It takes one argument: _player
is the current player (1 for X and 2 for O).
The function checks for four possible winning conditions:
- Vertical win: The function checks each column of the board to see if the current player has marked all three squares in the column. If a winning condition is found, the function prints a message to the console, draws a vertical line on the screen to indicate the winning column, and returns
True
. - Horizontal win: The function checks each row of the board to see if the current player has marked all three squares in the row. If a winning condition is found, the function prints a message to the console, draws a horizontal line on the screen to indicate the winning row, and returns
True
. - Ascending diagonal win: The function checks the ascending diagonal of the board (from the top-left to the bottom-right) to see if the current player has marked all three squares in the diagonal. If a winning condition is found, the function prints a message to the console, draws an ascending diagonal line on the screen to indicate the winning diagonal, and returns
True
. - Descending diagonal win: The function checks the descending diagonal of the board (from the top-right to the bottom-left) to see if the current player has marked all three squares in the diagonal. If a winning condition is found, the function prints a message to the console, draws a descending diagonal line on the screen to indicate the winning diagonal, and returns
True
.
If no winning conditions are found, the function returns False
.
Step 6: Draw Winning Lines
These functions are called from the check_win()
function when a winning condition is detected.
def draw_vertical_winning_line(col):
pos_x = col * SQUARE_SIZE + SQUARE_SIZE // 2
color = BLACK
pygame.draw.line(screen, color, (pos_x, 15), (pos_x, HEIGHT - 15), WIN_LINE_WIDTH)
def draw_horizontal_winning_line(row):
pos_y = row * SQUARE_SIZE + SQUARE_SIZE // 2
color = BLACK
pygame.draw.line(screen, color, (15, pos_y), (WIDTH - 15, pos_y), WIN_LINE_WIDTH)
def draw_asc_diagonal():
color = BLACK
pygame.draw.line(screen, color, (15, HEIGHT - 15), (WIDTH - 15, 15), WIN_LINE_WIDTH)
def draw_desc_diagonal():
color = BLACK
pygame.draw.line(screen, color, (15, 15), (WIDTH - 15, HEIGHT - 15), WIN_LINE_WIDTH)
This code defines four functions, draw_vertical_winning_line()
, draw_horizontal_winning_line()
, draw_asc_diagonal()
, and draw_desc_diagonal()
, that are used to draw lines on the screen to indicate the winning squares.
draw_vertical_winning_line(col)
function:
This function is responsible for drawing a vertical line on the screen to indicate the winning squares in a vertical winning condition. It takes one argument: col
is the index of the column that contains the winning squares.
The function calculates the x-coordinate of the center of the column using the SQUARE_SIZE
variable. It then uses the pygame.draw.line()
function to draw a line from the top of the screen to the bottom of the screen, passing through the center of the column. The line is drawn in the BLACK
color and has a width of WIN_LINE_WIDTH
pixels.
draw_horizontal_winning_line(row)
function:
This function is responsible for drawing a horizontal line on the screen to indicate the winning squares in a horizontal winning condition. It takes one argument: row
is the index of the row that contains the winning squares.
The function calculates the y-coordinate of the center of the row using the SQUARE_SIZE
variable. It then uses the pygame.draw.line()
function to draw a line from the left of the screen to the right of the screen, passing through the center of the row. The line is drawn in the BLACK
color and has a width of WIN_LINE_WIDTH
pixels.
draw_asc_diagonal()
function:
This function is responsible for drawing an ascending diagonal line on the screen to indicate the winning squares in an ascending diagonal winning condition. It takes no arguments.
The function uses the pygame.draw.line()
function to draw a line from the top-left corner of the screen to the bottom-right corner of the screen. The line is drawn in the BLACK
color and has a width of WIN_LINE_WIDTH
pixels.
draw_desc_diagonal()
function:
This function is responsible for drawing a descending diagonal line on the screen to indicate the winning squares in a descending diagonal winning condition. It takes no arguments.
The function uses the pygame.draw.line()
function to draw a line from the top-right corner of the screen to the bottom-left corner of the screen. The line is drawn in the BLACK
color and has a width of WIN_LINE_WIDTH
pixels.
Step 7: Main Game Loop
Set up the main game loop to handle events and update the screen.
This article is for paid members only
To continue reading this article, upgrade your account to get full access.
Subscribe NowAlready have an account? Sign In