Build Maze Game Using Pygame

PythonPythonBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

In this project, we will create a maze game using the Pygame library in Python. The game involves navigating a player through a maze to collect food items while avoiding walls. We will split the development process into multiple steps to make it easier to understand and follow along.

👀 Preview

Alt text

ðŸŽŊ Tasks

In this project, you will learn:

  • How to set up the game environment using Pygame
  • How to create the maze using cells and walls
  • How to add food items for the player to collect
  • How to implement player movement and collision detection
  • How to handle game logic, including scoring and game over conditions
  • How to keep track of the player's record
  • How to display game statistics such as time, score, and record on the screen

🏆 Achievements

After completing this project, you will be able to:

  • Use the Pygame library for game development
  • Apply object-oriented programming concepts to create game elements
  • Demonstrate algorithmic thinking and problem-solving skills for maze generation
  • Handle event processing and player input
  • Implement collision detection and movement mechanics in a game environment
  • Manage file handling for storing and retrieving game records
  • Display game statistics and information on the screen

Setting up the Environment

First, we'll create the project files for the Maze game.

cd ~/project
touch maze.py
sudo pip install pygame

In this step, we'll set up the Pygame environment and define constants.

import pygame
from random import choice, randrange

## Constants for screen dimensions and tile size
RES = WIDTH, HEIGHT = 1202, 902
TILE = 100
cols, rows = WIDTH // TILE, HEIGHT // TILE

## The rest of your code will go here...

In this step:

  • We import the necessary libraries (Pygame and random).
  • We define constants for screen dimensions and tile size.
  • Pygame is initialized, and the game window is set up.
  • We load background images for the game.
âœĻ Check Solution and Practice

Creating the Cell Class

In this step, we'll define the Cell class to represent maze cells.

## Define a class to represent cells in the maze
class Cell:
    def __init__(self, x, y):
        self.x, self.y = x, y
        ## Walls represent the boundaries of the cell
        self.walls = {"top": True, "right": True, "bottom": True, "left": True}
        self.visited = False
        self.thickness = 4

    ## Draw the cell's walls
    def draw(self, sc):
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y), (x + TILE, y), self.thickness
            )
        if self.walls["right"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y),
                (x + TILE, y + TILE),
                self.thickness,
            )
        if self.walls["bottom"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y + TILE),
                (x, y + TILE),
                self.thickness,
            )
        if self.walls["left"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y + TILE), (x, y), self.thickness
            )

    ## Get the rectangles representing each wall of the cell
    def get_rects(self):
        rects = []
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            rects.append(pygame.Rect((x, y), (TILE, self.thickness)))
        if self.walls["right"]:
            rects.append(pygame.Rect((x + TILE, y), (self.thickness, TILE)))
        if self.walls["bottom"]:
            rects.append(pygame.Rect((x, y + TILE), (TILE, self.thickness)))
        if self.walls["left"]:
            rects.append(pygame.Rect((x, y), (self.thickness, TILE)))
        return rects

    ## Check if a neighboring cell exists
    def check_cell(self, x, y):
        find_index = lambda x, y: x + y * cols
        if x < 0 or x > cols - 1 or y < 0 or y > rows - 1:
            return False
        return self.grid_cells[find_index(x, y)]

    ## Get neighboring cells that have not been visited
    def check_neighbors(self, grid_cells):
        self.grid_cells = grid_cells
        neighbors = []
        top = self.check_cell(self.x, self.y - 1)
        right = self.check_cell(self.x + 1, self.y)
        bottom = self.check_cell(self.x, self.y + 1)
        left = self.check_cell(self.x - 1, self.y)
        if top and not top.visited:
            neighbors.append(top)
        if right and not right.visited:
            neighbors.append(right)
        if bottom and not bottom.visited:
            neighbors.append(bottom)
        if left and not left.visited:
            neighbors.append(left)
        return choice(neighbors) if neighbors else False

## The rest of your code will go here...

In this step:

  • We define the Cell class with its properties and methods for drawing walls and checking neighbors.
âœĻ Check Solution and Practice

Removing Walls and Generating the Maze

In this step, we'll create functions to remove walls and generate the maze.

