使用Java写迷宫之迷宫界面的创建
这里引用小蝌蚪找妈妈为题材,生成迷宫
(非常恳请大佬能够提出您宝贵的意见,我将感激涕零!)
思想
迷宫的特点
1、通路和障碍物是同宽度的(您也可以通过计算来使宽度不一样)
2、迷宫的起点到终点肯定是存在一条通路的。
我们把这个迷宫的所有路拉直,就变成为一个无向图。那我们是否可以转换为无向图的两个顶点之间的通路问题, 图的遍历。
遍历
遍历有很多种,这里运用了较简单的深度优先遍历和广度优先遍历。
深度优先遍历(DFS)
什么是深度优先遍历?
重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
类比到迷宫的生成,我们可以这样:
先生成迷宫的大致节点(为深度优先做准备):
其中灰色的是墙,黄色的是结点
可以说,深度优先遍历就是一条路走到黑,没有路了,就回溯直到找到下一条路,最后实现起始点与终点的连接。
这与递归算法相似。
广度优先遍历(BFS)
什么是广度优先遍历?
从图的某一结点出发,首先依次访问该结点的所有邻接结点Vi1,vi2,vi3,……Vin再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点;
重复此过程,直至所有顶点均被访问位置。
比如说:
v1 -> v3 -> v2 -> v6 -> v4 -> v5 -> v8 -> v7
v1 -> v2 -> v4 -> v8 -> v3 -> v7 -> v5 -> v6
…
可以说,广度优先遍历,就是每到一个新的结点,就有许多周围的结点可以选择。
这里我使用的是队列。
迷宫的显示
这里我使用的是JFrame里添加JPanel,通过在JPanel中绘图,添加至JFrame中,来正常显示,所以说,下面的代码仅生成迷宫的界面,如果想要更多内容,请看下一部分内容,将两者代码合并,源代码在下两章,请您如果需要请看下两章。
素材的制作:通过在网上找图片,通过Photoshop裁剪旋转
通过生成的map(请见下文),处于不同类型时添加不同类型的图片,长度宽度我都设为了30像素。
正式开始
变量的定义
一般来说,都是边写代码边加变量名。这里为了更好观看下面的代码,先显示变量的定义。
//地图宽度和高度
private int width;
private int height;
private int model;
private final int DFSMethod = 1;
private final int BFSMethod = 2;
//地图种类定义
private final int WALL = 0;
private final int EMPTY = 1;
private final int SELF = 2;
private final int END = 3;
private final int TESTFLAG = 4;
private final int WALKED = 5;
private final int ANS = 6;
//地图具体参数定义
private int side = 30;
private int[][] map;
private int mapWidth;//地图的列数
private int mapHeight;//地图的行数
private String direction = "R";
private boolean isSucceed = false;
//为A*算法准备
private int[][] mapForComputer;
//图片定义
private ImageIcon wall;
private ImageIcon floor;
private ImageIcon frog;
private ImageIcon tadpoleRight;
private ImageIcon tadpoleLeft;
private ImageIcon tadpoleUp;
private ImageIcon tadpoleDown;
//与前一个框建立连接
private JFrame Jf;
//电脑自动开关按钮
private boolean buttonOpen = false;
private JButton ansButton;
//判断是否开始
private boolean go = false;
//方向数组,便于后面加减,分别为上下左右
int[][] dir = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
//音乐
Clip bgm;
迷宫初始化
这个也是为了方便解释,先贴上迷宫的初始化,看看迷宫初始化需要做什么。
MyPanel(int widthCount, int heightCount, int model, JFrame newJFrame) {
//初始化图片
loadImages();
//初始化音乐
loadBGM();
//初始化长和宽
this.width = widthCount * side;
this.height = heightCount * side;
this.model = model;
this.map = new CreateMap(widthCount, heightCount, model).getMap();
this.mapForComputer = map;
this.mapWidth = map[0].length;
this.mapHeight = map.length;
this.Jf = newJFrame;
//添加键盘事件监听
this.setFocusable(true);
this.addKeyListener(this);
//初始化答案
initAns();
//添加按钮
initButton();
this.add(ansButton);
//开启音乐
playBGM();
}
图片的初始化
如图所示,图片在src下的images目录下
定义的每个变量对应着一张图片,如果您想要打包,可以使用java中的getClass方法以及加上相对路径。
private void loadImages() {
wall = new ImageIcon("src\\images\\wall.jpg");
frog = new ImageIcon("src\\images\\frog.png");
tadpoleRight = new ImageIcon("src\\images\\tadpoleRight.jpg");
tadpoleLeft = new ImageIcon("src\\images\\tadpoleLeft.jpg");
tadpoleUp = new ImageIcon("src\\images\\tadpoleUp.jpg");
tadpoleDown = new ImageIcon("src\\images\\tadpoleDown.jpg");
floor = new ImageIcon("src\\images\\floor.jpg");
}
初始化音乐同理,暂时就不介绍了。
按键事件的监听
KeyListener
在实现KeyListener接口后,就需要重写方法
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();//获取键盘背后对应的数字
if (keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) {
goRight();
} else if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) {
goLeft();
} else if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) {
goUp();
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) {
goDown();
这里键盘的上下左右以及AWSD,都可以用来控制小蝌蚪的移动,具体请您看goRight方法。
private void goRight() {
go = true;
direction = "R";
if (!isSucceed) {
moveRight();
}
repaint();
}
moveRight()
这里的go变量请您先不用管,他是用来对按钮进行控制的。
首先先将方向变量进行修改(在之后的paintComponent方法中会用到),以及判断是否成功,如果小蝌蚪以及到达了青蛙的位置,那么他就不能在移动了,最后调用repaint()方法的重绘功能。下面介绍里面的moveRight方法(俄罗斯套娃.-.)
private void moveRight() {
//这里的x和y,代表的是map中的坐标位置
int[][] selfPos = getSELFxy();
int selfX = selfPos[0][0];
int selfY = selfPos[0][1];
//System.out.println(inArea(selfX + 1,selfY));
//判断是否到达了终点
if (inArea(selfX,selfY+1) && map[selfX][selfY+1] == END) {
map[selfX][selfY+1] = SELF;
map[selfX][selfY] = EMPTY;
isSucceed = true;
congratulate();
return;
}
if (inArea(selfX ,selfY + 1) &&
(map[selfX][selfY+1] == EMPTY
|| map[selfX][selfY+1] == TESTFLAG
|| map[selfX][selfY+1] == WALKED
|| map[selfX][selfY+1] == ANS)) {
map[selfX][selfY+1] = SELF;
map[selfX][selfY] = WALKED;
}
//printMap();
}
首先先获取SELF的位置坐标,调用getSELFxy方法,取出x坐标和y坐标,
这里要说明的是,x代表的是行坐标,y代表的是纵坐标(如果您不太认可,可以在代码的基础上修改一下,但可能会比较乱),也就是说,如果地图是这样的
0 0 0 0 0 0
0 1 2 1 1 0
0 0 1 0 1 0
那么如果要找到SELF代表的数字2,他的x就为1,y就为2,及map[1][2],
那么,接下来的就是判断是否为终点,以及如果不是终点,就开始行走。首先是行走,前面我们已经知道x和y的具体意思,所以如果小蝌蚪想要往右走,我们需要判断的是map[1][3]位置的具体情况,不好意思,这里遗留了一些历史问题(原来打算使用TESTFLAG来实现小蝌蚪的自动寻找功能,结果最后添加了一个新的变量ANS,替代了TESTFLAG),这里麻烦您将TESTFLAG看做为EMPTY一样,可以用来小蝌蚪进行行走,我们仅需将右边的一个位置变为SELF,原来的位置变为WALKED变量,表示已经行走过了。
inArea()
其实这个方法在这里不是很需要,但是如果再做更多功能时,可能就需要了。
//是否在地图内
private boolean inArea(int x,int y)
{
if(x > 0 && x < mapHeight-1 && y > 0 && y < mapWidth-1)
return true;
return false;
}
congratulate()
private void congratulate() {
stopBGM();
int num = JOptionPane.showConfirmDialog(this,
"恭喜您,让小蝌蚪找到了妈妈!您还需要再来一局吗?",
"恭喜",
JOptionPane.YES_NO_OPTION);
if (num == JOptionPane.YES_OPTION) {
MazeFrame frame = new MazeFrame();
frame.init();
Jf.setVisible(false);
} else if (num == JOptionPane.NO_OPTION) {
System.exit(0);
}
}
这个方法是在当判断为下一个结点为END,就会调用,窗口会弹出选择,问您是否要再来一局和退出。
重绘函数的编写
对于map二维数组对应的所有类型,在相应的位置上绘制图片,每按一次键盘,会运行重绘机制。
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
if (map[i][j] == WALL) {
wall.paintIcon(null,g,j * side,i * side);
} else if (map[i][j] == SELF) {
if (direction == "R") {
tadpoleRight.paintIcon(null,g,j * side,i * side);
} else if (direction == "L") {
tadpoleLeft.paintIcon(null,g,j * side,i * side);
} else if (direction == "U") {
tadpoleUp.paintIcon(null,g,j * side,i * side);
} else {
tadpoleDown.paintIcon(null,g,j * side,i * side);
}
} else if (map[i][j] == END) {
frog.paintIcon(null,g,j * side,i * side);
} else if (map[i][j] == WALKED) {
floor.paintIcon(null, g, j * side, i * side);
}
}
}
}
源代码
(非常恳请大佬能够提出您宝贵的意见,我将感激涕零!)
package exp3;
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.io.InputStream;
public class MyPanel extends JPanel implements ActionListener, KeyListener {
//地图宽度和高度
private int width;
private int height;
private int model;
private final int DFSMethod = 1;
private final int BFSMethod = 2;
//地图种类定义
private final int WALL = 0;
private final int EMPTY = 1;
private final int SELF = 2;
private final int END = 3;
private final int TESTFLAG = 4;
private final int WALKED = 5;
private final int ANS = 6;
//地图具体参数定义
private int side = 30;
private int[][] map;
private int mapWidth;//地图的列数
private int mapHeight;//地图的行数
private String direction = "R";
private boolean isSucceed = false;
//为A*算法准备
private int[][] mapForComputer;
//图片定义
private ImageIcon wall;
private ImageIcon floor;
private ImageIcon frog;
private ImageIcon tadpoleRight;
private ImageIcon tadpoleLeft;
private ImageIcon tadpoleUp;
private ImageIcon tadpoleDown;
//与前一个框建立连接
private JFrame Jf;
//电脑自动开关按钮
private boolean buttonOpen = false;
private JButton ansButton;
//判断是否开始
private boolean go = false;
//方向数组,便于后面加减,分别为上下左右
int[][] dir = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
//音乐
Clip bgm;
class LookWay extends Thread {
@Override
public void run() {
while (!isSucceed && buttonOpen) {
//todo
int[][] xy = getSELFxy();
int selfX = xy[0][0];
int selfY = xy[0][1];
moveDirect(selfX, selfY);
//System.out.println(selfX + " " + selfY);
//printMap();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void moveDirect(int x, int y) {
for (int i = 0; i < 4; i++) {
if (map[x + dir[i][0]][y + dir[i][1]] == ANS
|| map[x + dir[i][0]][y + dir[i][1]] == END) {
if (i == 0) {
goUp();
System.out.println(i);
return;
} else if (i == 1) {
goDown();
System.out.println(i);
return;
} else if (i == 2) {
goLeft();
System.out.println(i);
return;
} else if (i == 3) {
goRight();
System.out.println(i);
return;
}
}
}
}
}
MyPanel(int widthCount, int heightCount, int model, JFrame newJFrame) {
//初始化图片
loadImages();
//初始化音乐
loadBGM();
//初始化长和宽
this.width = widthCount * side;
this.height = heightCount * side;
this.model = model;
this.map = new CreateMap(widthCount, heightCount, model).getMap();
this.mapForComputer = map;
this.mapWidth = map[0].length;
this.mapHeight = map.length;
this.Jf = newJFrame;
//添加键盘事件监听
this.setFocusable(true);
this.addKeyListener(this);
//初始化答案
initAns();
//添加按钮
initButton();
this.add(ansButton);
//开启音乐
playBGM();
}
void initButton() {
ansButton = new JButton("开启自动寻路");
ansButton.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (go && !buttonOpen) {
JOptionPane.showMessageDialog(null,
"您已经开始,所以无法再帮您寻找通路了",
"警告",JOptionPane.WARNING_MESSAGE);
return;
}
buttonOpen = !buttonOpen;
if (!buttonOpen) {
ansButton.setText("开启自动寻路");
} else {
ansButton.setText("关闭自动寻路");
go = true;
}
map = mapForComputer;
Thread thread = new Thread(new LookWay());
thread.start();
}
});
ansButton.setVisible(true);
ansButton.setFocusable(false);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
if (map[i][j] == WALL) {
wall.paintIcon(null,g,j * side,i * side);
} else if (map[i][j] == SELF) {
if (direction == "R") {
tadpoleRight.paintIcon(null,g,j * side,i * side);
} else if (direction == "L") {
tadpoleLeft.paintIcon(null,g,j * side,i * side);
} else if (direction == "U") {
tadpoleUp.paintIcon(null,g,j * side,i * side);
} else {
tadpoleDown.paintIcon(null,g,j * side,i * side);
}
} else if (map[i][j] == END) {
frog.paintIcon(null,g,j * side,i * side);
} else if (map[i][j] == WALKED) {
floor.paintIcon(null, g, j * side, i * side);
}
}
}
}
//暂时没啥用,将来可以用来补充功能
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("----actionPerformed---");
repaint();
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();//获取键盘背后对应的数字
if (keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) {
goRight();
} else if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) {
goLeft();
} else if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) {
goUp();
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) {
goDown();
}
}
private void goDown() {
go = true;
direction = "D";
if (!isSucceed) {
moveDown();
}
repaint();
}
private void goUp() {
go = true;
direction = "U";
if (!isSucceed) {
moveUp();
}
repaint();
}
private void goLeft() {
go = true;
direction = "L";
if (!isSucceed) {
moveLeft();
}
repaint();
}
private void goRight() {
go = true;
direction = "R";
if (!isSucceed) {
moveRight();
}
repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
private void initAns() {
mapForComputer = new int[mapHeight][mapWidth];
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
mapForComputer[i][j] = map[i][j];
}
}
SolveMap solveMap = new SolveMap(mapForComputer);
mapForComputer = solveMap.getMap();
}
private void moveRight() {
//这里的x和y,代表的是map中的坐标位置
int[][] selfPos = getSELFxy();
int selfX = selfPos[0][0];
int selfY = selfPos[0][1];
//System.out.println(inArea(selfX + 1,selfY));
//判断是否到达了终点
if (inArea(selfX,selfY+1) && map[selfX][selfY+1] == END) {
map[selfX][selfY+1] = SELF;
map[selfX][selfY] = EMPTY;
isSucceed = true;
congratulate();
return;
}
if (inArea(selfX ,selfY + 1) &&
(map[selfX][selfY+1] == EMPTY
|| map[selfX][selfY+1] == TESTFLAG
|| map[selfX][selfY+1] == WALKED
|| map[selfX][selfY+1] == ANS)) {
map[selfX][selfY+1] = SELF;
map[selfX][selfY] = WALKED;
}
//printMap();
}
private void moveLeft() {
int[][] selfPos = getSELFxy();
int selfX = selfPos[0][0];
int selfY = selfPos[0][1];
//System.out.println(inArea(selfX - 1,selfY));
//判断是否到达了终点
if (inArea(selfX,selfY-1) && map[selfX][selfY-1] == END) {
map[selfX][selfY-1] = SELF;
map[selfX][selfY] = EMPTY;
isSucceed = true;
congratulate();
return;
}
if (inArea(selfX,selfY-1) &&
(map[selfX][selfY-1] == EMPTY
|| map[selfX][selfY-1] == TESTFLAG
|| map[selfX][selfY-1] == WALKED
|| map[selfX][selfY-1] == ANS)) {
map[selfX][selfY-1] = SELF;
map[selfX][selfY] = WALKED;
}
//printMap();
}
private void moveUp() {
int[][] selfPos = getSELFxy();
int selfX = selfPos[0][0];
int selfY = selfPos[0][1];
//System.out.println(inArea(selfX,selfY - 1));
//判断是否到达了终点
if (inArea(selfX-1,selfY) && map[selfX-1][selfY] == END) {
map[selfX-1][selfY] = SELF;
map[selfX][selfY] = EMPTY;
isSucceed = true;
congratulate();
return;
}
if (inArea(selfX-1,selfY) &&
(map[selfX-1][selfY] == EMPTY
|| map[selfX-1][selfY] == TESTFLAG
|| map[selfX-1][selfY] == WALKED
|| map[selfX-1][selfY] == ANS)) {
map[selfX-1][selfY] = SELF;
map[selfX][selfY] = WALKED;
}
//printMap();
}
private void moveDown() {
int[][] selfPos = getSELFxy();
int selfX = selfPos[0][0];
int selfY = selfPos[0][1];
//System.out.println(inArea(selfX+1,selfY));
//判断是否到达了终点
if (inArea(selfX+1,selfY) && map[selfX+1][selfY] == END) {
map[selfX+1][selfY] = SELF;
map[selfX][selfY] = EMPTY;
isSucceed = true;
congratulate();
return;
}
if (inArea(selfX+1,selfY) &&
(map[selfX+1][selfY] == EMPTY
|| map[selfX+1][selfY] == TESTFLAG
|| map[selfX+1][selfY] == WALKED
|| map[selfX+1][selfY] == ANS)) {
map[selfX+1][selfY] = SELF;
map[selfX][selfY] = WALKED;
}
//printMap();
}
private void loadImages() {
wall = new ImageIcon("src\\images\\wall.jpg");
frog = new ImageIcon("src\\images\\frog.png");
tadpoleRight = new ImageIcon("src\\images\\tadpoleRight.jpg");
tadpoleLeft = new ImageIcon("src\\images\\tadpoleLeft.jpg");
tadpoleUp = new ImageIcon("src\\images\\tadpoleUp.jpg");
tadpoleDown = new ImageIcon("src\\images\\tadpoleDown.jpg");
floor = new ImageIcon("src\\images\\floor.jpg");
}
private void congratulate() {
stopBGM();
int num = JOptionPane.showConfirmDialog(this,
"恭喜您,让小蝌蚪找到了妈妈!您还需要再来一局吗?",
"恭喜",
JOptionPane.YES_NO_OPTION);
if (num == JOptionPane.YES_OPTION) {
MazeFrame frame = new MazeFrame();
frame.init();
Jf.setVisible(false);
} else if (num == JOptionPane.NO_OPTION) {
System.exit(0);
}
}
private void loadBGM() {
// 定义字节输入流变量
InputStream is;
// 该类为inputstream的直接子类,用于读取音频
AudioInputStream ais;
// 该类提供对一系列浮点值的控制,此处定义用来控制音频音量大小
FloatControl gainControl;
try {
bgm = AudioSystem.getClip();
// 通过类加载器找到字节流
is = this.getClass().getClassLoader().
getResourceAsStream("music\\morning.wav");
ais = AudioSystem.getAudioInputStream(is);// 转换成音频字节流
bgm.open(ais);
gainControl = (FloatControl) bgm.getControl(FloatControl.Type.MASTER_GAIN);
gainControl.setValue(1.0f);// 控制调整bgm的音量大小
} catch (LineUnavailableException
| IOException
| UnsupportedAudioFileException e) {
e.printStackTrace();
}
}
private void playBGM() {
bgm.loop(Clip.LOOP_CONTINUOUSLY);
}
private void stopBGM() {
bgm.stop();
}
private int[][] getSELFxy() {
int[][] temp = new int[1][2];
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
if (map[i][j] == SELF) {
temp[0][0] = i;
temp[0][1] = j;
return temp;
}
}
}
return temp;
}
//是否在地图内
private boolean inArea(int x,int y)
{
if(x > 0 && x < mapHeight-1 && y > 0 && y < mapWidth-1)
return true;
return false;
}
//test
private void printMap() {
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
//test
private void getENDxy() {
int[][] temp = new int[1][2];
for (int i = 0; i < mapHeight; i++) {
for (int j = 0; j < mapWidth; j++) {
if (map[i][j] == END) {
temp[0][0] = i;
temp[0][1] = j;
System.out.println("x = " + i + "y = " + j);
}
}
}
}
}