一、实验内容
1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
3)要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
编程语言:java
开发工具:idea
深度优先迷宫生成算法,这个算法可以表示为
1.将起点作为当前迷宫单元并标记为已访问
2.当还存在未标记的迷宫单元,进行循环
(1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元
1.随机选择一个未访问的相邻迷宫单元
2.将当前迷宫单元入栈
3.移除当前迷宫单元与相邻迷宫单元的墙
4.标记相邻迷宫单元并用它作为当前迷宫单元
( 2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
1.栈顶的迷宫单元出栈
2.令其成为当前迷宫单元
初始化地图:
System.out.println("初始化地图:"); for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { LabId[i][j] = 0;// 将所有格子都设为墙, 0 为墙 1为路 if (i % 2 == 1 && j % 2 == 1)// 将奇数行奇数列设为路,1为路,0为墙 LabId[i][j] = 1; System.out.print(LabId[i][j] + " ");// 打印初始化地图,在控制台输出查看 } System.out.println(); }
深度优先构建迷宫的思想就是,每次把新找到的未访问迷宫单元作为优先,寻找其相邻的未访问过的迷宫单元,直到所有的单元都被访问到。通俗的说,就是从起点开始随机走,走不通了就返回上一步,从下一个能走的地方再开始随机走。一般来说,深度优先法生成的迷宫极度扭曲,有着一条明显的主路。
使用DFS算法,借助递归思想访问某一顶点v,找v点附近且未被访问的点w,在找w附近未被访问的点(循环...),直到没有继续能找下去的点,依次退回最近被访问的点,如果还有该顶点的其他邻居没有被访问,就从邻居点开始继续搜索,把相邻的部分格子打通。
public void DFS(int[] LabG, int v) { LabG[v] = 1;// 访问顶点 int[] neighbor = { v + row, v - row, v - 1, v + 1 };// 该点的四个邻居 上下左右 int[] offR = { 0, 0, -1, 1 }, offC = { 1, -1, 0, 0 };// Row上个方向的偏移 Column上各方向的偏移,上下左右 int[] tag = { -1, -1, -1, -1 };// 记录打通位置 int n = 0;// 打通的次数 while (n < 4) { // 上下左右四个方向都遍历, int i = rand.nextInt(4);// 随机打通一个方向 if (tag[i] == 1) continue;// 进入下一轮循环 tag[i] = 1;// 打通墙,设为1 n++; int w = neighbor[i];// 定义一个该方向上的邻居 if (w > LabG.length - 1 || w < 0) continue; // w不存在,即该方向上没有邻居 // 取出现在的v点的位置 int x = v % row; int y = v / row; // 遍历到四个边界时再往边界方向就没有邻居了,进入下一轮循环 if (i == 0 && y == column - 1) continue;// 上方向 if (i == 1 && y == 0) continue;// 下方向 if (i == 2 && x == 0) continue;// 左方向 if (i == 3 && x == row - 1) continue;// 右方向 // 如果该点有未访问的邻居,则把该点与其邻居间的墙打通,即相邻的格子中间的位置放1 if (LabG[w] == 0) { LabId[2 * x + 1 + offR[i]][2 * y + 1 + offC[i]] = 1; DFS(LabG, w);// 递归 } }
A*搜寻算法
A*搜寻算法,俗称A星算法,作为启发式搜索算法中的一种,这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。
A*算法最为核心的部分,就在于它的一个估值函数的设计上:
f(n)=g(n)+h(n)
其中f(n)是每个可能试探点的估值,它有两部分组成:
一部分,为g(n),它表示从起始搜索点到当前点的代价(通常用某结点在搜索树中的深度来表示)。
另一部分,即h(n),它表示启发式搜索中最为重要的一部分,即当前结点到目标结点的估值,
h(n)设计的好坏,直接影响着具有此种启发式函数的启发式算法的是否能称为A*算法。
一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是: 当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。 对于一个搜索问题,显然,条件1,2,3都是很容易满足的,而条件4: h(n)<=h*(n)是需要精心设计的,由于h*(n)显然是无法知道的,所以,一个满足条件4的启发策略h(n)就来的难能可贵了。 不过,对于图的最优路径搜索和八数码问题,有些相关策略h(n)不仅很好理解,而且已经在理论上证明是满足条件4的,从而为这个算法的推广起到了决定性的作用。 且h(n)距离h*(n)的呈度不能过大,否则h(n)就没有过强的区分能力,算法效率并不会很高。对一个好的h(n)的评价是:h(n)在h*(n)的下界之下,并且尽量接近h*(n)。 A*算法的寻路详细步骤 1. 把起点加入 open list 。 2. 重复如下过程: a. 遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。 b. 把这个节点移到 close list 。 c. 对当前方格的 8 个相邻方格的每一个方格? ◆ 如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。 ◆ 如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。 ◆ 如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。 d. 停止,当你 ◆ 把终点加入到了 open list 中,此时路径已经找到了,或者 ◆ 查找终点失败,并且 open list 是空的,此时没有路径。 3. 保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。 计算G值: 计算H值:
1、搜索树上存在着从起始点到终了点的最优路径。
2、问题域是有限的。
3、所有结点的子结点的搜索代价值>0。
4、h(n)=private int calcG(Node start, Node node) {
int G = STEP;
int parentG = node.parent != null ? node.parent.G : 0;
return G + parentG;
}
private int calcH(Node end, Node node) {
int step = Math.abs(node.x - end.x) + Math.abs(node.y - end.y);
return step * STEP;
}