## Function to remove walls between two adjacent cells
def remove_walls(current, next):
    dx = current.x - next.x
    if dx == 1:
        current.walls["left"] = False
        next.walls["right"] = False
    elif dx == -1:
        current.walls["right"] = False
        next.walls["left"] = False
    dy = current.y - next.y
    if dy == 1:
        current.walls["top"] = False
        next.walls["bottom"] = False
    elif dy == -1:
        current.walls["bottom"] = False
        next.walls["top"] = False


## Function to generate the maze
def generate_maze():
    grid_cells = [Cell(col, row) for row in range(rows) for col in range(cols)]
    current_cell = grid_cells[0]
    array = []
    break_count = 1

    while break_count != len(grid_cells):
        current_cell.visited = True
        next_cell = current_cell.check_neighbors(grid_cells)
        if next_cell:
            next_cell.visited = True
            break_count += 1
            array.append(current_cell)
            remove_walls(current_cell, next_cell)
            current_cell = next_cell
        elif array:
            current_cell = array.pop()
    return grid_cells

## The rest of your code will go here...

In this step:

  • We define the remove_walls function to remove walls between adjacent cells.
  • We create the generate_maze function to generate the maze using a depth-first search algorithm.
âœĻ Check Solution and Practice

Adding Food to the Game

In this step, we'll create a Food class to add food items to the game.

## Class to represent food in the game
class Food:
    def __init__(self):
        ## Load the food image
        self.img = pygame.image.load("img/food.png").convert_alpha()
        self.img = pygame.transform.scale(self.img, (TILE - 10, TILE - 10))
        self.rect = self.img.get_rect()
        self.set_pos()

    ## Set the position of the food randomly
    def set_pos(self):
        self.rect.topleft = randrange(cols) * TILE + 5, randrange(rows) * TILE + 5

    ## Draw the food on the screen
    def draw(self):
        game_surface.blit(self.img, self.rect)

## The rest of your code will go here...

In this step:

  • We define the Food class with methods for setting the position and drawing food items.
âœĻ Check Solution and Practice

Player Movement and Collision Detection

In this step, we'll set up player controls, movement, and collision detection.

## Check if the player collides with walls
def is_collide(x, y):
    tmp_rect = player_rect.move(x, y)
    if tmp_rect.collidelist(walls_collide_list) == -1:
        return False
    return True

## The rest of your code will go here...

In this step:

  • We define the is_collide function to check if the player collides with walls.
âœĻ Check Solution and Practice

Gameplay and Scoring

In this step, we'll implement gameplay logic, including eating food and scoring.

## Check if the player has eaten any food
def eat_food():
    for food in food_list:
        if player_rect.collidepoint(food.rect.center):
            food.set_pos()
            return True
    return False


## Check if the game is over (time runs out)
def is_game_over():
    global time, score, record, FPS
    if time < 0:
        pygame.time.wait(700)
        player_rect.center = TILE // 2, TILE // 2
        [food.set_pos() for food in food_list]
        set_record(record, score)
        record = get_record()
        time, score, FPS = 60, 0, 60

## The rest of your code will go here...

In this step:

  • We define the eat_food function to check if the player has eaten any food.
  • We create the is_game_over function to check if the game is over when time runs out.
âœĻ Check Solution and Practice

Handling Records

In this step, we'll implement record keeping for the game.

## Function to get the current record from a file
def get_record():
    try:
        with open("record") as f:
            return f.readline()
    except FileNotFoundError:
        with open("record", "w") as f:
            f.write("0")
            return "0"

## Function to set and update the record in a file
def set_record(record, score):
    rec = max(int(record), score)
    with open("record", "w") as f:
        f.write(str(rec))

## The rest of your code will go here...

In this step:

  • We define functions to retrieve the current record from a file and update it.
âœĻ Check Solution and Practice

Game Initialization

In this step, we'll perform game initialization tasks.

## Initialize Pygame and set up the game window
FPS = 60
pygame.init()
game_surface = pygame.Surface(RES)
surface = pygame.display.set_mode((WIDTH + 300, HEIGHT))
clock = pygame.time.Clock()

## Load background images
bg_game = pygame.image.load("img/bg_1.jpg").convert()
bg = pygame.image.load("img/bg_main.jpg").convert()

