How do I prevent the player from moving through the walls in a maze?

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.

How do I prevent the player from moving through the walls in a maze?

Minimal and reproducible example:

import pygame, random

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)

def draw_maze(surf, maze, x, y, l, color, width):
    lines = [((x, y), (x + l * len(maze.walls), y)), ((x, y), (x, y + l * len(maze.walls[0])))] 
    for i, row in enumerate(maze.walls):
        for j, cell in enumerate(row):
            if cell[0]: lines += [((x + i*l + l, y + j*l), (x + i*l + l, y + j*l + l))]
            if cell[1]: lines += [((x + i*l, y + j*l + l), (x + i*l + l, y + j*l + l))]
    for line in lines:
        pygame.draw.line(surf, color, *line, width)

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

maze = Maze()
player_rect = pygame.Rect(190, 190, 20, 20)

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    keys = pygame.key.get_pressed()
    player_rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 3
    player_rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 3

    window.fill(0)
    draw_maze(window, maze, 20, 20, 40, (196, 196, 196), 3)
    pygame.draw.circle(window, (255, 255, 0), player_rect.center, player_rect.width//2)
    pygame.display.flip()

pygame.quit()
exit()

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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 is not working). 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

See also Maze collision detection


Minimal example: How do I prevent the player from moving through the walls in a maze? repl.it/@Rabbid76/PyGame-Maze-MaskCollision

How do I prevent the player from moving through the walls in a maze?

import pygame, random

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)

def draw_maze(surf, maze, x, y, l, color, width):
    lines = [((x, y), (x + l * len(maze.walls), y)), ((x, y), (x, y + l * len(maze.walls[0])))] 
    for i, row in enumerate(maze.walls):
        for j, cell in enumerate(row):
            if cell[0]: lines += [((x + i*l + l, y + j*l), (x + i*l + l, y + j*l + l))]
            if cell[1]: lines += [((x + i*l, y + j*l + l), (x + i*l + l, y + j*l + l))]
    for line in lines:
        pygame.draw.line(surf, color, *line, width)

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

maze_pos = 20, 20
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)
maze_mask = pygame.mask.from_surface(maze_surf)

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)

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    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)

    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

    window.fill(0)
    window.blit(maze_surf, maze_pos)
    window.blit(player_surf, player_rect)
    pygame.display.flip()

pygame.quit()
exit()

Method 2

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

See also Maze collision detection


Minimal example: How do I prevent the player from moving through the walls in a maze? repl.it/@Rabbid76/PyGame-Maze-CollisionLogic

How do I prevent the player from moving through the walls in a maze?

import pygame, random

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)

    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]

def draw_maze(surf, maze, x, y, l, color, width):
    lines = [((x, y), (x + l * len(maze.walls), y)), ((x, y), (x, y + l * len(maze.walls[0])))] 
    for i, row in enumerate(maze.walls):
        for j, cell in enumerate(row):
            if cell[0]: lines += [((x + i*l + l, y + j*l), (x + i*l + l, y + j*l + l))]
            if cell[1]: lines += [((x + i*l, y + j*l + l), (x + i*l + l, y + j*l + l))]
    for line in lines:
        pygame.draw.line(surf, color, *line, width)

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

maze = Maze()
player_rect = pygame.Rect(190, 190, 20, 20)

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    maze_pos = 20, 20
    cell_size = 40
    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  

    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

    window.fill(0)
    draw_maze(window, maze, 20, 20, cell_size, (196, 196, 196), 3)
    pygame.draw.circle(window, (255, 255, 0), player_rect.center, player_rect.width//2)
    pygame.display.flip()

pygame.quit()
exit()

Method 3

Both @Rabbid76 solutions are good, but I would like to provide another approach.

In this approach, you would need two variables x and y to store the previous position, position before the collision. You would then need to store all the maze line’s rect in a list.

Now, check if the player’s rect has collided with any of the rect in the list either by iterating through the list and using Rect.colliderect or use pygame’s Rect.collidelist(note: collidelist will return -1 if there is no collision). If they have collided reset the current position to the previous position.

code:

import pygame, random

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)


def draw_maze(surf, maze, x, y, l, color, width):
    lines = maze_lines(maze, x, y, l)
    for line in lines:
        pygame.draw.line(surf, color, *line, width)


def maze_lines(maze, x, y, l):
    lines = [((x, y), (x + l * len(maze.walls), y)), ((x, y), (x, y + l * len(maze.walls[0])))]

    for i, row in enumerate(maze.walls):
        for j, cell in enumerate(row):
            if cell[0]: lines += [((x + i * l + l, y + j * l), (x + i * l + l, y + j * l + l))]
            if cell[1]: lines += [((x + i * l, y + j * l + l), (x + i * l + l, y + j * l + l))]

    return lines

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

maze = Maze()
player_rect = pygame.Rect(190, 190, 20, 20)

line_rects = [pygame.draw.line(window, (0, 0, 0), *line) for line in maze_lines(maze, 20, 20, 40)]  # first store all the line's rect in the list
prev_x, prev_y = 0, 0

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    keys = pygame.key.get_pressed()

    prev_x, prev_y = player_rect.x, player_rect.y
    player_rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 3
    player_rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 3

    # if any(x.colliderect(player_rect) for x in line_rects):
    if player_rect.collidelist(line_rects) != -1:
        player_rect.x = prev_x
        player_rect.y = prev_y

    window.fill(0)
    draw_maze(window, maze, 20, 20, 40, (196, 196, 196), 3)

    pygame.draw.circle(window, (255, 255, 0), player_rect.center, player_rect.width//2)
    pygame.display.flip()

pygame.quit()
exit()

Output:

How do I prevent the player from moving through the walls in a maze?


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x