实验三 基于A*算法的迷宫游戏

软件实习项目三 —— Java实现基于A*算法的迷宫游戏

一、实验任务

(1)该题要求随机生成一个迷宫,并求解迷宫;

(2)要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于A*算法实现,输出走迷宫的最优路径并显示。

(3)设计交互友好的游戏图形界面。

二、实验准备

(1)具体编程语言:JAVA

(2)确定所用图形界面:Java Swing

(3)算法思想:递归分割法来随机生成迷宫,A*算法来自动寻路

三、设计思路

(1)如何生成迷宫

1.利用Java Swing的相关函数,进行绘画,以砖块为墙,豆豆人为角色,脚印为走过的路的痕迹,空白部分为路来画迷宫。

2.递归分割法的思想:
①开始创建迷宫,使整个空间没有壁,我们称之为“室”。
②在随机位置生成壁将室分割为两个子室,并在壁上随机开孔,使子室联通。
③重复步骤②,直到所有子室全部不可分割(即子室某一个维度等于1)。

(2)控制角色移动

利用Java函数addKeyListener来监听按键⬆️⬇️⬅️➡️的输入,只有当角色周围是路,角色才能继续移动。当角色到达终点时就宣布游戏结束。

(3)基于A*算法的自动寻路

A*算法的主要思想为:
①先判断目标能移动一格的位置,如果只能移动到之前的位置执行④。
②然后再将每一个与终点的距离进行比较。
③移动到距离最近的那一格上,重复①~③。
④返回上一步的位置。

四、功能实现

StartUI类:程序的启动界面,设置界面的布局及按钮,并提供玩家用户选择场地地图大小,以选择游戏难度。

public class StartUI extends JFrame implements ActionListener {

    private JButton button1 = new JButton("24 X 24");
    private JButton button2 = new JButton("35 X 35");
    private JButton button3 = new JButton("46 X 46");
    private JButton button4 = new JButton("?");

    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);
    }


Map类:生成地图界面,地图框架的设计,包括菜单栏、剩余时间显示等,并为各组件、按钮、键盘、事件注册监听器,再初始化地图组件,随机生成迷宫。创建一个时间控制的线程和进行控制时间的方法。

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

    //时间限制
    static Thread timeThread;                //时间控制线程
    static int timelimit, remaintime;
    static JPanel timePanel = new JPanel() {     //剩余时间显示面板
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            String rt;
            if (timelimit == 0) {
                rt = "无敌版";
                setForeground(Color.GREEN);    //绿色表示无时间限制
            } else {
                rt = remaintime / 3600 + " : " + (remaintime - (remaintime / 3600) * 3600) / 60 + " : " + remaintime % 60;
                if (remaintime > 10)
                    setForeground(Color.BLUE);      //剩余时间充足时为蓝色
                else
                    setForeground(Color.RED);      //剩余时间很少时为红色
            }
            g.drawString("剩余时间:  " + rt, 220, 16);
        }
    };

    // 菜单项
    private JMenuItem m_start = new JMenuItem("开始新游戏(S)");
    private JMenuItem m_time = new JMenuItem("游戏时间限制(L)");
    private JMenuItem m_return = new JMenuItem("返回主界面(R)");
    private JMenuItem m_exit = new JMenuItem("退出游戏(Q)");
    private JMenuItem m_selfconfig = new JMenuItem("编辑当前地图(E)");
    private JMenuItem m_randommake = new JMenuItem("随机生成地图(Z)");
    private JMenuItem m_sortpath = new JMenuItem("显示最短路径(T)");

    Map(int x, int y) {
        m = x;
        n = y;
        tp = new Paint[m][n];
        timelimit = remaintime = 0;                //初始化时,时间为0,代表没有时间限制

        timeThread = new Thread(this);
        timeThread.start();
        timeThread.checkAccess();

        //初始化地图组件,并生成随机路径
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) {
                tp[i][j] = new Paint();
            }
        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]);
            }
        }

Paint类:显示墙、路、玩家角色、终点、路径的面板。创建标志flag并为其赋值,不同的值画出的图形类别不同,使Operations类根据其需求转换flag的值,以画出墙、路和路径。

