【基于深度优先算法的迷宫游戏开发】软件实习项目三

【基于深度优先算法的迷宫游戏开发】软件实习项目三,第1张

【基于深度优先算法的迷宫游戏开发】软件实习项目三 一、项目准备

需求分析
(1)迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
(2)要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制;
(3)系统走迷宫路径基于逆二叉树搜索,输出走迷宫的路径并显示。
(4)设计交互友好的游戏图形界面
编程语言及开发工具
java 基于IDEA

二、实现过程

利用深度遍历的思想。访问到一个节点时,搜索这个节点没有被访问过的相邻节点,选择一个继续做同样的 *** 作,直到没有邻节点为止再回溯到上一个访问的节点,并选择另外的邻节点。

算法思想

  1. 从第一个单元开始,检查当前单元是否堵塞(即周围四个单元都是已被访问或不可访问)
  2. 若不堵塞,则随机选择一个相邻单元作为下一单元,检查是否可访问
  3. 若可访问,则打破当前单元与下一单元之间的墙壁,将当前单元入栈,将下一单元作为当前单元;若不不可访问,则回到步骤2
  4. 若当前单元堵塞,则回退至上一单元
  5. 如此遍历,直到所有的单元都被访问

面板设计

  1. 利用Java Swing的相关函数,进行实线的绘画,以线为墙,实心圆为角色,空白部分为路来画迷宫。
  2. 利用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,例如不继续闯关会停留在终点,下次可以在终点继续闯关……
面板设计方面,还未添加难度选择模块。
在期末考试结束后,有时间我会继续改进和维护这个项目的。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5683983.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存