实施递归回溯器以生成迷宫

时间:2020-03-04 18:09:17

标签: python recursive-backtracking

我正在尝试创建一个递归创建迷宫函数,但是,由于我不知道如何递归调用它并放置墙,我陷入了困境。

有人可以告诉我如何编辑代码以使其正常工作吗?谢谢

编辑:由于我没有添加迷宫类,所以我想将其添加以帮助查看整个代码。

class Maze:
    def __init__(self, Width, Height):
        assert Width>= 1 and Height>= 1

        self.Width= Width
        self.Height= Height
        self.board = np.zeros((Width, Height), dtype=WALL_TYPE)
        self.board.fill(EMPTY)

    def set_borders(self):
        self.board[0, :] = self.board[-1, :] = WALL
        self.board[:, 0] = self.board[:, -1] = WALL

    def is_wall(self, x, y):
        assert self.in_maze(x, y)
        return self.board[x][y] == WALL

    def set_wall(self, x, y):
        assert self.in_maze(x, y)
        self.board[x][y] = WALL

def create_maze(Width, Height, seed=None):
        Width = (Width // 2) * 2 + 1
        Height = (Height // 2) * 2 + 1

        if seed is not None:
            np.random.seed(seed)

        maze = Maze(Width, Height)
        maze.set_borders()

        x, y = rand(0, Width // 2) * 2, rand(0, Height // 2) * 2
        maze.set_wall(x, y)

        visited = []

        visited.append((x,y))

        while len(visited):
            start = visited[-1]

            if maze.in_maze(x - 2, y):
                visited.append((x - 2, y))

            if maze.in_maze(x + 2, y):
                visited.append((x + 2, y))

            if maze.in_maze(x, y - 2):
                visited.append((x, y - 2))

            if maze.in_maze(x, y + 2):
                visited.append((x, y + 2))

            visited.remove(start) # This goes somewhere but I don't know where

            if not maze.is_wall(x,y):
                maze.set_wall(x,y)


        create_maze() #recurse?

1 个答案:

答案 0 :(得分:6)

最初的问题

好的,因此从您开始通常不会像这样设置递归函数。由于您需要一遍又一遍地调用相同的函数,因此您不想在每次调用时都重新创建迷宫对象。您要执行递归函数调用的所有设置和计算外部,并且仅递归地执行一项非常具体的工作。

设置

我假设您为迷宫课程设置的有点像这样:

import random

class Maze:
    def __init__(self, width, height):

        self.width = width // 2 * 2 + 1
        self.height = height // 2 * 2 + 1

        # this creates a 2d-array for your maze data (False: path, True: wall)
        self.cells = [
                      [True for x in range(self.width)] 
                      for y in range(self.height)
                     ]

    def set_path(self, x, y):
        self.cells[y][x] = False

    def set_wall(self, x, y):
        self.cells[y][x] = True

想想我们的迷宫

好的,现在我可以开始递归生成本身了。现在,我不再采用添加墙壁的方法,而是用墙壁填充整个迷宫,然后“挖掘”自己的路径。

在迷宫中,即使我们将其视为具有墙壁和路径的简单的开/关2d网格,它也有助于将其描绘成一系列节点(结)和它们之间的链接。我可以看到您开始以确保您的迷宫宽度和高度为(Width // 2) * 2 + 1的奇数的方式开始实施此操作,并且您只选择了偶数单元格(尽管我不知道该做什么)

下面是一个简短的图表,可直观显示我的意思:

Maze with nodes

每个红色圆圈是一个节点,并且您可以看到每个单个奇数图块都包含一个节点 始终是路径图块。我们将自动假设每个奇数瓦将包含一条路径(并因此包含一个节点)。这意味着在生成迷宫时,当我们“挖”通孔时,我们将一次移动两个 个单元,以便在节点(灰色单元)之间创建链接。 / p>

算法

我将要实现的算法的实质如下:

  1. 向随机方向移动,“挖掘”前进的方向,跟踪我去过的地方
  2. 重复直到我死胡同
  3. 在我去过的地方“回溯”,直到找到一条至少有一条可行路径的路径。转到步骤1


以下是相同的步骤,

  1. 向随机方向移动,跟踪我去过的地方

    1.1。我们环顾每个方向,看看我们的移动选项在哪里

    1.2。选择有有效路径的随机方向

    1.3。移到新节点(请记住,我们要移动两个单元格)

  2. 重复直到死胡同

    2.1。继续重复第一步中的过程

    2.2。如果在第一步中没有找到任何路径选项,则说明已经走到了尽头

  3. 回溯我去过的地方

    3.1。跟随您的足迹(回溯)遍历先前访问的节点

    3.2。重复直到发现一个节点具有至少一个未访问路径的尝试

    3.3。转到第一步(例如,开始沿新路径随机走动)


现在要使用递归函数来实现这些步骤。我们到新节点的每个步骤(通过移动两个单元格)将是具有新x-y坐标的新函数调用。这是相同的步骤,但是是递归的:

  1. 在随机方向上移动以跟踪我去过的地方

    1.1。随机选择一个方向并检查它是否尚未被访问(例如,如果我们已经走过它,我们将已经“挖过”,所以它将是一条路径)。因此,选择墙壁的任何方向(例如,未访问的人)

    1.2。现在,朝该方向移动两个单元格(但请记住,将两个节点之间的节点单元格设置为路径,否则您将跳过墙)。记住,当“移动”到新单元格时,我们将使用新节点的x-y坐标再次调用该函数

  2. 重复直到死胡同

    2.1。如果在第一步中发现所有方向都包含路径(例如,您已经访问了该节点上的每个方向),则需要回溯

    2.2。现在回溯,我们要退出当前函数调用。这意味着我们正在将向后移至先前将我们移至当前节点的上一个函数

  3. 回溯直到找到路径

    3.1。退出函数调用后,您现在已返回到具有先前x-y坐标的上一个节点。现在,您进入第一步,寻找可能的方向,如果没有,则进入第二步并进一步回溯

代码

好的,因此,在完成所有理论和计划后,我们现在可以将设计实现为代码了。

我将创建递归函数作为Maze类的方法,如下所示:

class Maze:
    # ...
    # constructor and other methods go here
    # ...

    def create_maze(self, x, y):
        # our recursive function goes here

这意味着要完全创建迷宫,我们将其称为maze.create_maze(1, 1)(将1, 1替换为您的起始坐标)。

因此,让我们逐步完成之前设计的每个步骤,然后将它们转换为代码。

1.1选择一个随机可行的方向

def create_maze(self, x, y):
    # set the current cell to a path, so that we don't return here later
    self.set_path(self, x, y)

    # we create a list of directions (in a random order) we can try
    all_directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
    random.shuffle(all_directions)

    # we keep trying the next direction in the list, until we have no directions left
    while len(all_directions) > 0:

        # we remove and return the last item in our directions list
        direction_to_try = all_directions.pop()

        # calculate the new node's coordinates using our random direction.
        # we *2 as we are moving two cells in each direction to the next node
        node_x = x + (direction_to_try[0] * 2)
        node_y = y + (direction_to_try[1] * 2)

        # check if the test node is a wall (eg it hasn't been visited)
        if self.is_wall(node_x, node_y):
            # success code: we have found a path
    # failure code: we find no paths

# a function to return if the current cell is a wall, and if the cell is within the maze bounds
def is_wall(self, x, y):
    # checks if the coordinates are within the maze grid
    if 0 <= x < self.width and 0 <= y < self.height:
        # if they are, then we can check if the cell is a wall
        return self.cells[y][x]
    # if the coordinates are not within the maze bounds, we don't want to go there
    else:
        return False

1.2。沿该方向移动两个单元格,然后创建链接路径单元格

所以现在的问题是,一旦找到可行的路径选项,我们该怎么办?答案:我们将链接单元格变成一条路径(这样我们就不会越过墙到达新节点了),并向新方向移动两个单元格。

它变成:

# success code: we have found a path

# set our linking cell (between the two nodes we're moving from/to) to a path
link_cell_x = x + direction_to_try[0]
link_cell_y = y + direction_to_try[1]
self.set_path(link_cell_x, link_cell_y)

# "move" to our new node. remember we are calling the function every
#  time we move, so we call it again but with the updated x and y coordinates
self.create_maze(node_x, node_y)

然后将我们的成功代码集成到我们的create_maze函数中,它变为:

def create_maze(self, x, y):
    self.set_path(x, y)
    all_directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
    random.shuffle(all_directions)
    while len(all_directions) > 0:
        direction_to_try = all_directions.pop()
        node_x = x + (direction_to_try[0] * 2)
        node_y = y + (direction_to_try[1] * 2)
        if self.is_wall(node_x, node_y):
            # success code: we have found a path

            # set our linking cell (between the two nodes we're moving from/to) to a path
            link_cell_x = x + direction_to_try[0]
            link_cell_y = y + direction_to_try[1]
            self.set_path(link_cell_x, link_cell_y)

            # "move" to our new node. remember we are calling the function every
            #  time we move, so we call it again but with the updated x and y coordinates
            self.create_maze(node_x, node_y)
    # failure code: we find no paths

2.1。如果我们所有的方向都包含路径(它们已经被访问过),那么我们需要回溯

2.2。要回溯,我们退出函数调用,该调用将我们带到上一个节点

结束函数的一种简单方法是调用return。这样可以停止在该函数中运行任何其他代码,并返回到先前调用它的函数。

# failure code: we find no paths

return

并将其添加到我们的递归函数中,它现在变为:

def create_maze(self, x, y):
    self.set_path(x, y)
    all_directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
    random.shuffle(all_directions)
    while len(all_directions) > 0:
        direction_to_try = all_directions.pop()
        node_x = x + (direction_to_try[0] * 2)
        node_y = y + (direction_to_try[1] * 2)
        if self.is_wall(node_x, node_y):
            link_cell_x = x + direction_to_try[0]
            link_cell_y = y + direction_to_try[1]
            self.set_path(link_cell_x, link_cell_y)
            self.create_maze(node_x, node_y)
    # failure code: we find no paths

   return

3.1。再次尝试执行第一步,如果不执行第二步

从本质上讲,我们已经编程了一个功能完备的迷宫生成程序,使用(尽我所能)一种相当不错的递归方法。一旦功能陷入僵局,它将返回并回溯到上一个功能,并继续执行该过程,直到该功能陷入僵局为止。

最终代码

import random

class Maze:
    def __init__(self, width, height):

        self.width = width // 2 * 2 + 1
        self.height = height // 2 * 2 + 1

        # this creates a 2d-array for your maze data (False: path, True: wall)
        self.cells = [
                      [True for x in range(self.width)] 
                      for y in range(self.height)
                     ]

    def set_path(self, x, y):
        self.cells[y][x] = False

    def set_wall(self, x, y):
        self.cells[y][x] = True

    # a function to return if the current cell is a wall,
    #  and if the cell is within the maze bounds
    def is_wall(self, x, y):
        # checks if the coordinates are within the maze grid
        if 0 <= x < self.width and 0 <= y < self.height:
            # if they are, then we can check if the cell is a wall
            return self.cells[y][x]
        # if the coordinates are not within the maze bounds, we don't want to go there
        else:
            return False

    def create_maze(self, x, y):
        # set the current cell to a path, so that we don't return here later
        self.set_path(x, y)
        # we create a list of directions (in a random order) we can try
        all_directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
        random.shuffle(all_directions)

        # we keep trying the next direction in the list, until we have no directions left
        while len(all_directions) > 0:

            # we remove and return the last item in our directions list
            direction_to_try = all_directions.pop()

            # calculate the new node's coordinates using our random direction.
            # we *2 as we are moving two cells in each direction to the next node
            node_x = x + (direction_to_try[0] * 2)
            node_y = y + (direction_to_try[1] * 2)

            # check if the test node is a wall (eg it hasn't been visited)
            if self.is_wall(node_x, node_y):
                # success code: we have found a path

                # set our linking cell (between the two nodes we're moving from/to) to a path
                link_cell_x = x + direction_to_try[0]
                link_cell_y = y + direction_to_try[1]
                self.set_path(link_cell_x, link_cell_y)

                # "move" to our new node. remember we are calling the function every
                #  time we move, so we call it again but with the updated x and y coordinates
                self.create_maze(node_x, node_y)
        return

然后我们去!生成迷宫的递归算法。抱歉,我没有使用更多的代码,但是由于我不太确定您要使用的代码,我认为至少可以通过向您展示一些有效的代码来提供帮助。

我们可以快速添加的最后一件事是打印功能,以便我们可以将迷宫打印到屏幕上。通过使用“双下划线方法”(dundermethod)__repr__ 更新: ,请使用方法名称__str__,请参见注释),我们可以覆盖print功能。以下代码生成一个字符串,表示我们新生成的迷宫:

def __repr__(self):
    string = ""
    conv = {
        True: "██",
        False: "  "
    }
    for y in range(self.height):
        for x in range(self.width):
            string += conv[self.cells[y][x]]
        string += "\n"
    return string

通过将其放置在迷宫类中,我们现在可以执行以下操作,甚至可以从偶数单元开始!

>>> maze.create_maze(1, 1)
>>> print(maze)
██████████████████
██      ██      ██
██████  ██  ██  ██
██  ██  ██  ██  ██
██  ██  ██████  ██
██  ██          ██
██  ██████████  ██
██              ██
██████████████████
>>> maze.create_maze(4, 4)
          ██      
  ██████  ██████  
  ██              
  ██████████████  
  ██      ██      
  ██  ██████████  
  ██          ██  
  ██████████  ██  
              ██  

希望有帮助!