搭建环境:

首先下载Anaconda,这一步是大前提!!!!!!

https://www.bilibili.com/video/BV1tNK3z3EeE/?vd_source=4f1a7ab31a6e7a590a8a8a5088e74b3c

之后可以再base里面,也可以搭建新的环境

这里我conda create -n keshe python=3.8搭建了一个叫做keshe的环境

之后我们再VScode里面使用这个环境,如图右下角是环境选取,右上角三角是开始运行

会看到有下面的报错,

这个时候我们在环境中增加对应的包

pip install PyQt5,再次运行即可成功。。

如果没有Vscode的话,你可以桌面建立一个文本,然后把代码复制进去

import time, sys
from PyQt5.QtWidgets import QDialogButtonBox, QDialog, QMainWindow, QGridLayout, QTextEdit, QLineEdit, QWidget, \
    QMessageBox, QApplication, QLabel, QPushButton, QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import Qt, QTimer, QObject, pyqtSignal, QBasicTimer
from PyQt5.QtGui import QPainter, QColor, QFont, QPen
import json
 
 
class config:
    WIDTH = 67  # 地图列数 - 根据迷宫数据确定
    HEIGHT = 41  # 地图行数 - 根据迷宫数据确定
    blockLength = 12  # 绘制画面时每一个节点方块的边长 - 改小一点以适应更大的迷宫
 
 
class point:  # 点类(每一个唯一坐标只有对应的一个实例)
    _list = []  # 储存所有的point类实例
    _tag = True  # 标记最新创建的实例是否为_list中的已有的实例,True表示不是已有实例
 
    def __new__(cls, x, y):  # 重写new方法实现对于同样的坐标只有唯一的一个实例
        for i in point._list:
            if i.x == x and i.y == y:
                point._tag = False
                return i
        nt = super(point, cls).__new__(cls)
        point._list.append(nt)
        return nt
 
    def __init__(self, x, y):
        if point._tag:
            self.x = x
            self.y = y
            self.father = None
            self.F = 0  # 当前点的评分  F=G+H
            self.G = 0  # 起点到当前节点所花费的消耗
            self.cost = 0  # 父节点到此节点的消耗
        else:
            point._tag = True
 
    @classmethod
    def clear(cls):  # clear方法,每次搜索结束后,将所有点数据清除,以便进行下一次搜索的时候点数据不会冲突。
        point._list = []
 
    def __eq__(self, T):  # 重写==运算以便实现point类的in运算
        if type(self) == type(T):
            return (self.x, self.y) == (T.x, T.y)
        else:
            return False
 
    def __str__(self):
        return '(%d,%d)[F=%d,G=%d,cost=%d][father:(%s)]' % (self.x, self.y, self.F, self.G, self.cost, str(
            (self.father.x, self.father.y)) if self.father != None else 'null')
 
 
