计算机软件实习

迷宫游戏开发

本次实验要求能随机生成一个迷宫,并且可以通过控制人物上下左右行走来通过迷宫;
实验还要求显示出最短路径。

随机生成迷宫

这里我采用的是深度优先搜索算法(DFS)

首先,将所有的点初始化为墙,然后随机生成蝴蝶的位置,再将相应点修改成蝴蝶,终点的位置修改好;然后,从蝴蝶位置出发,利用深度优先遍历算法,通过随机遍历方向,将所有与蝴蝶位置X、Y坐标差值均为偶数的点遍历出来,并修改成路,最终连接成一条能联通所有这些点的通道。在此过程中,当我们需要由一个点连接到下一个点时,仅需要打通隔开它们的那堵墙(将它修改成路)即可。

在这里插入图片描述

代码:
//深度优先遍历创建迷宫
    public static void creatMaze() {
        m = map.m;
        n = map.n;
        //遍历前初始化工作
        isBeVisit = new boolean[m * n];
        for (int i = 0; i < m * n; i++) isBeVisit[i] = false;      //是否已被访问

        //迷宫初始化
        for (int i = 0; i < m; i++) {                  //防止发生两边上全为墙的情况
            map.tp[i][0].change(Math.random() * 3 > 1 ? 0 : 1);
            map.tp[i][n - 1].change(Math.random() * 3 > 1 ? 0 : 1);
        }
        for (int i = 0; i < n; i++) {
            map.tp[0][i].change(Math.random() * 3 > 1 ? 0 : 1);
            map.tp[m - 1][i].change(Math.random() * 3 > 1 ? 0 : 1);
        }
        for (int i = 1; i < m - 1; i++)
            for (int j = 1; j < n - 1; j++)
                map.tp[i][j].change(0);  //内部的位置初始化全为墙

        m_startx = (int) (Math.random() * m / 2);
        m_starty = (int) (Math.random() * n / 2);    //随机生成蝴蝶位置

        //******************从蝴蝶位置开始深度优先遍历与它x 、y坐标相差均为偶数的点构成的图
        DFS(m_startx * n + m_starty);

        //*******************     这一步在 tp[m-2][n-2]与蝴蝶位置x 、y坐标相差均为偶数时非常重要,保证有到达粮仓的路径
        if (Math.random() * 2 > 1)
            map.tp[m - 2][n - 1].change(1);
        else
            map.tp[m - 1][n - 2].change(1);    //两者只要有一个为路即可,故随机取其一

        //蝴蝶和钻石的位置作另作处理
        map.tp[m_startx][m_starty].change(2);  //蝴蝶的位置是随机的
        map.tp[m - 1][n - 1].change(3);            //固定钻石位置为右下角

        changeable_key = false;  //键盘不可控制蝴蝶移动
        m_currex = m_startx;
        m_currey = m_starty;  //开始新游戏前蝴蝶当前位置与开始位置相等
        restart = false;
    }

    //****************从S点开始深度优先遍历与它x 、y坐标相差均为偶数的点构成的图,并打通每一步需要通过的墙
    public static void DFS(int s) {
        map.tp[s / n][s % n].change(1);
        isBeVisit[s] = true;

        int[] direction = new int[4];              //用于以随机顺序存储方向   右0下1左2上3
        boolean[] isStored = new boolean[4];
        for (int i = 0; i < 4; i++) isStored[i] = false;    //方向是否已被存储
        int currex = s / n, currey = s % n;                 //当前点对应的实际坐标

        //按随机顺序存储方向
        int rand, length = 0;      //随机数 用于产生随机顺序 ,length表示已存储方向的个数
        while (length < 4) {
            rand = (int) (Math.random() * 4);    //0~3
            if (!isStored[rand]) {
                direction[length++] = rand;
                isStored[rand] = true;            //修改为true,防止重复存储
            }
        }
        for (int i = 0; i < 4; i++) {
            switch (direction[i]) {
                case 0:
                    if (currey + 2 < n) {                             //右
                        if (!isBeVisit[s + 2]) {
                            map.tp[currex][currey + 1].change(1); //打通[currex][currey]与[currex][currey+2]之间的墙,下同
                            DFS(s + 2);
                        }
                    }
                    break;
                case 1:
                    if (currex + 2 < m) {                              //下
                        if (!isBeVisit[s + 2 * n]) {
                            map.tp[currex + 1][currey].change(1);
                            DFS(s + 2 * n);
                        }
                    }
                    break;
                case 2:                                               //左
                    if (currey - 2 >= 0) {
                        if (!isBeVisit[s - 2]) {
                            map.tp[currex][currey - 1].change(1);
                            DFS(s - 2);
                        }
                    }
                    break;
                case 3:                                            //上
                    if (currex - 2 >= 0) {
                        if (!isBeVisit[s - 2 * n]) {
                            map.tp[currex - 1][currey].change(1);
                            DFS(s - 2 * n);
                        }
                    }
                    break;
            }
        }
    }

