计算机软件技术实习(实验准备)——基于A*搜索算法迷宫游戏开发

实验内容:

  1. 迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;

2) 要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。

3)要求迷宫游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统提示迷宫路径要求基于A*算法实现,输出玩家当前位置到迷宫出口的最优路径。设计交互友好的游戏图形界面。

示例:


import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

public class Test extends JFrame implements ActionListener,KeyListener{
    boolean map[][] = new PMap().prim(2, 0, 20, 19, true);
    PaintMap p = new PaintMap(map,new EMap(map).exitmap());

    public Test() {           //迷宫的窗口信息
        this.setTitle("迷宫游戏");
        this.add(p);
        this.setSize(500,500);
        this.setVisible(true);
        this.setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        addKeyListener(this);//监听键盘
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Test().setVisible(true);
            }
        });

    }

    public void keyPressed(KeyEvent key) {//监听键盘,上下左右空格
        switch(key.getKeyCode()) {
            case KeyEvent.VK_UP:p.moveUp();break;
            case KeyEvent.VK_DOWN:p.moveDown();break;
            case KeyEvent.VK_LEFT:p.moveLeft();break;
            case KeyEvent.VK_RIGHT:p.moveRight();break;
            case KeyEvent.VK_SPACE:p.PressSp();break;
        }
    }

    public void keyReleased(KeyEvent arg0) {}
    public void keyTyped(KeyEvent arg0) {}
    public void actionPerformed(ActionEvent arg0) {}
}

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class PaintMap extends JPanel{
    final int unitSize = 10;//单位大小
    private int width;//宽
    private int height;//高
    private int startX;//开始点
    private int startY;
    private boolean block;
    private boolean b[][];
    private boolean IsDisplay;
    private boolean flag;
    private ArrayList<Integer> ToExit = new ArrayList<Integer>(); //通向终点的路径的结点组成的列表
    public PaintMap(boolean b[][],ArrayList<Integer>a) {//初始化
        ToExit = a;
        this.b = b;
        width = b.length;
        height = b[0].length;
        startX = 0; //初始位置
        startY = 1;
        block = true;    //初始化全是墙
        IsDisplay = false;
        flag = true;
    }
    public void paint(Graphics g) {
        //墙的颜色
        g.setColor(Color.DARK_GRAY);
        for(int i = 0; i < width; i++)
            for(int j = 0; j < height; j++)
                if(!b[i][j]) {
                    g.fill3DRect(30 + i * unitSize, 30 + j * unitSize, unitSize, unitSize, true);//参数依次表示为:要填充矩形的x坐标、y坐标、要填充矩形的宽度、高度
                }


        //出口路线颜色:
        if(IsDisplay) {     //只有在敲击空格键的情况下显示最佳路径
            g.setColor(Color.LIGHT_GRAY);
            for(int i = 0; i < ToExit.size(); i += 2) {     //路径横纵坐标依次存储在ToExit中,所以间隔加2
                g.fill3DRect(30 + ToExit.get(i) * unitSize, 30 + ToExit.get(i + 1) * unitSize, unitSize, unitSize, true);
                flag=true;
            }
        }

        //
        if(!flag) {      //只有在敲击空格键的情况下显示最佳路径
            g.setColor(Color.ORANGE);
            for(int j = 0; j < ToExit.size(); j += 2) {    //路径横纵坐标依次存储在ToExit中,所以间隔加2
                g.fill3DRect(30 + ToExit.get(j) * unitSize, 30 + ToExit.get(j + 1) * unitSize, unitSize, unitSize,true);
            }
        }

        //控制格子颜色:
        g.setColor(Color.PINK);
        if(IsEdge(startX, startY)) {
            g.fill3DRect(30 + startX * unitSize, 30 + startY * unitSize, unitSize, unitSize, true);
        }
        else {
            g.fill3DRect(30 + unitSize,30, unitSize, unitSize, true);
        }
    }


    //本程序中使用的坐标轴为:以左上角顶点为原点,向右为 x 轴正方向,向下为 y 轴正方向

    public void moveUp() {   //向上移动
        startY -= 1;    //坐标先改变
        if(IsEdge(startX, startY)) {   //如果这个点在迷宫边界或超出迷宫范围,将改变还原
            if(!b[startX][startY]) {
                block = false;
                startY += 1;
            }
            if(block)
                repaint();
            else
                block = true;
            Win(startX , startY);
        }
        else
            startY += 1;
    }

    public void moveDown() {
        startY += 1;
        if(IsEdge(startX, startY)) {
            if(!b[startX][startY]) {
                block = false;
                startY -= 1;
            }
            if(block)
                repaint();
            else
                block = true;
            Win(startX , startY);
        }
        else
            startY -= 1;
    }

    public void moveLeft() {
        startX -= 1;
        if(IsEdge(startX, startY)) {
            if(!b[startX][startY]) {
                block = false;
                startX += 1;
            }
            if(block)
                repaint();
            else
                block = true;
            Win(startX , startY);
        }
        else
            startX += 1;
    }

    public void moveRight() {
        startX += 1;
        if(IsEdge(startX , startY)) {
            if(!b[startX][startY]) {
                block = false;
                startX -= 1;
            }
            if(block)
                repaint();
            else
                block = true;
            Win(startX , startY);
        }
        else
            startX -= 1;
    }

    public void PressSp() {      //点击空格显示路径
        if(IsDisplay && flag) {
            IsDisplay = false;
            flag = false;}
        else {
            IsDisplay = true;
            flag = true;
            repaint();
        }
    }

    private boolean IsEdge(int x,int y) {//判断是否在迷宫内,在就返回true
        return (x < width && y < height && x >= 0 && y >= 0) ;
    }

    private void Win(int x,int y) {  //判断赢没赢游戏
        if(x == width - 1 && y == height - 2) {  //终点,是固定不变的
            Object[] options = {"不服再来!","狠心离开!"};
            int response=JOptionPane.showOptionDialog ( this, "出来了","Game Over",JOptionPane.YES_OPTION ,JOptionPane.PLAIN_MESSAGE, null,
                    options, options[0] ) ;
            if (response == 0){     //选再来一局的话
                removeAll();
                repaint();
                b = new PMap().prim(0, 0, (width - 1) / 2,(height - 1) / 2, true);
                ToExit = new EMap(b).exitmap();
                startX = 0;
                startY = 1;
                block = true;
                IsDisplay = false;
                repaint();
            }
            else //选择退出
                System.exit(0);

        }
    }
}

