Simon JH
Simon JH

Reputation: 41

Creating collisions between a Sprite and a list (that aren't sprites) in a Maze

I have been working on a randomly generated maze game with enemies that move through the maze toward the player. However, I am having the issue of the player being able to move through the walls of the maze, the problem I have is that the walls of the maze aren't sprites and therefore cannot use sprite collide, or Rect collide. Is there another way I can stop the player from moving through the walls? The only option I can think of is to get the position of each wall used and prevent the player from moving into that value of x or y. Below is the visual representation of what I want to occur: Example of what happens and what I want to happen

Maze code

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
        self.maze_generated = False

    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
    # if x and y are within the bounds of the walls

    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):
        # if self.maze_generated == False:
        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]
        # else:
        self.maze_generated = True

Upvotes: 1

Views: 269

Answers (1)

Rabbid76
Rabbid76

Reputation: 210909

Calculate the rows and columns of the corner points points of the player rectangle.

col_l = player.rect.left // CELL_SIZE  
col_r = player.rect.right // CELL_SIZE
row_t = player.rect.top // CELL_SIZE  
row_b = player.rect.bottom // CELL_SIZE  

If the player moves, test if the player is entering a new cell. Skip the movement if there is a wall between the current cell and the new cell:

keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
    new_rect = player.rect.move(-1, 0)
    next_col = new_rect.left // CELL_SIZE
    if col_l == next_col or not (m.maze[col_l][row_t].walls[3] or m.maze[col_l][row_b].walls[3]):
        player.rect = new_rect
if keys[pygame.K_RIGHT]:
    new_rect = player.rect.move(1, 0)
    next_col = new_rect.right // CELL_SIZE
    if col_r == next_col or not (m.maze[col_r][row_t].walls[1] or m.maze[col_r][row_b].walls[1]):
        player.rect = new_rect
if keys[pygame.K_UP]:
    new_rect = player.rect.move(0, -1)
    next_row = new_rect.top // CELL_SIZE
    if row_t == next_row or not (m.maze[col_l][row_t].walls[0] or m.maze[col_r][row_t].walls[0]):
        player.rect = new_rect
if keys[pygame.K_DOWN]:
    new_rect = player.rect.move(0, 1)
    next_row = new_rect.bottom // CELL_SIZE
    if row_b == next_row or not (m.maze[col_l][row_b].walls[2] or m.maze[col_r][row_b].walls[2]):
        player.rect = new_rect

See also Adding collision to maze walls and How to implement barriers to stop the player moving through walls.


Complete example, base on your previous question How to generate my maze instantly so I don't have to watch it Generate?:

import pygame
import random

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]


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((20, 20))
        self.image.fill((255, 0, 0))
        self.rect = self.image.get_rect(center = (20, 20))

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))
    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()
    while True:
        m.create_maze()
        if len(m.stack) == 0:
            break

    player = Player()
    all_sprites = pygame.sprite.Group(player)

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

        col_l = player.rect.left // CELL_SIZE  
        col_r = player.rect.right // CELL_SIZE
        row_t = player.rect.top // CELL_SIZE  
        row_b = player.rect.bottom // CELL_SIZE  

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            new_rect = player.rect.move(-1, 0)
            next_col = new_rect.left // CELL_SIZE
            if col_l == next_col or not (m.maze[col_l][row_t].walls[3] or m.maze[col_l][row_b].walls[3]):
                player.rect = new_rect
        if keys[pygame.K_RIGHT]:
            new_rect = player.rect.move(1, 0)
            next_col = new_rect.right // CELL_SIZE
            if col_r == next_col or not (m.maze[col_r][row_t].walls[1] or m.maze[col_r][row_b].walls[1]):
                player.rect = new_rect
        if keys[pygame.K_UP]:
            new_rect = player.rect.move(0, -1)
            next_row = new_rect.top // CELL_SIZE
            if row_t == next_row or not (m.maze[col_l][row_t].walls[0] or m.maze[col_r][row_t].walls[0]):
                player.rect = new_rect
        if keys[pygame.K_DOWN]:
            new_rect = player.rect.move(0, 1)
            next_row = new_rect.bottom // CELL_SIZE
            if row_b == next_row or not (m.maze[col_l][row_b].walls[2] or m.maze[col_r][row_b].walls[2]):
                player.rect = new_rect

        SCREEN.fill(WHITE)
        for i in range(m.cols):
            for j in range(m.rows):
                m.maze[i][j].draw_cell()
        all_sprites.draw(SCREEN)
        pygame.display.flip()

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

Upvotes: 2

Related Questions