public class Paint extends JPanel implements MouseListener {
    private boolean changeable_click;
    //标志 0:墙 1:路 2:豆豆人 3:小黄豆 4:脚印
    private int flag;
    private Image wall = new ImageIcon("/Users/xiaoying/Desktop/IMG_8697.jpg").getImage();        //墙
    private Image road = new ImageIcon("/Users/xiaoying/Desktop/IMG_8689.jpg").getImage();        //路
    private Image role = new ImageIcon("/Users/xiaoying/Desktop/IMG_8665.PNG").getImage();        //豆豆人
    private Image bean = new ImageIcon("/Users/xiaoying/Desktop/IMG_8663.PNG").getImage();        //小黄豆
    private Image step = new ImageIcon("/Users/xiaoying/Desktop/IMG_8701.jpg").getImage();        //脚印

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

    Paint() {
        this(0);
    }

    //重写paintComponent方法,画图
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (flag == 0)
            g.drawImage(wall, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 1)
            g.drawImage(road, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 2)
            g.drawImage(role, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 3)
            g.drawImage(bean, 0, 0, getWidth(), getHeight(), this);
        else
            g.drawImage(step, 0, 0, getWidth(), getHeight(), this);

    }

Operations类:自定义随机生成地图的数据,深度算法遍历DFS生成至少有一条随机路径的迷宫地图,使用A*算法搜寻并显示最短路径。设置开始游戏时时间的控制,玩家角色的移动,走过路径的生成,也可以提示用户玩家最佳路径。

public class Operations{
    static int m, n;   //用于拷贝map.m和map.n的值
    static int m_currex, m_currey;   //豆豆人当前位置
    static int m_startx, m_starty;   //豆豆人开始位置
    static boolean changeable_key = true;     //可用键盘控制豆豆人移动
    static boolean restart = false;

    private static boolean[] isBeVisit = null;//生成随机地图专用数据

    //迷宫地图最短路径深度图算法
    public static void findPath() {

        Map.timeThread.checkAccess();   ;    //时间控制线程休眠
        changeable_key = true;   //可用键盘控制豆豆人
        setEditable(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 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 = true;  //键盘可控制豆豆人移动
        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;

五、成果展示

迷宫游戏开始界面
实验三 基于A*算法的迷宫游戏_第1张图片

迷宫游戏界面+角色走过的路径显示
实验三 基于A*算法的迷宫游戏_第2张图片

提供显示最佳路径
实验三 基于A*算法的迷宫游戏_第3张图片

迷宫通关界面
实验三 基于A*算法的迷宫游戏_第4张图片

源代码如下:

package Maze;
//程序开始界面

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

import javax.swing.*;

@SuppressWarnings("serial")
public class StartUI extends JFrame implements ActionListener {

    private JButton button1 = new JButton("24 X 24");
    private JButton button2 = new JButton("35 X 35");
    private JButton button3 = new JButton("46 X 46");
    private JButton button4 = new JButton("?");


    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();
            new Map(18, 18);
        } else if (e.getSource() == button2) {
            dispose();
            new Map(25, 25);
        } else if (e.getSource() == button3) {
            dispose();
            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);
        }

    }

}

