mazeGenerator.py (original)


import random

"""
maze generator code

algorithm:
start with an empty grid
draw a wall with gaps, dividing the grid in 2
repeat recursively for each sub-grid

pacman details:
players 1,3 always start in the bottom left; 2,4 in the top right
food is placed in dead ends and then randomly (though not too close to the pacmen starting positions)

notes:
the final map includes a symmetric, flipped copy
the first wall has k gaps, the next wall has k/2 gaps, etc. (min=1)

@author: Dan Gillick
"""

W = '%'
F = '.'
E = ' '

class Maze:

    def __init__(self, rows, cols, anchor=(0, 0), root=None):
        """
        generate an empty maze
        anchor is the top left corner of this grid's position in its parent grid
        """
        self.r = rows
        self.c = cols
        self.grid = [[E for col in range(cols)] for row in range(rows)]
        self.anchor = anchor
        self.rooms = []
        self.root = root
        if not self.root: self.root = self

    def to_map(self):
        """
        add a flipped symmetric copy on the right
        add a border
        """

        ## add a flipped symmetric copy
        for row in range(self.r):
            for col in range(self.c-1, -1, -1):
                self.grid[self.r-row-1].append(self.grid[row][col])
        self.c *= 2

        ## add a border
        for row in range(self.r):
            self.grid[row] = [W] + self.grid[row] + [W]
        self.c += 2
        self.grid.insert(0, [W for c in range(self.c)])
        self.grid.append([W for c in range(self.c)])
        self.r += 2

    def __str__(self):
        s = ''
        for row in range(self.r):
            for col in range(self.c):
                s += self.grid[row][col]
            s += '\n'
        return s[:-1]

    def add_wall(self, i, gaps=1, vert=True):
        """
        add a wall with gaps
        """
        add_r, add_c = self.anchor
        if vert:
            gaps = min(self.r, gaps)
            slots = [add_r+x for x in range(self.r)]
            if not 0 in slots:
                if self.root.grid[min(slots)-1][add_c+i] == E: slots.remove(min(slots))
                if len(slots) <= gaps: return 0 
            if not self.root.c-1 in slots:
                if self.root.grid[max(slots)+1][add_c+i] == E: slots.remove(max(slots))
            if len(slots) <= gaps: return 0
            random.shuffle(slots)
            for row in slots[gaps:]:
                self.root.grid[row][add_c+i] = W
            self.rooms.append(Maze(self.r, i, (add_r,add_c), self.root))
            self.rooms.append(Maze(self.r, self.c-i-1, (add_r,add_c+i+1), self.root))
        else:
            gaps = min(self.c, gaps)
            slots = [add_c+x for x in range(self.c)]
            if not 0 in slots:
                if self.root.grid[add_r+i][min(slots)-1] == E: slots.remove(min(slots))
                if len(slots) <= gaps: return 0
            if not self.root.r-1 in slots:
                if self.root.grid[add_r+i][max(slots)+1] == E: slots.remove(max(slots))
            if len(slots) <= gaps: return 0
            random.shuffle(slots)
            for col in slots[gaps:]:
                self.root.grid[add_r+i][col] = W
            self.rooms.append(Maze(i, self.c, (add_r,add_c), self.root))
            self.rooms.append(Maze(self.r-i-1, self.c, (add_r+i+1,add_c), self.root))

        return 1
      
def make(room, depth, gaps=1, vert=True, min_width=1):
    """
    recursively build a maze
    TODO: randomize number of gaps?
    """
    
    ## extreme base case
    if room.r <= min_width and room.c <= min_width: return    
    
    ## decide between vertical and horizontal wall
    if vert: num = room.c
    else: num = room.r    
    if num < min_width + 2:
        vert = not vert
        if vert: num = room.c
        else: num = room.r
    
    ## add a wall to the current room
    if depth==0: wall_slots = [num-2]  ## fix the first wall
    else: wall_slots = range(1, num-1)
    if len(wall_slots) == 0: return
    choice = random.choice(wall_slots)
    if not room.add_wall(choice, gaps, vert): return

    ## recursively add walls
    for sub_room in room.rooms:
        make(sub_room, depth+1, max(1,gaps/2), not vert, min_width)

def copy_grid(grid):
    new_grid = []
    for row in range(len(grid)):
        new_grid.append([])
        for col in range(len(grid[row])):
            new_grid[row].append(grid[row][col])
    return new_grid
  
def add_pacman_stuff(maze, max_food=60):
    """
    add pacmen starting position
    add food at dead ends plus some extra
    """

    ## parameters
    max_depth = 2
    
    ## add food at dead ends
    depth = 0
    total_food = 0
    while True:
        new_grid = copy_grid(maze.grid)
        depth += 1
        num_added = 0
        for row in range(1, maze.r-1):
            for col in range(1, (maze.c/2)-1):
                if (row > maze.r-6) and (col < 6): continue
                if maze.grid[row][col] != E: continue
                neighbors = (maze.grid[row-1][col]==E) + (maze.grid[row][col-1]==E) + (maze.grid[row+1][col]==E) + (maze.grid[row][col+1]==E)
                if neighbors == 1: 
                    new_grid[row][col] = F
                    new_grid[maze.r-row-1][maze.c-(col)-1] = F
                    num_added += 2
                    total_food += 2
        maze.grid = new_grid
        if num_added == 0: break
        if depth >= max_depth: break

    ## starting pacmen positions
    maze.grid[maze.r-2][1] = '3'
    maze.grid[maze.r-3][1] = '1'
    maze.grid[1][maze.c-2] = '4'
    maze.grid[2][maze.c-2] = '2'

    ## extra random food
    while total_food < max_food:
        row = random.randint(1, maze.r-1)
        col = random.randint(1, (maze.c/2)-1)
        if (row > maze.r-6) and (col < 6): continue
        if maze.grid[row][col] == E:
            maze.grid[row][col] = F
            maze.grid[maze.r-row-1][maze.c-(col)-1] = F
            total_food += 2
  
MAX_DIFFERENT_MAZES = 20

if __name__ == '__main__':
    import random, sys
    random.seed(random.randint(1,MAX_DIFFERENT_MAZES))
    maze = Maze(16,16)
    make(maze, depth=0, gaps=6, vert=True, min_width=1)
    maze.to_map()
    add_pacman_stuff(maze, 2*(maze.r*maze.c/20))
    print maze