【软件实现实验三:迷宫】

一.需求分析
1、迷宫随机生成

2、玩家走迷宫,留下足迹;

3、系统用A*算法寻路,输出路径

二.主要算法
对于迷宫的生成主要是采用了深度优先遍历,迷宫的寻路主要是采用了A*算法

深度优先遍历
        首先实在地图上生成规律的数字,列入我的代码中是用0代表墙,用1代表路,选择一个靠近边缘的1作为起点,在它的周围随机找另一个的1(这里的“周围”指的是上下左右4个方向)。找到就把他们联通,并且把两个1之间的0(灰色墙)也变成通路。
把上一步”终点”的格子作为新的一个“起点”格子,不断循环第2步的过程
直到,找不到周围有黄色的1,就回溯,回到之前的位置,看看周围是否有黄色的1,如果有,就按照2步骤,不断将黄色1变联通,接下来就是不停地重复上面的步骤,找到就联通,找不到就往回走
遍历完所有的点即可生成一个迷宫,然后再选择出口与入口,一个完整的迷宫就形成 了。这样就可以看出用红色连通的是路,没有连起来的0全部都当作墙,这时一个完整的地图就生成了

A*算法

1.A*算法的详细原理之寻路原理

        开始寻路FindPath

①开列表,关列表初始化

②添加开始点到开列表,然后获得周围点集合并加入开列表,接着又把开始点从开列表中移除,并添加到关列表

③判断这些周围点集合是否已经在开列表中

不在则更新这些点的F和父亲点,并添加到开列表;

在则重新计算G值,G较小则更新G,F和父亲点

④从周围点集合中找到F最小的点,然后获得周围点集合,接着又把找到F最小的点从开列表中移除,并添加到关列表⑤接着执行第③步骤

2.A*算法的寻路详细步骤

1.从起点s开始,把s作为一一个等待检查的方格,放入到‘开启列表”中(“开启列表'就是一一个存放等待检查方格的列表)

2.寻找起点s周围可以到达的方格(最多八个) ,将它们放入到“开启列表”并设置它们的父方格为S

3.从“开启列表”中删除起点s,并将放入到“关闭列表”中

(“关闭列表”存放的是不再需要检查的方格)

4.计算每个周围方格的F值

G表示从起点A移动到指定方格的移动消耗,我们假设横向移动个格子消耗10,斜向移动一个格子消耗14 (具体值可以根据情况修改)

H表示从指定的方格移动到目标E点的预计消耗,我们假设H的计算方法,忽略障碍物,只可以纵横向计算
 

5. 从“开启列表”中选择F值最低的方格a,将其从“开启列表”中删除,放入到“关闭列表”中6.检查a所有临近并且可达的方格

a)障碍物和“关闭列表”中的方格不考虑

b)如果这些方格还不在“开启列表” 中的话,将它们加入到“开启列表”,并且计算这些方格的F值,并设置父方格为a

c)如果某相邻的方格c已经在“开启列表”,计算新的路径从S到达方格c (即经过a的路径)判断是否需要更新: G值是否更低- -点

如果新的G值更低,则修改父方格为方格a,重新计算F值,H值不需要改变,因为方格到达目标点的预计消耗是固定的

如果新的G值比较高,则说明新的路径消耗更高,则值不做改变(G值不变也不更新)

7.继续从“开启列表”中找出F 值最小的,从“开启列表'中删除,添加到"关闭列表",再继续找出周围可以到达的方块,如此循环

8.结束判断
当“开启列表”中出现目标方块E时,说明路径已经找到

当“开启列表”中没有了数据,则说明没有合适路径
 

代码实现 

A*算法的估价函数的实现

class Node {
    public int x,y;
    public Node parent;
    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int F;//F为估价函数
    public int G;//G代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销
    public int H;//H表示待检测结点到目标节点B的估计移动开销
    public void calcF() {
        this.F = this.G + this.H;
    }//计算估价函数
}

迷宫地图的设计



import javax.swing.*;
import javax.swing.JLabel;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