显示最短路径

先用深度优先搜索查找路径,然后再用最短路径深度图显示最短路径
有两个概念:
路径深度: 从某位置走到出口的最短路径的长度,设每一方块为单位路径长度。
假定出口处路径深度为0 , 障碍处路径深度为 - 1 。
路径深度图: 与迷宫对应的图, 每一个节点值为与该节点对应的迷宫单元格的路径深度

代码:
    //***************迷宫最短路径深度图算法

    public static void sortestPath() {
        changeable_key = false;   //不可用键盘控制蝴蝶

        m = map.m;
        n = map.n;
        int max = m * n;                 //任意一点到钻石的最短路径长度不会超出m*n。
        int[] depthGraph = new int[m * n];     //路径深度图

        //路径深度图初始化
        depthGraph[m * n - 1] = 0;                //钻石到自己的距离自然是0
        for (int i = 0; i < m * n - 1; i++) {
            if (map.tp[i / n][i % n].isWall())
                depthGraph[i] = -1;           //墙表示为-1,表示无通路
            else
                depthGraph[i] = max;           //未确定距离时已max表示
        }


        boolean flag = true;             //循环过程中是否有某点的路径深度被修改
        int currex, currey;               //记录当前访问点的坐标
        int aroundmin;                   //周围可行方向的 最小路径深度 + 1

        //动态更新路径深度图直至其达到稳态(即最后一次循环过程中不再有路径深度被修改)
        while (flag) {
            flag = false;
            for (int s = m * n - 1; s >= 0; s--) {
                if (depthGraph[s] != -1) {
                    aroundmin = depthGraph[s];
                    currex = s / n;
                    currey = s % n;
                    if (currey + 1 < n && depthGraph[s + 1] != -1 && depthGraph[s + 1] + 1 < aroundmin)
                        aroundmin = depthGraph[s + 1] + 1;
                    if (currex + 1 < m && depthGraph[s + n] != -1 && depthGraph[s + n] + 1 < aroundmin)
                        aroundmin = depthGraph[s + n] + 1;
                    if (currey - 1 >= 0 && depthGraph[s - 1] != -1 && depthGraph[s - 1] + 1 < aroundmin)
                        aroundmin = depthGraph[s - 1] + 1;
                    if (currex - 1 >= 0 && depthGraph[s - n] != -1 && depthGraph[s - n] + 1 < aroundmin)
                        aroundmin = depthGraph[s - n] + 1;
                    if (aroundmin < depthGraph[s]) {
                        depthGraph[s] = aroundmin;
                        flag = true;
                    }

                }
            }
        }

        //利用已生成的路径深度图,找到从蝴蝶到钻石之间的最短路径
        int[] path = new int[m * n];                    //用于存放最短路径的数组
        int currePoint = m_startx * n + m_starty;         //当前访问点,初始值为蝴蝶位置
        int depth = depthGraph[currePoint];         //蝴蝶位置的路径深度值
        int step = depth - 1;                     //当前要查找的路径深度
        while (step > 0) {
            currex = currePoint / n;
            currey = currePoint % n;
            if (currey + 1 < n && depthGraph[currePoint + 1] == step) {
                currePoint += 1;
            } else if (currex + 1 < m && depthGraph[currePoint + n] == step) {
                currePoint += n;
            } else if (currey - 1 >= 0 && depthGraph[currePoint - 1] == step) {
                currePoint -= 1;
            } else if (currex - 1 >= 0 && depthGraph[currePoint - n] == step) {
                currePoint -= n;
            }
            path[step--] = currePoint;
        }
        int s;         //临时存放位置
        for (int i = 1; i < depth; i++) {
            s = path[i];
            map.tp[s / n][s % n].change(2);   //显示最短路径
        }

        restart = true;                //可开始新游戏

    }


    //****************深度优先搜索
    public static void findPath() {

        changeable_key = false;   //不可用键盘控制蝴蝶

        m = map.m;
        n = map.n;
        int currex = m_startx, currey = m_starty;
        int direction = 0;
        int distance;
        int[] point = new int[m * n];
        boolean[] isBeVisit = new boolean[m * n];
        for (int i = 0; i < m * n - 1; i++) isBeVisit[i] = false;
        int step = 0;
        point[step] = currex * n + currey;
        step++;
        for (; ; ) {
            if (currex == m - 1 && currey == n - 1) {      //已找到钻石
                for (int i = 1; i < step - 1; i++)         //step-1对应钻石,不再重画。
                    map.tp[point[i] / n][point[i] % n].change(2);
                restart = true;                //可开始新游戏
                return;
            }
            switch (direction) {       //按右0下1左2上3的优先顺序遍历
                case 0:
                    if (currey + 1 < n && !map.tp[currex][currey + 1].isWall() && !isBeVisit[currex * n + currey + 1]) {
                        point[step] = currex * n + currey + 1;
                        isBeVisit[currex * n + currey + 1] = true;
                        currey++;
                        step++;
                        direction = 0;

                    } else direction++;
                    break;
                case 1:
                    if (currex + 1 < m && !map.tp[currex + 1][currey].isWall() && !isBeVisit[(currex + 1) * n + currey]) {
                        point[step] = (currex + 1) * n + currey;
                        isBeVisit[(currex + 1) * n + currey] = true;
                        currex++;
                        step++;
                        direction = 0;

                    } else direction++;
                    break;
                case 2:
                    if (currey - 1 >= 0 && !map.tp[currex][currey - 1].isWall() && !isBeVisit[currex * n + currey - 1]) {
                        point[step] = currex * n + currey - 1;
                        isBeVisit[currex * n + currey - 1] = true;
                        currey--;
                        step++;
                        direction = 0;
                    } else direction++;
                    break;
                case 3:
                    if (currex - 1 >= 0 && !map.tp[currex - 1][currey].isWall() && !isBeVisit[(currex - 1) * n + currey]) {
                        point[step] = (currex - 1) * n + currey;
                        isBeVisit[(currex - 1) * n + currey] = true;
                        currex--;
                        step++;
                        direction = 0;
                    } else direction++;
                    break;
                default:              //此路不通,后退一步查看下一方向
                    step--;
                    isBeVisit[point[step]] = false;
                    if (step <= 0) {
                        JOptionPane.showMessageDialog(null, "抱歉,该迷宫没有通路,请使用其它迷宫开始新的游戏。", "该迷宫没有通路!", JOptionPane.ERROR_MESSAGE);
                        restart = true;
                        return;
                    }

                    distance = point[step] - point[step - 1];
                    if (distance == 1) {        //在上一步的右方向(0),则返回上一步时 再找下一个方向 direction=1   下同
                        currex = point[step - 1] / n;
                        currey = point[step - 1] % n;
                        direction = 1;
                    } else if (distance == n) {
                        currex = point[step - 1] / n;
                        currey = point[step - 1] % n;
                        direction = 2;
                    } else if (distance == -1) {
                        currex = point[step - 1] / n;
                        currey = point[step - 1] % n;
                        direction = 3;
                    } else {
                        direction = 4;       //继续后退
                    }
            }
        }
    }

