要求: 1、迷宫随机生成 2、玩家走迷宫,留下足迹; 3、系统用A*算法寻路,输出路径 解决问题: 1、如何显示迷宫的图形界面; 2、如何生成随机的迷宫; 3、怎样移动游戏中走迷宫的“玩家”; 4、用A*算法求解迷宫; 1、如何显示游戏的图形界面? Java Swing GUI 图形界面窗口开发 Swing 是 Java 为图形界面应用开发提供的一组工具包,是 Java 基础类的一部分。它包含了构建图形界面(GUI)的各种组件,如: 窗口、标签、按钮、文本框等。 2、如何生成随机的迷宫 迷宫地图的分类 1)主路扭曲型 2)自然分岔型 3)块状分割型 …… 迷宫有什么特点? 通路和障碍物是同宽度的, 迷宫的起点到终点肯定是存在一条通路的。 我们把这个迷宫的所有路拉直,就变成为一个无向图。那我们是否可以转换为无向图的两个顶点之间的通路问题, 图的遍历。
在这个图中,任意两点直接是联通的,可达的。但是上面这个经过遍历生成的路径图看起来还不像一个迷宫,因为还缺少了“墙”,将遍历路径周围的节点补上灰色即可成为一个完整的迷宫 迷宫生成策略:利用深度遍历的思想。访问到一个节点时,搜索这个节点没有被访问过的相邻节点,选择一个继续做同样的操作,直到没有邻节点为止再回溯到上一个访问的节点,并选择另外的邻节点。 实验结果 Node.class class Node { public int x,y; public Node parent; public Node(int x, int y) { this.x = x; this.y = y; } public int F;//F为估价函数 public int G;//G代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销 public int H;//H表示待检测结点到目标节点B的估计移动开销 public void calcF() { this.F = this.G + this.H; }//计算估价函数 }
MyMaze.class 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; public class MyMaze extends JPanel implements KeyListener { //生成地图用到变量 final static int wall =0; //代表墙 final static int road =1; //代表空地 static int num = 21; //迷宫长度 int width = 20; //迷宫宽度 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 = false; //A*算法使用到的变量 public int sValue = 10;//设每一步的权值为10 private ArrayList<Node> openList = new ArrayList<>();//维护一个开放列表 private 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; } } 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 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<Node>(); for(int i = 0; i < nei.length-1; i++) { int x = node.x + nei[i]; int y = node.y + nei[i+1]; 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.lightGray);//画墙 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); } } } if(drawPath){//画出A*算法求得的路径 g.setColor(Color.MAGENTA); Node parent = findPath(start, end); //父节点 ArrayList<Node> arrayList = new ArrayList<Node>(); 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.PINK);//画移动的轨迹 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.RED);//画点的移动 g.fillOval(10+movePerson.x*width+width/4 , 10+movePerson.y*width+width/4, width / 2, width / 2); } private boolean isOutOfBorder(int x, int y) {//越界检测 if (x > num-1 || y >num-1 || x < 0 || y < 0) { return true; } return false; } private void checkWin() {//通关检测 if (movePerson.x==num-1 && movePerson.y==num-2) { JOptionPane.showMessageDialog(null, "Congratulation! " +"\n", "My Maze", JOptionPane.PLAIN_MESSAGE); init(); } } //A*算法 public Node findMinFNodeInOpenList() {//寻找最小移动开销的节点 Node tempNode = openList.get(0); for (Node node : openList) { if (node.F < tempNode.F) { tempNode = node; } } return tempNode; } public ArrayList<Node> findNeighborNodes(Node currentNode) {//找上下左右四个方向的邻居节点 ArrayList<Node> arrayList = new ArrayList<Node>(); 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值最小的节点,把它作为当前要处理的节点 openList.remove(currentNode);// 从open list中移除 closeList.add(currentNode);// 把这个节点移到 close list ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);//寻找邻居节点 for (Node node : neighborNodes) { if (exists(openList, node)) {//如果邻居节点在open列表中 foundPoint(currentNode, node);//更新列表中父节点和估价函数信息 } else { notFoundPoint(currentNode, endNode, node);//如果邻居节点不在open列表中,则将该点加入open列表中 } } 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(tempStart, 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(tempStart, node); node.H = calcH(end, node); node.calcF(); openList.add(node); } private int calcG(Node start, 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: //空格键选择是否路径的显示的情况 if (drawPath) { drawPath = false; } else { drawPath = true; } 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("MY MAZE(按空格键显示或隐藏路径提示)"); frame.setIconImage(icon); frame.getContentPane().add(p);//添加画布 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//使用 System exit 方法退出应用程序。 frame.setSize(460, 480);//设置窗口大小 frame.setLocation(200, 200); frame.setVisible(true); } }