计算机软件实习项目三 基于Prim算法和A*算法的走迷宫游戏

项目要求:

1.要求随机生成迷宫地图,并求解迷宫。

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

3.要求设计良好的交互界面。

算法思想:

Prim算法生成地图:

算法描述:
1. 把图中预置的通路节点分成两个集合,已经在通路里的属于U,未添加到通路里的属于V-U;
2. 任意选出一个点作为初始顶点,标记为visit,随机选择一个可达的顶点,并标记为visit,并将所联通路径上的顶点添加到U;
3. 重复以上操作,直到所有顶点都已经添加到集合U

步骤:

如图1首先先生成一个空地图:

图1

1.如图2随机选择一个蓝色0,然后看红色1隔着这个蓝色0对面的格子,是否是黄色的1,
1)如果是,则把对面的黄色1标记成红色1,即变成通路,然后把蓝色0变成红色的,即也变成通路;
2)如果不是,就把这个蓝色的0变成灰色的;

图2

2.继续为红色通路集合寻找待定的蓝色0,然后随机选择一个蓝色0,然后看红色1隔着这个蓝色0对面的格子,是否是黄色的1,           
1)如果是,则把对面的黄色1标记成红色1,即变成通路,然后把蓝色0变成红色的,即也变成通路;
2)如果不是,就把这个蓝色的0变成灰色的

图3

3)如果随机选的蓝色的0隔着红色1对面的不是黄色1,说明这不是一条通路,就把这个蓝色的0变成灰色0,变成确定的障碍物;

图4

接下来就继续随机找另一个蓝色的0;
即不停地重复上面的步骤3,直到整个地图都没有蓝色0了,地图就生成完毕。

A*算法求解最优路径

算法描述:

搜索区域(The Search Area):搜索区域被划分为简单的二维数组,数组每个元素对应一个结点。
开放列表(Open List):将寻路过程中待检测的结点存放于Open List中,而已检测过的结点则存放于Close List中。
路径排序(Path Sorting):下一步怎么移动由以下公式确定;F(n)=G+H。F(n)为估价函数,G代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销。H表示待检测结点到目标节点B的估计移动开销。
启发函数(Heuristics  Function): H为启发函数,可以看作是一种试探,由于在找到唯一路径前,不确定在前面会出现什么障碍物,因此用了一种计算H的算法,具体可以根据实际情况决定。为了简化问题,H采用的是传统的曼哈顿距离,也就是横纵向走的距离之和。

步骤:

