随机迷宫之A*求解思路:
经过网上学习及参考大佬大部分代码,小白愿与大家一起解析迷宫生成及A*自动寻路思路,共同走进迷宫这一小游戏。极乐世界,影影迷宫。
step1:迷宫随机生成
类BackGroundPanel中,用方法public void paintComponent(Graphics graphics){}
进行划线,画出网格。然后再用方法public static void getYANAiList(){},随机生成60个障碍方块
step2:A*自动求解
①、先找到A点方块的上下左右四个方块,计算出各个方块的G、H、F值,然后将A方块放入closedlist中(已经扩散过了),将A方块的上下左右四个方块放入openlist中(需要继续向外扩散)。然后遍历openlist集合,向外扩散。
②、以C点方块为例,找到C点方块的上下右三个点(其中左边的A点方块已经在closedlist中,所以不考虑),计算出GHF值后,将上下右三个方块放入openlist中,将C点方块放入closedlist中。
PS:一定要注意,G值,一定是前一个点G值+1得到。
一直向外扩散,当K点方块向外扩散的时候,周围方块包括了终点,将K点方块放入closedlist中,再将B点方块也放入closedlist中。。
此时的closedlist中,必然包括了从A点到B点的路线。那么,接下来就是如何取路线。
在第①步的时候,找到了A点方块的上下左右四个方块,在将这四个方块放入openlist之前添加一步:将这四个方块的父级方块设置为A方块。
在第②步的时候,找到了C点方块的上下右三个方块,也将这三个方块的父级方块设置为C方块,然后再放入openlist中。
同样,每次根据中心方块找到四周的方块的时候,都将四周的方块的父级方块设置为这个中心方块。那么在根据K方块找到四周方块的时候,你会发现,终点B方块的父级方块是K方块。
此时遍历closedlist,找到终点B方块,放入autowaylist中,然后再遍历closedlist,找到B方块的父级方块,让如autowaylist中。一直循环,直到找到起点A方块,放入autowaylist中,那么这个autowaylist集合就是路线。
这两部分一共包括六个类:
1、JPanel类扩展类BackGroundPanel
2、类FangKuaiPosition
3、JPanel类扩展类MyPanel
4、工具类CommonUtil
5、JFrame类扩展类BasePanel
6、类AutoFindWay
小白第一天
上午:背景面板类
类BackGroundPanel包含两个方法:
1、public BackGroundPanel(){}
用于布局管理
public BackGroundPanel(){
this.setLayout(null);
}
2、public void paintComponent(Graphics graphics){}
用于划线,生成迷宫网格
public void paintComponent(Graphics graphics){
super.paintComponent(graphics);
graphics.drawLine(50, 0, 50, 700);
graphics.drawLine(100, 0, 100, 700);
graphics.drawLine(150, 0, 150, 700);
graphics.drawLine(200, 0, 200, 700);
graphics.drawLine(250, 0, 250, 700);
graphics.drawLine(300, 0, 300, 700);
graphics.drawLine(350, 0, 350, 700);
graphics.drawLine(400, 0, 400, 700);
graphics.drawLine(450, 0, 450, 700);
graphics.drawLine(500, 0, 500, 700);
graphics.drawLine(550, 0, 550, 700);
graphics.drawLine(600, 0, 600, 700);
graphics.drawLine(650, 0, 650, 700);
graphics.drawLine(700, 0, 700, 700);
graphics.drawLine(750, 0, 750, 700);
graphics.drawLine(800, 0, 800, 700);
graphics.drawLine(0, 50, 850, 50);
graphics.drawLine(0, 100, 850, 100);
graphics.drawLine(0, 150, 850, 150);
graphics.drawLine(0, 200, 850, 200);
graphics.drawLine(0, 250, 850, 250);
graphics.drawLine(0, 300, 850, 300);
graphics.drawLine(0, 350, 850, 350);
graphics.drawLine(0, 400, 850, 400);
graphics.drawLine(0, 450, 850, 450);
graphics.drawLine(0, 500, 850, 500);
graphics.drawLine(0, 550, 850, 550);
graphics.drawLine(0, 600, 850, 600);
graphics.drawLine(0, 650, 850, 650);
}
下午:方块类
类 FangKuaiPosition包含方法如下:
1、public FangKuaiPosition(int x, int y) {}
根据方块坐标生成方块(这里的坐标指的是网格坐标,不是像素坐标)
@param x, x方向的方块单位(即x方向像素/size)
@param y, y方向的方块单位(即y方向像素/size)
public FangKuaiPosition(int x, int y) {
this.x = x;
this.y = y;
}
2、 public FangKuaiPosition(int x, int y,FangKuaiPosition fk) {}
根据方块坐标生成方块(这里的坐标指的是网格坐标),参数与方法1不同
@param x, x方向的方块单位(即x方向像素/size)
@param y, y方向的方块单位(即y方向像素/size)
@param fk, 前一个方块(即父级方块)
public FangKuaiPosition(int x, int y,FangKuaiPosition fk){
this.x = x;
this.y = y;
this.previousFK = fk;
}
3、public FangKuaiPosition(MyPanel myPpanel) {}
根据jp生成方块
@param myPpanel mypanel对象
public FangKuaiPosition(MyPanel myPpanel) {
this.x = myPpanel.getX() / MyPanel.size;
this.y = myPpanel.getY() / MyPanel.size;
}
4、一些get和set方法获取:
x方向的方块单位(即x方向像素/size);
y方向的方块单位(即y方向像素/size);
F,和值,G+H;
G,该点到出发点的移动量;
H,该点到目的点的估算移动量;
previousFK,父节点
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 getF() {
return F;
}
public void setF(int f) {
F = f;
}
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 FangKuaiPosition getPreviousFK() {
return previousFK;
}
public void setPreviousFK(FangKuaiPosition previousFK) {
this.previousFK = previousFK;
}
5、public boolean equals(Object obj) {}
对equals方法重写,用于判断两个网格是否相等,以及比较这两个网格的X和Y是否相等
@Override
public boolean equals(Object obj) {
if(((FangKuaiPosition)obj).getX() == this.x && ((FangKuaiPosition)obj).getY() == this.y){
return true;
} else {
return false;
}
}
小白第二天
上午:类MyPanel
MyPanel类包含两个方法:
1、public MyPanel(int x, int y){}
根据方块的位置设置panel的位置属性
@param x 方块的X单位
@param y 方块的Y单位
public MyPanel(int x, int y){
this.setBounds(x * size, y * size, size, size);
}
2、public MyPanel(FangKuaiPosition fk){}
根据类Fangkuaiposition设置panel位置属性
@param fk Fangkuaiposition对象
public MyPanel(FangKuaiPosition fk){
this.setBounds(fk.getX() * size, fk.getY() * size, size, size);
}
下午:工具类CommonUtil
类CommonUtil包含一个方法:
public static Dimension getScreenSize(){}
获取屏幕尺寸
@return Dimension
Dimension.getWidth()
Dimension.getHeight()
public static Dimension getScreenSize(){
return Toolkit.getDefaultToolkit().getScreenSize();
}
小白第三天
上午:类BasePanel–step1
类BasePanel包含:
1、定义一些必要变量如起点cat、终点fish 对象bgp,
集合如openList–需要向外扩散的方块集合,openList–已走过路线集合,YANList–地图上的障碍物或不可穿越地方的集合
private static final int frameWidth = 815;//jf的宽
public static int frameHeight;//jf的高
public static int width;//内部panel的宽
static {
frameHeight = 635;
width = 800;
}
private static final int height = 600;//内部panel的高
public static int widthLength = 16;//方块单位的y方向最大值
public static int heightLength = 12;//方块单位的x方向最大值
public static BackGroundPanel bgp = new BackGroundPanel();//容器panel,所有的方块都放入这个panel中,然后将这个panel添加到jf中
public static MyPanel cat = new MyPanel(0,0);//起点
public static MyPanel fish = new MyPanel(ThreadLocalRandom.current().nextInt(widthLength),ThreadLocalRandom.current().nextInt(heightLength));//随机生成终点
public static List<FangKuaiPosition> YANList = new ArrayList<>(); //地图上的障碍物/不可穿越地方的集合
public static List<FangKuaiPosition> closedList; //已走过路线集合
static {
closedList = new ArrayList<>();
}
public static List<FangKuaiPosition> openList = new ArrayList<>(); //需要向外扩散的方块的集合
下午:类BasePanel–step2
2、方法1: public BasePanel(){}
public BasePanel(){
//获取屏幕尺寸相关信息
Dimension dimension = CommonUtil.getScreenSize();
//设定JFrame基础属性
//jf的x坐标
int beginX = (int) (dimension.getWidth() / 2 - 400);
//jf的y坐标
int beginY = (int) (dimension.getHeight() / 2 - 300);
this.setBounds(beginX, beginY, frameWidth, frameHeight);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(null);
//起点panel
cat.setBackground(Color.green);
//终点panel
fish.setBackground(Color.red);
bgp.setBounds(0, 0, width, height);
bgp.add(cat);
bgp.add(fish);
//将障碍物生成panel
for(FangKuaiPosition fk : YANList){
MyPanel panel = new MyPanel(fk);
panel.setBackground(Color.gray);
bgp.add(panel);
}
this.add(bgp);
this.repaint();
this.setVisible(true);
}
3、主函数:
public static void main(String[] args) throws InterruptedException {}
public static void main(String[] args) throws InterruptedException {
getYANAiList();
BasePanel bp = new BasePanel();
AutoFindWay afw = new AutoFindWay();
List<FangKuaiPosition> wayList = afw.getWayLine(cat, fish);
bp.movePanel(wayList);
}
4、方法 2:
public void movePanel(List wayList) throws InterruptedException{}
方块移动
@param wayList 移动路线
@throws InterruptedException
public void movePanel(List<FangKuaiPosition> wayList) throws InterruptedException{
if(wayList == null || wayList.size() == 0){
System.out.println("无法 到达终点 !");
return;
}
for(int i = wayList.size() - 2; i >= 0; i--){
FangKuaiPosition fk = wayList.get(i);
//向上
//方块自动移动的间隔时间
long sleepTime = 10;
while(cat.getY() > fk.getY() * MyPanel.size){
cat.setBounds(cat.getX(), cat.getY() - 2, MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向下
while(cat.getY() < fk.getY() * MyPanel.size){
cat.setBounds(cat.getX(), cat.getY() + 2, MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向左
while(cat.getX() > fk.getX() * MyPanel.size){
cat.setBounds(cat.getX() - 2, cat.getY(), MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向右
while(cat.getX() < fk.getX() * MyPanel.size){
cat.setBounds(cat.getX() + 2, cat.getY(), MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
}
System.out.println("寻路结束!");
}
5、方法3:
public static void getYANAiList(){}
随机生成障碍方块
public static void getYANAiList(){
//随机生成60个障碍方块
while(YANList.size() < 60){
int x = ThreadLocalRandom.current().nextInt(widthLength);
int y = ThreadLocalRandom.current().nextInt(heightLength);
/*
* 根据方块坐标生成方块(这里的坐标指的是网格坐标,不是像素坐标)
* @param x x方向的方块单位(即x方向像素/size)
* @param y y方向的方块单位(即y方向像素/size)
*/
FangKuaiPosition fk = new FangKuaiPosition(x,y);
//新生成的方块不能已存在YANlist中,也不能和起点/终点重合
if(YANList.contains(fk) || (cat.getX() == fk.getX()*MyPanel.size && cat.getY() == fk.getY() * MyPanel.size)
|| (fish.getX() == fk.getX() * MyPanel.size && fish.getY() == fk.getY() * MyPanel.size)){
continue;
}
YANList.add(fk);
}
}
小白第四天
上午:类AutoFindWay–step1
类AutoFindWay包含:
1、定义必要变量beginFk、endFk
public static FangKuaiPosition beginFk = null;
public static FangKuaiPosition endFk = null;
2、主函数:
public static void main(String[] args) {}
public static void main(String[] args) {
AutoFindWay afw = new AutoFindWay();
MyPanel cat = new MyPanel(4,6);
MyPanel fish = new MyPanel(10,10);
afw.getWayLine(cat,fish);
}
下午:类AutoFindWay–step2
3、方法1:
public List getWayLine(MyPanel cat, MyPanel fish){}
获取路线方法入口
@param cat 起点
@param fish 终点
@return 路线集合List
public List<FangKuaiPosition> getWayLine(MyPanel cat, MyPanel fish){
//定义返回的结果
List<FangKuaiPosition> wayList = new ArrayList<>();
//中心方块的四周方块集合
List<FangKuaiPosition> tmpList;
//将起点和终点转换为fangkuaiposition对象
beginFk = new FangKuaiPosition(cat);
beginFk.setG(0);
endFk = new FangKuaiPosition(fish);
//获取中心方块(起点)四周的方块
tmpList = aroundFk(beginFk);
//如果四周没有符合条件的方块,则说明是死路
if(tmpList == null || tmpList.size() == 0){
return wayList;
}
//放入openlist中,作为向外扩散的中心方块
BasePanel.openList.addAll(tmpList);
//遍历openlist,以每个方块作为中心方块,向外扩散
for(int i = 0; i < BasePanel.openList.size(); i++){
//获取新的中心方块,并获取四周方块
FangKuaiPosition tmpFk = BasePanel.openList.get(i);
tmpList = aroundFk(tmpFk);
//周围方块为空,说明 是死路,继续下一个 方块
if(tmpList == null || tmpList.size() == 0){
//如果openlist已经遍历完了,都没有四周方块,则要在for循环外面判断waylist是否包含终点,
//如果不包含,则到达不了终点
continue;
}
//如果周围方块包括终点方块,则结束寻路
if(tmpList.contains(endFk)){
//如果四周方块包含终点,则将终点添加到closelist中,并跳出openlist循环(已经到达终点)
for(FangKuaiPosition obj : tmpList){
if(obj.equals(endFk)){
BasePanel.closedList.add(obj);
break;
}
}
break;
小白第五天
上午:类AutoFindWay–step2
/*
* 将中心方块的周围方块添加到openlist集合
* 注意:如果openlist中已经存在,则 需要将消耗最少的方块更新到 openlist中
*/
for(FangKuaiPosition fk : tmpList){
if(BasePanel.openList.contains(fk)){
for(FangKuaiPosition openFk : BasePanel.openList){
if(openFk.equals(fk)){
if(openFk.getG() > fk.getG()){
openFk.setG(fk.getG());
openFk.setF(openFk.getG() + openFk.getH());
openFk.setPreviousFK(fk.getPreviousFK());
break;
}
}
}
}else{
BasePanel.openList.add(fk);
}
}
//删掉openlist中的当前中心方块,继续获取并处理下一个
BasePanel.openList.remove(i);
i--;
}
/*
* 从 closedlist中获取到路线
* 先获取终点,然后根据fangkuaiposition.previousFk获取上一个方块,一直获取到起点
*/
for(int i = 0; i < BasePanel.closedList.size(); i++){
//如果wayList<=0,说明还没有获取到第一个方块(终点);如果wayList>0,说明已经获取到第一个方块(终点),则不用再执行下一个if语句
if(wayList.size() > 0){
if(wayList.get(wayList.size() - 1).getPreviousFK().equals(BasePanel.closedList.get(i))){
wayList.add(BasePanel.closedList.get(i));
//如果获取到的方块是起点,则跳出for循环
if(BasePanel.closedList.get(i).equals(beginFk)){
break;
}
//获取到一个方块后,将该方块从closedlist中删除,然后从0开始遍历closedlist找到第一个方块的previousfk。
//所以需要赋值i=-1,因为continue的时候会执行一次i++
BasePanel.closedList.remove(BasePanel.closedList.get(i));
i = -1;
}
continue;
}
//第一个方块为终点,获取到一个方块后,将该方块从closedlist中删除,然后从0开始遍历closedlist找到第一个方块的previousfk。
//所以需要赋值i=-1,因为continue的时候会执行一次i++
if(BasePanel.closedList.get(i).equals(endFk)){
wayList.add(BasePanel.closedList.get(i));
BasePanel.closedList.remove(BasePanel.closedList.get(i));
i = -1;
}
}
return wayList;
}
下午:类AutoFindWay–step3
4、方法2:
public List aroundFk(FangKuaiPosition fk){}
获取周围方块
①判断是否超越边界
②判断是否是障碍物/已计算过的方块
@param fk 中心方块
@return 周围方块结集合
public List<FangKuaiPosition> aroundFk(FangKuaiPosition fk){
if(fk.getX() == 10 && fk.getY() == 11){
System.out.println(".....");
}
List<FangKuaiPosition> list = new ArrayList<>();
//判断上面的方块是否符合条件
//判断是否超过越边界
if(fk.getY() - 1 >= 0){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX(), fk.getY() - 1, fk);
//判断是否是障碍物/已计算过的方块
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断下面的方块是否符合条件
if(fk.getY() + 1 < BasePanel.heightLength){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX(), fk.getY() + 1, fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断左面的方块是否符合条件
if(fk.getX() - 1 >= 0){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX() - 1, fk.getY(), fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断右面的方块是否符合条件
if(fk.getX() + 1 < BasePanel.widthLength){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX() + 1, fk.getY(), fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//将中心方块添加到已处理过的集合中
BasePanel.closedList.add(fk);
getFGH(list,fk);
return list;
}
小白第六天
上午:类AutoFindWay–step3
5、方法3:
public void getFGH(List list,FangKuaiPosition currFk){}
给集合中的每个方块计算出FGH的值
@param list
public void getFGH(List<FangKuaiPosition> list,FangKuaiPosition currFk){
if(list != null && list.size() > 0){
for(FangKuaiPosition fk : list){
fk.setG(currFk.getG() + 1);
fk.setH(toGetH(fk,endFk));
fk.setF(fk.getG() + fk.getH());
}
}
}
6、方法4:
public int toGetH(FangKuaiPosition currentFangKuai,FangKuaiPosition targetFangKuai){}
获取从一个方块到另一个方块的移动量(按方块个数计算)
@param currentFangKuai
@param targetFangKuai
@return
public int toGetH(FangKuaiPosition currentFangKuai,FangKuaiPosition targetFangKuai){
int h = 0;
h += Math.abs(currentFangKuai.getX() - targetFangKuai.getX());
h += Math.abs(currentFangKuai.getY() - targetFangKuai.getY());
return h;
}
}
下午:运行查看
总代码:
BackGroundPanel.java
package Labyrinth_APP;
import java.awt.Graphics;
import javax.swing.JPanel;
public class BackGroundPanel extends JPanel{
public BackGroundPanel(){
this.setLayout(null);
}
public void paintComponent(Graphics graphics){
super.paintComponent(graphics);
graphics.drawLine(50, 0, 50, 700);
graphics.drawLine(100, 0, 100, 700);
graphics.drawLine(150, 0, 150, 700);
graphics.drawLine(200, 0, 200, 700);
graphics.drawLine(250, 0, 250, 700);
graphics.drawLine(300, 0, 300, 700);
graphics.drawLine(350, 0, 350, 700);
graphics.drawLine(400, 0, 400, 700);
graphics.drawLine(450, 0, 450, 700);
graphics.drawLine(500, 0, 500, 700);
graphics.drawLine(550, 0, 550, 700);
graphics.drawLine(600, 0, 600, 700);
graphics.drawLine(650, 0, 650, 700);
graphics.drawLine(700, 0, 700, 700);
graphics.drawLine(750, 0, 750, 700);
graphics.drawLine(800, 0, 800, 700);
graphics.drawLine(0, 50, 850, 50);
graphics.drawLine(0, 100, 850, 100);
graphics.drawLine(0, 150, 850, 150);
graphics.drawLine(0, 200, 850, 200);
graphics.drawLine(0, 250, 850, 250);
graphics.drawLine(0, 300, 850, 300);
graphics.drawLine(0, 350, 850, 350);
graphics.drawLine(0, 400, 850, 400);
graphics.drawLine(0, 450, 850, 450);
graphics.drawLine(0, 500, 850, 500);
graphics.drawLine(0, 550, 850, 550);
graphics.drawLine(0, 600, 850, 600);
graphics.drawLine(0, 650, 850, 650);
}
}
FangKuaiPosition.java
package Labyrinth_APP;
public class FangKuaiPosition {
/*
* 根据方块坐标生成方块(这里的坐标指的是网格坐标,不是像素坐标)
* @param x x方向的方块单位(即x方向像素/size)
* @param y y方向的方块单位(即y方向像素/size)
*/
public FangKuaiPosition(int x, int y) {
this.x = x;
this.y = y;
}
/*
* 根据方块坐标生成方块(这里的坐标指的是网格坐标)
* @param x x方向的方块单位(即x方向像素/size)
* @param y y方向的方块单位(即y方向像素/size)
* @param fk 前一个方块(父级方块)
*/
public FangKuaiPosition(int x, int y,FangKuaiPosition fk) {
this.x = x;
this.y = y;
this.previousFK = fk;
}
/*
* 根据jp生成方块
* @param myPpanel mypanel对象
*/
public FangKuaiPosition(MyPanel myPpanel) {
this.x = myPpanel.getX() / MyPanel.size;
this.y = myPpanel.getY() / MyPanel.size;
}
static public final int size = 50;//一个方块单位为50像素
private int x;//x方向的方块单位(即x方向像素/size)
private int y;//y方向的方块单位(即y方向像素/size)
private int F;//和值,G+H
private int G;//该点到出发点的移动量
private int H;//该点到目的点的估算移动量
private FangKuaiPosition previousFK;//父节点
public int getF() {
return F;
}
public void setF(int f) {
F = f;
}
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 FangKuaiPosition getPreviousFK() {
return previousFK;
}
public void setPreviousFK(FangKuaiPosition previousFK) {
this.previousFK = previousFK;
}
/*
* 重写equals方法,判断两个网格是否相等,比较这两个网格的X和Y是否相等
*/
@Override
public boolean equals(Object obj) {
if(((FangKuaiPosition)obj).getX() == this.x && ((FangKuaiPosition)obj).getY() == this.y){
return true;
} else {
return false;
}
}
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;
}
}
MyPanel.java
package Labyrinth_APP;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
//定义panel的默认size为50像素
public final static int size = 50;
/*
* 根据方块的位置设置panel的位置属性
* @param x 方块的X单位
* @param y 方块的Y单位
*/
public MyPanel(int x, int y){
this.setBounds(x * size, y * size, size, size);
}
/*
* 根据Fangkuaiposition类设置panel的位置属性
* @param fk Fangkuaiposition对象
*/
public MyPanel(FangKuaiPosition fk){
this.setBounds(fk.getX() * size, fk.getY() * size, size, size);
}
}
CommonUtil.java
package Labyrinth_APP;
import java.awt.Dimension;
import java.awt.Toolkit;
public class CommonUtil {
/*
* 获取屏幕尺寸
* @return Dimension
* Dimension.getWidth()
* Dimension.getHeight()
*/
public static Dimension getScreenSize(){
return Toolkit.getDefaultToolkit().getScreenSize();
}
}
BasePanel.java
package Labyrinth_APP;
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
public class BasePanel extends JFrame{
private static final int frameWidth = 815;//jf的宽
public static int frameHeight;//jf的高
public static int width;//内部panel的宽
static {
frameHeight = 635;
width = 800;
}
private static final int height = 600;//内部panel的高
public static int widthLength = 16;//方块单位的y方向最大值
public static int heightLength = 12;//方块单位的x方向最大值
public static BackGroundPanel bgp = new BackGroundPanel();//容器panel,所有的方块都放入这个panel中,然后将这个panel添加到jf中
public static MyPanel cat = new MyPanel(0,0);//起点
public static MyPanel fish = new MyPanel(ThreadLocalRandom.current().nextInt(widthLength),ThreadLocalRandom.current().nextInt(heightLength));//随机生成终点
public static List<FangKuaiPosition> YANList = new ArrayList<>(); //地图上的障碍物/不可穿越地方的集合
public static List<FangKuaiPosition> closedList; //已走过路线集合
static {
closedList = new ArrayList<>();
}
public static List<FangKuaiPosition> openList = new ArrayList<>(); //需要向外扩散的方块的集合
public BasePanel(){
//获取屏幕尺寸相关信息
Dimension dimension = CommonUtil.getScreenSize();
//设定JFrame基础属性
//jf的x坐标
int beginX = (int) (dimension.getWidth() / 2 - 400);
//jf的y坐标
int beginY = (int) (dimension.getHeight() / 2 - 300);
this.setBounds(beginX, beginY, frameWidth, frameHeight);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(null);
//起点panel
cat.setBackground(Color.green);
//终点panel
fish.setBackground(Color.red);
bgp.setBounds(0, 0, width, height);
bgp.add(cat);
bgp.add(fish);
//将障碍物生成panel
for(FangKuaiPosition fk : YANList){
MyPanel panel = new MyPanel(fk);
panel.setBackground(Color.gray);
bgp.add(panel);
}
this.add(bgp);
this.repaint();
this.setVisible(true);
}
public static void main(String[] args) throws InterruptedException {
getYANAiList();
BasePanel bp = new BasePanel();
AutoFindWay afw = new AutoFindWay();
List<FangKuaiPosition> wayList = afw.getWayLine(cat, fish);
bp.movePanel(wayList);
}
/*
* 方块移动
* @param wayList 移动路线
* @throws InterruptedException
*/
public void movePanel(List<FangKuaiPosition> wayList) throws InterruptedException{
if(wayList == null || wayList.size() == 0){
System.out.println("无法 到达终点 !");
return;
}
for(int i = wayList.size() - 2; i >= 0; i--){
FangKuaiPosition fk = wayList.get(i);
//向上
//方块自动移动的间隔时间
long sleepTime = 10;
while(cat.getY() > fk.getY() * MyPanel.size){
cat.setBounds(cat.getX(), cat.getY() - 2, MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向下
while(cat.getY() < fk.getY() * MyPanel.size){
cat.setBounds(cat.getX(), cat.getY() + 2, MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向左
while(cat.getX() > fk.getX() * MyPanel.size){
cat.setBounds(cat.getX() - 2, cat.getY(), MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
//向右
while(cat.getX() < fk.getX() * MyPanel.size){
cat.setBounds(cat.getX() + 2, cat.getY(), MyPanel.size, MyPanel.size);
Thread.sleep(sleepTime);
}
}
System.out.println("寻路结束!");
}
/*
* 随机生成障碍方块
*/
public static void getYANAiList(){
//随机生成60个障碍方块
while(YANList.size() < 60){
int x = ThreadLocalRandom.current().nextInt(widthLength);
int y = ThreadLocalRandom.current().nextInt(heightLength);
/*
* 根据方块坐标生成方块(这里的坐标指的是网格坐标,不是像素坐标)
* @param x x方向的方块单位(即x方向像素/size)
* @param y y方向的方块单位(即y方向像素/size)
*/
FangKuaiPosition fk = new FangKuaiPosition(x,y);
//新生成的方块不能已存在YANlist中,也不能和起点/终点重合
if(YANList.contains(fk) || (cat.getX() == fk.getX()*MyPanel.size && cat.getY() == fk.getY() * MyPanel.size)
|| (fish.getX() == fk.getX() * MyPanel.size && fish.getY() == fk.getY() * MyPanel.size)){
continue;
}
YANList.add(fk);
}
}
}
AutoFindWay.java
package Labyrinth_APP;
import java.util.ArrayList;
import java.util.List;
public class AutoFindWay {
public static FangKuaiPosition beginFk = null;
public static FangKuaiPosition endFk = null;
public static void main(String[] args) {
AutoFindWay afw = new AutoFindWay();
MyPanel cat = new MyPanel(4,6);
MyPanel fish = new MyPanel(10,10);
afw.getWayLine(cat,fish);
}
/*
* 获取路线方法入口
* @param cat 起点
* @param fish 终点
* @return 路线集合List<FangKuaiPosition>
*/
public List<FangKuaiPosition> getWayLine(MyPanel cat, MyPanel fish){
//定义返回的结果
List<FangKuaiPosition> wayList = new ArrayList<>();
//中心方块的四周方块集合
List<FangKuaiPosition> tmpList;
//将起点和终点转换为fangkuaiposition对象
beginFk = new FangKuaiPosition(cat);
beginFk.setG(0);
endFk = new FangKuaiPosition(fish);
//获取中心方块(起点)四周的方块
tmpList = aroundFk(beginFk);
//如果四周没有符合条件的方块,则说明是死路
if(tmpList == null || tmpList.size() == 0){
return wayList;
}
//放入openlist中,作为向外扩散的中心方块
BasePanel.openList.addAll(tmpList);
//遍历openlist,以每个方块作为中心方块,向外扩散
for(int i = 0; i < BasePanel.openList.size(); i++){
//获取新的中心方块,并获取四周方块
FangKuaiPosition tmpFk = BasePanel.openList.get(i);
tmpList = aroundFk(tmpFk);
//周围方块为空,说明 是死路,继续下一个 方块
if(tmpList == null || tmpList.size() == 0){
//如果openlist已经遍历完了,都没有四周方块,则要在for循环外面判断waylist是否包含终点,
//如果不包含,则到达不了终点
continue;
}
//如果周围方块包括终点方块,则结束寻路
if(tmpList.contains(endFk)){
//如果四周方块包含终点,则将终点添加到closelist中,并跳出openlist循环(已经到达终点)
for(FangKuaiPosition obj : tmpList){
if(obj.equals(endFk)){
BasePanel.closedList.add(obj);
break;
}
}
break;
}
/*
* 将中心方块的周围方块添加到openlist集合
* 注意:如果openlist中已经存在,则 需要将消耗最少的方块更新到 openlist中
*/
for(FangKuaiPosition fk : tmpList){
if(BasePanel.openList.contains(fk)){
for(FangKuaiPosition openFk : BasePanel.openList){
if(openFk.equals(fk)){
if(openFk.getG() > fk.getG()){
openFk.setG(fk.getG());
openFk.setF(openFk.getG() + openFk.getH());
openFk.setPreviousFK(fk.getPreviousFK());
break;
}
}
}
}else{
BasePanel.openList.add(fk);
}
}
//删掉openlist中的当前中心方块,继续获取并处理下一个
BasePanel.openList.remove(i);
i--;
}
/*
* 从 closedlist中获取到路线
* 先获取终点,然后根据fangkuaiposition.previousFk获取上一个方块,一直获取到起点
*/
for(int i = 0; i < BasePanel.closedList.size(); i++){
//如果wayList<=0,说明还没有获取到第一个方块(终点);如果wayList>0,说明已经获取到第一个方块(终点),则不用再执行下一个if语句
if(wayList.size() > 0){
if(wayList.get(wayList.size() - 1).getPreviousFK().equals(BasePanel.closedList.get(i))){
wayList.add(BasePanel.closedList.get(i));
//如果获取到的方块是起点,则跳出for循环
if(BasePanel.closedList.get(i).equals(beginFk)){
break;
}
//获取到一个方块后,将该方块从closedlist中删除,然后从0开始遍历closedlist找到第一个方块的previousfk。
//所以需要赋值i=-1,因为continue的时候会执行一次i++
BasePanel.closedList.remove(BasePanel.closedList.get(i));
i = -1;
}
continue;
}
//第一个方块为终点,获取到一个方块后,将该方块从closedlist中删除,然后从0开始遍历closedlist找到第一个方块的previousfk。
//所以需要赋值i=-1,因为continue的时候会执行一次i++
if(BasePanel.closedList.get(i).equals(endFk)){
wayList.add(BasePanel.closedList.get(i));
BasePanel.closedList.remove(BasePanel.closedList.get(i));
i = -1;
}
}
return wayList;
}
/*
* 获取周围方块
* ①判断是否超越边界
* ②判断是否是障碍物/已计算过的方块
* @param fk 中心方块
* @return 周围方块结集合
*/
public List<FangKuaiPosition> aroundFk(FangKuaiPosition fk){
if(fk.getX() == 10 && fk.getY() == 11){
System.out.println(".....");
}
List<FangKuaiPosition> list = new ArrayList<>();
//判断上面的方块是否符合条件
//判断是否超过越边界
if(fk.getY() - 1 >= 0){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX(), fk.getY() - 1, fk);
//判断是否是障碍物/已计算过的方块
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断下面的方块是否符合条件
if(fk.getY() + 1 < BasePanel.heightLength){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX(), fk.getY() + 1, fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断左面的方块是否符合条件
if(fk.getX() - 1 >= 0){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX() - 1, fk.getY(), fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//判断右面的方块是否符合条件
if(fk.getX() + 1 < BasePanel.widthLength){
FangKuaiPosition tmpFk = new FangKuaiPosition(fk.getX() + 1, fk.getY(), fk);
if(!BasePanel.YANList.contains(tmpFk)
&& !BasePanel.closedList.contains(tmpFk)){
list.add(tmpFk);
}
}
//将中心方块添加到已处理过的集合中
BasePanel.closedList.add(fk);
getFGH(list,fk);
return list;
}
/*
* 给集合中的每个方块计算出FGH的值
* @param list
*/
public void getFGH(List<FangKuaiPosition> list,FangKuaiPosition currFk){
if(list != null && list.size() > 0){
for(FangKuaiPosition fk : list){
fk.setG(currFk.getG() + 1);
fk.setH(toGetH(fk,endFk));
fk.setF(fk.getG() + fk.getH());
}
}
}
/*
* 获取从一个方块到另一个方块的移动量(按方块个数计算)
* @param currentFangKuai
* @param targetFangKuai
* @return
*/
public int toGetH(FangKuaiPosition currentFangKuai,FangKuaiPosition targetFangKuai){
int h = 0;
h += Math.abs(currentFangKuai.getX() - targetFangKuai.getX());
h += Math.abs(currentFangKuai.getY() - targetFangKuai.getY());
return h;
}
}
运行效果:
1、随机迷宫之寻走不通:
2、随机迷宫之查路成功
总结
迷宫万千,算法各异。求解迷宫方法有多种,广度优先搜索算法、dijkstra(迪杰斯特拉)算法、Greed-Best-First(最好优先贪婪算法)、A*算法。
其中最佳选择路径算法,则是A*算法,能求出迷宫最优解。
实随机迷宫生成及自动求解思路比较清晰,但是实际算法实现对于小白来说就十分困难。不过难度大就会迫使我们向优秀的大佬们学习,查看与之相关资料和学习视频。
不断学习积累,熟练一定程序语言与算法,小白会飞快成长,离成功之塔就更近一步。小白愿与大家学习交流,共同进步,欢迎大家留言!