class A_Search:  # 核心部分,寻路类
    def __init__(self, arg_start, arg_end, arg_map):
        self.start = arg_start  # 储存此次搜索的开始点
        self.end = arg_end  # 储存此次搜索的目的点
        self.Map = arg_map  # 一个二维数组,为此次搜索的地图引用
        self.open = []  # 开放列表:储存即将被搜索的节点
        self.close = []  # 关闭列表:储存已经搜索过的节点
        self.result = []  # 当计算完成后,将最终得到的路径写入到此属性中
        self.count = 0  # 记录此次搜索所搜索过的节点数
        self.useTime = 0  # 记录此次搜索花费的时间--在此演示中无意义,因为process方法变成了一个逐步处理的生成器,统计时间无意义。
        # 开始进行初始数据处理
        self.open.append(arg_start)
 
    def cal_F(self, loc):
        print('计算值:', loc)
        G = loc.father.G + loc.cost
        H = self.getEstimate(loc)
        F = G + H
        print("F=%d G=%d H=%d" % (F, G, H))
        return {'G': G, 'H': H, 'F': F}
 
    def F_Min(self):  # 搜索open列表中F值最小的点并将其返回,同时判断open列表是否为空,为空则代表搜索失败
        if len(self.open) <= 0:
            return None
        t = self.open[0]
        for i in self.open:
            if i.F < t.F:
                t = i
        return t
 
    def getAroundPoint(self, loc):  # 获取指定点周围所有可通行的点,并将其对应的移动消耗进行赋值。
        l = [(loc.x, loc.y + 1, 10), (loc.x + 1, loc.y + 1, 14), (loc.x + 1, loc.y, 10), (loc.x + 1, loc.y - 1, 14),
             (loc.x, loc.y - 1, 10), (loc.x - 1, loc.y - 1, 14), (loc.x - 1, loc.y, 10), (loc.x - 1, loc.y + 1, 14)]
        for i in l[::-1]:
            if i[0] < 0 or i[0] >= config.HEIGHT or i[1] < 0 or i[1] >= config.WIDTH:
                l.remove(i)
        nl = []
        for i in l:
            if self.Map[i[0]][i[1]] == 0:
                nt = point(i[0], i[1])
                nt.cost = i[2]
                nl.append(nt)
        return nl
 
    def addToOpen(self, l,
                  father):  # 此次判断的点周围的可通行点加入到open列表中,如此点已经在open列表中则对其进行判断,如果此次路径得到的F值较之之前的F值更小,则将其父节点更新为此次判断的点,同时更新F、G值。
        for i in l:
            if i not in self.open:
                if i not in self.close:
                    i.father = father
                    self.open.append(i)
                    r = self.cal_F(i)
                    i.G = r['G']
                    i.F = r['F']
            else:
                tf = i.father
                i.father = father
                r = self.cal_F(i)
                if i.F > r['F']:
                    i.G = r['G']
                    i.F = r['F']
                # i.father=father
                else:
                    i.father = tf
 
    def getEstimate(self, loc):  # H :从点loc移动到终点的预估花费
        return (abs(loc.x - self.end.x) + abs(loc.y - self.end.y)) * 10
 
    def DisplayPath(self):  # 在此演示中无意义
        print('搜索花费的时间:%.2fs.迭代次数%d,路径长度:%d' % (self.useTime, self.count, len(self.result)))
        if self.result != None:
            for i in self.result:
                self.Map[i.x][i.y] = 8
            for i in self.Map:
                for j in i:
                    if j == 0:
                        print('%s' % '□', end='')
                    elif j == 1:
                        print('%s' % '▽', end='')
                    elif j == 8:
                        print('%s' % '★', end='')
                print('')
        else:
            print('搜索失败,无可通行路径')
 
    def process(self):  # 使用yield将process方法变成一个生成器,可以逐步的对搜索过程进行处理并返回关键数据
        while True:
            self.count += 1
            tar = self.F_Min()  # 先获取open列表中F值最低的点tar
            if tar == None:
                self.result = None
                self.count = -1
                break
            else:
                aroundP = self.getAroundPoint(tar)  # 获取tar周围的可用点列表aroundP
                self.addToOpen(aroundP, tar)  # 把aroundP加入到open列表中并更新F值以及设定父节点
                self.open.remove(tar)  # 将tar从open列表中移除
                self.close.append(tar)  # 已经迭代过的节点tar放入close列表中
                if self.end in self.open:  # 判断终点是否已经处于open列表中
                    e = self.end
                    self.result.append(e)
                    while True:
                        e = e.father
                        if e == None:
                            break
                        self.result.append(e)
                    yield (tar, self.open, self.close)
                    break
 
            # self.repaint()
            # print('返回')
            yield (tar, self.open, self.close)
            time.sleep(0.05)  # 暂停时间改短一点,加快动画速度
        # self.useTime = time2 - time1
 
 