public class MyMaze extends JPanel implements KeyListener {
    //生成地图用到变量
    final static int wall =0; //代表墙
    final static int road =1; //代表空地
    static int num = 21; //迷宫长度
    int width = 20; //迷宫宽度
    static int [][] mMap; //迷宫
    boolean[][] visit; //用来标记某一格是否被访问过
    Node start = new Node(1,1); //开始节点
    Node end = new Node(num-2,num-2);//结束节点
    Node cur; //当前格
    Node next; //下一格
    Stack<Node> path = new Stack<>();//记录生成地图时遍历的顺序
    //走迷宫时用到的变量
    private Node movePerson;
    List<Integer> xPath=new ArrayList<>();//记录迷宫中行进的轨迹
    List<Integer> yPath=new ArrayList<>();
    private boolean drawPath = false;



    //A*算法使用到的变量
    public int sValue = 10;//设每一步的权值为10
    private ArrayList<Node> openList = new ArrayList<>();//维护一个开放列表
    private ArrayList<Node> closeList = new ArrayList<>();//维护一个关闭列表

    //初始化,初始化迷宫参数
    MyMaze(){
        mMap = new int [num][num];
        visit = new boolean[num][num];
        for (int i = 0; i < num; i = i+2) {//初始化地图的空格
            for (int j = 0; j < num; j=j+2){
                mMap[i][j] = wall;//其余均为墙
                visit[i][j] = false;
            }
        }
        for (int i = 1; i < num; i = i+2) {//初始化地图的空格
            for (int j = 1; j < num; j=j+2){
                mMap[i][j] = road;//奇数行奇数列的格子均为路
                visit[i][j] = false;
            }
        }
        visit[start.x][start.y] = true;//被访问的开始节点
        mMap[start.x][start.y] = road;//地图上的开始节点为路
        cur = start; //将 当前格 标记为开始格
        movePerson=new Node(start.x-1,start.y);
        drawPath=false;//
        createMaze();
        this.addKeyListener(this);
        this.setFocusable(true);//移动光标能聚焦在组件上
    }

    public void init(){//第一轮结束后,再次初始化地图
        mMap = new int [num][num];
        visit = new boolean[num][num];
        for (int i = 0; i < num; i = i+2) {//初始化地图的空格
            for (int j = 0; j < num; j=j+2){
                mMap[i][j] = wall;//其余均为墙
                visit[i][j] = false;
            }
        }
        for (int i = 1; i < num; i = i+2) {//初始化地图的空格
            for (int j = 1; j < num; j=j+2){
                mMap[i][j] = road;//奇数行奇数列的格子均为路
                visit[i][j] = false;
            }
        }
        visit[start.x][start.y] = true;
        mMap[start.x][start.y] = road;
        cur = start; //将当前格标记为开始格
        movePerson=new Node(start.x-1,start.y);//设置移动的起始点
        drawPath=false;
        xPath.clear();//path迷宫中行进的轨迹 清空行走的路径坐标
        yPath.clear();
        openList.clear();//A*中的开放列表清空
        closeList.clear();//A*中的关闭列表
        createMaze();
        this.setFocusable(true);//获取控件点击事件
        repaint();
    }

    void printMaze() {
        for(int i = 0; i < num; i++)
        {
            for(int j = 0; j < num; j++)
            {
                System.out.print(mMap[i][j]+" ");
            }
            System.out.println();
        }
    }

    //深度优先遍历
    void createMaze() {
        path.push(cur); //将 当前格 压入栈
        while(!path.empty()) {
            ArrayList<Node> mNei=notVisitedNei(cur);//
            if(mNei.size()==0){//如果该格子没有可访问的邻接格,则跳回上一个格子
                cur = path.pop();
                continue;
            }
            next = mNei.get(new Random().nextInt(mNei.size()));//随机选取一个邻接格
            int x = next.x;
            int y = next.y;

            if(visit[x][y]){//如果该节点被访问过,则回到上一步继续寻找
                cur = path.pop();
            }
            else{//否则将当前格压入栈,标记当前格为已访问,并且在迷宫地图上移除障碍物
                path.push(next);
                visit[x][y] = true;
                mMap[x][y] = road;
                mMap[(cur.x + x) / 2][(cur.y + y) / 2] = road; //打通当前格与下一格
                cur = next;//当前格等于下一格
            }
        }
        mMap[start.x-1][start.y]=1;//设置入口
        mMap[end.x+1][end.y]=1;//设置出口
    }

