一、问题需求:
迷宫寻路游戏中,有一些小怪物要攻击主角,现在希望你给这些小怪物加上聪 明的AI (Artificial Intelligence,人工智能),让它们可以自动绕过迷宫中的障碍物,寻找到主角的所在。
A星寻路算法 (A*search algorithm),是一种用于寻找有效路径的算法。
迷宫游戏的场景通常都是由小方格组成的。假设我们有一个6x7大小的迷宫
绿色的格子是起点,红色的格子是终点,中间的3个紫色格子是一堵墙。
A星寻路算法通过计算和量化行走的各个方向的代价,来选择最优路径。
公式:F = G + H
G:从起点走到当前格子的成本,也就是已经花费了多少步。H:在不考虑障碍的情况下,从当前格子走到目标格子的距离,也就是离目标还有多远。
F:G和H的综合评估,也就是从起点到达当前格子,再从当前格子到达目标格子的总步数。
每个方格都标上了 F , G , H 的值,就像起点右边的方格那样,左上角是 F ,左下角是 G ,右下角是 H 。
二、操作步骤:
两个集合如下:
open_list――可到达的格子
close_list—―已到达的格子
第1轮寻路历程
第1步:把起点放入open_list可到达格子的集合。
open_list:grid(2,1)
close_list:
第2步:找出open_list中F值最小的方格作为当前方格。虽然我们没有直接计算起点方格的F值,但此时open_list中只有唯一的方格grid (2,1),把当前格子移出open_list,放入close_list。代表这个格子已到达并检查过了。
open_list:
close_list:grid(2,1)
第3步:找出当前方格(刚刚检查过的格子)上、下、左、右所有可到达的格子,看它们是否在open_list或close_list当中。如果不在,则将它们加入open_list,计算出相应的G、H、F值,并把当前格子作为它们的“父节点”。
我们需要一次又一次重复刚才的第 2步和第3步,直到找到终点为止。
第2轮寻路历程
第1步,找出open_list中F值最小的方格,即方格grid (2,2),将它作为当前方格,并把当前方格移出open_list,放入close_list。代表这个格子已到达并检查过了。
第2步,找出当前方格上、下、左、右所有可到达的格子,看它们是否在open_list或 close_list当中。如果不在,则将它们加入open_list,计算出相应的G、H、F值,并把当前格子作为它们的“父节点”。 为什么这一次open_list只增加了2个新格子呢?因为grid (2,3)是墙壁,自然不用考虑,而grid (2,1)在close_list中,说明已经检查过了,也不用考虑。
第3轮寻路历程
第1步,找出open_list中F值最小的方格。由于此时有多个方格的F值相等,任意选择一个即可,如将grid (2,3)作为当前方格,并把当前方格移出open_list,放入close_list。代表这个格子已到达并检查过了。
第2步,找出当前方格上、下、左、右所有可到达的格子,看它们是否在open_list当中。如果不在,则将它们加入open_list,计算出相应的G、H、F值,并把当前格子作为它们的“父节点”。
剩下的操作就是以前面的方式继续迭代,直到open_list中出现终点方格为止。
''''
pip install colorama
'''
from colorama import init, Fore, Back, Style
def a_star_search(start, end):
# 初始化
# 待访问的格子
open_list = []
# 已访问的格子
close_list = []
# 把起点加入open_list
open_list.append(start)
# 主循环,每一轮检查一个当前方格节点
while len(open_list) > 0:
# 在open_list中查找 F值最小的节点作为当前方格节点
current_grid = find_min_gird(open_list)
# 当前方格节点从openList中移除
open_list.remove(current_grid)
# 当前方格节点进入 closeList
close_list.append(current_grid)
# 找到所有邻近节点
neighbors = find_neighbors(current_grid, open_list, close_list)
for grid in neighbors:
# 邻近节点不在openList中,标记父亲、G、H、F,并放入openList
grid.init_grid(current_grid, end)
open_list.append(grid)
# 如果终点在openList中,直接返回终点格子
for grid in open_list:
if (grid.x == end.x) and (grid.y == end.y):
return grid
# openList用尽,仍然找不到终点,说明终点不可到达,返回空
return None
def find_min_gird(open_list=[]):
# 找到openList中F值最小的节点
return min(open_list, key=lambda x: x.f)
def find_neighbors(grid, open_list=[], close_list=[]):
grid_list = []
# 上下左右四个方向
if is_valid_grid(grid.x, grid.y-1, open_list, close_list):
grid_list.append(Grid(grid.x, grid.y-1))
if is_valid_grid(grid.x, grid.y+1, open_list, close_list):
grid_list.append(Grid(grid.x, grid.y+1))
if is_valid_grid(grid.x-1, grid.y, open_list, close_list):
grid_list.append(Grid(grid.x-1, grid.y))
if is_valid_grid(grid.x+1, grid.y, open_list, close_list):
grid_list.append(Grid(grid.x+1, grid.y))
return grid_list
def is_valid_grid(x, y, open_list=[], close_list=[]):
# 是否超过边界
if x < 0 or x >= len(MAZE) or y < 0 or y >= len(MAZE[0]):
return False
# 是否有障碍物
if MAZE[x][y] == 1:
return False
# 是否已经在open_list中
if contain_grid(open_list, x, y):
return False
# 是否已经在closeList中
if contain_grid(close_list, x, y):
return False
return True
def contain_grid(grids, x, y):
for grid in grids:
if (grid.x == x) and (grid.y == y):
return True
return False
class Grid:
def __init__(self, x, y):
self.x = x
self.y = y
self.f = 0
self.g = 0
self.h = 0
self.parent = None
def init_grid(self, parent, end):
self.parent = parent
self.g = parent.g + 1
self.h = abs(self.x - end.x) + abs(self.y - end.y)
self.f = self.g + self.h
# 迷宫地图
MAZE = [
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]
]
if __name__ == '__main__':
# 设置起点和终点
start_grid = Grid(2, 1)
# end_grid = Grid(2, 5)
end_grid = Grid(3, 4)
# 搜索迷宫终点
result_grid = a_star_search(start_grid, end_grid)
# 回溯迷宫路径
path = []
while result_grid is not None:
path.append(Grid(result_grid.x, result_grid.y))
result_grid = result_grid.parent
# 输出迷宫和路径,路径用星号表示
for i in range(0, len(MAZE)):
for j in range(0, len(MAZE[0])):
if contain_grid(path, i, j):
print(Fore.RED + "*, "+Fore.RESET, end='')
else:
print(str(MAZE[i][j]) + ", ", end='')
print()