1. 把起点加入 open list 。
2.重复如下过程:

  • 遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
  • 把这个节点移到 close list 。
  • 对当前节点的 所有相邻的节点遍历,并分三种情况处理

        1)如果相邻节点是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。

        2)如果它不在 open list 中,把它加入 open list ,并且把当前节点设置为它的父亲,记录该方格的 F , G 和 H 值。

        3)如果它已经在 open list 中,检查这条路径 ( 即经由当前节点到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。

  • 停止,当你把终点加入到了 open list 中,此时路径已经找到了,或者查找终点失败,并且 open list 是空的,此时没有路径。

3. 保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。

代码实现:

MainFrame类:

package Proj_Labyrinth;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;

public class MainFrame extends JFrame {
    public static final int WALL = 0;
    public static final int ROAD = 1;
    public static final int AUTOTRACE = 2;
    public static final int TRACE = 3;
    public static final int GAME_WIDTH = 800;//窗体宽度
    public static final int GAME_HEIGHT = 650;//窗体高度

    private JPanel Gamepanel;

    public static int[][] map = new generateMap().getMap();
    private Player player;
    private LinkedList<Node> trace;
    private Node Start;
    private Node End;

    private boolean OpenAS = false;

    public MainFrame() throws HeadlessException {

        player = new Player();
        trace = player.getTrace();
        Start = new Node(1, 1);
        End = new Node(39, 39);
        //初始化窗口
        initFrame();
        //初始化画板,并生成随机地图
        initGamepanel();
        //添加功能按钮
        setButton();
        //设置键盘监听
        setKeyboardListenner();
    }

    private void setKeyboardListenner() {
        //玩家移动时,不能碰到墙壁
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                Node head = player.getTrace().getFirst();
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP: {
                        if (map[head.getX()][head.getY() - 1] != WALL) {
                            player.setDirection(Direction.UP);
                            player.move();
                            Gamepanel.repaint();
                            break;
                        }
                    }
                    case KeyEvent.VK_DOWN: {
                        if (map[head.getX()][head.getY() + 1] != WALL) {
                            player.setDirection(Direction.DOWN);
                            player.move();
                            Gamepanel.repaint();
                            break;
                        }
                    }
                    case KeyEvent.VK_LEFT: {
                        if (map[head.getX() - 1][head.getY()] != WALL) {
                            player.setDirection(Direction.LEFT);
                            player.move();
                            Gamepanel.repaint();
                            break;
                        }
                    }
                    case KeyEvent.VK_RIGHT: {
                        if (map[head.getX() + 1][head.getY()] != WALL) {
                            player.setDirection(Direction.RIGHT);
                            player.move();
                            Gamepanel.repaint();
                            break;
                        }
                    }
                }
                //到终点提示是否重新开始
                if ((head.getX() + 1 == End.getX() && head.getY() == End.getY())
                        || (head.getX() == End.getX() && head.getY() + 1 == End.getY())) {
                    int select = JOptionPane.showConfirmDialog(null, "恭喜你,游戏胜利" + "\n" + "是否重新开始", "走迷宫", JOptionPane.YES_NO_OPTION);
                    if (select == JOptionPane.YES_OPTION) {
                        map = new generateMap().getMap();
                        player = new Player();
                        trace = player.getTrace();
                        OpenAS = false;
                        Gamepanel.repaint();
                    }
                }
            }
        });
        requestFocus();
    }

    private void initGamepanel() {

        Gamepanel = new JPanel() {
            @Override
            public void paint(Graphics g) {
                for (int i = 0; i <= 41; i++) {
                    g.drawLine(0, i * 15, 615, i * 15);
                }
                for (int i = 0; i <= 41; i++) {
                    g.drawLine(i * 15, 0, i * 15, 615);
                }
                for (int rows = 0; rows <= 40; rows++) {
                    for (int cols = 0; cols <= 40; cols++) {
                        if (map[rows][cols] == WALL) {
                            g.setColor(Color.BLACK);
                            g.fillRect(rows * 15, cols * 15, 15, 15);
                        }
                        if (map[rows][cols] == ROAD) {
                            g.setColor(Color.yellow);
                            g.fillRect(rows * 15, cols * 15, 15, 15);
                        }
                    }
                    g.setColor(Color.blue);
                    g.fillRect(39 * 15, 39 * 15, 15, 15);
                }

                Node Head = trace.getFirst();
                for (Node node : trace) {
                    if (node.getX() == Head.getX() && node.getY() == Head.getY()) {
                        g.setColor(Color.BLUE);
                    } else {
                        g.setColor(Color.RED);
                    }
                    g.fillRect(node.getX() * 15, node.getY() * 15, 15, 15);
                    map[node.getX()][node.getY()] = TRACE;
                }

                //绘制搜索结果路径
                AStar A = new AStar();
                for (int rows = 0; rows < A.getASmap().length; rows++) {
                    for (int cols = 0; cols < A.getASmap()[0].length; cols++) {
                        if (A.getASmap()[rows][cols] == AUTOTRACE) {
                            if ((rows == Start.getY() && cols == Start.getX()) || (rows == End.getY() && cols == End.getX())) {
                                continue;
                            }
                            if (OpenAS) {
                                g.setColor(Color.green);
                                g.fillRect(rows * 15, cols * 15, 15, 15);
                            }
                            if (!OpenAS) {
                                g.setColor(Color.yellow);
                                g.fillRect(rows * 15, cols * 15, 15, 15);
                            }
                        }
                    }
                }
            }
        };
        Gamepanel.setSize(615, GAME_HEIGHT);
        add(Gamepanel);

    }

    private void setButton() {
        //设置重开按钮
        JButton restart;
        restart = new JButton("重新开始");
        restart.setBounds(650, 250, 100, 50);
        restart.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                map = new generateMap().getMap();
                player = new Player();
                trace = player.getTrace();
                OpenAS = false;
                Gamepanel.repaint();
                requestFocus();
            }
        });
        add(restart);

        //设置自动寻路按钮:
        JButton AutoSearch;
        AutoSearch = new JButton("自动寻路");
        AutoSearch.setBounds(650, 310, 100, 50);
        AutoSearch.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                OpenAS = true;
                Gamepanel.repaint();
                requestFocus();
            }
        });
        add(AutoSearch);

        //设置隐藏自动路径按钮
        JButton Hide;
        Hide = new JButton("隐藏路径");
        Hide.setBounds(650, 370, 100, 50);
        Hide.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                OpenAS = false;
                Gamepanel.repaint();
                requestFocus();
            }
        });
        add(Hide);
    }

    //初始化窗口
    private void initFrame() {
        setTitle("走迷宫");
        setResizable(false);
        setSize(GAME_WIDTH, GAME_HEIGHT);
        setLayout(null);
        setLocationRelativeTo(null);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setFocusable(true);
    }

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