```java
package Maze;

//地图界面

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

import javax.swing.*;

@SuppressWarnings("serial")
public class Map extends JFrame implements ActionListener, KeyListener, Runnable {
    static int m, n;
    static Paint[][] tp = null;        //显示动画,同一包内类均可访问

    //时间限制
    static Thread timeThread;                //时间控制线程
    static int timelimit, remaintime;
    static JPanel timePanel = new JPanel() {     //剩余时间显示面板
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            String rt;
            if (timelimit == 0) {
                rt = "无敌版";
                setForeground(Color.GREEN);    //绿色表示无时间限制
            } else {
                rt = remaintime / 3600 + " : " + (remaintime - (remaintime / 3600) * 3600) / 60 + " : " + remaintime % 60;
                if (remaintime > 10)
                    setForeground(Color.BLUE);      //剩余时间充足时为蓝色
                else
                    setForeground(Color.RED);      //剩余时间很少时为红色
            }
            g.drawString("剩余时间:  " + rt, 220, 16);
        }
    };

    // 菜单项
    private JMenuItem m_start = new JMenuItem("开始新游戏(S)");
    private JMenuItem m_time = new JMenuItem("游戏时间限制(L)");
    private JMenuItem m_return = new JMenuItem("返回主界面(R)");
    private JMenuItem m_exit = new JMenuItem("退出游戏(Q)");
    private JMenuItem m_selfconfig = new JMenuItem("编辑当前地图(E)");
    private JMenuItem m_randommake = new JMenuItem("随机生成地图(Z)");
    private JMenuItem m_sortpath = new JMenuItem("显示最短路径(T)");

    Map(int x, int y) {
        m = x;
        n = y;
        tp = new Paint[m][n];
        timelimit = remaintime = 0;                //初始化时,时间为0,代表没有时间限制

        timeThread = new Thread(this);
        timeThread.start();
        timeThread.checkAccess();

        //菜单
        JMenu game = new JMenu("游戏");
        JMenu edit = new JMenu("编辑");
        JMenu tip = new JMenu("提示");
        game.add(m_start);
        game.add(m_time);
        game.add(m_return);
        game.add(m_exit);
        edit.add(m_selfconfig);
        edit.add(m_randommake);
        tip.add(m_sortpath);

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

        //初始化地图组件,并生成随机路径
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) {
                tp[i][j] = new Paint();
            }
        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);
        northPanel.add(timePanel);
        timePanel.setBackground(new Color(245, 240, 245));
        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_time.addActionListener(this);
        m_return.addActionListener(this);
        m_exit.addActionListener(this);
        m_selfconfig.addActionListener(this);
        m_randommake.addActionListener(this);
        m_sortpath.addActionListener(this);
        addKeyListener(this);

        //基本设置
        setTitle("豆豆人找朋友");
        setSize(850, 650);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        setResizable(false);

    }

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



    //重写run方法,进行时间控制
    public void run() {
        if (timelimit > 0) {
            while (true) {
                try {
                    Thread.sleep(1000);
                    if (remaintime > 0)
                        remaintime--;
                    timePanel.repaint();
                    if (timelimit > 0 && remaintime == 0) {
                        if (Operations.m_currex != m - 1 || Operations.m_currey != n - 1) {
                            Object[] options = {"新游戏", "重来一次"};
                            int response = JOptionPane.showOptionDialog(this,
                                    "  很遗憾,你没有在规定时间内完成任务\n请选择开始新的游戏,或重玩此游戏",
                                    "游戏超时!", JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
                            if (response == 0) {
                                Operations.restart = true;
                                Operations.start();
                            } else {
                                remaintime = timelimit;
                                tp[Operations.m_currex][Operations.m_currey].change(1);
                                Operations.m_currex = Operations.m_startx;
                                Operations.m_currey = Operations.m_starty;
                                tp[Operations.m_currex][Operations.m_currey].change(2);
                            }

                        }
                    }
                } catch (Exception e) {
                }
            }
        }
    }


    //菜单事件处理
    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_selfconfig) {
            Operations.selfconfig();
        } else if (e.getSource() == m_randommake) {
            Operations.randommake();
        } else if (e.getSource() == m_sortpath) {
            Operations.findPath();
        } else if (e.getSource() == m_time) {
            Operations.setTime();
        }

    }

    //键盘事件处理
    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;
            case KeyEvent.VK_S:
                Operations.start();
                break;
            case KeyEvent.VK_Q:
                System.exit(0);
                break;
            case KeyEvent.VK_R:
                dispose();
                new StartUI();
                break;
            case KeyEvent.VK_E:
                Operations.selfconfig();
                break;
            case KeyEvent.VK_Z:
                Operations.randommake();
                break;
            case KeyEvent.VK_T:
                Operations.findPath();
                break;
            case KeyEvent.VK_L:
                Operations.setTime();
                break;
        }

    }

}

```java
package Maze;


import sun.lwawt.macosx.CPrinterGraphics;