    public ArrayList<Node> notVisitedNei(Node node)//寻找未访问的邻接节点
    {
        int []nei={2,0,-2,0,2};//因为在生成地图时是先0 1 相间隔的,1与1之间相隔为2
        ArrayList<Node> list = new ArrayList<Node>();
        for(int i = 0; i < nei.length-1; i++)
        {
            int x = node.x + nei[i];//对3个方向进行访问
            int y = node.y + nei[i+1];
            if( x >= 0 && x < num && y >= 0 && y < num)
            {
                if(!visit[x][y])//未访问,则入数组
                    list.add(new Node(x,y));
            }
        }
        return list;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setBackground(Color.WHITE);
        g.setColor(Color.darkGray);//画墙   没有被深度遍历到的0就绘制成为墙
        for(int i=0;i<num;i++){
            for(int j=0;j<num;j++){
                if(mMap[i][j]==0){//0代表墙
                    g.fillRect(10+i*width,10+j*width,width,width);//绘制矩形,就是墙
                }
            }
        }

        if(drawPath){//画出A*算法求得的路径  提示路径
            g.setColor(Color.MAGENTA);
            Node parent = findPath(start, end); //父节点  开始节点和出口
            ArrayList<Node> arrayList = new ArrayList<Node>();
            while (parent != null) {//当存在开始节点和结束节点时
                arrayList.add(new Node(parent.x, parent.y));
                parent = parent.parent;
            }
            for (int i = 0; i <num; i++) {
                for (int j = 0; j < num; j++) {
                    if (exists(arrayList, i, j)) {
                        g.fillOval(10+i*width+width/4 , 10+j*width+width/4,
                                width / 2, width / 2);
                    }
                }
            }
        }

        g.setColor(Color.CYAN);//画移动的轨迹
        for(int i=0;i<xPath.size();i++){
            g.fillOval(10+xPath.get(i)*width+width/4 , 10+yPath.get(i)*width+width/4,
                    width / 2, width / 2);
        }
        g.setColor(Color.BLUE);//画点的移动
        g.fillOval(10+movePerson.x*width+width/4 , 10+movePerson.y*width+width/4,
                width / 2, width / 2);

    }

    private boolean isOutOfBorder(int x, int y) {//越界检测
        if (x > num-1 || y >num-1 || x < 0 || y < 0) {
            return true;
        }
        return false;
    }

    private void checkWin() {//通关检测
        if (movePerson.x==num-1 && movePerson.y==num-2) {
            JOptionPane.showMessageDialog(null, "Congratulation! " +"\n"+
                            "Success belongs to you!", "My Maze",
                    JOptionPane.PLAIN_MESSAGE);
            init();
        }
    }

    //A*算法
    public Node findMinFNodeInOpenList() {//寻找最小移动开销的节点
        Node tempNode = openList.get(0);
        for (Node node : openList) {
            if (node.F < tempNode.F) {//通过估价函数找出值较小的那个节点
                tempNode = node;
            }
        }
        return tempNode;
    }

    public ArrayList<Node> findNeighborNodes(Node currentNode) {//找上下左右四个方向的邻居节点
        ArrayList<Node> arrayList = new ArrayList<Node>();//走迷宫时的一些限制,是墙就走不了,不是墙才可以走
        int topX = currentNode.x;
        int topY = currentNode.y - 1;
        if (!isOutOfBorder(topX, topY) && !exists(closeList, topX, topY) && mMap[topX][topY]==1) {
            arrayList.add(new Node(topX, topY));
        }
        int bottomX = currentNode.x;
        int bottomY = currentNode.y + 1;
        if (!isOutOfBorder(bottomX, bottomY) && !exists(closeList, bottomX, bottomY) && mMap[bottomX][bottomY]==1) {
            arrayList.add(new Node(bottomX, bottomY));
        }
        int leftX = currentNode.x - 1;
        int leftY = currentNode.y;
        if (!isOutOfBorder(leftX, leftY) && !exists(closeList, leftX, leftY) && mMap[leftX][leftY]==1) {
            arrayList.add(new Node(leftX, leftY));
        }
        int rightX = currentNode.x + 1;
        int rightY = currentNode.y;
        if (!isOutOfBorder(rightX, rightY) && !exists(closeList, rightX, rightY) && mMap[rightX][rightY]==1) {
            arrayList.add(new Node(rightX, rightY));
        }
        return arrayList;
    }