Node类(保存每个节点的信息):

package Proj_Labyrinth;

public class Node {
    private int x;
    private int y;
    private int G;//G代表的是从初始位置Start沿着已生成的路径到指定待检测结点移动开销
    private int H;//H表示待检测结点到目标节点End的估计移动开销
    private int F;//F值表示每个节点的权值,即F=G+H
    private Node parent;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getG() {
        return G;
    }

    public void setG(int g) {
        G = g;
    }

    public int getH() {
        return H;
    }

    public void setH(int h) {
        H = h;
    }

    public int getF() {
        return F;
    }

    public void setF(int f) {
        F = f;
    }

    public void calcF() {
        this.F = this.G + this.H;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

generateMap类(生成随机迷宫):

package Proj_Labyrinth;

import java.util.LinkedList;
import java.util.Random;

public class generateMap {
    public static final int WALL = 0;//设置墙的参数
    public static final int EMPTY = -1;//设置未组成路径的节点的参数
    public static final int ROAD = 1;
    public static final int mapWidth = 41;
    public static final int mapHeight = 41;

    private int[][] map;
    private final int[][] dir;
    private final Node Start;

    public generateMap() {
        //dir为4*2的方向数组,参数依次为左右上下
        dir = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        Start = new Node(1, 1);
        //初始化地图
        initMap();
        //Prim算法生成地图
        PRIMCreate(Start.getX(), Start.getY());

    }

    public int[][] getMap() {
        return map;
    }

    //为地图设置初始化参数
    private void initMap() {
        map = new int[mapHeight][mapWidth];
        for (int rows = 0; rows < mapHeight; rows++) {
            for (int cols = 0; cols < mapWidth; cols++) {
                //边界设为WALL,即默认为墙,每个节点设为EMPTY
                if (rows % 2 != 0 && cols % 2 != 0) {
                    map[rows][cols] = EMPTY;
                }
            }
        }
        map[Start.getX()][Start.getY()] = ROAD;
    }

    //判段选定节点是否有邻居节点
    public boolean hasNeighbor(int x, int y) {
        //对该节点的四个方向循环检索
        for (int i = 0; i < 4; i++) {
            if (inArea(x + 2 * dir[i][0], y + 2 * dir[i][1]) && map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] == EMPTY)
                return true;
        }
        return false;
    }

    //Prim算法生成地图参数, @param 起点坐标
    private void PRIMCreate(int x, int y) {
        //list用于存储遍历过的节点的坐标值
        LinkedList<Node> list = new LinkedList<>();
        Random random = new Random();
        int i;
        //随机选择一个蓝色0,然后看红色1(ROAD)隔着这个蓝色0对面的格子,是否是黄色的1{
        while (hasNeighbor(x, y)) {
            i = random.nextInt(4);
            //如果是,则把对面的黄色1(EMPTY)标记成红色1(ROAD),即变成通路,然后把蓝色0变成红色的,即也变成通路;
            if (inArea(x + 2 * dir[i][0], y + 2 * dir[i][1])
                    && (map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] == EMPTY)) {
                map[x + 2 * dir[i][0]][y + 2 * dir[i][1]] = ROAD;
                map[x + dir[i][0]][y + dir[i][1]] = ROAD;
                //将标记的节点添加到list
                list.add(new Node(x + 2 * dir[i][0], y + 2 * dir[i][1]));
            }
            //如果不是,就把这个蓝色的0变成灰色的;
            else {
                if (map[x + dir[i][0]][y + dir[i][1]] == ROAD) {
                    continue;
                }
                map[x + dir[i][0]][y + dir[i][1]] = WALL;
            }
        }
        if (list.size() == 0) {
            return;
        }
        //遍历所有经过的节点,并生成随机路径
        for (Node node : list) {
            PRIMCreate(node.getX(), node.getY());
        }
    }