import javax.swing.*;
import java.awt.*;
import java.awt.image.ImageObserver;
import java.util.ArrayList;

public class Operations{
    static int m, n;   //用于拷贝map.m和map.n的值
    static int m_currex, m_currey;   //豆豆人当前位置
    static int m_startx, m_starty;   //豆豆人开始位置
    static boolean changeable_key = true;     //可用键盘控制豆豆人移动
    static boolean restart = false;

    private static boolean[] isBeVisit = null;//生成随机地图专用数据

    //迷宫地图最短路径深度图算法
    public static void findPath() {

        Map.timeThread.checkAccess();   ;    //时间控制线程休眠
        changeable_key = true;   //可用键盘控制豆豆人
        setEditable(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 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 = true;  //键盘可控制豆豆人移动
        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;

        //用于以随机顺序存储方向   右0下1左2上3
        int[] direction = new int[4];
        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;
                //修改为true,防止重复存储
                isStored[rand] = true;
            }
        }
        for (int i = 0; i < 4; i++) {
            switch (direction[i]) {
                case 0:
                    if (currey + 2 < n) {                             //右
                        if (!isBeVisit[s + 2]) {
                            //打通[currex][currey]与[currex][currey+2]之间的墙,下同
                            Map.tp[currex][currey + 1].change(1);
                            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;
            }
        }
    }

    //开始游戏
    public static void start() {
        if (restart) creatMaze();
        Map.remaintime = Map.timelimit;
        Map.timeThread.checkAccess();
        changeable_key = true;    //使用键盘控制豆豆人
        setEditable(false);       //不可编辑
    }

    //设置时间
    public static void setTime() {
        int time;
        String timeStr;
        try {
            timeStr = JOptionPane.showInputDialog("请输入最大时间限制(单位为秒):\n提示:输入0代表无时间限制)");
            time = Integer.parseInt(timeStr);
            if (time < 0) throw new Exception();
            Map.timelimit = time;   //设置完时间后重新开始游戏
            Object[] options = {"新游戏", "当前游戏"};
            int response = JOptionPane.showOptionDialog(null, "请选择是否开始新游戏还是重新玩当前游戏",
                    "游戏时间设置成功", JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
            if (response == 0) {
                restart = true;
                start();
            } else if (response == 1) {
                Map.tp[m_currex][m_currey].change(1);
                m_currex = m_startx;
                m_currey = m_starty;
                Map.tp[m_currex][m_currey].change(2);
                restart = false;
                start();
            }
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "由于用户取消或输入不符合要求等原因,游戏时间限定设置失败。",
                    "未更改游戏时间限制", JOptionPane.ERROR_MESSAGE);
            Map.timeThread.checkAccess(); //返回调用前状态
        }

    }

    //豆豆人的移动
    //只有往右走和往下走才有可能到小黄豆,因此只检测这两种情况是否成功找到小黄豆
    public static void down() {
        if (!changeable_key) return;
        if (m_currex == m - 1 && m_currey == n - 1) return;
        if (m_currex + 1 == m - 1 && m_currey == n - 1) {
            Map.tp[m_currex][m_currey].change(1);
            m_currex++;
            restart = true;
            int anwser = JOptionPane.showConfirmDialog(null, "恭喜你帮助豆豆人找到小黄豆,是否开始新的游戏。",
                    "成功找到小黄豆!", JOptionPane.YES_NO_CANCEL_OPTION);
            if (anwser == JOptionPane.YES_OPTION) start();
        } else if (m_currex + 1 < m && !Map.tp[m_currex + 1][m_currey].isWall()) {
            Map.tp[m_currex][m_currey].change(4);
            Map.tp[++m_currex][m_currey].change(2);
        }
    }

    public static void up() {
        if (!changeable_key) return;
        if (m_currex == m - 1 && m_currey == n - 1) return;
        if (m_currex - 1 >= 0 && !Map.tp[m_currex - 1][m_currey].isWall()) {
            Map.tp[m_currex][m_currey].change(4);
            Map.tp[--m_currex][m_currey].change(2);
        }
    }

    public static void left() {
        if (!changeable_key) return;
        if (m_currex == m - 1 && m_currey == n - 1) return;
        if (m_currey - 1 >= 0 && !Map.tp[m_currex][m_currey - 1].isWall()) {
            Map.tp[m_currex][m_currey].change(4);
            Map.tp[m_currex][--m_currey].change(2);
        }
    }

    public static void right() {
        if (!changeable_key) return;
        if (m_currex == m - 1 && m_currey == n - 1) return;
        if (m_currex == m - 1 && m_currey + 1 == n - 1) {
            Map.tp[m_currex][m_currey].change(1);
            m_currey++;
            restart = true;
            int anwser = JOptionPane.showConfirmDialog(null, "恭喜你帮助豆豆人找到小黄豆,是否开始新的游戏。",
                    "成功找到小黄豆!", JOptionPane.YES_NO_CANCEL_OPTION);
            if (anwser == JOptionPane.YES_OPTION) start();
        } else if (m_currey + 1 >= 0 && !Map.tp[m_currex][m_currey + 1].isWall()) {
            Map.tp[m_currex][m_currey].change(4);
            Map.tp[m_currex][++m_currey].change(2);
        }
    }


    //设置是否能编辑
    public static void setEditable(boolean e) {
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                Map.tp[i][j].setChangeable_click(e);
        //即使在编辑模式下豆豆人和小黄豆也不能修改
        Map.tp[m - 1][n - 1].setChangeable_click(false);
        Map.tp[m_startx][m_starty].setChangeable_click(false);
    }

    //自定义地图
    public static void selfconfig() {
        Map.timeThread.checkAccess();    //时间控制线程休眠
        changeable_key = true;      //可用键盘移动豆豆人
        setEditable(true);           //可以使墙变路、路变墙
        m_startx = m_currex;
        m_startx = m_currex;
        restart = false;               //保证开始游戏时使用的是编辑得到的地图
        Map.timeThread.checkAccess();   //时间控制回到调用函数前状态
    }

    //随机迷宫
    @SuppressWarnings("deprecation")
    public static void randommake() {
        Map.timeThread.checkAccess();    //时间控制线程休眠
        creatMaze();
        changeable_key = false;
        setEditable(true);
        restart = false;        //保证开始游戏时使用的是编辑得到的地图
        Map.timeThread.checkAccess();   //时间控制回到调用函数前状态
    }
}

```java
package Maze;

//显示墙、路、豆豆人和小黄豆的面板


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

@SuppressWarnings("serial")
public class Paint extends JPanel implements MouseListener {
    private boolean changeable_click;
    //标志 0:墙 1:路 2:豆豆人 3:小黄豆 4:脚印
    private int flag;
    private Image wall = new ImageIcon("/Users/xiaoying/Desktop/IMG_8697.jpg").getImage();        //墙
    private Image road = new ImageIcon("/Users/xiaoying/Desktop/IMG_8689.jpg").getImage();        //路
    private Image role = new ImageIcon("/Users/xiaoying/Desktop/IMG_8665.PNG").getImage();        //豆豆人
    private Image bean = new ImageIcon("/Users/xiaoying/Desktop/IMG_8663.PNG").getImage();        //小黄豆
    private Image step = new ImageIcon("/Users/xiaoying/Desktop/IMG_8701.jpg").getImage();        //脚印

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

    Paint() {
        this(0);
    }

    //重写paintComponent方法,画图
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (flag == 0)
            g.drawImage(wall, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 1)
            g.drawImage(road, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 2)
            g.drawImage(role, 0, 0, getWidth(), getHeight(), this);
        else if (flag == 3)
            g.drawImage(bean, 0, 0, getWidth(), getHeight(), this);
        else
            g.drawImage(step, 0, 0, getWidth(), getHeight(), this);

    }


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

    //是否为墙
    public boolean isWall() {
        return flag == 0;
    }

    //是否可通过点击实现墙路互变
    public boolean isChangeable() {
        return changeable_click;
    }

    //设置为是否能墙路互变
    public void setChangeable_click(boolean c) {
        changeable_click = c;
    }

    //修改标志并重画面板
    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();
        }
    }

}



你可能感兴趣的:(笔记,经验分享)