## Generate the maze
maze = generate_maze()

## Player settings
player_speed = 5
player_img = pygame.image.load("img/0.png").convert_alpha()
player_img = pygame.transform.scale(
    player_img, (TILE - 2 * maze[0].thickness, TILE - 2 * maze[0].thickness)
)
player_rect = player_img.get_rect()
player_rect.center = TILE // 2, TILE // 2
directions = {
    "a": (-player_speed, 0),
    "d": (player_speed, 0),
    "w": (0, -player_speed),
    "s": (0, player_speed),
}
keys = {"a": pygame.K_LEFT, "d": pygame.K_RIGHT, "w": pygame.K_UP, "s": pygame.K_DOWN}
direction = (0, 0)

## Food settings
food_list = [Food() for i in range(3)]

## Create a list of rectangles representing walls for collision detection
walls_collide_list = sum([cell.get_rects() for cell in maze], [])

## Timer, score, and record
pygame.time.set_timer(pygame.USEREVENT, 1000)
time = 60
score = 0
record = get_record()

## Fonts
font = pygame.font.SysFont("Impact", 150)
text_font = pygame.font.SysFont("Impact", 80)

## The rest of your code will go here...

In this step:

  • We perform various initialization tasks, including setting up Pygame, loading images, generating the maze, and initializing player and food-related variables.
âœĻ Check Solution and Practice

Main Game Loop

In this step, we'll set up the main game loop and display game elements.

## Main game loop
while True:
    ## Blit background images
    surface.blit(bg, (WIDTH, 0))
    surface.blit(game_surface, (0, 0))
    game_surface.blit(bg_game, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        if event.type == pygame.USEREVENT:
            time -= 1

    ## Handle player controls and movement
    pressed_key = pygame.key.get_pressed()
    for key, key_value in keys.items():
        if pressed_key[key_value] and not is_collide(*directions[key]):
            direction = directions[key]
            break
    if not is_collide(*direction):
        player_rect.move_ip(direction)

    ## Draw the maze
    [cell.draw(game_surface) for cell in maze]

    ## Gameplay: Check if the player has eaten food and if the game is over
    if eat_food():
        FPS += 10
        score += 1
    is_game_over()

    ## Draw the player
    game_surface.blit(player_img, player_rect)

    ## Draw food items
    [food.draw() for food in food_list]

    ## The rest of your code will go here...

In this step:

  • We set up the main game loop that handles events, player movement, and game rendering.
âœĻ Check Solution and Practice

Displaying Game Statistics

In this step, we'll display game statistics on the screen.

    ## Draw game statistics
    surface.blit(
        text_font.render("TIME", True, pygame.Color("cyan"), True), (WIDTH + 70, 30)
    )
    surface.blit(font.render(f"{time}", True, pygame.Color("cyan")), (WIDTH + 70, 130))
    surface.blit(
        text_font.render("score:", True, pygame.Color("forestgreen"), True),
        (WIDTH + 50, 350),
    )
    surface.blit(
        font.render(f"{score}", True, pygame.Color("forestgreen")), (WIDTH + 70, 430)
    )
    surface.blit(
        text_font.render("record:", True, pygame.Color("magenta"), True),
        (WIDTH + 30, 620),
    )
    surface.blit(
        font.render(f"{record}", True, pygame.Color("magenta")), (WIDTH + 70, 700)
    )

    pygame.display.flip()
    clock.tick(FPS)

In this step:

  • We use fonts to display game-related information such as time, score, and the record.
  • We use the blit() method to draw the text on the screen.
  • We use the flip() method to update the display.
âœĻ Check Solution and Practice

Run the Game

Now that we've completed all the steps, we can run the Maze game using the following command:

cd ~/project
python maze.py
Alt text
âœĻ Check Solution and Practice

Summary

In this project, we've split the process of building a maze game using Pygame into ten clear and manageable steps. You'll learn how to set up the game environment, create maze cells, generate the maze, handle player movement and collision detection, implement gameplay and scoring, manage records, and more. By following these steps, you'll be able to create a fully functional maze game in Python.

Other Python Tutorials you may like