需求分析
(1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
(2)要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制;
(3)系统走迷宫路径基于逆二叉树搜索,输出走迷宫的路径并显示。
(4)设计交互友好的游戏图形界面
编程语言及开发工具
java 基于IDEA
利用深度遍历的思想。访问到一个节点时,搜索这个节点没有被访问过的相邻节点,选择一个继续做同样的 *** 作,直到没有邻节点为止再回溯到上一个访问的节点,并选择另外的邻节点。
算法思想
- 从第一个单元开始,检查当前单元是否堵塞(即周围四个单元都是已被访问或不可访问)
- 若不堵塞,则随机选择一个相邻单元作为下一单元,检查是否可访问
- 若可访问,则打破当前单元与下一单元之间的墙壁,将当前单元入栈,将下一单元作为当前单元;若不不可访问,则回到步骤2
- 若当前单元堵塞,则回退至上一单元
- 如此遍历,直到所有的单元都被访问
面板设计
- 利用Java Swing的相关函数,进行实线的绘画,以线为墙,实心圆为角色,空白部分为路来画迷宫。
- 利用Java函数addKeyListener来监听按键↑↓←→的输入,只有当角色周围是路上,才能移动。当角色到达终点时进入下一关。
package com.company; import java.awt.Color; //颜色相关 *** 作的类; import java.awt.Graphics; //用以基本计何图形的绘制; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; //随机类 import java.util.Stack; //栈 import javax.swing.*; import javax.imageio.ImageIO; class grid { static final int InTree = 1; static final int NotIntree = 0; private int x = -1; private int y = -1; private int flag = NotIntree; private grid father = null; public grid(int xx, int yy) { //构造函数; x = xx; y = yy; } public int getX() { //获取该方格的横坐标;(由于不可直接用对象访问私有变量); return x; } public int getY() { //获取纵坐标; return y; } public int getFlag() { //获取标志位; return flag; } public grid getFather() { //获取方格对象的父节点; return father; } public void setFather(grid f) { //修改方格对象的父节点; father = f; } public void setFlag(int f) { flag = f; //修改标志位; } public String toString() { //以字符串形式导出坐标; return new String("(" + x + "," + y + ")n"); } } public class Main extends JPanel { private static final long serialVersionUID = -8300339045454852626L; private int NUM, width, padding; // NUM为界面总边长,width为每个格子的边长;padding为内边距; private grid[][] maze; private int myX, myY; //定义了两个点; int sum = 0; //记录步数; int rand = 0; //记录关数; //JButton Level1,Level2,Level3; private boolean drawPath = false; //路径标志位; Main(int n, int w, int p) { //Main的构造方法; NUM = n; //窗口边长; width = w; //子方格边长; padding = p; //边线长度 maze = new grid[NUM][NUM]; //调用Lattice的构造函数,对尾端的小方格构造; for (int i = 0; i <= NUM - 1; i++) //对除最后一格外的每一个坐标认定为一个小方格对象并构造; for (int j = 0; j <= NUM - 1; j++) maze[i][j] = new grid(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); //设置每个小方格的父节点为null; maze[i][j].setFlag(grid.NotIntree); //设置每个方格的标志位; } myX = 0; myY = 0; drawPath = false; //路径绘制标识; createMaze(); this.setFocusable(true); //使能控件获得焦点能力 repaint(); //重新绘制; } public int getCenterX(int x) { return padding + x * width + width / 2; //内边距属性;第x+1位的小方格中心点横坐标位置; } public int getCenterY(int y) { return padding + y * width + width / 2; //内边距属性;第y+1位的小方格中心点纵坐标位置; } public int getCenterX(grid p) { return padding + p.getY() * width + width / 2; //得到方格对象P的第x+1位的小方格中心点横坐标位置; } public int getCenterY(grid p) { return padding + p.getX() * width + width / 2; //得到方格对象P的第x+1位的小方格中心点纵坐标位置; } private void checkIsWin() { if (myX == NUM - 1 && myY == NUM - 1) { //如果当前方格位置坐标对于末尾方格的坐标; JOptionPane.showMessageDialog(null, "你走出了迷宫 !"+"一共走了"+sum+"步"); rand++; sum = 0; // Object[] options ={ "继续", "退出" }; //自定义按钮上的文字 int m = JOptionPane.show/confirm/iDialog(null, "你已经通过"+rand+"关,是否要继续闯关?", "是否继续",JOptionPane.YES_NO_OPTION); //返回值为0或1 // int m = JOptionPane.showOptionDialog(null, "你是否要继续闯关?", "第"+rand+"关",JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if(m == 0) { init(); } if(m == 1){ JOptionPane.showMessageDialog(null, "本次你一共通过了"+rand+"关,下次继续挑战"); } } } synchronized private void move(int c ) { int tx = myX, ty = myY; switch (c) { case KeyEvent.VK_LEFT : //监控按键,对应按键有对应的当前位置坐标的修改; ty--; sum++; // g.drawLine(myX,myY,tx,ty); break; case KeyEvent.VK_RIGHT : ty++; sum++; break; case KeyEvent.VK_UP : tx--; sum++; break; case KeyEvent.VK_DOWN : tx++; sum++; break; case KeyEvent.VK_ENTER: //如果是回车键; if (drawPath == true) { //如果路径绘制标识符为1,就置0; drawPath = false; } else { drawPath = true; //否则就把路径标识符置1; } break; default : } if (!isOutOfBorder(tx, ty) && (maze[tx][ty].getFather() == maze[myX][myY] || maze[myX][myY].getFather() == maze[tx][ty])) { //是否越界判断; myX = tx; //越界则不动; myY = ty; } } private void setKeyListener() { //设置按键监视器; this.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { //按键事件; int c = e.getKeyCode(); //获取当前按键信息; move(c); //根据按键信息继续坐标操作; repaint(); //将操作后的信息修正展示; checkIsWin(); //检测是否赢了; } }); } private boolean isOutOfBorder(grid 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 grid[] getNeis(grid p) { final int[] adds = {-1, 0, 1, 0, -1}; // 顺序为左上右下 一个数组; if (isOutOfBorder(p)) { //未越界不操作; return null; } grid[] ps = new grid[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; //生成【0,num】间的随机数做横纵坐标; int ry = Math.abs(random.nextInt()) % NUM; Stack四、程序功能演示s = new Stack (); //生成栈; grid p = maze[rx][ry]; //由随机坐标生成随机方格对象; grid neis[] = null; //生成方格对象数组neis; s.push(p); //随机方格对象压栈; while (!s.isEmpty()) { //如果栈非空; p = s.pop(); p.setFlag(grid.InTree); //将之前随机生成的点都修改标志位; neis = getNeis(p); //获取当前方格的全部可行方向,存入neis中; int ran = Math.abs(random.nextInt()) % 4; //产生【0,3】的随机整数; for (int a = 0; a <= 3; a++) { ran++; ran %= 4; //每次修改ran的值并保证其在【0,4】区间 if (neis[ran] == null || neis[ran].getFlag() == grid.InTree) continue; //如果不存在或者已经标记,就不操作; s.push(neis[ran]); //对于可用的点都入栈操作; neis[ran].setFather(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); //super 调用父类的方法; for (int i = 0; i <= NUM; i++) { g.drawLine(padding + i * width, padding,padding + i * width, padding + NUM * width); } //需要两个坐标(x1,y1)和(x2,y2)绘制全局格框(注意空出出入口); 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--) { grid 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); //补充绘制外边框; //插入图片表示玩家当前所在位置 BufferedImage block = null; try { block = ImageIO.read(this.getClass().getResourceAsStream("green.png")); g.drawImage(block,getCenterX(myY) - width / 3, getCenterY(myX) - width / 3,width / 2, width / 2,null); } catch (IOException e) { e.printStackTrace(); } if (drawPath == true) drawPath(g); } //绘制迷宫路径; private void drawPath(Graphics g) { //完整路径寻路;逆二叉树搜索; if (drawPath == true) g.setColor(new Color(122,188,50)); //显示正确的路径; else g.setColor(this.getBackground()); //不需要答案提示情况下显示背景颜色(白色) grid p = maze[NUM - 1][NUM - 1]; //生成对象P为最后一个对象位置; while (p.getFather() != null) { //当存在父节点时; p.setFlag(2); //将节点标志位置2; p = p.getFather(); //再将当前节点更迭至其父节点处 } //经过一遍遍历,从末尾格局父节点唯一原则,生成了一条从末尾遍历至开头的通路,路上全标记为2; p = maze[0][0]; //对p置首位; while (p.getFather() != null) { //存在父节点时; if (p.getFlag() == 2) { //如果对象标志位为2 p.setFlag(3); //对象标志位置为3; g.setColor(new Color(236,236,236)); } g.drawLine(getCenterX(p), getCenterY(p), getCenterX(p.getFather()), getCenterY(p.getFather())); p = p.getFather(); //将p点对象更迭为其父类对象位置; } g.setColor(new Color(122,188,50)); //绘制颜色(颜色覆盖); 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=20,width = 600, padding = 18, LX = 400, LY = 100; // n为边界的方格数目,width设置窗口边长; JPanel p = new Main(n, (width - padding - padding) / n, padding); Jframe frame = new Jframe("迷宫游戏"); frame.getContentPane().add(p); frame.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE); //设置窗口可关闭; frame.setSize(width + padding, width + padding + padding); //设置窗口大小; frame.setLocation(LX, LY); //设置窗口位置; frame.setVisible(true); //通过 setVisible()并设置参数为true,把内存中的窗口显示在屏幕上; } }
初始界面如下,玩家按下回车键后会出现路径提示
再次按下回车键可关闭提示。
到达终点后显示总共步数。
按下确定后提示选择“继续”或是“结束”
由于时间关系本项目仍存在bug,例如不继续闯关会停留在终点,下次可以在终点继续闯关……
面板设计方面,还未添加难度选择模块。
在期末考试结束后,有时间我会继续改进和维护这个项目的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)