这就是两个核心算法啦

开始界面的设计

开始界面我设计了2X2四个方块,分别代表四种不同的迷宫样式:25X25,35X35,45X45,还有“?”代表可以自定义

代码:
package maze4;
//程序开始界面

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;


public class StartUI extends JFrame implements ActionListener {
  private ImageIcon start1 = new ImageIcon("./start1.jpg");
  private ImageIcon start2 = new ImageIcon("./start2.jpg");
  private ImageIcon start3 = new ImageIcon("./start3.jpg");
  private ImageIcon start4 = new ImageIcon("./start4.jpg");
  private JButton button1 = new JButton(start1);
  private JButton button2 = new JButton(start2);
  private JButton button3 = new JButton(start3);
  private JButton button4 = new JButton(start4);


  StartUI() {

      //将选择按钮包含在选择面板上
      JPanel choose = new JPanel();
      choose.setLayout(new GridLayout(2, 2));
      choose.add(button1);
      choose.add(button2);
      choose.add(button3);
      choose.add(button4);

      //注册监听器
      button1.addActionListener(this);
      button2.addActionListener(this);
      button3.addActionListener(this);
      button4.addActionListener(this);

      //提示信息
      JPanel message = new JPanel() {   //匿名内部类
          protected void paintComponent(Graphics g) {
              setSize(200, 300);
              g.drawString("请选择场地大小", 100, 35);
          }
      };

      //主界面布局
      setLayout(new BorderLayout(120, 40));
      add(choose, BorderLayout.CENTER);
      add(message, BorderLayout.NORTH);
      add(new JPanel(), BorderLayout.EAST);
      add(new JPanel(), BorderLayout.WEST);
      add(new JPanel(), BorderLayout.SOUTH);

      //基本设置
      setTitle("迷宫游戏之酒醉的蝴蝶");
      setSize(800, 600);
      setLocationRelativeTo(null);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);
      setResizable(false);
  }

  public static void main(String[] args) {
      new StartUI();
  }

  //按钮事件处理
  public void actionPerformed(ActionEvent e) {
      if (e.getSource() == button1) {
          dispose(); // setVisible(false);
          new map(18, 18);
      } else if (e.getSource() == button2) {
          dispose();  //setVisible(false);
          new map(25, 25);
      } else if (e.getSource() == button3) {
          dispose();  //setVisible(false);
          new map(40, 40);
      } else {
          getData();
      }
  }

  public void getData() {
      int m = 0, n = 0;
      String crowString;
      try {
          crowString = JOptionPane.showInputDialog("请输入自定义的行数(>5)");
          m = Integer.parseInt(crowString);
          crowString = JOptionPane.showInputDialog("请输入自定义的列数(>5)");
          n = Integer.parseInt(crowString);
          if (m <= 5 || n <= 5) throw new Exception();
          else {
              dispose();  //setVisible(false);
              new map(m, n);
          }
      } catch (Exception e) {
          JOptionPane.showMessageDialog(null, "由于用户取消或输入不符合要求等原因,未正常创建迷宫。", "未创建迷宫!", JOptionPane.ERROR_MESSAGE);
      }

  }

}

