Search code examples
pythonpygamemaze

How to generate my maze instantly so I don't have to watch it Generate?


So I'm creating a game and I'm using Recursive backtracking algorithm to create the maze, however, I don't want it to show the maze generation and just to instantly generate the maze. I'm unsure of how to actually do this though so any help would be appreciated, I've already tried not drawing the generated white part but that then doesn't create the maze.

import pygame
import random
import time


class Cell(object):
    def __init__(self, x, y, cell_size, screen, black, white, red, blue):
        # position in matrix
        self.x = x
        self.y = y
        # keeps track of which walls are still visible
        self.walls = [True, True, True, True]
        # checks if cell has been visited during generation
        self.generated = False
        # checks if cell is on path during solving
        self.on_path = False
        # checks if cell has been visited during solving
        self.visited = False
        self.cell_size = cell_size
        self.screen = screen
        self.black = black
        self.white = white
        self.red = red
        self.blue = blue

    def draw_cell(self):
        # coordinates on screen
        x = self.x * self.cell_size
        y = self.y * self.cell_size
        # draws a wall if it still exists
        if self.walls[0]:
            pygame.draw.line(self.screen, self.black, (x, y), (x + self.cell_size, y), 5)
        if self.walls[1]:
            pygame.draw.line(self.screen, self.black,
                             (x, y + self.cell_size), (x + self.cell_size, y + self.cell_size), 5)
        if self.walls[2]:
            pygame.draw.line(self.screen, self.black,
                             (x + self.cell_size, y), (x + self.cell_size, y + self.cell_size), 5)
        if self.walls[3]:
            pygame.draw.line(self.screen, self.black, (x, y), (x, y + self.cell_size), 5)
        # marks out white if generated during generation
        if self.generated:
            pygame.draw.rect(self.screen, self.white, (x, y, self.cell_size, self.cell_size))


class Maze:
    def __init__(self, screen, cell_size, rows, cols, white, black, red, blue):
        self.screen = screen
        self.cell_size = cell_size
        self.rows = rows
        self.cols = cols
        self.state = None
        self.maze = []
        self.stack = []
        self.current_x = 0
        self.current_y = 0
        self.row = []
        self.neighbours = []
        self.black = black
        self.white = white
        self.red = red
        self.blue = blue
        self.cell = None

    def on_start(self):
        # maintains the current state
        # maze matrix of cell instances
        self.maze = []
        # stack of current cells on path
        self.stack = []
        self.current_x, self.current_y = 0, 0

        self.maze.clear()
        self.stack.clear()
        for x in range(self.cols):
            self.row = []
            for y in range(self.rows):
                self.cell = Cell(x, y, self.cell_size, self.screen, self.black, self.white, self.red, self.blue)
                self.row.append(self.cell)
            self.maze.append(self.row)

    def in_bounds(self, x, y):
        return 0 <= x < self.cols and 0 <= y < self.rows

    def find_next_cell(self, x, y):
        # keeps track of valid neighbors
        self.neighbours = []

        # loop through these two arrays to find all 4 neighbor cells
        dx, dy = [1, -1, 0, 0], [0, 0, 1, -1]
        for d in range(4):
            # add cell to neighbor list if it is in bounds and not generated
            if self.in_bounds(x + dx[d], y + dy[d]):
                if not self.maze[x + dx[d]][y + dy[d]].generated:
                    self.neighbours.append((x + dx[d], y + dy[d]))
        # returns a random cell in the neighbors list, or -1 -1 otherwise
        if len(self.neighbours) > 0:
            return self.neighbours[random.randint(0, len(self.neighbours) - 1)]
        else:
            return -1, -1

    def remove_wall(self, x1, y1, x2, y2):
        # x distance between original cell and neighbor cell
        xd = self.maze[x1][y1].x - self.maze[x2][y2].x
        # to the bottom
        if xd == 1:
            self.maze[x1][y1].walls[3] = False
            self.maze[x2][y2].walls[1] = False
        # to the top
        elif xd == -1:
            self.maze[x1][y1].walls[1] = False
            self.maze[x2][y2].walls[3] = False
        # y distance between original cell and neighbor cell
        xy = self.maze[x1][y1].y - self.maze[x2][y2].y
        # to the right
        if xy == 1:
            self.maze[x1][y1].walls[0] = False
            self.maze[x2][y2].walls[2] = False
        # to the left
        elif xy == -1:
            self.maze[x1][y1].walls[2] = False
            self.maze[x2][y2].walls[0] = False

    def create_maze(self):
        self.maze[self.current_x][self.current_y].generated = True
        # self.maze[self.current_x][self.current_y].draw_current()
        next_cell = self.find_next_cell(self.current_x, self.current_y)
        # checks if a neighbor was returned
        if next_cell[0] >= 0 and next_cell[1] >= 0:
            self.stack.append((self.current_x, self.current_y))
            self.remove_wall(self.current_x, self.current_y, next_cell[0], next_cell[1])
            self.current_x = next_cell[0]
            self.current_y = next_cell[1]
        # no neighbor, so go to the previous cell in the stack
        elif len(self.stack) > 0:
            previous = self.stack.pop()
            self.current_x = previous[0]
            self.current_y = previous[1]


def main():
    WIDTH, HEIGHT = 800, 800
    CELL_SIZE = 40
    ROWS, COLUMNS = int(HEIGHT / CELL_SIZE), int(WIDTH / CELL_SIZE)

    # color variables
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    RED = (255, 0, 0)
    BLUE = (0, 0, 255)

    # initialize pygame
    pygame.init()
    SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
    SCREEN.fill(WHITE)
    pygame.display.set_caption("Maze Gen")
    CLOCK = pygame.time.Clock()
    FPS = 60
    m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
    m.on_start()

    running = True
    while running:
        CLOCK.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        for i in range(m.cols):
            for j in range(m.rows):
                m.maze[i][j].draw_cell()

        m.create_maze()

        pygame.display.flip()


if __name__ == "__main__":
    main()
pygame.quit()

Solution

  • Call m.create_maze() in a loop before the application loop. Terminate the loop when len(m.stack) == 0:

    def main():
        # [...]
    
        m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
        m.on_start()
        while True:
            m.create_maze()
            if len(m.stack) == 0:
                break
    
        running = True
        while running:
            CLOCK.tick(FPS)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
    
            for i in range(m.cols):
                for j in range(m.rows):
                    m.maze[i][j].draw_cell()
    
            pygame.display.flip()