老程序员教你一天时间完成Java迷宫小游戏

目录

  • 效果图
  • 实现思路
  • 迷宫算法(网上参考的)
  • 相关图示说明
  • 代码实现
    • 创建窗口
    • 创建菜单及菜单选项
  • 绘制迷宫的每个单元
    • 计算并打通迷宫
    • 绘制起点终点
  • 加入键盘移动监听
    • 收尾
  • 总结

    效果图 老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    实现思路
    1.创建运行窗口。
    2.创建菜单。
    3.绘制迷宫的每个单元。
    4.通过算法计算迷宫路径,并打通路径,形成迷宫。
    5.绘制起点终点。
    6.添加键盘事件控制起点方块移动。
    7.收尾。

    迷宫算法(网上参考的) 1.将起点作为当前迷宫单元并标记为已访问
    2.当还存在未标记的迷宫单元,进行循环

    1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元

    (1).随机选择一个未访问的相邻迷宫单元

    (2).将当前迷宫单元入栈

    (3).移除当前迷宫单元与相邻迷宫单元的墙

    (4).标记相邻迷宫单元并用它作为当前迷宫单元

    2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空

    (1).栈顶的迷宫单元出栈

    (2).令其成为当前迷宫单元
    **这个算法叫做“深度优先”,简单来说,就是从起点开始走,寻找它的上下左右4个邻居,然后随机一个走,到走不通的时候就返回上一步继续走,直到全部单元都走完。 **

    相关图示说明 每个单元的墙,分为上墙、右墙、下墙、左墙,把这些墙用长度为4的数组表示,元素的值为true则表示墙存在,否则墙不存在,代码里数组的下标方式来确定墙是否存在。

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片

    单元是根据行列来创建的,会用到双循环,类似表格,比如第二行用 i 来表示的话就是 1,第3列用 j 来表示就是2,那第二行第3列的元素组合起来就是(1,2)

    【老程序员教你一天时间完成Java迷宫小游戏】老程序员教你一天时间完成Java迷宫小游戏
    文章图片

    那同理它的上邻居就是(0,2),右邻居(1,3),下邻居(2,2),左邻居(1,1),也就是上下邻居是 i 减加1,左右邻居是 j 减加1。

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片

    正方形4个点的坐标分别为(x1,y1)(x2,y2)(x3,y3)(x4,y4),计算坐标的公式为(其中start为相对偏移量,为了让迷宫两边有些空隙):
    //i代表行 j代表列 h为单元高度//左上角坐标this.x1=start+j*h; this.y1=start+i*h; //右上角坐标this.x2=start+(j+1)*h; this.y2=start+i*h; //右下角坐标this.x3=start+(j+1)*h; this.y3=start+(i+1)*h; //左下角坐标this.x4=start+j*h; this.y4=start+(i+1)*h;

    计算坐标,假如每个正方形的宽高都是40,那么(1,2)这个单元的坐标如下图:

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    5. 墙的处理,之前说到墙是以一个4个元素的数组来表示的,比如数组为:[true,true,true,true],则图为:

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    如果数组为[false,true,true,true],则图为:

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    6. 如果要联通右边的邻居要怎么做呢?当前单元去除右墙,右边单元去除左墙,这样就联通了。

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    去除后就这样,以此类推

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    代码实现
    创建窗口
    首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。
    import javax.swing.JFrame; /** *窗体类 */public class GameFrame extends JFrame { //构造方法 public GameFrame(){setTitle("迷宫"); //设置标题setSize(420, 470); //设置窗体大小setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭后进程退出setLocationRelativeTo(null); //居中setResizable(false); //不允许变大//setVisible(true); //设置显示窗体 }}

    创建面板容器GamePanel继承至JPanel
    import javax.swing.JMenuBar; import javax.swing.JPanel; /* * 画布类 */public class GamePanel extends JPanel{ private JMenuBar jmb = null; private GameFrame mainFrame = null; private GamePanel panel = null; private String gameFlag="start"; //游戏状态 //构造方法 public GamePanel(GameFrame mainFrame){this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; }}

    再创建一个Main类,来启动这个窗口。
    //Main类public class Main { public static void main(String[] args) {GameFrame frame = new GameFrame(); GamePanel panel = new GamePanel(frame); frame.add(panel); frame.setVisible(true); }}

    右键执行这个Main类,窗口建出来了

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    创建菜单及菜单选项
    创建菜单
    private Font createFont(){ return new Font("思源宋体",Font.BOLD,18); }//创建菜单private void createMenu() { //创建JMenuBar jmb = new JMenuBar(); //取得字体 Font tFont = createFont(); //创建游戏选项 JMenu jMenu1 = new JMenu("游戏"); jMenu1.setFont(tFont); //创建帮助选项 JMenu jMenu2 = new JMenu("帮助"); jMenu2.setFont(tFont); JMenuItem jmi1 = new JMenuItem("新游戏"); jmi1.setFont(tFont); JMenuItem jmi2 = new JMenuItem("退出"); jmi2.setFont(tFont); //jmi1 jmi2添加到菜单项“游戏”中 jMenu1.add(jmi1); jMenu1.add(jmi2); JMenuItem jmi3 = new JMenuItem("操作帮助"); jmi3.setFont(tFont); JMenuItem jmi4 = new JMenuItem("胜利条件"); jmi4.setFont(tFont); //jmi13 jmi4添加到菜单项“游戏”中 jMenu2.add(jmi3); jMenu2.add(jmi4); jmb.add(jMenu1); jmb.add(jMenu2); mainFrame.setJMenuBar(jmb); //添加监听 jmi1.addActionListener(this); jmi2.addActionListener(this); jmi3.addActionListener(this); jmi4.addActionListener(this); //设置指令 jmi1.setActionCommand("restart"); jmi2.setActionCommand("exit"); jmi3.setActionCommand("help"); jmi4.setActionCommand("win"); }

    实现ActionListener并重写方法actionPerformed

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    此时GamePanel是报错的,需重写actionPerformed方法。

    actionPerformed方法的实现
    @Overridepublic void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); System.out.println(command); UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18))); UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18))); if ("exit".equals(command)) {Object[] options = { "确定", "取消" }; int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,options, options[0]); if (response == 0) {System.exit(0); } }else if("restart".equals(command)){restart(); }else if("help".equals(command)){JOptionPane.showMessageDialog(null, "通过键盘的上下左右来移动","提示!", JOptionPane.INFORMATION_MESSAGE); }else if("win".equals(command)){JOptionPane.showMessageDialog(null, "移动到终点获得胜利","提示!", JOptionPane.INFORMATION_MESSAGE); } }

    此时的GamePanel代码如下:
    package main; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.plaf.FontUIResource; /* * 画布类 */public class GamePanel extends JPanel implements ActionListener{ private JMenuBar jmb = null; private GameFrame mainFrame = null; private GamePanel panel = null; private String gameFlag="start"; //游戏状态 //构造方法 public GamePanel(GameFrame mainFrame){this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; //创建菜单createMenu(); } private Font createFont(){return new Font("思源宋体",Font.BOLD,18); } //创建菜单 private void createMenu() {//创建JMenuBarjmb = new JMenuBar(); //取得字体Font tFont = createFont(); //创建游戏选项JMenu jMenu1 = new JMenu("游戏"); jMenu1.setFont(tFont); //创建帮助选项JMenu jMenu2 = new JMenu("帮助"); jMenu2.setFont(tFont); JMenuItem jmi1 = new JMenuItem("新游戏"); jmi1.setFont(tFont); JMenuItem jmi2 = new JMenuItem("退出"); jmi2.setFont(tFont); //jmi1 jmi2添加到菜单项“游戏”中jMenu1.add(jmi1); jMenu1.add(jmi2); JMenuItem jmi3 = new JMenuItem("操作帮助"); jmi3.setFont(tFont); JMenuItem jmi4 = new JMenuItem("胜利条件"); jmi4.setFont(tFont); //jmi13 jmi4添加到菜单项“游戏”中jMenu2.add(jmi3); jMenu2.add(jmi4); jmb.add(jMenu1); jmb.add(jMenu2); mainFrame.setJMenuBar(jmb); //添加监听jmi1.addActionListener(this); jmi2.addActionListener(this); jmi3.addActionListener(this); jmi4.addActionListener(this); //设置指令jmi1.setActionCommand("restart"); jmi2.setActionCommand("exit"); jmi3.setActionCommand("help"); jmi4.setActionCommand("win"); } @Override public void actionPerformed(ActionEvent e) {String command = e.getActionCommand(); System.out.println(command); UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18))); UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18))); if ("exit".equals(command)) {Object[] options = { "确定", "取消" }; int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,options, options[0]); if (response == 0) {System.exit(0); } }else if("restart".equals(command)){restart(); }else if("help".equals(command)){JOptionPane.showMessageDialog(null, "通过键盘的上下左右来移动","提示!", JOptionPane.INFORMATION_MESSAGE); }else if("win".equals(command)){JOptionPane.showMessageDialog(null, "移动到终点获得胜利","提示!", JOptionPane.INFORMATION_MESSAGE); } } void restart(){ }}

    运行它

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    绘制迷宫的每个单元 初始化相关参数
    public final int ROWS=20; //行public final int COLS=20; //列public final int H=20; //每一块的宽高Block[][] blocks = null; //存储每个单元的二维数组

    创建迷宫单元类(如果对坐标计算不明白,可以往上翻,有图示说明解释)
    import java.awt.Graphics; import java.util.ArrayList; import java.util.List; /* * 迷宫单元类 */public class Block { private GamePanel panel = null; private int i=0; //二维数组的下标i private int j=0; //二维数组的下标j private int h=0; //宽高 private int start=6; //偏移像素 //4个顶点坐标 private int x1=0; //x1坐标 private int y1=0; //y1坐标 private int x2=0; //x2坐标 private int y2=0; //y2坐标 private int x3=0; //x3坐标 private int y3=0; //y3坐标 private int x4=0; //x4坐标 private int y4=0; //y4坐标 //上下左右4个墙是否显示,true:显示,false:隐藏 boolean[] walls=new boolean[4]; private boolean visited=false; //是否被访问 //构造 public Block(int i,int j,int h,GamePanel panel){this.i=i; this.j=j; this.h=h; this.panel=panel; //计算4个顶点的坐标init(); } //计算4个顶点的坐标 private void init() {//i代表行 j代表列//左上角坐标this.x1=start+j*h; this.y1=start+i*h; //右上角坐标this.x2=start+(j+1)*h; this.y2=start+i*h; //右下角坐标this.x3=start+(j+1)*h; this.y3=start+(i+1)*h; //左下角坐标this.x4=start+j*h; this.y4=start+(i+1)*h; //默认上下左右4个墙都显示walls[0]=true; walls[1]=true; walls[2]=true; walls[3]=true; } //绘制指示器的方法 public void draw(Graphics g) {//绘制迷宫块drawBlock(g); } //绘制迷宫块 private void drawBlock(Graphics g) {//判断上、右、下、左 的墙,true的话墙就会有,否则墙就没有boolean top= walls[0]; boolean right= walls[1]; boolean bottom = walls[2]; boolean left= walls[3]; if(top){//绘制上墙g.drawLine(x1, y1, x2, y2); }if(right){//绘制右墙g.drawLine(x2, y2, x3, y3); }if(bottom){//绘制下墙g.drawLine(x3, y3, x4, y4); }if(left){//绘制左墙g.drawLine(x4, y4, x1, y1); } }}

    在GamePanel类中创建方法createBlocks
    //创建数组内容private void createBlocks() { blocks = new Block[ROWS][COLS]; Block block ; for (int i = 0; i < ROWS; i++) {for (int j = 0; j < COLS; j++) {block = new Block(i, j,H,this); blocks[i][j]=block; } }}

    在构造函数中调用此方法
    //构造方法public GamePanel(GameFrame mainFrame){ this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; //创建菜单 createMenu(); //创建数组内容 createBlocks(); }

    在GamePanel中重新paint方法,绘制这些方块
    public void paint(Graphics g) { super.paint(g); //绘制网格 drawBlock(g); }//绘制迷宫块private void drawBlock(Graphics g) { Block block ; for (int i = 0; i < ROWS; i++) {for (int j = 0; j < COLS; j++) {block = blocks[i][j]; if(block!=null){block.draw(g); }} }}

    运行可以看到一个个的方形绘制出来了

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    计算并打通迷宫
    给每个单元都增加邻居查找方法(Block类中)
    //查找当前单元是否有未被访问的邻居单元public List findNeighbors() { //邻居分为上下左右 List res= new ArrayList(); //返回的数组 Block top= this.getNeighbor(0,false); Block right= this.getNeighbor(1,false); Block bottom = this.getNeighbor(2,false); Block left= this.getNeighbor(3,false); if(top!=null){res.add(top); } if(right!=null){res.add(right); } if(bottom!=null){res.add(bottom); } if(left!=null){res.add(left); } return res; //返回邻居数组}//根据方向,获得邻居public Block getNeighbor(int type,boolean lose_visited) { Block neighbor; int ti=0,tj=0; if(type==0){ti = this.i-1; tj = this.j; }else if(type==1){ti = this.i; tj = this.j+1; }else if(type==2){ti = this.i+1; tj = this.j; }else if(type==3){ti = this.i; tj = this.j-1; } Block[][] blocks = panel.blocks; if(ti<0 || tj<0 || ti>=panel.ROWS || tj>=panel.COLS){//超出边界了neighbor = null; }else{//首先找到这个邻居neighbor = blocks[ti][tj]; //判断是否被访问,如果被访问了返回nullif(neighbor.visited && !lose_visited){//lose_visited等于true表示忽略访问neighbor = null; } } return neighbor; }

    计算

    跟着算法来写的代码,唯一要注意的是我设置了一个值unVisitedCount,初始值为所有单元的数量,每当一个单元被标记为已访问后,这个值就递减1,当值为0后就终止循环,结束算法。
    //线路的计算处理private void computed(){ /* 1.将起点作为当前迷宫单元并标记为已访问 2.当还存在未标记的迷宫单元,进行循环1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元(1).随机选择一个未访问的相邻迷宫单元(2).将当前迷宫单元入栈(3).移除当前迷宫单元与相邻迷宫单元的墙(4).标记相邻迷宫单元并用它作为当前迷宫单元2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空(1).栈顶的迷宫单元出栈(2).令其成为当前迷宫单元*/ Random random = new Random(); Stack stack = new Stack(); //栈 Block current = blocks[0][0]; //取第一个为当前单元 current.setVisited(true); //标记为已访问 int unVisitedCount=ROWS*COLS-1; //因为第一个已经设置为访问了 List neighbors ; //定义邻居 Block next; while(unVisitedCount>0){neighbors = current.findNeighbors(); //查找邻居集合(未被访问的)if(neighbors.size()>0){//如果当前迷宫单元有未被访问过的的相邻的迷宫单元//随机选择一个未访问的相邻迷宫单元int index = random.nextInt(neighbors.size()); next = neighbors.get(index); //将当前迷宫单元入栈stack.push(current); //移除当前迷宫单元与相邻迷宫单元的墙this.removeWall(current,next); //标记相邻迷宫单元并用它作为当前迷宫单元next.setVisited(true); //标记一个为访问,则计数器递减1unVisitedCount--; //递减current = next; }else if(!stack.isEmpty()){//如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空/*1.栈顶的迷宫单元出栈2.令其成为当前迷宫单元*/Block cell = stack.pop(); current = cell; } }}

    移除墙
    //移除当前迷宫单元与相邻迷宫单元的墙private void removeWall(Block current, Block next) { if(current.getI()==next.getI()){//横向邻居if(current.getJ()>next.getJ()){//匹配到的是左边邻居//左边邻居的话,要移除自己的左墙和邻居的右墙current.walls[3]=false; next.walls[1]=false; }else{//匹配到的是右边邻居//右边邻居的话,要移除自己的右墙和邻居的左墙current.walls[1]=false; next.walls[3]=false; } }else if(current.getJ()==next.getJ()){//纵向邻居if(current.getI()>next.getI()){//匹配到的是上边邻居//上边邻居的话,要移除自己的上墙和邻居的下墙current.walls[0]=false; next.walls[2]=false; }else{//匹配到的是下边邻居//下边邻居的话,要移除自己的下墙和邻居的上墙current.walls[2]=false; next.walls[0]=false; } }}

    在构造函数中调用computed方法
    //构造方法public GamePanel(GameFrame mainFrame){ this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; //创建菜单 createMenu(); //创建数组内容 createBlocks(); //计算处理线路 computed(); }

    运行效果

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    绘制起点终点
    创建Rect类
    package main; import java.awt.Color; import java.awt.Graphics; //开始结束方块public class Rect { private int i=0; //二维数组的下标i private int j=0; //二维数组的下标j private int x=0; //x坐标 private int y=0; //y坐标 private int h=0; //宽高 private int start=6; //偏移像素 private String type=""; //start end public Rect(int i,int j,int h,String type){this.i=i; this.j=j; this.h=h; this.type=type; } //初始化 private void init() {this.x=start+j*h+2; this.y=start+i*h+2; } //绘制 void draw(Graphics g){//计算x、y坐标init(); Color oColor = g.getColor(); if("start".equals(type)){//红色g.setColor(Color.red); }else{g.setColor(Color.blue); }g.fillRect(x, y, h-3, h-3); g.setColor(oColor); } //移动 public void move(int type, Block[][] blocks,GamePanel panel) {//根据当前start方形,获得对应的迷宫单元Block cur = blocks[this.i][this.j]; boolean wall = cur.walls[type]; //得到对应的那面墙 if(!wall){//得到移动方块对应的单元Block next = cur.getNeighbor(type,true); if(next!=null){this.i = next.getI(); this.j = next.getJ(); panel.repaint(); //判断如果i,j等于终点的,则表示获得成功if(this.i==panel.end.i && this.j==panel.end.j){panel.gameWin(); }}} } public int getI() {return i; } public void setI(int i) {this.i = i; } public int getJ() {return j; } public void setJ(int j) {this.j = j; }}

    在GamePanel类中创建方法,并在构造中调用。
    //创建开始结束的方形private void createRects() { start = new Rect(0, 0, H, "start") ; end = new Rect(ROWS-1, COLS-1, H, "end") ; }

    //构造方法public GamePanel(GameFrame mainFrame){ this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; //创建菜单 createMenu(); //创建数组内容 createBlocks(); //计算处理线路 computed(); //创建开始结束的方形 createRects(); }

    在paint方法中绘制
    @Overridepublic void paint(Graphics g) { super.paint(g); //绘制网格 drawBlock(g); //绘制开始结束方向 drawRect(g); }//绘制开始结束方块private void drawRect(Graphics g) { end.draw(g); start.draw(g); }

    运行一下

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    加入键盘移动监听 创建监听方法
    //添加键盘监听private void createKeyListener() { KeyAdapter l = new KeyAdapter() {//按下@Overridepublic void keyPressed(KeyEvent e) {if(!"start".equals(gameFlag)) return ; int key = e.getKeyCode(); switch (key) {//向上case KeyEvent.VK_UP:case KeyEvent.VK_W:if(start!=null) start.move(0,blocks,panel); break; //向右 case KeyEvent.VK_RIGHT:case KeyEvent.VK_D:if(start!=null) start.move(1,blocks,panel); break; //向下case KeyEvent.VK_DOWN:case KeyEvent.VK_S:if(start!=null) start.move(2,blocks,panel); break; //向左case KeyEvent.VK_LEFT:case KeyEvent.VK_A:if(start!=null) start.move(3,blocks,panel); break; }}//松开@Overridepublic void keyReleased(KeyEvent e) {} }; //给主frame添加键盘监听 mainFrame.addKeyListener(l); }

    在构造中调用
    //构造方法public GamePanel(GameFrame mainFrame){ this.setLayout(null); this.setOpaque(false); this.mainFrame=mainFrame; this.panel =this; //创建菜单 createMenu(); //创建数组内容 createBlocks(); //计算处理线路 computed(); //创建开始结束的方形 createRects(); //添加键盘事件监听 createKeyListener(); }

    运行

    老程序员教你一天时间完成Java迷宫小游戏
    文章图片


    收尾
    此时代码已经基本完成,加入游戏胜利、重新开始等方法即可
    //重新开始void restart() { /*参数重置 1.游戏状态 2.迷宫单元重置 3.重新计算线路 */ //1.游戏状态 gameFlag="start"; //2.迷宫单元重置 Block block ; for (int i = 0; i < ROWS; i++) {for (int j = 0; j < COLS; j++) {block = blocks[i][j]; if(block!=null){block.setVisited(false); block.walls[0]=true; block.walls[1]=true; block.walls[2]=true; block.walls[3]=true; }} } //3.计算处理线路 computed(); //开始方块归零 start.setI(0); start.setJ(0); //重绘 repaint(); }


    总结 本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

      推荐阅读