Make a maze game with Python

I believe everyone has played the game of mazes. For simple mazes, we can see the pathways at a glance, but for complex mazes, it may take a long time to search carefully, or even spend several days, and then may have to look at the entrance and exit respectively. Can only find the access, and may not even find the access.

Although the problem of walking the maze is more complicated for us humans, it is a very simple problem for computers. Why do you say that, because it seems complicated but there are rules to follow.

We can do this by carrying a long rope and walking from the entrance. If there is a fork in the road, we will go to the leftmost fork until we reach a dead end or find a way out. If it is a dead end, then retreat to the last fork in the road, we call it fork A,

At this time, enter the second fork on the left, and repeat the steps of the first fork after entering the second fork until you find a way out or retreat from a dead end. After going through all the forks of the fork, walk back along the rope before finding the way out, to the intersection B before the fork A, and repeat the above steps.

I don’t know if you have discovered that this is actually a process of constant recursion, and this is what computers are good at.

The above algorithm for walking the maze is the depth-first traversal algorithm we often say, and the opposite is the breadth-first traversal algorithm. With the theoretical foundation, let's try to use the program to implement a small program that walks the maze.

Maze
generation There are many algorithms for generating mazes, the commonly used ones are recursive backtracking, recursive segmentation and random Prim algorithm. Today we are using the last algorithm.

The main steps of the algorithm are as follows:
1. The rows and columns of the maze must be odd
2. The intersection of the odd rows and the odd columns is the road, the rest of the points are walls, and the maze is surrounded by walls
3. Select a cell that is the road ( In this example, select [1,1]), and then put its adjacent wall into the list wall
4. When there is a wall in the list wall:
4.1. Randomly select a wall from the list, if the wall separates two units Only one cell of the cell has been visited
4.1.1, then remove the wall from the list, and open the wall at the same time
4.1.2, mark the cell as visited
4.1.3, set the neighbor of the unvisited cell Add the wall to the list wall
4.2. If the cells on both sides of the wall have been visited, remove the wall from the list

We define a Maze class, using a two-dimensional array to represent the maze map, where 1 represents the wall and 0 represents the road, and 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 = widthself.height = heightself.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 # entrance self.map[height-2][width-1] = 0 # exit self.visited = []# right up left downself.dx = [1, 0, -1, 0]self.dy = [0, -1, 0, 1] The
next step is to generate the main function of the maze.

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)
There are two main functions in this function get_neighbor_road(point) and deal_with_not_visited() The neighbor node of the incoming coordinate point point, the return value is a two-dimensional array, and the latter deal_with_not_visited() function processes the logic of step 4.1.

Because the Prim random algorithm randomly selects all the cells in the list at random, the probability of the newly added cell and the old added cell being selected is the same, so it has more branches and the generated maze is more complicated. It's more difficult, and of course it looks more natural. The generated maze.
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 1 , 1, 1, 1, 0, 1, 1, 1, 1, 1]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 1, 1, 0 , 1, 0, 1, 0, 1, 0, 1]
[1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0 , 1, 1, 1, 1, 1]
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
[1, 0, 1, 0, 1, 1, 1, 0 , 1, 0, 1]
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1 , 1]

Get out of the labyrinth and
get the map of the labyrinth, then just follow the idea of ​​our first article to walk the labyrinth. The main function logic is as follows:

def dfs(self, x, y, path, visited=[]):# outOfIndexif self.is_out_of_index(x, y):return False# visited or is wallif [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# recursivefor 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])

Obviously, this is a typical recursive program. When the node coordinates are out of bounds, the node has been visited, or the node is a wall, return directly, because the node is definitely not part of the path we are looking for, otherwise the node will be added to the set of visited nodes and paths in.

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.

Let's take a look at the path results obtained by our dfs:

[[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [ 8, 1], [9, 1], [9, 1], [8, 1], [7, 1], [6, 1], [5, 1], [5, 2], [5, 3], [6, 3], [7, 3], [8, 3], [9, 3], [9, 4], [9, 5], [9, 5], [9, 4] , [9, 3], [8, 3], [7, 3], [7, 4], [7, 5], [7, 5], [7, 4], [7, 3], [ 6, 3], [5, 3], [4, 3], [3, 3], [2, 3], [1, 3], [1, 3], [2, 3], [3, 3], [3, 4], [3, 5], [2, 5], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9] , [1, 9], [1, 8], [1, 7], [1, 6], [1, 5], [2, 5], [3, 5], [3, 6], [ 3, 7], [3, 8], [3, 9], [3, 9], [3, 8], [3, 7], [3, 6], [3, 5], [3, 4], [3, 3], [4, 3], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [6, 7] , [7, 7], [8, 7], [9, 7], [9, 8], [9, 9], [10, 9]]
Visualization has the
maze map and the pathway path, the rest of the work It is to render these coordinate points. The visualization library we use today is pyxel, which is a Python library for writing pixel-level games.

Of course, you need to install this library before using it.

Win users can install it directly with the pip install -U pyxel command.

Mac users use the following command to install:

brew install python3 gcc sdl2 sdl2_image gifsicle
pip3 install -U pyxel
Let’s look at a simple Demo first.

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

import pyxelclass App: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.widthdef draw(self):
pyxel.cls(0)
pyxel.rect(self.x, 0, 8, 8, 9)


The execution logic of the App() class App 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.

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

width, height = 37, 21
my_maze = Maze(width, height)
my_maze.generate()class App: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 = Falsedef draw(self):# draw mazefor 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)

App()
looks okay. I used 37 and 21 pixel grids to generate the width and height respectively, so the generated maze is not very complicated. If there are many pixels, it will be complicated.

Next, we need to modify the update function and the draw function to render the path. In order to facilitate the operation, we have added several attributes to the init function.

self.index = 0 self.route = [] # Used to record the path to be rendered self.step = 1 # Step length, the smaller the value, the faster the speed, 1: one grid each time; 10: 1/10 grid each time self .color = start_point_colorself.bfs_route = my_maze.bfs_route()
where index and step are used to control the rendering speed. In the draw function, index is incremented by 1 each time, and then the remainder of step is calculated to get the current real subscript real_index. In other words, real_index will increase by one every time the index increases by step, and the rendering path will take one step forward.

def draw(self):# draw mazefor 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 / 2for 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)
def update(self):if pyxel.btn(pyxel.KEY_Q):
pyxel.quit()if pyxel.btn(pyxel.KEY_S):self.death = Falseif 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 = Truedef update_route(self):
index = int(self.index / self.step)self.index += 1if index == len(self.route): # moveif 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]])

App() So
far, we have completely realized from the maze generation, to finding the path, and then to the path visualization. Call the main function App() directly and press the S keyboard to start the game

Summary
Today we have used the depth-first algorithm to traverse the maze. For novices, the idea of ​​recursion may be more difficult to understand, but this is in line with computer thinking. With the deepening of experience, the understanding will become more and more profound.

Secondly, we use the pyxel library to realize the path visualization. The difficulty lies in the calculation and update of the coordinates. The details are more and cumbersome. Of course, readers can also use other libraries or directly use the web page to achieve it.
Article source: web game http://www.hp91.cn/web game

Guess you like

Origin blog.51cto.com/14621511/2679085