文章目录
概要
- 迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
3) 要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
Maze生成
采用深度优先遍历,0为墙,1为路。设一个长宽为2*x+1,2*y+1的二维数组,并将他们全设为0。在[x][y]设为1。
随机选一个路为起点,标为已访问。随机访问当前路的上下左右的路,如果未被访问,标记为已访问,并且打通之间的墙壁。如果已访问,或者超界则什么都不做。如果上下左右全都访问过了则随机再选一个已访问的路为新起点重复以上操作直到所有路都访问过后返回返回值。
package com.company;
import java.util.Random;
class Maze {
// 初始化一个地图,产生的二维数组大小实际为(2width+1) * (2height+1)
private static int width;
private static int height;
public static int[][] map;// 存放迷宫的数组
private static int r;
private static int c;
Maze(int r0, int c0) {
width = r0;
height = c0;
r = 2 * width + 1;
c = 2 * height + 1;
map = new int[r][c];
}
public static int[][] Init() {
for (int i = 0; i < r; i++) // 将所有格子都设为墙
for (int j = 0; j < c; j++)
map[i][j] = 0;// 0 为墙 1为路
// 中间格子放为1
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
map[2 * i + 1][2 * j + 1] = 1;// 0 为墙 1为路
rdPrime();
return map;
}
public static void rdPrime() {
// ok存放已访问队列,not存放没有访问队列
int[] ok, not;
int sum = width * height;
int count = 0;// 记录访问过点的数量
ok = new int[sum];
not = new int[sum];
// 0左 1右 3上 2下
int[] offR = {-1, 1, 0, 0};
int[] offC = {0, 0, 1, -1};
// 四个方向的偏移 左右上下
int[] offS = {-1, 1, width, -width}; // 向上向下移动都是变化一行
// 初始化 ok中0代表未访问,not中0代表未访问
for (int i = 0; i < sum; i++) {
ok[i] =0;
not[i] = 0;
}
// 起点
Random rd = new Random();
ok[0] = rd.nextInt(sum);// 起始点
int pos = ok[0];
// 第一个点存入
not[pos] = 1;//设为已访问
while (count < sum) {
// 取出现在的点
int x = pos % width;
int y = pos / width;// 该点的坐标
int offpos = -1;
int m = 0;
while (++m < 4) {
// 随机访问最近的点
int point = rd.nextInt(4); // 0-3
int repos;
int movex, movey;
// 计算出移动方位
repos = pos + offS[point];// 移动后的下标
movex = x + offR[point];// 移动后的方位
movey = y + offC[point];
if (movey >= 0 && movex >= 0 && movex < width && movey < height && repos >= 0 && repos < sum
&& not[repos] != 1) {
not[repos] = 1;// 把该点标记为已访问
ok[++count] = repos;//
pos = repos;// 把该点作为起点
offpos = point;
// 打通墙壁
map[2 * x + 1 + offR[point]][2 * y + 1 + offC[point]] = 1;
break;//回到while (count < sum)
} else {
if (count == sum - 1)
return;
}
}
if (offpos < 0) {//初始值为-1,如果上下左右有未访问的路则大于0,小于0则说明上下左右都访问过了。
pos = ok[rd.nextInt(count + 1)];
}
}
}
}
A*算法
首先定义open list和close list,open list存放已知但还没有探索过的区块,close list存放已经探索过的区块。
最短路径肯定涉及到距离度量,在A*算法中距离分为两个部分:G 和H,总距离F=G + H。
为了方便计算和寻路,我们为每个节点设置一个父节点。父节点可以这样理解,在目前已知条件下,存在一条从起点到当前指定方格的最优路径,而父亲节点就是这条路径上的指定方格的上一个节点,计算当前方格的 G 值的方法就是找出其父亲的 G 值,然后按在父亲节点直线方向方向加上 10。
H为从当前节点到终点的估计距离,是对剩余距离的估算值,而不是实际值。它是一种理想值,忽略了障碍物的影响。
整个算法流程为:
- 把起点加入open list,重复以下流程
- 如果open list为空,寻路失败,找不到到达终点的路径。遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
//寻找最小F方法 public Node findMinFNodeInOpenList() { Node tempNode = openList.get(0); for (Node node : openList) { if (node.F < tempNode.F) { tempNode = node; } } return tempNode; }
- 把这个节点移到 close list
- 对当前方格的 4个相邻方格的每一个方格
//遍历当前节点上下左右四个邻居的方法, public ArrayList<Node> findNeighborNodes(Node currentNode) { ArrayList<Node> arrayList = new ArrayList<Node>(); // 只考虑上下左右,不考虑斜对角 int topX = currentNode.x; int topY = currentNode.y - 1; if (canReach(topX, topY) && !exists(closeList, topX, topY)) { arrayList.add(new Node(topX, topY)); } int bottomX = currentNode.x; int bottomY = currentNode.y + 1; if (canReach(bottomX, bottomY) && !exists(closeList, bottomX, bottomY)) { arrayList.add(new Node(bottomX, bottomY)); } int leftX = currentNode.x - 1; int leftY = currentNode.y; if (canReach(leftX, leftY) && !exists(closeList, leftX, leftY)) { arrayList.add(new Node(leftX, leftY)); } int rightX = currentNode.x + 1; int rightY = currentNode.y; if (canReach(rightX, rightY) && !exists(closeList, rightX, rightY)) { arrayList.add(new Node(rightX, rightY)); } return arrayList; } //判断此处坐标是否可达,若超界或者是墙则不可达 public boolean canReach(int x, int y) { if (x >=0 && x < NODES.length && y >=0 && y < NODES.length && NODES[x][y]==1) { return true; } return false; } //下面两个是exist方法的重载,判断不同参数情况时节点是否在列表里 public static boolean exists(List<Node> nodes, Node node) { for (Node n : nodes) { if ((n.x == node.x) && (n.y == node.y)) { return true; } } return false; } public static boolean exists(List<Node> nodes, int x, int y) { for (Node n : nodes) { if ((n.x == x) && (n.y == y)) { return true; } } return false; }
- 如果它是不可抵达的或者它在 close list 中,忽略。
- 如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,计算该方格的 F , G 和 H 值。
- 如果它已经在 open list 中,检查通过当前方格到达该方格是否代价更小,即G值更小。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。
- 如果终点加入到了open list中,此时路径已经找到,从终点开始,每个方格沿着父节点移动直至起点,这就是最优路径。
- 如果open list为空,寻路失败,找不到到达终点的路径。遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
由算法可以看出通过总距离F选出当前处理节点,通过G来更新路径(改变节点的父节点就是改变了路径)。
//A*寻路过程
public Node findPath(Node startNode, Node endNode) {
openList.add(startNode);// 把起点加入 open list
while (openList.size() > 0) {
Node currentNode = findMinFNodeInOpenList();// 遍历 open list ,查找 F值最小的节点,把它作为当前要处理的节点
openList.remove(currentNode);// 从open list中移除
closeList.add(currentNode);// 把这个节点移到 close list
ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);
for (Node node : neighborNodes) {//遍历四个邻居
if (exists(openList, node)) {
foundPoint(currentNode, node);
} else {
notFoundPoint(currentNode, endNode, node);
}
}
if (find(openList, endNode) != null) {
return find(openList, endNode);//找到终点了并返回
}
}
return find(openList, endNode);
}