Java编写迷宫小游戏
缘起:
去年(大三上学期)比较喜欢写小游戏,于是想试着写个迷宫试一下。
程序效果:
按下空格显示路径:
思考过程:
迷宫由一个一个格子组成,要求从入口到出口只有一条路径.
想了一下各种数据结构,似乎树是比较合适的,从根节点到每一个子节点都只有一条路径。假设入口是根节点,出口是树中某个子节点,那么,从根节点到该子节点的路径肯定是唯一的。
所以如果能构造一棵树把所有的格子都覆盖到,也就能够做出一个迷宫了。
另外还要求树的父节点和子节点必须是界面上相邻的格子。
在界面显示时,父节点和子节点之间共用的边不画,其他的边都画出来,就能画出一个迷宫。
之后就是想一下该怎么实现这样一棵树。
首要的两个问题:
1、树怎么表示?
2、怎么构造这棵树?
1.树怎么表示?
假设像写二叉树一样实现这棵树,那么每个树节点里就要存储一个坐标(x,y)表示一个格子,另外还要存储四个指针。指针中有的为空,有的不为空,不为空的指针指向子节点,子节点保存邻居格子的坐标。这样做最大的问题是无法判定是否所有的格子都在树中。也许还要用一个二维数组作标志数组。
假如用二维数组表示迷宫的格子。每个数组元素存储一个指向父节点的引用,这样也可以形成一个虚拟的树。于是就用一个n*n的二维数组,表示n*n个格子,每个数组元素(lattice)中有一个指向父节点的引用(father)。另外,为了能方便的获取格子的坐标,还要保存坐标信息。
2.怎么构造这棵树?
首先选定一个格子作为根节点。为了让迷宫的形状够随机,我选择随机生成一个坐标作为根节点。其实,选择确定的一个坐标也可以。
然后,怎样往这棵树上增加节点呢?
在这里我走了不少弯路,一开始想的是一种现在看来类似回溯的算法(当时还不知道回溯算法。。),但是时间复杂度很高,大概当迷宫为64*64的时候,算法就不出结果了。
然后,又使用了一种扫深度搜索也是回溯描的方法,每次扫描在当前树中找一个节点,看它的邻居格子是否在树中,如果还没在树中,就将该邻居格子加入树中,如果已在树中,就看下一个邻居格子,如果该节点所有邻居格子都在树中了,就找下一个节点,继续同样的操作。另外为了让迷宫生成的随机,扫描的起始位置是随机的就可以了。但是,该方法生成的迷宫中的路径总是不够深,没有我想要的曲折深入的效果。毕竟是类似广度搜索的方法。而且,这样做总还像是靠蛮力,算法不够聪明简洁。
最后,我终于想到使用深度搜索。。大概是因为数据结构已经学过了一年,又没太练,忘了不少,所以一直没想到这个应该第一想到的方法。。
随机选择一个格子作为根节点,从它开始随机地深度搜索前进,开出一条路来,直到无路可走了,退回一步,换另一条路,再走到无路可走,回退一步,换另一条……如此循环往复,直到完全无路可走。。。其实也还是回溯。
在程序里就是以下过程(详见代码中的createmaze()函数):
随机选择一个格子作为根节点,将它压进栈里。
然后在栈不为空的时候执行以下循环:
取出一个格子,将它的intree标志设置为1,然后将它的所有不在树中的邻居格子压进栈里(顺序随机),并且让这些邻居格子的father指向该格子。
解决了这两个问题,其余的画迷宫、显示路径、小球移动也就比较简单了。
代码
package maze; import java.awt.color; import java.awt.graphics; import java.awt.event.keyadapter; import java.awt.event.keyevent; import java.util.random; import java.util.stack; import javax.swing.jframe; import javax.swing.joptionpane; import javax.swing.jpanel; class lattice { static final int intree = 1; static final int notintree = 0; private int x = -1; private int y = -1; private int flag = notintree; private lattice father = null; public lattice(int xx, int yy) { x = xx; y = yy; } public int getx() { return x; } public int gety() { return y; } public int getflag() { return flag; } public lattice getfather() { return father; } public void setfather(lattice f) { father = f; } public void setflag(int f) { flag = f; } public string tostring() { return new string("(" + x + "," + y + ")\n"); } } public class maze extends jpanel { private static final long serialversionuid = -8300339045454852626l; private int num, width, padding;// width 每个格子的宽度和高度 private lattice[][] maze; private int ballx, bally; private boolean drawpath = false; maze(int m, int wi, int p) { num = m; width = wi; padding = p; maze = new lattice[num][num]; for (int i = 0; i <= num - 1; i++) for (int j = 0; j <= num - 1; j++) maze[i][j] = new lattice(i, j); createmaze(); setkeylistener(); this.setfocusable(true); } private void init() { for (int i = 0; i <= num - 1; i++) for (int j = 0; j <= num - 1; j++) { maze[i][j].setfather(null); maze[i][j].setflag(lattice.notintree); } ballx = 0; bally = 0; drawpath = false; createmaze(); // setkeylistener(); this.setfocusable(true); repaint(); } public int getcenterx(int x) { return padding + x * width + width / 2; } public int getcentery(int y) { return padding + y * width + width / 2; } public int getcenterx(lattice p) { return padding + p.gety() * width + width / 2; } public int getcentery(lattice p) { return padding + p.getx() * width + width / 2; } private void checkiswin() { if (ballx == num - 1 && bally == num - 1) { joptionpane.showmessagedialog(null, "you win !", "你走出了迷宫。", joptionpane.plain_message); init(); } } synchronized private void move(int c) { int tx = ballx, ty = bally; // system.out.println(c); switch (c) { case keyevent.vk_left : ty--; break; case keyevent.vk_right : ty++; break; case keyevent.vk_up : tx--; break; case keyevent.vk_down : tx++; break; case keyevent.vk_space : if (drawpath == true) { drawpath = false; } else { drawpath = true; } break; default : } if (!isoutofborder(tx, ty) && (maze[tx][ty].getfather() == maze[ballx][bally] || maze[ballx][bally].getfather() == maze[tx][ty])) { ballx = tx; bally = ty; } } private void setkeylistener() { this.addkeylistener(new keyadapter() { public void keypressed(keyevent e) { int c = e.getkeycode(); move(c); repaint(); checkiswin(); } }); } private boolean isoutofborder(lattice p) { return isoutofborder(p.getx(), p.gety()); } private boolean isoutofborder(int x, int y) { return (x > num - 1 || y > num - 1 || x < 0 || y < 0) ? true : false; } private lattice[] getneis(lattice p) { final int[] adds = {-1, 0, 1, 0, -1};// 顺序为上右下左 if (isoutofborder(p)) { return null; } lattice[] ps = new lattice[4];// 顺序为上右下左 int xt; int yt; for (int i = 0; i <= 3; i++) { xt = p.getx() + adds[i]; yt = p.gety() + adds[i + 1]; if (isoutofborder(xt, yt)) continue; ps[i] = maze[xt][yt]; } return ps; } private void createmaze() { random random = new random(); int rx = math.abs(random.nextint()) % num; int ry = math.abs(random.nextint()) % num; stack<lattice> s = new stack<lattice>(); lattice p = maze[rx][ry]; lattice neis[] = null; s.push(p); while (!s.isempty()) { p = s.pop(); p.setflag(lattice.intree); neis = getneis(p); int ran = math.abs(random.nextint()) % 4; for (int a = 0; a <= 3; a++) { ran++; ran %= 4; if (neis[ran] == null || neis[ran].getflag() == lattice.intree) continue; s.push(neis[ran]); neis[ran].setfather(p); } } // changefather(maze[0][0],null); } private void changefather(lattice p, lattice f) { if (p.getfather() == null) { p.setfather(f); return; } else { changefather(p.getfather(), p); } } private void clearfence(int i, int j, int fx, int fy, graphics g) { int sx = padding + ((j > fy ? j : fy) * width), sy = padding + ((i > fx ? i : fx) * width), dx = (i == fx ? sx : sx + width), dy = (i == fx ? sy + width : sy); if (sx != dx) { sx++; dx--; } else { sy++; dy--; } g.drawline(sx, sy, dx, dy); } protected void paintcomponent(graphics g) { super.paintcomponent(g); for (int i = 0; i <= num; i++) { g.drawline(padding + i * width, padding, padding + i * width, padding + num * width); } for (int j = 0; j <= num; j++) { g.drawline(padding, padding + j * width, padding + num * width, padding + j * width); } g.setcolor(this.getbackground()); for (int i = num - 1; i >= 0; i--) { for (int j = num - 1; j >= 0; j--) { lattice f = maze[i][j].getfather(); if (f != null) { int fx = f.getx(), fy = f.gety(); clearfence(i, j, fx, fy, g); } } } g.drawline(padding, padding + 1, padding, padding + width - 1); int last = padding + num * width; g.drawline(last, last - 1, last, last - width + 1); g.setcolor(color.red); g.filloval(getcenterx(bally) - width / 3, getcentery(ballx) - width / 3, width / 2, width / 2); if (drawpath == true) drawpath(g); } private void drawpath(graphics g) { color path_color = color.orange, both_path_color = color.pink; if (drawpath == true) g.setcolor(path_color); else g.setcolor(this.getbackground()); lattice p = maze[num - 1][num - 1]; while (p.getfather() != null) { p.setflag(2); p = p.getfather(); } g.filloval(getcenterx(p) - width / 3, getcentery(p) - width / 3, width / 2, width / 2); p = maze[0][0]; while (p.getfather() != null) { if (p.getflag() == 2) { p.setflag(3); g.setcolor(both_path_color); } g.drawline(getcenterx(p), getcentery(p), getcenterx(p.getfather()), getcentery(p.getfather())); p = p.getfather(); } g.setcolor(path_color); p = maze[num - 1][num - 1]; while (p.getfather() != null) { if (p.getflag() == 3) break; g.drawline(getcenterx(p), getcentery(p), getcenterx(p.getfather()), getcentery(p.getfather())); p = p.getfather(); } } public static void main(string[] args) { final int n = 30, width = 600, padding = 20, lx = 200, ly = 100; jpanel p = new maze(n, (width - padding - padding) / n, padding); jframe frame = new jframe("maze(按空格键显示或隐藏路径)"); frame.getcontentpane().add(p); frame.setdefaultcloseoperation(jframe.exit_on_close); frame.setsize(width + padding, width + padding + padding); frame.setlocation(lx, ly); frame.setvisible(true); } }
上一篇: PHP构造二叉树算法示例
下一篇: Leetcode134:加油站