import java.util.ArrayList;
import javax.swing.JPanel;


//生成随机的迷宫
public class PMap extends JPanel  {

    public boolean[][] prim(int startX , int startY , int widthLimit , int heightLimit , boolean haveBorder){    //0,1,20,19,true
        final boolean block = false , unblock = true;	  //block表示是墙,unblock表示是可以走的路

        //针对异常情况
        if(widthLimit < 1)//宽度最低设置为1
            widthLimit = 1;
        if(heightLimit < 1)//高度最低设置为1
            heightLimit = 1;
        if(startX < 0 || startX >= widthLimit)  //起点不能超出迷宫
            startX = (int)Math.round( Math.random() * (widthLimit - 1) );//超出就在迷宫内随机选择一个作为起点
        if(startY < 0 || startY >= heightLimit)
            startY=(int)Math.round( Math.random() * (heightLimit - 1) );
        if(!haveBorder) {
            --widthLimit;
            --heightLimit;
        }
        //迷宫尺寸换算成带墙尺寸
        widthLimit *= 2;
        heightLimit *= 2;
        //迷宫起点换算成带墙起点
        startX *= 2;
        startY *= 2;
        if(haveBorder) {
            ++startX;
            ++startY;
        }
        //初始化迷宫
        boolean[][]mazeMap = new boolean [widthLimit + 1][heightLimit + 1];
        //设置全为墙
        for(int i = 0; i <= widthLimit; i++)
            for(int j = 0; j <= heightLimit; j++)
                mazeMap[i][j] = block;
        mazeMap[0][1] = unblock;   //入口
        mazeMap[widthLimit][heightLimit - 1] = unblock;   //出口

        ArrayList<Integer> blockPos = new ArrayList<Integer>(); //存放邻居墙的列表

        int targetX = startX , targetY = startY;

        mazeMap[targetX][targetY] = unblock;

        //首先针对起点,将起点邻墙加入列表,和起点(0,1)点进行比较
        //列表在意义上是每三个元素为一组:一个点的 x 坐标,一个点的 y 坐标,将要移动的方向

        if(targetY > 1) {
            blockPos.add(targetX); blockPos.add(targetY - 1); blockPos.add(0);//上
        }
        if (targetX < widthLimit)
        {
            blockPos.add(targetX + 1);blockPos.add(targetY);blockPos.add(1);//右
        }
        if (targetY < heightLimit)
        {
            blockPos.add(targetX);blockPos.add(targetY + 1);blockPos.add(2);//下
        }
        if (targetX > 1)
        {
            blockPos.add(targetX - 1);blockPos.add(targetY);blockPos.add(3);//左
        }

        //列表不为空时
        while(!blockPos.isEmpty()) {
            int blockIndex = (int)Math.round(Math.random() * (blockPos.size() / 3-1)) * 3;//选中三个一组的最开始那个,即x坐标位,+1位表示y坐标位,+2表示方向位
            if(blockIndex + 2 < blockPos.size()) {
                if(blockPos.get(blockIndex + 2).equals(0)) { //+2表示方向位,如果向上,那么坐标跟着改变
                    targetX = blockPos.get(blockIndex);
                    targetY = blockPos.get(blockIndex + 1) - 1;
                }
                else if(blockPos.get(blockIndex + 2).equals(1)) {
                    targetX = blockPos.get(blockIndex) + 1;
                    targetY = blockPos.get(blockIndex + 1);
                }
                else if(blockPos.get(blockIndex + 2).equals(2)) {
                    targetX = blockPos.get(blockIndex);
                    targetY = blockPos.get(blockIndex + 1) + 1;
                }
                else if(blockPos.get(blockIndex+2).equals(3)) {
                    targetX = blockPos.get(blockIndex) - 1;
                    targetY = blockPos.get(blockIndex + 1);
                }

            }
            //例子:口口口,最左的是起点,中间是它的邻墙,右面的则是上几行得到的,也就是 “对面 ”的格子

            if(mazeMap[targetX][targetY] == block) {     //起点对面格子是墙,打通邻墙
                //打通墙
                if(blockIndex + 1 < blockPos.size())
                    mazeMap[blockPos.get(blockIndex)][blockPos.get(blockIndex+1)] = unblock;
                else
                    System.out.println("error");
                mazeMap[targetX][targetY] = unblock;
                //然后再添加当前目标的邻墙
                if (targetY > 1 && mazeMap[targetX][targetY - 1] == block && mazeMap[targetX][targetY - 2] == block)
                {
                    blockPos.add(targetX);blockPos.add(targetY-1);blockPos.add(0);//向上情况
                }
                if (targetX < widthLimit -1&& mazeMap[targetX + 1][targetY] == block && mazeMap[targetX + 2][targetY] == block)
                {
                    blockPos.add(targetX+1);blockPos.add(targetY);blockPos.add(1);//向右情况
                }
                if (targetY < heightLimit-1 && mazeMap[targetX][targetY + 1] == block && mazeMap[targetX][targetY + 2] == block)
                {
                    blockPos.add(targetX);blockPos.add(targetY+1);blockPos.add(2);//向下情况
                }
                if (targetX > 1 && mazeMap[targetX - 1][targetY] == block && mazeMap[targetX - 1][targetY] == block)
                {
                    blockPos.add(targetX - 1); blockPos.add(targetY); blockPos.add(3);//向左情况
                }
            }
            //对面已经是通路了,就从列表移除这面墙
            for(int l = blockIndex , k = 0; k < 3; k++) {
                blockPos.remove(l);
            }
        }
        return mazeMap;
    }
}

