迷宫
- 转载自:http://zpsailor.iteye.com/blog/651141
- 使用swing组件的一个项目
- awt 和 util 包里的list冲突,util不能写成util.*;
- @SuppressWarnings(“serial”) //告诉编译器忽略指定的警告,不用在编译完成后出现警告信息
- public class Maze extends JFrame implements ActionListener 自身就是监听器
- 结合了图的深度优先遍历,高效的产生复杂的迷宫
- 二维数组,0、1矩阵表示迷宫
- 使用java.awt.Timer类来代替时间的线程
- 使用java.awt.Canvas类来描述每个迷宫格子,每次只渲染两个小格字就可以了
迷宫生成算法
- 既然要通过图的深度优先遍历来生成迷宫,我们首先需要的是一张图,这张图的节点中,要包含迷宫的开始点和结束点。在我们的算法中,最初构造了这样一张图:
- 在这张图中,每一个白色小块表示图中的一个节点,而横向和竖向上的红色方块是这个图的边。在整个图中,白色的部分表示通道,而红色部分表示的是墙壁。我们规定左上角是迷宫的入口,右下角是迷宫的出口。在这种情况下,我们只需要从入口处开始,采用深度优先遍历方法,遍历这个图,在遍历的过程中,当从一个节点移动到另一个节点的过程中,将其移动过程中的边变成通道,这样遍历完图后,就将产生一个类似下图的迷宫:
- 这样,我们的迷宫便生成好了。在这个过程中,需要用到递归算法和回溯法。
走迷宫的算法
- 在走迷宫的过程中,需要用到两个列表结构willvisit和 comed,分别记录了当前可以移动到的位置和已经走过的位置。在willvisit中不包含comed中的记录的位置。这样利用回溯法,在当前位置的willvisit中随机选取一个位置移动,并将移动过的位置记录到comed的尾部。当当前所在位置不能产生下一个移动时,将当前位置添加到comed的头部,从comed的尾部取出一个位置,然后将这个位置作为当前位置。再判断能否移动下一步,依次这样移动,直到移动到出口处。
jframe.setDefaultCloseOperation(int operation):
- 设置用户在此窗体上发起 “close” 时默认执行的操作。方法中的参数解释如下:
- 为“0”或DO_NOTHING_ON_CLOSE:
- (在 WindowConstants 中定义):不执行任何操作;要求程序在已注册的WindowListener 对象的 windowClosing 方法中处理该操作。
- 比如实例程序代码中更改为f.setDefaultCloseOperation(f. DO_NOTHING_ON_CLOSE);或者f.setDefaultCloseOperation(0),然后查看效果,可以发现窗口无法关闭,下面是相同测试方法,不再解释了。
- 为“1”或HIDE_ON_CLOSE
- 调用任意已注册的 WindowListener 对象后自动隐藏该窗体。此时没有关闭程序,只是将程序界面隐藏了。可以打开任务管理器,可以看到一个叫“java.exe”的进程(如果调试运行了多个java程序,则会看到多个“java.exe”的进程),如果此时用EditPlus测试程序,会发现当单击窗口的关闭按钮关闭窗口后,却无法再次对程序进行调试,因为程序线程没有关闭,在任务管理器中关闭java.exe(如果有多个“java.exe”的进程,则先都关闭掉,再来测试该问题)基础后,EditPlus才可以重新编译改程序。
- 为“2”或DISPOSE_ON_CLOSE
- 调用任意已注册 WindowListener 的对象后自动隐藏并释放该窗体。但继续运行应用程序,释放了窗体中占用的资源。
- 为“3”EXIT_ON_CLOSE(在 JFrame 中定义):
- 使用 System exit 方法退出应用程序。仅在应用程序中使用。结束了应用程序。
- 默认情况下,该值被设置为 HIDE_ON_CLOSE。
@SuppressWarnings(“serial”)
- 简介:java.lang.SuppressWarnings是J2SE 5.0中标准的Annotation之一。可以标注在类、字段、方法、参数、构造方法,以及局部变量上。
- 作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。
- 使用:
- @SuppressWarnings(“”)
- @SuppressWarnings({})
@SuppressWarnings(value={})
根据sun的官方文档描述:
- value – 将由编译器在注释的元素中取消显示的警告集。允许使用重复的名称。忽略第二个和后面出现的名称。出现未被识别的警告名不是 错误:编译器必须忽略无法识别的所有警告名。但如果某个注释包含未被识别的警告名,那么编译器可以随意发出一个警告。
- 各编译器供应商应该将它们所支持的警告名连同注释类型一起记录。鼓励各供应商之间相互合作,确保在多个编译器中使用相同的名称。
- @SuppressWarnings(“unchecked”)
- 告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
- @SuppressWarnings(“serial”)
- 如果编译器出现这样的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long 使用这个注释将警告信息去掉。
- @SuppressWarnings(“deprecation”)
- 如果使用了使用@Deprecated注释的方法,编译器将出现警告信息。 使用这个注释将警告信息去掉。
- @SuppressWarnings(“unchecked”, “deprecation”)
- 告诉编译器同时忽略unchecked和deprecation的警告信息。
- @SuppressWarnings(value={“unchecked”, “deprecation”})
- 等同于@SuppressWarnings(“unchecked”, “deprecation”)
Maze.java
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask; //这里不能写成java.util.*; 因为awt包里面也有List,会冲突
import javax.swing.*;
@SuppressWarnings("serial") // 告诉编译器忽略指定的警告,不用在编译完成后出现警告信息
public class Maze extends JFrame implements ActionListener {
private int ROW = 25;// ROW 和COL目前暂定只能是奇数
private int COL = 25;
private JPanel panel;
private JPanel northPanel;
private JPanel centerPanel;
private MazeGrid grid[][];
private JButton restart;
private JButton dostart;
private List<String> willVisit = new ArrayList<String>(); // 记录了当前可以移动到的位置
// ,在willvisit中不包含comed中的记录的位置
private List<String> visited = new ArrayList<String>();
private LinkedList<String> comed = new LinkedList<String>(); // 已经走过的位置
private long startTime;
private long endTime;
public Maze() {
init();
this.setTitle("回溯法--走迷宫");
this.add(panel);
this.pack(); // 不加pack就只剩标题栏了
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 用户单击窗口的关闭按钮时程序执行的操作
}
public void init() {
panel = new JPanel();
northPanel = new JPanel();
centerPanel = new JPanel();
panel.setLayout(new BorderLayout());
restart = new JButton("重新生成迷宫");
dostart = new JButton("开始走迷宫");
grid = new MazeGrid[ROW][COL];
centerPanel.setLayout(new GridLayout(ROW, COL, 1, 1));
centerPanel.setBackground(new Color(0, 0, 0));
northPanel.add(restart);
northPanel.add(dostart);
dostart.addActionListener(this);
restart.addActionListener(this);
for (int i = 0; i < grid.length; i++)
for (int j = 0; j < grid[i].length; j++) {
if (j % 2 == 0 && i % 2 == 0)
grid[i][j] = new MazeGrid(true, 20, 20);
else
grid[i][j] = new MazeGrid(false, 20, 20);
}
grid[0][0].setVisited(true);
grid[0][0].setPersonCome(true);
grid[0][0].setStart(true);
visited.add("0#0");
grid[ROW - 1][COL - 1].setEnd(true);
grid = createMap(grid, 0, 0);
for (int i = 0; i < grid.length; i++)
for (int j = 0; j < grid[i].length; j++) {
grid[i][j].repaint();
centerPanel.add(grid[i][j]);
}
panel.add(northPanel, BorderLayout.NORTH);
panel.add(centerPanel, BorderLayout.CENTER);
}
public MazeGrid[][] createMap(MazeGrid mazeGrid[][], int x, int y) {
int visitX = 0;
int visitY = 0;
if (x - 2 >= 0) {
if (!mazeGrid[x - 2][y].isVisited()) {
willVisit.add((x - 2) + "#" + y);
}
}
if (x + 2 < COL) {
if (!mazeGrid[x + 2][y].isVisited()) {
willVisit.add((x + 2) + "#" + y);
}
}
if (y - 2 >= 0) {
if (!mazeGrid[x][y - 2].isVisited()) {
willVisit.add(x + "#" + (y - 2));
}
}
if (y + 2 < ROW) {
if (!mazeGrid[x][y + 2].isVisited()) {
willVisit.add(x + "#" + (y + 2));
}
}
if (!willVisit.isEmpty()) {
int visit = (int) (Math.random() * willVisit.size());
String id = willVisit.get(visit);
visitX = Integer.parseInt(id.split("#")[0]);
visitY = Integer.parseInt(id.split("#")[1]);
mazeGrid[(visitX + x) / 2][(visitY + y) / 2].setMark(true);
mazeGrid[visitX][visitY].setVisited(true);
if (!visited.contains(id)) {// 将这个点加到已访问中去
visited.add(id);
}
willVisit.clear();
createMap(mazeGrid, visitX, visitY);
} else {
if (!visited.isEmpty()) {
String id = visited.remove(visited.size() - 1);// 取出最后一个元素
visitX = Integer.parseInt(id.split("#")[0]);
visitY = Integer.parseInt(id.split("#")[1]);
mazeGrid[visitX][visitY].setVisited(true);
createMap(mazeGrid, visitX, visitY);
}
}
return mazeGrid;
}
public String goMaze(MazeGrid mazeGrid[][], int x, int y) {
int comeX = 0;
int comeY = 0;
// left
if (x - 1 >= 0) {
if (mazeGrid[x - 1][y].isMark()) {
if (!comed.contains((x - 1) + "#" + y))
willVisit.add((x - 1) + "#" + y);
}
}
// right
if (x + 1 < COL) {
if (mazeGrid[x + 1][y].isMark()) {
if (!comed.contains((x + 1) + "#" + y))
willVisit.add((x + 1) + "#" + y);
}
}
// up
if (y - 1 >= 0) {
if (mazeGrid[x][y - 1].isMark()) {
if (!comed.contains(x + "#" + (y - 1)))
willVisit.add(x + "#" + (y - 1));
}
}
// down
if (y + 1 < ROW) {
if (mazeGrid[x][y + 1].isMark()) {
if (!comed.contains(x + "#" + (y + 1)))
willVisit.add(x + "#" + (y + 1));
}
}
if (!willVisit.isEmpty()) {
int visit = (int) (Math.random() * willVisit.size());
String id = willVisit.get(visit);
comeX = Integer.parseInt(id.split("#")[0]);
comeY = Integer.parseInt(id.split("#")[1]);
mazeGrid[x][y].setPersonCome(false);
mazeGrid[comeX][comeY].setPersonCome(true);
mazeGrid[x][y].repaint();
mazeGrid[comeX][comeY].repaint();
willVisit.clear();
comed.add(x + "#" + y);
} else {
if (!comed.isEmpty()) {
String id = comed.removeLast();
comeX = Integer.parseInt(id.split("#")[0]);
comeY = Integer.parseInt(id.split("#")[1]);
mazeGrid[x][y].setPersonCome(false);
mazeGrid[comeX][comeY].setPersonCome(true);
mazeGrid[x][y].repaint();
mazeGrid[comeX][comeY].repaint();
comed.addFirst(x + "#" + y);
}
}
return comeX + "#" + comeY;
}
int comeX = 0;
int comeY = 0;
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("重新生成迷宫")) {
refreshMap(grid);
} else if (e.getActionCommand().equals("开始走迷宫")) {
startTime = System.currentTimeMillis();
dostart.setVisible(false);
restart.setText("禁止刷新");
int delay = 1000;
int period = 500;// 循环间隔
java.util.Timer timer = new java.util.Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if (grid[ROW - 1][COL - 1].isPersonCome()) {
endTime = System.currentTimeMillis();
JOptionPane.showMessageDialog(null, "已经走出迷宫,耗时" + (endTime - startTime) / 1000 + "秒", "消息提示",
JOptionPane.ERROR_MESSAGE);
this.cancel();
restart.setText("重新生成迷宫");
} else {
String id = goMaze(grid, comeX, comeY);
comeX = Integer.parseInt(id.split("#")[0]);
comeY = Integer.parseInt(id.split("#")[1]);
}
}
}, delay, period);
}
}
public void refreshMap(MazeGrid mazeGrid[][]) {
comeX = 0;
comeY = 0;
willVisit.clear();
visited.clear();
comed.clear();
this.remove(panel);
init();
this.add(panel);
this.pack();
this.setVisible(true);
}
public static void main(String args[]) {
long start = System.currentTimeMillis();
new Maze();
long end = System.currentTimeMillis();
System.out.println("使用ArrayList生成迷宫耗时:" + (end - start) + "毫秒");
}
}
MazeGrid.java
import java.awt.*;
@SuppressWarnings("serial")
public class MazeGrid extends Canvas { //Canvas控件代表一个矩形区域,应用程序可以画的东西,或者可以由用户创建的接收输入。
//用法类似与Frame
private boolean mark;// 标记是否是通路,TRUE为通路,FALSE为不通
private boolean isVisited;// 标记是否是访问过的,这是在生成迷宫的时候判断的。
private boolean isPersonCome;// 标记是否已经走过
private boolean isStart;// 判断是否为入口
private boolean isEnd;// 判断是否为出口
public MazeGrid() {
this.setBackground(new Color(120, 0, 0));
this.setSize(25, 25);
}
public MazeGrid(boolean mark, int width, int height) {
this.mark = mark;
this.setSize(width, height);
if (mark == true) {
this.setBackground(new Color(255, 255, 255));
} else {
this.setBackground(new Color(120, 0, 0));
}
}
public boolean isMark() {
return mark;
}
public void setMark(boolean mark) {
this.mark = mark;
}
public void paint(Graphics g) {
if (this.mark) {
if (this.isStart || this.isEnd) {
this.setBackground(new Color(255,0,0));
} else
this.setBackground(new Color(255, 255, 255));
} else {
this.setBackground(new Color(120, 0, 0));
}
if (this.isPersonCome) {
g.setColor(Color.BLACK);
g.fillOval(2, 2, 15, 15);
}
}
public boolean isVisited() {
return isVisited;
}
public void setVisited(boolean isVisited) {
this.isVisited = isVisited;
}
public boolean isPersonCome() {
return isPersonCome;
}
public void setPersonCome(boolean isPersonCome) {
this.isPersonCome = isPersonCome;
}
public boolean isStart() {
return isStart;
}
public void setStart(boolean isStart) {
this.isStart = isStart;
}
public boolean isEnd() {
return isEnd;
}
public void setEnd(boolean isEnd) {
this.isEnd = isEnd;
}
}