    //判断节点是否出界
    private boolean inArea(int x, int y) {
        return x > 0 && x < mapHeight - 1 && y > 0 && y < mapWidth - 1;
    }

}

AStar类(A*算法搜索路径):

package Proj_Labyrinth;

import java.util.ArrayList;
import java.util.List;

public class AStar {
    public final int AUTOTRACE = 2;//用于标记寻路路径
    public  int[][] ASmap;//定义一个迷宫单元数组
    public int STEP = 1;//设每一步的权值为1
    private ArrayList<Node> openList = new ArrayList<>();//定义一个开放列表
    private ArrayList<Node> closeList = new ArrayList<>();//定义一个关闭列表

    public AStar() {
        ASmap = MainFrame.map;//初始化迷宫单元为新生成的对应地图,把generateMap类里面生成的地图传给NODES,再在此地图基础上用A*算法寻路径
        Node startNode = new Node(1, 1);//起点
        Node endNode = new Node(39, 39);//终点
        Node parent = findPath(startNode, endNode); //父节点
        ArrayList<Node> arrayList = new ArrayList<>();//路径搜索完后,所有的父节点所组成的路径就是最优路径,故用array list 来存储此路径
        while (parent != null) {
            arrayList.add(new Node(parent.getX(), parent.getY()));
            parent = parent.getParent();
        }
        //把路径映射到地图上
        for (int i = 0; i < ASmap.length; i++) {
            for (int j = 0; j < ASmap.length; j++) {
                if (exists(arrayList, i, j)) {
                    ASmap[i][j] = AUTOTRACE;//标记关闭列表里的方格为2,为了方便后面在界面画系统寻路路径
                }
            }
        }
    }

    //寻找开放列表里F值最小的节点的方法
    public Node findMinFNodeInOpneList() {
        Node tempNode = openList.get(0);
        for (Node node : openList) {
            if (node.getF() < tempNode.getF()) {
                tempNode = node;
            }
        }
        return tempNode;
    }

    //遍历当前节点上下左右四个邻居的方法,
    public ArrayList<Node> findNeighborNodes(Node currentNode) {
        int[][] dir = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        ArrayList<Node> arrayList = new ArrayList<>();
        // 只考虑上下左右,不考虑斜对角
        int x = currentNode.getX();
        int y = currentNode.getY();
        for (int i = 0; i < 4; i++) {
            if (canReach(x + dir[i][0], y + dir[i][1]) && !exists(closeList, x + dir[i][0], y + dir[i][1])) {
                arrayList.add(new Node(x + dir[i][0], y + dir[i][1]));
            }
        }
        return arrayList;
    }