import java.util.ArrayList;

public class EMap {
    private ArrayList<Integer> blockPos = new ArrayList<Integer>();
    private int d[][]= { {0 , -1} , {1 , 0} , {0 , 1} , {-1 , 0} };//上 右 下 左
    private boolean a[][];
    private int width;
    private int height;
    private boolean fl = false;
    public EMap(boolean b[][]) {//b是 PMap.java用 Prim 算法生成的迷宫,复制到 a中,使得不修改 b 中迷宫
        width = (b.length - 1) / 2;
        height = (b[0].length - 1) / 2;
        a = new boolean [b.length][b[0].length];
        for(int i = 0; i < b.length; i++)
            for(int j = 0; j < b[0].length; j++)
                a[i][j] = b[i][j];
    }

    /**深度优先搜索找迷宫通路
     * 1.从一个点向四周开始搜索,选择一个方向向下搜索
     * 2.直到遇到墙返回上一父节点,选择其他方向搜索,不断递归,并对已经搜索过的方向进行标注以免重复搜索,并删除列表中的路径
     * 3.如果能够到达终点,就追溯之前路径,加入到列表中
     */
    private void dfs(int x,int y,int c) {        //c表示不能走的方向;

        if(x == (width * 2) && y == (height * 2 - 1)) {      //找到终点,fl设为true
            fl = true;
            return ;
        }

        for(int i = 0; i < 4; i++) {         //从一个点向四周开始搜索
            if(c == i)continue;           //c表示你从一个方向搜过来的,就不要再重新搜回去了
            int dx = x + d[i][0];
            int dy = y + d[i][1];
            if(ise(dx,dy) && a[dx][dy]) {
                if(fl)  break;             //如果找到终点,直接跳出,不继续不搜索
                blockPos.add(dx);  blockPos.add(dy);          //路径加入到列表中
                a[dx][dy] = false;
                dfs(dx , dy , (i + 2) % 4);            //从新的结点继续开始搜
            }
        }

        if(!fl) {             //没找到终点,说明不通,这次尝试不对,就将不对的移除列表
            blockPos.remove(blockPos.size() - 1);
            blockPos.remove(blockPos.size() - 1);
        }

    }


    public ArrayList<Integer> exitmap() {

        blockPos.add(0); blockPos.add(1); dfs(0,1,3);            //默认从(0,1)开始,不能走的方向为向下

        return blockPos;
    }
    private boolean ise(int dx,int dy) {
        return (0 <= dx && dx <= width * 2 && 0 <= dy && dy <= height * 2);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值