class GameBoard(QMainWindow):  # 可视化类,pyqt5进行编写。
    def __init__(self):
        print('初始化地图...')
        self.Map = []
        self.startPoint = None
        self.endPoint = None
        self.createMazeFromText()  # 从文本数据创建迷宫
        
        self.search = None
        self.centerTimer = None
        self.yi = None
        self.special = None
        self.displayFlush = False
        super().__init__()
        print('初始化UI...')
        self.initUI()

    def createMazeFromText(self):
        """从文本数据创建迷宫地图"""
        maze_text = """#################################################################################
#.#...#....$....#...................#...#.........#.......#.............#.......#
#.#.#.#.###.###.#########.#########.#.#####.#####.#####.#.#.#######.###.#.#####.#
#...#.....#...#.#.........#.#.....#.#...#...#...#.......#.#.#.......#.#.#.#...#.#
#############.#.#.#########.#.###.#.###.#.###.#.#.#######.###.#######.#.#.#.#.#.#
#...........#.#...#.#.....#...#...#...#.#.#.#.#...#...#.......#.......#.#.#.#.#.#
#.#########.#.#####.#.#.#.#.###.#####.#.#.#.#.#####.#.#########.###.###.###.#.#.#
#.#.........#...#...#.#.#.#...#.....#.#.#.#...#.#...#.......#.....#.#...#...#...#
#.#########.#.#.###.#.#.#####.###.#.#.#.#.#.###.#.#########.#####.#.#.###.#####.#
#.#.......#.#.#...#...#.#.....#.#.#.#...#.#.....#.#.....#.#...#...#.......#...#.#
#.#.#####.#.#.###.#####.#.#####.#.#.###.#.#######.###.#.#.###.#.###########.#.#.#
#...#...#.#.#...#.....#.#.......#.#.#...#.....#...#...#.....#.#.#...#...#...#...#
#####.#.#.#.#########.#.#######.#.###.#######.#.###.#########.###.#.#.#.#.#######
#.....#...#.#.........#.......#.#...#.#.#.....#.#.....#.......#...#.#.#.#.#.....#
#.#########.#.#########.###.###.###.#.#.#.###.#.#.###.#.#######.###.#.###.#.###.#
#...#.#.....#...#.....#.#.#...#.#.#.....#...#.#.#...#.#...#...#...#.#.#...#...#.#
###.#.#.#####.#.#.#.###.#.###.#.#.#####.###.###.#####.###.#.#.#.###.#.#.#####.#.#
#...#...#.....#.#.#.#...#...#.....#...#.#...#...........#.#.#...#...#.......#.#.#
#.###.#########.#.#.#.###.#.#####.#.#.###.###.###########.#.#####.#########.###.#
#.#.............#.#.......#.#...#.#.#...#.#...#.#.......#.......#.#...#.....#...#
#.#.#############.#########.#.#.###.###.#.#.###.#.#####.#.#######.#.#.#.#####.#.#
#.#.#...........#.#.#.#.....#.#.....#...#.#.....#...#.#.#.#.#...#.#.#.#.#.....#.#
#.###.#########.#.#.#.#######.#######.###.#####.###.#.#.#.#.###.#.#.#.#.#####.#.#
#.....#...#.....#...#.........#.....#...#.....#...#...#.#.....#.#...#.#.#.....#.#
#.#####.#.#.#######.###########.#######.#.#######.###.#.###.###.#####.#.#.#####.#
#.....#.#.#...#...#.#.......#.........#.#...#.......#.#.#...#...#.....#.#.#...#.#
#######.#.###.#.###.#.#####.#.#####.###.#.#.#.#######.#.#####.###.#####.#.###.#.#
#.......#.#...#.....#.#...#.#...#.#.....#.#.#.#.#.....#...#...#...#.....#...#.#.#
#.#######.#.#.#####.#.###.#.###.#.#######.#.#.#.#.#######.#.###.#.###.#####.#.#.#
#.#.#.....#.#.#...#.#...#.#...#...#.#...#.#...#.#.....#.#...#...#...#.......#...#
#.#.#.#####.#.#.#.#####.#.###.###.#.#.#.#.#####.#####.#.#####.#####.#########.###
#.#...#.....#.#.#.#...#...#.#.#...#...#.#.#...#.....#...#.#...#...#.....#...#.#.#
#.###.###.#.###.#.#.#.###.#.#.#.#######.#.#.#.#####.###.#.#.###.#.#####.###.#.#.#
#...#...#.#.#...#.#.#...#.#.#.#.#.......#...#.........#.#...#...#.#...#...#.#...#
#.#.###.#.#.#.###.#.###.#.#.#.#.###.###.###########.###.#.###.###.###.###.#.###.#
#.#...#.#.#.#...#...#...#.#.#.#.....#...#...#.....#.#...#.....#.....#.#...#...#.#
#.###.#.#.#####.#####.#.#.#.#.#######.###.#.#####.#.#.#############.#.#.###.#.#.#
#...#.#...#...#.....#.#.#.#.#.#...#...#.#.#.......#.#.#...#...#...#...#.#.#.#...#
###.#.#####.#.#####.#.###.#.#.#.#.#.###.#.#########.#.#.#.#.#.#.#.#####.#.#.#####
#...#.......#.......#.......#...#.......@...........#...#...#...#.......#.......#
#################################################################################"""
        
        lines = maze_text.strip().split('\n')
        
        if not lines:
            print("迷宫数据为空")
            return
            
        # 设置地图尺寸
        config.HEIGHT = len(lines)
        config.WIDTH = len(lines[0])
        
        # 初始化地图
        self.Map = []
        for i in range(config.HEIGHT):
            row = []
            for j in range(config.WIDTH):
                char = lines[i][j]
                if char == '#':  # 墙壁
                    row.append(1)
                elif char == '$':  # 起点
                    row.append(0)
                    self.startPoint = (j, i)
                    print(f"找到起点: ({j}, {i})")
                elif char == '@':  # 终点
                    row.append(0)
                    self.endPoint = (j, i)
                    print(f"找到终点: ({j}, {i})")
                else:  # 可通行的路径
                    row.append(0)
            self.Map.append(row)
            
        print(f"迷宫创建成功: {config.WIDTH}x{config.HEIGHT}")
        print(f"起点: {self.startPoint}, 终点: {self.endPoint}")
 
    def initUI(self):
        # 开始初始化UI部分
        # 创建UI控件
        self.label_tips = QLabel(
            "<p style='color:green'>迷宫求解器</p>基于A*算法的迷宫求解可视化程序\n<p style='color:green'>颜色说明:</p>\n黄色代表起点,绿色代表终点,黑色代表墙壁,红色代表待搜索的open列表,灰色代表已搜索过的close列表,蓝色代表当前搜索到的路径",
            self)
        self.label_display = QLabel("", self)
        self.button_start = QPushButton("开始搜索", self)
        self.button_reset = QPushButton("重置迷宫", self)

        # 设置控件属性
        self.label_tips.setWordWrap(True)
        self.label_display.setWordWrap(True)
        # 设置控件样式
        self.label_display.setStyleSheet("border:1px solid black")
        self.label_display.setAlignment(Qt.AlignLeft)
        self.label_display.setAlignment(Qt.AlignTop)
        # 设置控件的尺寸和位置
        self.label_tips.resize(200, 150)
        self.button_start.resize(80, 30)
        self.button_reset.resize(80, 30)  # 修复这里的笔误

        self.label_tips.move(50 + (config.WIDTH) * config.blockLength, 20)
        self.label_display.move(50 + (config.WIDTH) * config.blockLength, 200)
        self.button_start.move(50 + (config.WIDTH) * config.blockLength, 170)
        self.button_reset.move(150 + (config.WIDTH) * config.blockLength, 170)
        
        # 给控件绑定事件
        self.button_start.clicked.connect(self.button_StartEvent)
        self.button_reset.clicked.connect(self.button_Reset)
        
        # UI初始化完成
        window_width = 100 + (config.WIDTH * config.blockLength) + 250
        window_height = 100 + (config.HEIGHT * config.blockLength)
        self.setGeometry(100, 100, window_width, window_height)
        self.setMinimumSize(window_width, window_height)
        self.setMaximumSize(window_width, window_height)
        self.setWindowTitle('迷宫求解器 - A*算法')
        self.show()
 
    def addDisplayText(self, text):
        if self.displayFlush:
            self.label_display.setText(text + '\n')
            self.displayFlush = False
        else:
            self.label_display.setText(self.label_display.text() + text + '\n')
 
    def button_StartEvent(self):
        if self.startPoint != None and self.endPoint != None:
            if self.centerTimer == None:
                self.centerTimer = QBasicTimer()
            self.button_start.setEnabled(False)
            self.centerTimer.start(30, self)  # 加快动画速度
            self.search = A_Search(point(self.startPoint[1], self.startPoint[0]),
                                   point(self.endPoint[1], self.endPoint[0]), self.Map)
            self.yi = self.search.process()
            self.addDisplayText('开始进行搜索')

    def button_Reset(self):
        """重置迷宫"""
        if self.centerTimer and self.centerTimer.isActive():
            self.centerTimer.stop()
        self.search = None
        self.yi = None
        self.special = None
        point.clear()
        self.button_start.setEnabled(True)
        self.displayFlush = True
        self.addDisplayText('迷宫已重置')
        self.repaint()
 
    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        self.drawBoard(event, qp)
        qp.end()
 
    def drawBoard(self, event, qp):
        self.drawMap(qp)
 
    def drawMap(self, qp):  # 画面绘制方法,每次地图有所改动都将重绘
        if self.search != None:
            if self.special != None:
                e = self.special[0]
                path = [e]
                while True:
                    e = e.father
                    if e != None:
                        path.append(e)
                    else:
                        break
            else:
                path = None
            pen = QPen(QColor(0, 0, 0), 1, Qt.SolidLine)
            qp.setPen(pen)
            for i in range(len(self.Map)):
                for j in range(len(self.Map[i])):
                    wordTag = False
                    if i == self.search.start.x and j == self.search.start.y:
                        qp.setBrush(QColor(255, 255, 0))  # 起点 - 黄色
                    elif i == self.search.end.x and j == self.search.end.y:
                        qp.setBrush(QColor(100, 200, 50))  # 终点 - 绿色
                    else:
                        if self.Map[i][j] == 0:  # 可通行路径
                            tagx = True
                            if path:
                                for k in path:
                                    if k.x == i and k.y == j:
                                        tagx = False
                                        qp.setBrush(QColor(0, 100, 255))  # 路径 - 蓝色
                            if tagx:
                                if self.special != None:
                                    if i == self.special[0].x and j == self.special[0].y:
                                        qp.setBrush(QColor(0, 255, 0))  # 当前搜索点 - 绿色
                                    else:
                                        tag = True
                                        for k in self.special[1]:  # open列表
                                            if k.x == i and k.y == j:
                                                tag = False
                                                wordTag = True
                                                word = str(k.F)
                                                qp.setBrush(QColor(150, 0, 0))  # open列表 - 红色
                                                break
                                        if tag:
                                            for k in self.special[2]:  # close列表
                                                if k.x == i and k.y == j:
                                                    qp.setBrush(QColor(150, 150, 150))  # close列表 - 灰色
                                                    break
                                            else:
                                                qp.setBrush(QColor(220, 220, 220))  # 空白路径 - 浅灰色
                                else:
                                    qp.setBrush(QColor(220, 220, 220))  # 空白路径 - 浅灰色
                        elif self.Map[i][j] == 1:  # 墙壁
                            qp.setBrush(QColor(0, 0, 0))  # 墙壁 - 黑色
                        else:
                            qp.setBrush(QColor(255, 0, 0))
                    qp.drawRect(50 + j * config.blockLength, 50 + i * config.blockLength, config.blockLength,
                                config.blockLength)
        else:
            # 没有搜索时的绘制
            for i in range(len(self.Map)):
                for j in range(len(self.Map[i])):
                    if (j, i) == self.startPoint:
                        qp.setBrush(QColor(255, 255, 0))  # 起点 - 黄色
                    elif (j, i) == self.endPoint:
                        qp.setBrush(QColor(100, 200, 50))  # 终点 - 绿色
                    else:
                        if self.Map[i][j] == 0:
                            qp.setBrush(QColor(220, 220, 220))  # 可通行路径 - 浅灰色
                        elif self.Map[i][j] == 1:
                            qp.setBrush(QColor(0, 0, 0))  # 墙壁 - 黑色
                        else:
                            qp.setBrush(QColor(255, 0, 0))
 
                    qp.drawRect(50 + j * config.blockLength, 50 + i * config.blockLength, config.blockLength,
                                config.blockLength)
 
    def timerEvent(self, e):
        try:
            data = next(self.yi)
        except Exception as e:
            self.addDisplayText('搜索结束:')
            print('搜索结束!')
            if self.search.result == None:
                self.addDisplayText('未找到可行路径')
                print('搜索结束!')
            else:
                self.addDisplayText('总计搜索节点数:%d' % self.search.count)
                self.addDisplayText('最终路径长度:%d' % len(self.search.result))
            self.centerTimer.stop()
            self.button_start.setEnabled(True)
            self.displayFlush = True
        else:
            self.special = data
            self.repaint()
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = GameBoard()
    sys.exit(app.exec_())