    //判断此处坐标是否可达,若超界或者是墙则不可达
    public boolean canReach(int x, int y) {
        return x >= 0 && x < ASmap.length && y >= 0 && y < ASmap.length && ASmap[x][y] == 1;
    }


    //A*寻路过程
    public Node findPath(Node startNode, Node endNode) {
        openList.add(startNode);// 把起点加入open list
        while (openList.size() > 0) {
            Node currentNode = findMinFNodeInOpneList();// 遍历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 list已经存在
                    foundPoint(currentNode, node);//计算G值并寻找最优路径
                } else {
                    node.setParent(currentNode);
                    node.setG(calcG(node));
                    node.setH(calcH(endNode, node));
                    node.calcF();
                    openList.add(node);
                }
            }
            if (find(openList, endNode) != null) {
                return find(openList, endNode);//找到终点了并返回
            }
        }
        return find(openList, endNode);
    }

    //在列表里可以找到节点后的情况
    private void foundPoint(Node tempStart, Node node) {
        int G = calcG(node);
        if (tempStart.getG()<G) {//G值比较,选取G值小的做最优路径
            node.setParent(tempStart);
            node.setG(G);
            node.calcF();
        }
    }

    //计算G值的方法
    private int calcG(Node node) {
        int G = STEP;
        int parentG = node.getParent() != null ? node.getParent().getG() : 0;
        return G + parentG;
    }

    //计算H值的方法
    private int calcH(Node end, Node node) {
        int step = Math.abs(node.getX() - end.getX()) + Math.abs(node.getY() - end.getY());
        return step * STEP;
    }

    //找到终点的方法
    public  Node find(List<Node> nodes, Node point) {
        for (Node n : nodes)
            if ((n.getX() == point.getX()) && (n.getY() == point.getY())) {
                return n;
            }
        return null;
    }

    //下面两个是exist方法的重载,判断不同参数情况时节点是否在列表里
    public  boolean exists(List<Node> nodes, Node node) {
        for (Node n : nodes) {
            if ((n.getX() == node.getX()) && (n.getY() == node.getY())) {
                return true;
            }
        }
        return false;
    }

    public  boolean exists(List<Node> nodes, int x, int y) {
        for (Node n : nodes) {
            if ((n.getX() == x) && (n.getY() == y)) {
                return true;
            }
        }
        return false;
    }

    public int[][] getASmap() {
        return ASmap;
    }

}

Player类(保存玩家路径和操控设置):

package Proj_Labyrinth;

import java.util.LinkedList;

public class Player {
    private LinkedList<Node> trace;
    private Direction direction = Direction.RIGHT;
    public Player() {
        trace =new LinkedList<>();
        trace.add(new Node(1,1));
    }

    public LinkedList<Node> getTrace() {
        return trace;
    }

    public void setTrace(LinkedList<Node> trace) {
        this.trace = trace;
    }

    public void setDirection(Direction direction) {
        this.direction = direction;
    }
    public void move() {
        Node head = trace.getFirst();
        switch (direction) {
            case UP -> trace.addFirst(new Node(head.getX(), head.getY()-1));
            case DOWN -> trace.addFirst(new Node(head.getX(), head.getY()+1));
            case LEFT -> trace.addFirst(new Node(head.getX()-1, head.getY()));
            case RIGHT -> trace.addFirst(new Node(head.getX()+1, head.getY()));
        }
    }

}

Direction类(方向设置):

package Proj_Labyrinth;

public enum Direction {
    //方向枚举
    UP,DOWN,RIGHT,LEFT
}

效果展示:

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值