Table of Contents

PyMaze

Normally this would be a very simple example of a game, maybe even in Season 1. However, if it is used there only introduce the very simple maze algorithms!

main.py

from Window import Window
from Game import Game

def main():
    window = Window()
    window.setLogo("logo32x32.png")
    window.setCaption("Maze Game by Appledog")
    window.setSize(800, 600)
    window.setFont("PxPlus_IBM_VGA_8x16-2x.ttf", 32)

    game = Game(window)
    game.start()

if __name__ == "__main__":
    main()

As you can see from this code, this is going to be a pygame terminal framework game. We're going to use a 2x wide (40 column) font for this for aesthetic purposes.

Game.py

Standard, but there are some changes to draw(), update() and so forth. Also observe the changes to the game data at the start of the class.

import random
import pygame
from Maze import Maze

class Game:
    def __init__(self, window):
        self.window = window
        self.screen = window.screen
        self.font = window.font
        self.logo = window.logo

        # Game Data
        self.FPS = 60
        self.clock = pygame.time.Clock()

        self.maze = Maze(19, 14, 30, 30)
        self.maze.binary_tree_maze()
        self.maze.cwidth = 32
        self.maze.cheight = 32

        self.px = 1
        self.py = 1
        self.tx = random.randint(0, self.maze.width)
        self.ty = random.randint(0, self.maze.height)


    def start(self):
        while True:
            self.checkEvents()
            self.update()
            self.draw()
            self.clock.tick(self.FPS)

    def update(self):
        if self.px == self.tx and self.py == self.ty:
            print("YOU WIN!")
            self.quit()

    def draw(self):
        self.screen.fill((0, 0, 0))  # Clear the screen.
        self.maze.draw(self.screen)
        self.drawText(self.tx, self.ty, "X", "gold")
        self.drawText(self.px, self.py, "@", "gray")
        pygame.display.flip()  # update the display.

    def checkEvents(self):
        for event in pygame.event.get():
            # Window Quit Event
            if event.type == pygame.QUIT:
                self.quit()
                return

            # Keyboard Events
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_q:
                    self.quit()
                elif event.key == pygame.K_LEFT:
                    if not self.maze.get_wall(self.px, self.py, 'west'):
                        self.px = self.px - 1
                elif event.key == pygame.K_RIGHT:
                    if not self.maze.get_wall(self.px, self.py, 'east'):
                        self.px = self.px + 1
                elif event.key == pygame.K_UP:
                    if not self.maze.get_wall(self.px, self.py, 'north'):
                        self.py = self.py - 1
                elif event.key == pygame.K_DOWN:
                    if not self.maze.get_wall(self.px, self.py, 'south'):
                        self.py = self.py + 1

    def drawText(self, at_x, at_y, text, color):
        text_surface = self.font.render(text, False, color)
        x = self.window.fontwidth * at_x
        y = self.window.fontheight * at_y
        self.screen.blit(text_surface, (x+2, y+2))

    def quit(self):
        quit()
        exit()

Window.py

Unchanged.

Cell.py

class Cell:
    def __init__(self):
        self.north = True
        self.east = True
        self.west = True
        self.south = True
        self.wall_color = "gray"

Just a simple class to store the info about walls.

Maze.py

The work is done here. Note the requirement for class Cell (shown above):

import random
import pygame
from Cell import Cell

class Maze:
    def __init__(self, w, h, cw, ch):
        self.width = w
        self.height = h
        self.cwidth = cw
        self.cheight = ch

        self.map = [[Cell() for _ in range(w)] for _ in range(h)]

    def get_wall(self, x, y, d):
        if d == 'north' and self.map[y][x].north:
            return True
        elif d == 'south' and self.map[y][x].south:
            return True
        elif d == 'west' and self.map[y][x].west:
            return True
        elif d == 'east' and self.map[y][x].east:
            return True

        # fail false.
        return False

    def set_wall(self, x, y, d, s):
        if d == "north":
            self.map[y][x].north = s
            if y > 0:
                self.map[y-1][x].north = s

        elif d == "east":
            self.map[y][x].east = s
            if x < self.width:
                self.map[y][x+1].west = s

        elif d == "west":
            self.map[y][x].west = s
            if x > 0:
                self.map[y][x-1].east = s

        elif d == "south":
            self.map[y][x].south = s
            if y < self.width:
                self.map[y+1][x].north = s

        else:
            print("unknown wall position")
            quit()
            exit()

    def draw(self, screen):
        line_color = "gray"

        for y in range(self.height):
            for x in range(self.width):
                c = self.map[y][x]
                ax = 2 + x * self.cwidth
                ay = 2 + y * self.cheight
                bx = ax + self.cwidth
                by = ay + self.cheight
                if c.north:
                    pygame.draw.line(screen, line_color, (ax, ay), (bx, ay))
                if c.south:
                    pygame.draw.line(screen, line_color, (ax, by), (bx, by))
                if c.west:
                    pygame.draw.line(screen, line_color, (ax, ay), (ax, by))
                if c.east:
                    pygame.draw.line(screen, line_color, (bx, ay), (bx, by))


    def binary_tree_maze(self):
        for x in range(self.width):
            for y in range(self.height):
                if x == self.width - 1 and y == self.height - 1:
                    continue
                elif x == self.width - 1:
                    self.set_wall(x, y, "south", False)
                elif y == self.height - 1:
                    self.set_wall(x, y, "east", False)
                else:
                    if random.choice([True, False]):
                        self.set_wall(x, y, "east", False)
                    else:
                        self.set_wall(x, y, "south", False)

Binary Tree Maze Algorithm

The binary tree maze algorithm is a simple algorithm for generating mazes. It is often used in computer graphics, game development, and robotics.

The binary tree maze algorithm works by iteratively deciding whether to create passages to the east or south, resulting in a maze with paths that resemble a binary tree structure. The randomness adds variability to the generated mazes. This algorithm is relatively easy to understand, making it suitable for beginners exploring maze generation algorithms and basic grid-based world generation.

  1. Iteration over Cells:
    • The code iterates over each cell in the maze, represented by x and y coordinates.
  2. Corner Cells Handling:
    • The algorithm checks if the current cell is at the bottom-right corner of the maze (x == self.width - 1 and y == self.height - 1). If so, it continues to the next iteration, skipping the cell. This ensures that the bottom-right cell remains open.
    • If the cell is on the rightmost edge but not the bottom-right corner, a passage is opened to the south (self.set_wall(x, y, “south”, False)). This creates a path towards the bottom of the maze.
    • If the cell is on the bottom edge but not the bottom-right corner, a passage is opened to the east (self.set_wall(x, y, “east”, False)). This creates a path towards the right side of the maze.
  3. Inner Cells:
    • For inner cells (not on the right or bottom edge), a decision is made using random.choice([True, False]). This randomly chooses whether to create a passage to the east or south.
    • If True, a passage is opened to the east (self.set_wall(x, y, “east”, False)).
    • If False, a passage is opened to the south (self.set_wall(x, y, “south”, False)).