首先复制你的.py的文件路径

然后终端运行一下,如果你是base没有新建的话,那就直接在这个里面

python +文件地址,注意把双引号“”去掉

由此就成功了。

下面讲解一下代码部分:

1. 配置类 (config)

class config:
    WIDTH = 67  # 地图列数
    HEIGHT = 41  # 地图行数  
    blockLength = 12  # 每个方块的绘制边长

2. 点类 (point)

def __new__(cls, x, y):  # 重写new方法
    for i in point._list:
        if i.x == x and i.y == y:
            point._tag = False
            return i  # 返回已存在的实例
    nt = super(point, cls).__new__(cls)
    point._list.append(nt)
    return nt  # 创建新实例
  • 为了确保每个坐标只有唯一实例,避免重复创建相同坐标的点

3. A*搜索算法类 (A_Search)

初始化列表

def __init__(self, arg_start, arg_end, arg_map):
    self.open = []    # 待探索节点
    self.close = []   # 已探索节点  
    self.result = []  # 最终路径

核心算法流程

1. 代价计算

loc.cost就是邻居节点间的代价

def cal_F(self, loc):
    G = loc.father.G + loc.cost  # 实际代价
    H = self.getEstimate(loc)    # 预估代价
    F = G + H                   # 总代价
    return {'G': G, 'H': H, 'F': F}
2. 启发函数

用曼哈顿距离作为启发函数,哈佛曼就是两点间的距离D=|x1-x2|+|y1-y2|

并且由于我们的实际代价是一个格子是10,我们这里给我们的哈佛曼预测也乘以10,这样可以和移动代价保持一致

def getEstimate(self, loc):
    return (abs(loc.x - self.end.x) + abs(loc.y - self.end.y)) * 10
3. 邻居节点获取

def getAroundPoint(self, loc):
    l = [(loc.x, loc.y + 1, 10), (loc.x + 1, loc.y + 1, 14), ...]
4. 生成器实现逐步搜索
def process(self):
    while True:
        tar = self.F_Min()  # 获取F值最小的节点
        aroundP = self.getAroundPoint(tar)
        self.addToOpen(aroundP, tar)
        # ... 搜索逻辑
        yield (tar, self.open, self.close)  # 返回当前状态

4. 游戏界面类 (GameBoard)

迷宫数据解析

def createMazeFromText(self):

UI初始化

def initUI(self):

绘制系统

def drawMap(self, qp):

动画控制

def timerEvent(self, e):

剩下如果再加功能的话的功能就很简单了,你自己问ai加就行了

Logo

更多推荐