Cracking the maze based on depth-first algorithm


Preface

Different maps can be generated by prim, and the maze problem can be solved by A* algorithm


1. Prim algorithm generates map

Prim algorithm generates the map and solves it through the A* algorithm, but after a few days, the A* algorithm did not come out, so I changed the algorithm, depth first algorithm (DFS).

Two, realize the maze

2.1 Depth first algorithm

Depth first search is a kind of graph algorithm. The English abbreviation is DFS (Depth First Search.). In brief, the process is to deepen each possible branch path to the point where it can no longer be deep, and each node can only be accessed once. The following example:

Compared with the A* algorithm, this algorithm is more cumbersome and consumes more than A*, but it is easy to understand. An example from life: I lost a key at home, but I don't know the specific location of the key. Will be in any position. At this time, I need to look at every corner, and the corners I have been in are all in my mind, and I will not look again. This kind of carpet search until I find the key position.

2.2 Import library

This time I referenced the pyxel library. Pyxel is a python classic pixel style game production engine.
The maze can be visualized.

2.3 Implementation process

Prim generates the maze
still using Prim to generate the maze. We define a Maze class and use a two-dimensional array to represent the maze map, where 1 represents the wall and 0 represents the road. Then initialize the upper left corner as the entrance, the lower right corner as the exit, and finally define the downward direction vector .

class Maze:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.map = [[0 if x % 2 == 1 and y % 2 == 1 else 1 for x in range(width)] for y in range(height)]
        self.map[1][0] = 0  # 入口
        self.map[height - 2][width - 1] = 0  # 出口
        self.visited = []
        self.dx = [1, 0, -1, 0]
        self.dy = [0, -1, 0, 1]

The next step is to generate the main function of the maze. There are two main functions in this function: get_neighbor_road(point) and deal_with_not_visited(), the former will get the neighboring nodes of the incoming coordinate point point, the return value is a two-dimensional array, and the latter deal_with_not_visited() randomly selects a wall from the list , If only one cell of the two cells separated by this wall has been visited, remove this wall from the list and open the wall at the same time. Mark the
cell as the adjacent wall of the unvisited cell Join the list wall.

def generate(self):
    start = [1, 1]
    self.visited.append(start)
    wall_list = self.get_neighbor_wall(start)
    while wall_list:
        wall_position = random.choice(wall_list)
        neighbor_road = self.get_neighbor_road(wall_position)
        wall_list.remove(wall_position)
        self.deal_with_not_visited(neighbor_road[0], wall_position, wall_list)
        self.deal_with_not_visited(neighbor_road[1], wall_position, wall_list)

Prim's algorithm is to randomly select all the cells in the list at random. There is no difference between the newly added cells and the old ones, and both have a 50% probability. In this way, when a maze is generated,
DFS will naturally go out of the maze to
realize DFS. It is to implement a recursive program. When the coordinate of the node is out of bounds, the node has been visited, or the node is a wall, return directly, because it is not a path out of the maze. If it is, add it to the visited node and path.
Then if the node is an exit, it means that the program execution is over and the path is found. Otherwise, traverse the four direction vectors, pass the neighboring path of the node into the function dfs and continue the above steps until a way is found or all nodes of the program are traversed.

 
def dfs(self, x, y, path, visited=[]):
    # outOfIndex
    if self.is_out_of_index(x, y):
        return False
 
    # visited or is wall
    if [x, y] in visited or self.get_value([x, y]) == 1:
        return False
 
    visited.append([x, y])
    path.append([x, y])
 
    # end...
    if x == self.width - 2 and y == self.height - 2:
        return True
 
    # recursive
    for i in range(4):
        if 0 < x + self.dx[i] < self.width - 1 and 0 < y + self.dy[i] < self.height - 1 and \
                self.get_value([x + self.dx[i], y + self.dy[i]]) == 0:
            if self.dfs(x + self.dx[i], y + self.dy[i], path, visited):
                return True
            elif not self.is_out_of_index(x, y) and path[-1] != [x, y]:
                path.append([x, y])