运行后就是这个样子:
在这里插入图片描述

迷宫界面

迷宫界面就是点击开始界面后的游戏界面,可以添加一些按钮,及按钮的事件。

代码:
package maze4;

//迷宫界面


import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.RandomAccessFile;

import javax.swing.*;

public class map extends JFrame implements ActionListener, KeyListener {
  static int m, n;
  static wrmPane[] [] tp = null;        //显示动画,同一包内类均可访问

  // 菜单项
  private JMenuItem m_start = new JMenuItem("开始新游戏");
  private JMenuItem m_return = new JMenuItem("返回主界面");
  private JMenuItem m_exit = new JMenuItem("退出游戏");
  private JMenuItem m_sortpath = new JMenuItem("显示最短路径");
  private JMenuItem m_help = new JMenuItem("游戏使用说明");
  map(int x, int y) {
      m = x;
      n = y;
      tp = new wrmPane[m][n];

      //菜单
      JMenu game = new JMenu("游戏");
      JMenu tip = new JMenu("提示");
      JMenu help = new JMenu("帮助");
      game.add(m_start);
      game.add(m_return);
      game.add(m_exit);
      tip.add(m_sortpath);
      help.add(m_help);

      //菜单栏
      JMenuBar menu = new JMenuBar();
      menu.add(game);
      menu.add(tip);
      menu.add(help);

      //初始化迷宫组件,并生成随机路径
      for (int i = 0; i < m; i++)
          for (int j = 0; j < n; j++) {
              tp[i][j] = new wrmPane();
          }
      Operations.creatMaze();  //深度优先遍历生成至少有一条随机通道的迷宫

      //迷宫地图
      JPanel mazePane = new JPanel();
      mazePane.setLayout(new GridLayout(m, n, 0, 0));
      for (int i = 0; i < m; i++) {
          for (int j = 0; j < n; j++) {
              mazePane.add(tp[i][j]);
          }
      }
      JPanel northPanel = new JPanel();
      northPanel.setLayout(new GridLayout(1, 1));
      northPanel.add(menu);
      menu.setBackground(new Color(245, 240, 245));

      //添加到框架
      setLayout(new BorderLayout());
      add(northPanel, BorderLayout.NORTH);
      add(mazePane, BorderLayout.CENTER);
      add(new JPanel(), BorderLayout.SOUTH);

      //注册监听器
      m_start.addActionListener(this);
      m_return.addActionListener(this);
      m_exit.addActionListener(this);
      m_sortpath.addActionListener(this);
      m_help.addActionListener(this);
      addKeyListener(this);

      //基本设置
      setTitle("迷宫游戏之酒醉的蝴蝶");
      setSize(850, 650);
      setLocationRelativeTo(null);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);
      setResizable(false);

  }

  map() {
      this(25, 25);
  }


  //菜单事件处理
  public void actionPerformed(ActionEvent e) {
      if (e.getSource() == m_start) {
          Operations.start();
      } else if (e.getSource() == m_return) {
          dispose(); //关闭当前窗口
          new StartUI();
      } else if (e.getSource() == m_exit) {
          System.exit(0);
      } else if (e.getSource() == m_help) {
          Operations.showHelp();
      } else if (e.getSource() == m_sortpath) {
          Operations.sortestPath();
      }

  }

  //键盘事件处理
  public void keyTyped(KeyEvent e) {
  }

  public void keyReleased(KeyEvent e) {
  }

  public void keyPressed(KeyEvent e) {
      switch (e.getKeyCode()) {
          case KeyEvent.VK_DOWN:
              Operations.down();
              break;
          case KeyEvent.VK_UP:
              Operations.up();
              break;
          case KeyEvent.VK_LEFT:
              Operations.left();
              break;
          case KeyEvent.VK_RIGHT:
              Operations.right();
              break;
      }

  }
}

