PyGameExamplesAndAnswers

StackOverflow            reply.it reply.it


Maze

Maze collision detection

Related Stack Overflow questions:

I have a maze organized in a grid. Each cell of the grid stores the information about the walls to its right and bottom neighboring cell. The player is an object of a certain size whose bounding box is known. I want to move the player smoothly through the maze with the walls preventing them from going through.

move player in maze

class Maze:
    def __init__(self, rows = 9, columns = 9):
        self.size = (columns, rows)
        self.walls = [[[True, True] for _ in range(self.size[1])] for __ in range(self.size[0])]
        visited = [[False for _ in range(self.size[1])] for __ in range(self.size[0])]
        i, j = (self.size[0]+1) // 2, (self.size[1]+1) // 2
        visited[i][j] = True
        stack = [(i, j)]
        while stack:
            current = stack.pop()
            i, j = current
            nl = [n for n in [(i-1, j), (i+1, j), (i, j-1), (i, j+1)] 
                  if 0 <= n[0] < self.size[0] and 0 <= n[1] < self.size[1] and not visited[n[0]][n[1]]]
            if nl:
                stack.insert(0, current)
                next = random.choice(nl)
                self.walls[min(next[0], current[0])][min(next[1], current[1])][abs(next[1]-current[1])] = False
                visited[next[0]][next[1]] = True
                stack.insert(0, next)

There are several solutions to this problem. Implement simple logic that tests if there is a wall in the player’s path when the player moves. Discard the movement when a collision with a wall is detected.

Add methods to the Maze class that check for a wall between a cell and its neighboring cell:

class Maze:
    # [...]

    def wall_left(self, i, j):
        return i < 1 or self.walls[i-1][j][0]
    def wall_right(self, i, j):
        return i >= self.size[0] or self.walls[i][j][0]
    def wall_top(self, i, j):
        return j < 1 or self.walls[i][j-1][1]
    def wall_bottom(self, i, j):
        return j >= self.size[0] or self.walls[i][j][1]

Calculate the rows and columns of the corner points of the player’s bounding box.

i0 = (player_rect.left - maze_pos[0]) // cell_size 
i1 = (player_rect.right - maze_pos[0]) // cell_size
j0 = (player_rect.top - maze_pos[1]) // cell_size  
j1 = (player_rect.bottom - maze_pos[1]) // cell_size  

As the player moves, test to see if the player is entering a new cell. Use the new methods in the Maze class to test whether there is a wall in the player’s path. Skip the movement if the path is blocked by a wall:

keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
    new_rect = player_rect.move(-3, 0)
    ni = (new_rect.left - maze_pos[0]) // cell_size
    if i0 == ni or not (maze.wall_left(i0, j0) or maze.wall_left(i0, j1) or (j0 != j1 and maze.wall_bottom(ni, j0))):
        player_rect = new_rect
if keys[pygame.K_RIGHT]:
    new_rect = player_rect.move(3, 0)
    ni = (new_rect.right - maze_pos[0]) // cell_size
    if i1 == ni or not (maze.wall_right(i1, j0) or maze.wall_right(i1, j1) or (j0 != j1 and maze.wall_bottom(ni, j0))):
        player_rect = new_rect
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
    new_rect = player_rect.move(0, -3)
    nj = (new_rect.top - maze_pos[1]) // cell_size
    if j0 == nj or not (maze.wall_top(i0, j0) or maze.wall_top(i1, j0) or (i0 != i1 and maze.wall_right(i0, nj))):
        player_rect = new_rect
if keys[pygame.K_DOWN]:
    new_rect = player_rect.move(0, 3)
    nj = (new_rect.bottom - maze_pos[1]) // cell_size
    if j1 == nj or not (maze.wall_bottom(i0, j1) or maze.wall_bottom(i1, j1) or (i0 != i1 and maze.wall_right(i0, nj))):
        player_rect = new_rect

maze move - collision logic

📁 Minimal example - Maze collision detection with logic

repl.it/@Rabbid76/PyGame-Maze-CollisionLogic

Another solution is to use mask collision. Draw the maze of a transparent pygame.Surface:

cell_size = 40
maze = Maze()
maze_surf = pygame.Surface((maze.size[0]*cell_size, maze.size[1]*cell_size), pygame.SRCALPHA)
draw_maze(maze_surf, maze, 0, 0, cell_size, (196, 196, 196), 3)

Crate a pygame.Mask from the Surface with pygame.mask.from_surface:

maze_mask = pygame.mask.from_surface(maze_surf)

Create a mask form the player:

player_rect = pygame.Rect(190, 190, 20, 20)
player_surf = pygame.Surface(player_rect.size, pygame.SRCALPHA)
pygame.draw.circle(player_surf, (255, 255, 0), (player_rect.width//2, player_rect.height//2), player_rect.width//2)
player_mask = pygame.mask.from_surface(player_surf)

Calculate the new position of the player:

keys = pygame.key.get_pressed()
new_rect = player_rect.move(
    (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 3,  
    (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 3)

Use pygame.mask.Mask.overlap to see if the masks are intersects (see Pygame collision with masks). Skip the movement when the mask of the maze intersects the player’s mask:

offset = (new_rect.x - maze_pos[0]), (new_rect.y - maze_pos[1])
if not maze_mask.overlap(player_mask, offset):
    player_rect = new_rect

maze move - mask collision][1]

📁 Minimal example - Maze collision detection with mask

repl.it/@Rabbid76/PyGame-Maze-MaskCollision