软件实习项目三 —— Java实现基于A*算法的迷宫游戏
一、实验任务
(1)该题要求随机生成一个迷宫,并求解迷宫;
(2)要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于A*算法实现,输出走迷宫的最优路径并显示。
(3)设计交互友好的游戏图形界面。
二、实验准备
(1)具体编程语言:JAVA
(2)确定所用图形界面:Java Swing
(3)算法思想:递归分割法来随机生成迷宫,A*算法来自动寻路
三、设计思路
(1)如何生成迷宫
1.利用Java Swing的相关函数,进行绘画,以砖块为墙,豆豆人为角色,脚印为走过的路的痕迹,空白部分为路来画迷宫。
2.递归分割法的思想:
①开始创建迷宫,使整个空间没有壁,我们称之为“室”。
②在随机位置生成壁将室分割为两个子室,并在壁上随机开孔,使子室联通。
③重复步骤②,直到所有子室全部不可分割(即子室某一个维度等于1)。
(2)控制角色移动
利用Java函数addKeyListener来监听按键⬆️⬇️⬅️➡️的输入,只有当角色周围是路,角色才能继续移动。当角色到达终点时就宣布游戏结束。
(3)基于A*算法的自动寻路
A*算法的主要思想为:
①先判断目标能移动一格的位置,如果只能移动到之前的位置执行④。
②然后再将每一个与终点的距离进行比较。
③移动到距离最近的那一格上,重复①~③。
④返回上一步的位置。
四、功能实现
StartUI类:程序的启动界面,设置界面的布局及按钮,并提供玩家用户选择场地地图大小,以选择游戏难度。
public class StartUI extends JFrame implements ActionListener {
private JButton button1 = new JButton("24 X 24");
private JButton button2 = new JButton("35 X 35");
private JButton button3 = new JButton("46 X 46");
private JButton button4 = new JButton("?");
StartUI() {
//将选择按钮包含在选择面板上
JPanel choose = new JPanel();
choose.setLayout(new GridLayout(2, 2));
choose.add(button1);
choose.add(button2);
choose.add(button3);
choose.add(button4);
//注册侦听器
button1.addActionListener(this);
button2.addActionListener(this);
button3.addActionListener(this);
button4.addActionListener(this);
//提示信息
JPanel message = new JPanel() {
//匿名内部类
protected void paintComponent(Graphics g) {
setSize(200, 300);
g.drawString("请选择地图大小", 100, 35);
}
};
//主界面布局
setLayout(new BorderLayout(120, 40));
add(choose, BorderLayout.CENTER);
add(message, BorderLayout.NORTH);
add(new JPanel(), BorderLayout.EAST);
add(new JPanel(), BorderLayout.WEST);
add(new JPanel(), BorderLayout.SOUTH);
//基本设置
setTitle("豆豆人找朋友");
setSize(800, 600);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setResizable(false);
}
Map类:生成地图界面,地图框架的设计,包括菜单栏、剩余时间显示等,并为各组件、按钮、键盘、事件注册监听器,再初始化地图组件,随机生成迷宫。创建一个时间控制的线程和进行控制时间的方法。
public class Map extends JFrame implements ActionListener, KeyListener, Runnable {
static int m, n;
static Paint[][] tp = null; //显示动画,同一包内类均可访问
//时间限制
static Thread timeThread; //时间控制线程
static int timelimit, remaintime;
static JPanel timePanel = new JPanel() {
//剩余时间显示面板
public void paintComponent(Graphics g) {
super.paintComponent(g);
String rt;
if (timelimit == 0) {
rt = "无敌版";
setForeground(Color.GREEN); //绿色表示无时间限制
} else {
rt = remaintime / 3600 + " : " + (remaintime - (remaintime / 3600) * 3600) / 60 + " : " + remaintime % 60;
if (remaintime > 10)
setForeground(Color.BLUE); //剩余时间充足时为蓝色
else
setForeground(Color.RED); //剩余时间很少时为红色
}
g.drawString("剩余时间: " + rt, 220, 16);
}
};
// 菜单项
private JMenuItem m_start = new JMenuItem("开始新游戏(S)");
private JMenuItem m_time = new JMenuItem("游戏时间限制(L)");
private JMenuItem m_return = new JMenuItem("返回主界面(R)");
private JMenuItem m_exit = new JMenuItem("退出游戏(Q)");
private JMenuItem m_selfconfig = new JMenuItem("编辑当前地图(E)");
private JMenuItem m_randommake = new JMenuItem("随机生成地图(Z)");
private JMenuItem m_sortpath = new JMenuItem("显示最短路径(T)");
Map(int x, int y) {
m = x;
n = y;
tp = new Paint[m][n];
timelimit = remaintime = 0; //初始化时,时间为0,代表没有时间限制
timeThread = new Thread(this);
timeThread.start();
timeThread.checkAccess();
//初始化地图组件,并生成随机路径
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
tp[i][j] = new Paint();
}
Operations.creatMaze(); //深度优先遍历生成至少有一条随机通道的迷宫地图
//地图界面生成
JPanel mazePane = new JPanel();
mazePane.setLayout(new GridLayout(m, n, 0, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
mazePane.add(tp[i][j]);
}
}
Paint类:显示墙、路、玩家角色、终点、路径的面板。创建标志flag并为其赋值,不同的值画出的图形类别不同,使Operations类根据其需求转换flag的值,以画出墙、路和路径。
public class Paint extends JPanel implements MouseListener {
private boolean changeable_click;
//标志 0:墙 1:路 2:豆豆人 3:小黄豆 4:脚印
private int flag;
private Image wall = new ImageIcon("/Users/xiaoying/Desktop/IMG_8697.jpg").getImage(); //墙
private Image road = new ImageIcon("/Users/xiaoying/Desktop/IMG_8689.jpg").getImage(); //路
private Image role = new ImageIcon("/Users/xiaoying/Desktop/IMG_8665.PNG").getImage(); //豆豆人
private Image bean = new ImageIcon("/Users/xiaoying/Desktop/IMG_8663.PNG").getImage(); //小黄豆
private Image step = new ImageIcon("/Users/xiaoying/Desktop/IMG_8701.jpg").getImage(); //脚印
Paint(int f) {
flag = f;
changeable_click = false; //初始化时不能通过鼠标点击改变 flag 的值
addMouseListener(this);
}
Paint() {
this(0);
}
//重写paintComponent方法,画图
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (flag == 0)
g.drawImage(wall, 0, 0, getWidth(), getHeight(), this);
else if (flag == 1)
g.drawImage(road, 0, 0, getWidth(), getHeight(), this);
else if (flag == 2)
g.drawImage(role, 0, 0, getWidth(), getHeight(), this);
else if (flag == 3)
g.drawImage(bean, 0, 0, getWidth(), getHeight(), this);
else
g.drawImage(step, 0, 0, getWidth(), getHeight(), this);
}
Operations类:自定义随机生成地图的数据,深度算法遍历DFS生成至少有一条随机路径的迷宫地图,使用A*算法搜寻并显示最短路径。设置开始游戏时时间的控制,玩家角色的移动,走过路径的生成,也可以提示用户玩家最佳路径。
public class Operations{
static int m, n; //用于拷贝map.m和map.n的值
static int m_currex, m_currey; //豆豆人当前位置
static int m_startx, m_starty; //豆豆人开始位置
static boolean changeable_key = true; //可用键盘控制豆豆人移动
static boolean restart = false;
private static boolean[] isBeVisit = null;//生成随机地图专用数据
//迷宫地图最短路径深度图算法
public static void findPath() {
Map.timeThread.checkAccess(); ; //时间控制线程休眠
changeable_key = true; //可用键盘控制豆豆人
setEditable(false); //不可编辑
m = Map.m;
n = Map.n;
int max = m * n; //任意一点到小黄豆的最短路径长度不会超出m*n。
int[] depthGraph = new int[m * n]; //路径深度图
//路径深度图初始化
depthGraph[m * n - 1] = 0; //小黄豆到自己的距离自然是0
for (int i = 0; i < m * n - 1; i++) {
if (Map.tp[i / n][i % n].isWall())
depthGraph[i] = -1; //墙表示为-1,表示无通路
else
depthGraph[i] = max; //未确定距离时已max表示
}
boolean flag = true; //循环过程中是否有某点的路径深度被修改
int currex, currey; //记录当前访问点的坐标
int aroundmin; //周围可行方向的最小路径深度 + 1
//动态更新路径深度图直至其达到稳态(即最后一次循环过程中不再有路径深度被修改)
while (flag) {
flag = false;
for (int s = m * n - 1; s >= 0; s--) {
if (depthGraph[s] != -1) {
aroundmin = depthGraph[s];
currex = s / n;
currey = s % n;
if (currey + 1 < n && depthGraph[s + 1] != -1 && depthGraph[s + 1] + 1 < aroundmin)
aroundmin = depthGraph[s + 1] + 1;
if (currex + 1 < m && depthGraph[s + n] != -1 && depthGraph[s + n] + 1 < aroundmin)
aroundmin = depthGraph[s + n] + 1;
if (currey - 1 >= 0 && depthGraph[s - 1]