显示面板

显示面板用来显示图片还有鼠标的事件处理

代码:
package maze4;
//显示面板


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class wrmPane extends JPanel implements MouseListener {
  private boolean changeable_click;
  private int flag;  //标志 0:花丛 1:路 2:蝴蝶 3:钻石
  private Image flower = new ImageIcon("./flower1.gif").getImage();        //花丛
  private Image road = new ImageIcon("./road.gif").getImage();        //路
  private Image butterfly = new ImageIcon("./butterfly.gif").getImage();      //蝴蝶
  private Image dimon = new ImageIcon("./dimon.gif").getImage();   //钻石

  wrmPane(int f) {
      flag = f;
      changeable_click = false;    //初始化时不能通过鼠标点击改变 flag 的值
      addMouseListener(this);
  }

  wrmPane() {
      this(0);
  }

  //重写paintComponent方法,画图
  public void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (flag == 0)
          g.drawImage(flower, 0, 0, getWidth(), getHeight(), this);
      else if (flag == 1)
          g.drawImage(road, 0, 0, getWidth(), getHeight(), this);
      else if (flag == 2)
          g.drawImage(butterfly, 0, 0, getWidth(), getHeight(), this);
      else
          g.drawImage(dimon, 0, 0, getWidth(), getHeight(), this);
  }

  //访问器
  public int getFlag() {
      return flag;
  }

  //是否为墙
  public boolean isWall() {
      return flag == 0;
  }
  
//修改标志并重画面板
  public void change(int f) {
      flag = f;
      repaint();
  }

  //鼠标事件处理
  public void mouseEntered(MouseEvent e) {
  }

  public void mouseExited(MouseEvent e) {
  }

  public void mousePressed(MouseEvent e) {
  }

  public void mouseReleased(MouseEvent e) {
  }
  public void mouseClicked(MouseEvent e) {
      if (!changeable_click) return;
      if (flag == 0) {
          flag = 1;
          repaint();
      } else {
          flag = 0;
          repaint();
      }
  }
}

运行界面
在这里插入图片描述
可以显示最短路径
在这里插入图片描述

实验总结

迷宫设计的关键就是创建迷宫和显示最短路径两个算法,但是好像使用A算法显示最短路径比较简便。
尽量有时间再看看用A
算法怎么写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值