实验内容:
- 迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
3)要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。
示例:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Test extends JFrame implements ActionListener,KeyListener{
boolean map[][] = new PMap().prim(2, 0, 20, 19, true);
PaintMap p = new PaintMap(map,new EMap(map).exitmap());
public Test() { //迷宫的窗口信息
this.setTitle("迷宫游戏");
this.add(p);
this.setSize(500,500);
this.setVisible(true);
this.setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);//监听键盘
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Test().setVisible(true);
}
});
}
public void keyPressed(KeyEvent key) {//监听键盘,上下左右空格
switch(key.getKeyCode()) {
case KeyEvent.VK_UP:p.moveUp();break;
case KeyEvent.VK_DOWN:p.moveDown();break;
case KeyEvent.VK_LEFT:p.moveLeft();break;
case KeyEvent.VK_RIGHT:p.moveRight();break;
case KeyEvent.VK_SPACE:p.PressSp();break;
}
}
public void keyReleased(KeyEvent arg0) {}
public void keyTyped(KeyEvent arg0) {}
public void actionPerformed(ActionEvent arg0) {}
}
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class PaintMap extends JPanel{
final int unitSize = 10;//单位大小
private int width;//宽
private int height;//高
private int startX;//开始点
private int startY;
private boolean block;
private boolean b[][];
private boolean IsDisplay;
private boolean flag;
private ArrayList<Integer> ToExit = new ArrayList<Integer>(); //通向终点的路径的结点组成的列表
public PaintMap(boolean b[][],ArrayList<Integer>a) {//初始化
ToExit = a;
this.b = b;
width = b.length;
height = b[0].length;
startX = 0; //初始位置
startY = 1;
block = true; //初始化全是墙
IsDisplay = false;
flag = true;
}
public void paint(Graphics g) {
//墙的颜色
g.setColor(Color.DARK_GRAY);
for(int i = 0; i < width; i++)
for(int j = 0; j < height; j++)
if(!b[i][j]) {
g.fill3DRect(30 + i * unitSize, 30 + j * unitSize, unitSize, unitSize, true);//参数依次表示为:要填充矩形的x坐标、y坐标、要填充矩形的宽度、高度
}
//出口路线颜色:
if(IsDisplay) { //只有在敲击空格键的情况下显示最佳路径
g.setColor(Color.LIGHT_GRAY);
for(int i = 0; i < ToExit.size(); i += 2) { //路径横纵坐标依次存储在ToExit中,所以间隔加2
g.fill3DRect(30 + ToExit.get(i) * unitSize, 30 + ToExit.get(i + 1) * unitSize, unitSize, unitSize, true);
flag=true;
}
}
//
if(!flag) { //只有在敲击空格键的情况下显示最佳路径
g.setColor(Color.ORANGE);
for(int j = 0; j < ToExit.size(); j += 2) { //路径横纵坐标依次存储在ToExit中,所以间隔加2
g.fill3DRect(30 + ToExit.get(j) * unitSize, 30 + ToExit.get(j + 1) * unitSize, unitSize, unitSize,true);
}
}
//控制格子颜色:
g.setColor(Color.PINK);
if(IsEdge(startX, startY)) {
g.fill3DRect(30 + startX * unitSize, 30 + startY * unitSize, unitSize, unitSize, true);
}
else {
g.fill3DRect(30 + unitSize,30, unitSize, unitSize, true);
}
}
//本程序中使用的坐标轴为:以左上角顶点为原点,向右为 x 轴正方向,向下为 y 轴正方向
public void moveUp() { //向上移动
startY -= 1; //坐标先改变
if(IsEdge(startX, startY)) { //如果这个点在迷宫边界或超出迷宫范围,将改变还原
if(!b[startX][startY]) {
block = false;
startY += 1;
}
if(block)
repaint();
else
block = true;
Win(startX , startY);
}
else
startY += 1;
}
public void moveDown() {
startY += 1;
if(IsEdge(startX, startY)) {
if(!b[startX][startY]) {
block = false;
startY -= 1;
}
if(block)
repaint();
else
block = true;
Win(startX , startY);
}
else
startY -= 1;
}
public void moveLeft() {
startX -= 1;
if(IsEdge(startX, startY)) {
if(!b[startX][startY]) {
block = false;
startX += 1;
}
if(block)
repaint();
else
block = true;
Win(startX , startY);
}
else
startX += 1;
}
public void moveRight() {
startX += 1;
if(IsEdge(startX , startY)) {
if(!b[startX][startY]) {
block = false;
startX -= 1;
}
if(block)
repaint();
else
block = true;
Win(startX , startY);
}
else
startX -= 1;
}
public void PressSp() { //点击空格显示路径
if(IsDisplay && flag) {
IsDisplay = false;
flag = false;}
else {
IsDisplay = true;
flag = true;
repaint();
}
}
private boolean IsEdge(int x,int y) {//判断是否在迷宫内,在就返回true
return (x < width && y < height && x >= 0 && y >= 0) ;
}
private void Win(int x,int y) { //判断赢没赢游戏
if(x == width - 1 && y == height - 2) { //终点,是固定不变的
Object[] options = {"不服再来!","狠心离开!"};
int response=JOptionPane.showOptionDialog ( this, "出来了","Game Over",JOptionPane.YES_OPTION ,JOptionPane.PLAIN_MESSAGE, null,
options, options[0] ) ;
if (response == 0){ //选再来一局的话
removeAll();
repaint();
b = new PMap().prim(0, 0, (width - 1) / 2,(height - 1) / 2, true);
ToExit = new EMap(b).exitmap();
startX = 0;
startY = 1;
block = true;
IsDisplay = false;
repaint();
}
else //选择退出
System.exit(0);
}
}
}
import java.util.ArrayList;
import javax.swing.JPanel;
//生成随机的迷宫
public class PMap extends JPanel {
public boolean[][] prim(int startX , int startY , int widthLimit , int heightLimit , boolean haveBorder){ //0,1,20,19,true
final boolean block = false , unblock = true; //block表示是墙,unblock表示是可以走的路
//针对异常情况
if(widthLimit < 1)//宽度最低设置为1
widthLimit = 1;
if(heightLimit < 1)//高度最低设置为1
heightLimit = 1;
if(startX < 0 || startX >= widthLimit) //起点不能超出迷宫
startX = (int)Math.round( Math.random() * (widthLimit - 1) );//超出就在迷宫内随机选择一个作为起点
if(startY < 0 || startY >= heightLimit)
startY=(int)Math.round( Math.random() * (heightLimit - 1) );
if(!haveBorder) {
--widthLimit;
--heightLimit;
}
//迷宫尺寸换算成带墙尺寸
widthLimit *= 2;
heightLimit *= 2;
//迷宫起点换算成带墙起点
startX *= 2;
startY *= 2;
if(haveBorder) {
++startX;
++startY;
}
//初始化迷宫
boolean[][]mazeMap = new boolean [widthLimit + 1][heightLimit + 1];
//设置全为墙
for(int i = 0; i <= widthLimit; i++)
for(int j = 0; j <= heightLimit; j++)
mazeMap[i][j] = block;
mazeMap[0][1] = unblock; //入口
mazeMap[widthLimit][heightLimit - 1] = unblock; //出口
ArrayList<Integer> blockPos = new ArrayList<Integer>(); //存放邻居墙的列表
int targetX = startX , targetY = startY;
mazeMap[targetX][targetY] = unblock;
//首先针对起点,将起点邻墙加入列表,和起点(0,1)点进行比较
//列表在意义上是每三个元素为一组:一个点的 x 坐标,一个点的 y 坐标,将要移动的方向
if(targetY > 1) {
blockPos.add(targetX); blockPos.add(targetY - 1); blockPos.add(0);//上
}
if (targetX < widthLimit)
{
blockPos.add(targetX + 1);blockPos.add(targetY);blockPos.add(1);//右
}
if (targetY < heightLimit)
{
blockPos.add(targetX);blockPos.add(targetY + 1);blockPos.add(2);//下
}
if (targetX > 1)
{
blockPos.add(targetX - 1);blockPos.add(targetY);blockPos.add(3);//左
}
//列表不为空时
while(!blockPos.isEmpty()) {
int blockIndex = (int)Math.round(Math.random() * (blockPos.size() / 3-1)) * 3;//选中三个一组的最开始那个,即x坐标位,+1位表示y坐标位,+2表示方向位
if(blockIndex + 2 < blockPos.size()) {
if(blockPos.get(blockIndex + 2).equals(0)) { //+2表示方向位,如果向上,那么坐标跟着改变
targetX = blockPos.get(blockIndex);
targetY = blockPos.get(blockIndex + 1) - 1;
}
else if(blockPos.get(blockIndex + 2).equals(1)) {
targetX = blockPos.get(blockIndex) + 1;
targetY = blockPos.get(blockIndex + 1);
}
else if(blockPos.get(blockIndex + 2).equals(2)) {
targetX = blockPos.get(blockIndex);
targetY = blockPos.get(blockIndex + 1) + 1;
}
else if(blockPos.get(blockIndex+2).equals(3)) {
targetX = blockPos.get(blockIndex) - 1;
targetY = blockPos.get(blockIndex + 1);
}
}
//例子:口口口,最左的是起点,中间是它的邻墙,右面的则是上几行得到的,也就是 “对面 ”的格子
if(mazeMap[targetX][targetY] == block) { //起点对面格子是墙,打通邻墙
//打通墙
if(blockIndex + 1 < blockPos.size())
mazeMap[blockPos.get(blockIndex)][blockPos.get(blockIndex+1)] = unblock;
else
System.out.println("error");
mazeMap[targetX][targetY] = unblock;
//然后再添加当前目标的邻墙
if (targetY > 1 && mazeMap[targetX][targetY - 1] == block && mazeMap[targetX][targetY - 2] == block)
{
blockPos.add(targetX);blockPos.add(targetY-1);blockPos.add(0);//向上情况
}
if (targetX < widthLimit -1&& mazeMap[targetX + 1][targetY] == block && mazeMap[targetX + 2][targetY] == block)
{
blockPos.add(targetX+1);blockPos.add(targetY);blockPos.add(1);//向右情况
}
if (targetY < heightLimit-1 && mazeMap[targetX][targetY + 1] == block && mazeMap[targetX][targetY + 2] == block)
{
blockPos.add(targetX);blockPos.add(targetY+1);blockPos.add(2);//向下情况
}
if (targetX > 1 && mazeMap[targetX - 1][targetY] == block && mazeMap[targetX - 1][targetY] == block)
{
blockPos.add(targetX - 1); blockPos.add(targetY); blockPos.add(3);//向左情况
}
}
//对面已经是通路了,就从列表移除这面墙
for(int l = blockIndex , k = 0; k < 3; k++) {
blockPos.remove(l);
}
}
return mazeMap;
}
}
import java.util.ArrayList;
public class EMap {
private ArrayList<Integer> blockPos = new ArrayList<Integer>();
private int d[][]= { {0 , -1} , {1 , 0} , {0 , 1} , {-1 , 0} };//上 右 下 左
private boolean a[][];
private int width;
private int height;
private boolean fl = false;
public EMap(boolean b[][]) {//b是 PMap.java用 Prim 算法生成的迷宫,复制到 a中,使得不修改 b 中迷宫
width = (b.length - 1) / 2;
height = (b[0].length - 1) / 2;
a = new boolean [b.length][b[0].length];
for(int i = 0; i < b.length; i++)
for(int j = 0; j < b[0].length; j++)
a[i][j] = b[i][j];
}
/**深度优先搜索找迷宫通路
* 1.从一个点向四周开始搜索,选择一个方向向下搜索
* 2.直到遇到墙返回上一父节点,选择其他方向搜索,不断递归,并对已经搜索过的方向进行标注以免重复搜索,并删除列表中的路径
* 3.如果能够到达终点,就追溯之前路径,加入到列表中
*/
private void dfs(int x,int y,int c) { //c表示不能走的方向;
if(x == (width * 2) && y == (height * 2 - 1)) { //找到终点,fl设为true
fl = true;
return ;
}
for(int i = 0; i < 4; i++) { //从一个点向四周开始搜索
if(c == i)continue; //c表示你从一个方向搜过来的,就不要再重新搜回去了
int dx = x + d[i][0];
int dy = y + d[i][1];
if(ise(dx,dy) && a[dx][dy]) {
if(fl) break; //如果找到终点,直接跳出,不继续不搜索
blockPos.add(dx); blockPos.add(dy); //路径加入到列表中
a[dx][dy] = false;
dfs(dx , dy , (i + 2) % 4); //从新的结点继续开始搜
}
}
if(!fl) { //没找到终点,说明不通,这次尝试不对,就将不对的移除列表
blockPos.remove(blockPos.size() - 1);
blockPos.remove(blockPos.size() - 1);
}
}
public ArrayList<Integer> exitmap() {
blockPos.add(0); blockPos.add(1); dfs(0,1,3); //默认从(0,1)开始,不能走的方向为向下
return blockPos;
}
private boolean ise(int dx,int dy) {
return (0 <= dx && dx <= width * 2 && 0 <= dy && dy <= height * 2);
}
}