    public Node findPath(Node startNode, Node endNode) {
        openList.add(startNode);// 把起点加入 open list

        while (openList.size() > 0) {
            Node currentNode = findMinFNodeInOpenList();// 遍历 open list ,查找 F值最小的节点,把它作为当前要处理的节点
            openList.remove(currentNode);// 从open list中移除
            closeList.add(currentNode);// 把这个节点移到 close list

            ArrayList<Node> neighborNodes = findNeighborNodes(currentNode);//寻找邻居节点
            for (Node node : neighborNodes) {
                if (exists(openList, node)) {//如果邻居节点在open列表中
                    foundPoint(currentNode, node);//更新列表中父节点和估价函数信息
                } else {
                    notFoundPoint(currentNode, endNode, node);//如果邻居节点不在open列表中,则将该点加入open列表中
                }
            }

            if (find(openList, endNode) != null) {//如果找到尾节点,则返回尾节点
                return find(openList, endNode);
            }
        }

        // return find(openList, endNode);
        return null;
    }

    //判断是否更新G值
    private void foundPoint(Node tempStart, Node node) {//已经在开始列表中,新的G值更低则修改
        int G = calcG(tempStart, node);
        if (G < node.G) {
            node.parent = tempStart;
            node.G = G;
            node.calcF();
        }
    }

    private void notFoundPoint(Node tempStart, Node end, Node node) {//
        node.parent = tempStart;
        node.G = calcG(tempStart, node);
        node.H = calcH(end, node);
        node.calcF();
        openList.add(node);
    }

    private int calcG(Node start, Node node) {
        int G = sValue;
        int parentG = node.parent != null ? node.parent.G : 0;
        return G + parentG;
    }

    private int calcH(Node end, Node node) {
        int step = Math.abs(node.x - end.x) + Math.abs(node.y - end.y);
        return step * sValue;
    }

    public static Node find(List<Node> nodes, Node point) {
        for (Node n : nodes)
            if ((n.x == point.x) && (n.y == point.y)) {
                return n;
            }
        return null;
    }

    public static boolean exists(List<Node> nodes, Node node) {//判断节点是否存在某一list中
        for (Node n : nodes) {
            if ((n.x == node.x) && (n.y == node.y)) {
                return true;
            }
        }
        return false;
    }

    public static boolean exists(List<Node> nodes, int x, int y) {//判断节点是否存在某一list中
        for (Node n : nodes) {
            if ((n.x == x) && (n.y == y)) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {//响应键盘监听
        int keyCode = e.getKeyCode();
        int x=movePerson.x;
        int y=movePerson.y;
        switch (keyCode){
            case KeyEvent.VK_SPACE: //空格键选择是否路径的显示的情况
                if (drawPath) {
                    drawPath = false;
                } else {
                    drawPath = true;
                }
                repaint();
                break;
            case KeyEvent.VK_LEFT: //根据键盘控制移动
                x--;
                break;
            case KeyEvent.VK_RIGHT:
                x++;
                break;
            case KeyEvent.VK_UP:
                y--;
                break;
            case KeyEvent.VK_DOWN:
                y++;
                break;
        }
        if(!isOutOfBorder(x,y)&&mMap[x][y]==1){
            xPath.add(movePerson.x);
            yPath.add(movePerson.y);
            movePerson.x=x;
            movePerson.y=y;
        }
        repaint();
        checkWin();
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    public static void main(String[] args) {
        JPanel p = new MyMaze();
        Image icon=Toolkit.getDefaultToolkit().getImage("maze.png");//设置最小化的图标
        JFrame frame = new JFrame("(按空格键显示或隐藏路径提示)");
        frame.setIconImage(icon);
        frame.getContentPane().add(p);//添加画布
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//使用 System exit 方法退出应用程序。
        frame.setSize(460, 480);//设置窗口大小
        frame.setLocation(200, 200);
        frame.setVisible(true);

    }
}

实验结果

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值