The execution logic of pyxel to realize the visualization class Vision is to continuously call the update function and the draw function, so you can update the coordinates of the object in the update function, and then draw the image to the screen in the draw function.
In this way, we first draw the maze, and then render the dfs traversal animation.

import pyxel
 
class Vision:
    def __init__(self):
        pyxel.init(160, 120)
        self.x = 0
        pyxel.run(self.update, self.draw)
 
    def update(self):
        self.x = (self.x + 1) % pyxel.width
 
    def draw(self):
        pyxel.cls(0)
        pyxel.rect(self.x, 0, 8, 8, 9)
 
Vision()

The width and height of the generated maze are set to 48, 36

width, height = 48, 36
my_maze = Maze(width, height)
my_maze.generate()
 
class Vision:
    def __init__(self):
        pyxel.init(width * pixel, height * pixel)
        pyxel.run(self.update, self.draw)
 
    def update(self):
        if pyxel.btn(pyxel.KEY_Q):
            pyxel.quit()
 
        if pyxel.btn(pyxel.KEY_S):
            self.death = False
 
    def draw(self):
        # draw maze
        for x in range(height):
            for y in range(width):
                color = road_color if my_maze.map[x][y] is 0 else wall_color
                pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
        pyxel.rect(0, pixel, pixel, pixel, start_point_color)
        pyxel.rect((width - 1) * pixel, (height - 2) * pixel, pixel, pixel, end_point_color)
 
Vision()

Modify the update function and draw function to render the path

self.index = 0
self.route = [] # 用于记录待渲染的路径
self.step = 1  # 步长,数值越小速度越快,1:每次1格
self.color = start_point_color
self.bfs_route = my_maze.bfs_route()

Among them, index and step are used to control the rendering speed. In the draw function, index is incremented by 1 each time, and then the step is calculated to obtain the current real subscript real_index
function draw()

def draw(self):
    # draw maze
    for x in range(height):
        for y in range(width):
            color = road_color if my_maze.map[x][y] is 0 else wall_color
            pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
    pyxel.rect(0, pixel, pixel, pixel, start_point_color)
    pyxel.rect((width - 1) * pixel, (height - 2) * pixel, pixel, pixel, end_point_color)
 
    if self.index > 0:
        # draw route
        offset = pixel / 2
        for i in range(len(self.route) - 1):
            curr = self.route[i]
            next = self.route[i + 1]
            self.color = backtrack_color if curr in self.route[:i] and next in self.route[:i] else route_color
            pyxel.line(curr[0] + offset, (curr[1] + offset), next[0] + offset, next[1] + offset, self.color)
        pyxel.circ(self.route[-1][0] + 2, self.route[-1][1] + 2, 1, head_color)

Function update()

def update(self):
    if pyxel.btn(pyxel.KEY_ESCAPE):
        pyxel.quit()
 
    if pyxel.btn(pyxel.KEY_SPACE):
        self.death = False
 
    if not self.death:
        self.check_death()
        self.update_route()
 
def check_death(self):
    if self.dfs_model and len(self.route) == len(self.dfs_route) - 1:
        self.death = True
    elif not self.dfs_model and len(self.route) == len(self.bfs_route) - 1:
        self.death = True
 
def update_route(self):
    index = int(self.index / self.step)
    self.index += 1
    if index == len(self.route):  # move
        if self.dfs_model:
            self.route.append([pixel * self.dfs_route[index][0], pixel * self.dfs_route[index][1]])
        else:
            self.route.append([pixel * self.bfs_route[index][0], pixel * self.bfs_route[index][1]])
 
Vision()

Finally, the ESC key can exit, and the space key can execute the maze automatic path finding


to sum up

We use the depth-first algorithm to achieve the maze traversal. Compared with the A algorithm, the idea of ​​recursion may be acceptable, but it is indeed much slower than the A algorithm. Because you have to traverse every corner, you have time to use the A* algorithm. To improve the maze.
Attached display gif
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_44120833/article/details/111310293