package maze;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;
class Node {
public int x,y;
public Node parent;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
//F为估价函数
public int F;
//G(当前代价)代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销
public int G;
//H(预估代价)表示待检测结点到目标节点B的估计移动开销(利用曼哈顿距离)
public int H;
//计算估价函数
public void calcF() {
this.F = this.G + this.H;
}
}
//生成地图用到变量
public class MyMaze extends JPanel implements KeyListener {
//代表墙
final static int wall =0;
//代表空地
final static int road =1;
//迷宫长度(需要满足奇数行奇数列均为路,首行和尾行均是墙所以长度需要比宽度多1)
static int num = 23;
//迷宫宽度
int width = 22;
//迷宫
static int [][] mMap;
//用来标记某一格是否被访问过
boolean[][] visit;
//开始节点
Node start = new Node(1,1);
//结束节点
Node end = new Node(num-2,num-2);
//当前格
Node cur;
//下一格
Node next;
//记录生成地图时遍历的顺序
Stack<Node> path = new Stack<>();
//走迷宫时用到的变量
private Node movePerson;
//记录迷宫中行进的轨迹
List<Integer> xPath=new ArrayList<>();
List<Integer> yPath=new ArrayList<>();
private boolean drawPath;
//A*算法使用到的变量
//设每一步的权值为10
public int sValue = 10;
//维护一个开放列表
private final ArrayList<Node> openList = new ArrayList<>();
//维护一个关闭列表
private final ArrayList<Node> closeList = new ArrayList<>();
//初始化,初始化迷宫参数
MyMaze(){
mMap = new int [num][num];
visit = new boolean[num][num];
//初始化地图的空格
for (int i = 0; i < num; i = i+2) {
for (int j = 0; j < num; j=j+2){
//其余均为墙
mMap[i][j] = wall;
visit[i][j] = false;
}
}
//初始化地图的空格
for (int i = 1; i < num; i = i+2) {
for (int j = 1; j < num; j=j+2){
//奇数行奇数列的格子均为路
mMap[i][j] = road;
visit[i][j] = false;
}
}
//遍历过的点设置为true,奇数行奇数列的各自为路
visit[start.x][start.y] = true;
mMap[start.x][start.y] = road;
//将当前格标记为开始格
cur = start;
movePerson=new Node(start.x-1,start.y);
drawPath=false;
createMaze();
this.addKeyListener(this);
this.setFocusable(true);
}
//第一轮结束后,再次初始化地图
public void init(){
mMap = new int [num][num];
visit = new boolean[num][num];
//初始化地图的空格
for (int i = 0; i < num; i = i+2) {
for (int j = 0; j < num; j=j+2){
//其余均为墙
mMap[i][j] = wall;
visit[i][j] = false;
}
}
//初始化地图的空格
for (int i = 1; i < num; i = i+2) {
for (int j = 1; j < num; j=j+2){
//奇数行奇数列的格子均为路
mMap[i][j] = road;
visit[i][j] = false;
}
}
visit[start.x][start.y] = true;
mMap[start.x][start.y] = road;
//将当前格标记为开始格
cur = start;
//设置移动的起始点
movePerson=new Node(start.x-1,start.y);
drawPath=false;
//清空行走的路径坐标
xPath.clear();
yPath.clear();
openList.clear();
closeList.clear();
createMaze();
this.setFocusable(true);
repaint();
}
void printMaze() {
for(int i = 0; i < num; i++)
{
for(int j = 0; j < num; j++)
{
System.out.print(mMap[i][j]+" ");
}
System.out.println();
}
}
//深度优先遍历
void createMaze() {
//将当前格压入栈
path.push(cur);
while(!path.empty()) {
ArrayList<Node> mNei=notVisitedNei(cur);
if(mNei.size()==0){
//如果该格子没有可访问的邻接格,则跳回上一个格子
cur = path.pop();
continue;
}
//随机选取一个邻接格(并且在地图内)
next = mNei.get(new Random().nextInt(mNei.size()));
int x = next.x;
int y = next.y;
//如果该节点被访问过,则回到上一步继续寻找
if(visit[x][y]){
cur = path.pop();
}
//否则将当前格压入栈,标记当前格为已访问,并且在迷宫地图上移除障碍物
else{
path.push(next);
visit[x][y] = true;
mMap[x][y] = road;
//打通当前格与下一格
//打通墙,将墙的位置改换定义为路
mMap[(cur.x + x) / 2][(cur.y + y) / 2] = road;
//当前格等于下一格
cur = next;
}
}
//设置入口
mMap[start.x-1][start.y]=1;
//设置出口
mMap[end.x+1][end.y]=1;
}
//寻找未访问的邻接节点
public ArrayList<Node> notVisitedNei(Node node)
{
int []nei={2,0,-2,0,2};
ArrayList<Node> list = new ArrayList<>();
for(int i = 0; i < nei.length-1; i++)
{
int x = node.x + nei[i];
int y = node.y + nei[i+1];
//每相邻的两个单元格的位置的点必定是road(起始位就是road)
if( x >= 0 && x < num && y >= 0 && y < num) {
//未访问,则入数组
if(!visit[x][y])
list.add(new Node(x,y));
}
}
return list;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.setBackground(Color.white);
//画墙
g.setColor(Color.black);
for(int i=0;i<num;i++){
for(int j=0;j<num;j++){
if(mMap[i][j]==0){
g.fillRect(10+i*width,10+j*width,width,width);
}
}
}
//画出A*算法求得的最佳路径
//从终点开始,每个方格沿着父节点移动直至起点,就是最优路径
if(drawPath){
g.setColor(Color.RED);
//父节点
Node parent = findPath(start, end);
ArrayList<Node> arrayList = new ArrayList<>();
while (parent != null) {
arrayList.add(new Node(parent.x, parent.y));
parent = parent.parent;
}
for (int i = 0; i <num; i++) {
for (int j = 0; j < num; j++) {
if (exists(arrayList, i, j)) {
g.fillOval(10+i*width+width/4 , 10+j*width+width/4,
width / 2, width / 2);
}
}
}
}
//画移动的轨迹
g.setColor(Color.black);
for(int i=0;i<xPath.size();i++){
g.fillOval(10+xPath.get(i)*width+width/4 , 10+yPath.get(i)*width+width/4,
width / 2, width / 2);
}
//画点的移动
g.setColor(Color.yellow);
g.fillOval(10+movePerson.x*width+width/4 , 10+movePerson.y*width+width/4,
width / 2, width / 2);
}
//越界检测(确定x,y是在地图里的)
private boolean isOutOfBorder(int x, int y) {
return x > num - 1 || y > num - 1 || x < 0 || y < 0;
}
private void checkWin() {
//通关检测
if (movePerson.x==num-1 && movePerson.y==num-2) {
JOptionPane.showMessageDialog(null, "恭喜通关" +"\n"+""
,"MAZE",
JOptionPane.PLAIN_MESSAGE);
init();
}
}
//A*算法输出走迷宫的最佳路径
public Node findMinFNodeInOpenList() {
//寻找最小移动开销的节点F=G(当前代价)+H(预估代价)
Node tempNode = openList.get(0);
for (Node node : openList) {
if (node.F < tempNode.F) {
tempNode = node;
}
}
return tempNode;//得到移动开销最小的那个节点,即F最小的那个点
}
public ArrayList<Node> findNeighborNodes(Node currentNode) {
//找上下左右四个方向的邻居节点
ArrayList<Node> arrayList = new ArrayList<>();
int topX = currentNode.x;
int topY = currentNode.y - 1;
if (!isOutOfBorder(topX, topY) && !exists(closeList, topX, topY) && mMap[topX][topY]==1) {
arrayList.add(new Node(topX, topY));
}
int bottomX = currentNode.x;
int bottomY = currentNode.y + 1;
if (!isOutOfBorder(bottomX, bottomY) && !exists(closeList, bottomX, bottomY) && mMap[bottomX][bottomY]==1) {
arrayList.add(new Node(bottomX, bottomY));
}
int leftX = currentNode.x - 1;
int leftY = currentNode.y;
if (!isOutOfBorder(leftX, leftY) && !exists(closeList, leftX, leftY) && mMap[leftX][leftY]==1) {
arrayList.add(new Node(leftX, leftY));
}
int rightX = currentNode.x + 1;
int rightY = currentNode.y;
if (!isOutOfBorder(rightX, rightY) && !exists(closeList, rightX, rightY) && mMap[rightX][rightY]==1) {
arrayList.add(new Node(rightX, rightY));
}
return arrayList;
}
public Node findPath(Node startNode, Node endNode) {
openList.add(startNode);// 把起点加入 open list
while (openList.size() > 0) {
Node currentNode = findMinFNodeInOpenList();// 遍历 open list ,查找F值最小的节点,把它作为当前要处理的节点
//如果open list为空,寻路失败,找不到到达终点的路径(也就是迷宫没有路可以到达)
openList.remove(currentNode);// 将刚刚F最小的那个点从open list中移除
closeList.add(currentNode);// 把这个节点移到 close list
ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);//寻找邻居节点(4个中寻找)
for (Node node : neighborNodes) {
if (exists(openList, node)) {//如果邻居节点在open列表中
foundPoint(currentNode, node);//更新列表中父节点和估价函数信息
} else {
notFoundPoint(currentNode, endNode, node);//如果邻居节点不在open列表中,则将该点加入open列表中
}
}
//如果终点加入到了open list中,此时路径已经找到,从终点开始,每个方格沿着父节点移动直至起点,这就是最优路径
if (find(openList, endNode) != null) {//如果找到尾节点,则返回尾节点
return find(openList, endNode);
}
}
// return find(openList, endNode);
return null;
}
private void foundPoint(Node tempStart, Node node) {
int G = calcG(node);
if (G < node.G) {
node.parent = tempStart;
node.G = G;
node.calcF();
}
}
private void notFoundPoint(Node tempStart, Node end, Node node) {
node.parent = tempStart;
node.G = calcG(node);
node.H = calcH(end, node);
node.calcF();
openList.add(node);
}
private int calcG(Node node) {
int G = sValue;
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 * sValue;
}
public static Node find(List<Node> nodes, Node point) {
for (Node n : nodes)
if ((n.x == point.x) && (n.y == point.y)) {
return n;
}
return null;
}
public static boolean exists(List<Node> nodes, Node node) {//判断节点是否存在某一list中
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) {//判断节点是否存在某一list中
for (Node n : nodes) {
if ((n.x == x) && (n.y == y)) {
return true;
}
}
return false;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {//响应键盘
int keyCode = e.getKeyCode();
int x=movePerson.x;
int y=movePerson.y;
switch (keyCode){
case KeyEvent.VK_SPACE: //空格键选择是否路径的显示的情况
drawPath = !drawPath;
repaint();
break;
case KeyEvent.VK_LEFT: //根据键盘控制移动
x--;
break;
case KeyEvent.VK_RIGHT:
x++;
break;
case KeyEvent.VK_UP:
y--;
break;
case KeyEvent.VK_DOWN:
y++;
break;
}
if(!isOutOfBorder(x,y)&&mMap[x][y]==1){
xPath.add(movePerson.x);
yPath.add(movePerson.y);
movePerson.x=x;
movePerson.y=y;
}
repaint();
checkWin();
}
@Override
public void keyReleased(KeyEvent e) {
}
public static void main(String[] args) {
JPanel p = new MyMaze();
Image icon=Toolkit.getDefaultToolkit().getImage("maze.png");//设置最小化的图标
JFrame frame = new JFrame("MAZE");
frame.setIconImage(icon);
frame.getContentPane().add(p);//添加画布
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//使用 System exit 方法退出应用程序。
frame.setSize(540, 560);//设置窗口大小
frame.setLocation(600, 200);
frame.setVisible(true);
}
}