Python实现的A*路径搜索和迷宫生成可视化
本项目提供了A*("A-Star")路径搜索算法的高性能实现(基于Andrew Kravchuck的这个Lisp实现),以及各种迷宫生成技术,用于展示该算法的工作原理,并提供迷宫中路径搜索的高级动画可视化。迷宫使用多种不同方法生成,每种方法都呈现出不同的视觉效果,同时为路径搜索算法提供潜在挑战。A*算法旨在高效地在这些迷宫中找到最短路径,同时考虑各种启发式函数和邻居枚举器。
特性
- 优化的A*路径搜索:包括自定义优先队列和高效的状态处理,支持整数和浮点坐标。
- 多样化迷宫生成:多种算法用于创建复杂多变的迷宫,包括扩散限制聚合(DLA)、生命游戏、一维自动机、兰顿蚂蚁、沃罗诺伊图、分形分割、波函数坍缩、生长树、地形基础、音乐化、量子启发、艺术化、元胞自动机、傅里叶变换和反应-扩散等。
- 高级可视化:迷宫生成和路径搜索的详细视觉表示,包括探索和路径发现的动画。
<div align="center">
<a href="https://www.youtube.com/watch?v=iA6XJRE6CTM">
<img src="https://yellow-cdn.veclightyear.com/ab5030c0/789d7d51-04ab-4cfa-a86c-a6444eb454ac.jpg" alt="实际演示">
</a>
<br>
<em>实际演示(点击缩略图观看YouTube视频!)</em>
</div>
路径搜索实现
设计理念和性能
A*算法实现注重效率和可扩展性。主要特点包括:
-
自定义优先队列:优先队列是A*算法的基本组件,用于管理待探索节点的开放集(前沿)。在本实现中,优先队列针对快速插入和提取基于优先级值(表示到达目标的估计总成本)的元素进行了优 化。这使算法能够快速聚焦于最有希望的节点。
-
坐标编码:系统支持整数和浮点坐标,通过高效编码以优化内存使用和计算。这个编码过程涉及将浮点坐标转换为唯一的整数表示,确保精确和快速解码。编码方案支持广泛的值范围,适应精细精度和大规模地图。
-
启发式函数:提供各种启发式函数,包括曼哈顿距离、八方向距离和欧几里得距离启发式。每种启发式提供了不同的方式来估计从给定节点到达目标的成本,平衡了准确性和计算效率。启发式的选择可以显著影响A*算法的性能,更准确的启发式通常会以额外计算为代价带来更快的路径搜索。
-
邻居枚举:算法提供可自定义的邻居枚举器,定义在路径搜索过程中如何考虑相邻节点。选项包括4方向、8方向和更复杂的移动模式。这种灵活性使算法能够处理各种类型的地形和移动成本,例如对角移动比正交移动更昂贵。
精确和启发式成本函数
- 精确成本:此函数计算从一个节点移动到另一个节点的实际成本。它可以考虑各种因素,如节点之间的距离以及与某些类型地形或移动相关的任何惩罚。例如,对角移动可能比垂直或水平移动有更高的成本。
- 启发式成本:启发式成本是从给定节点到达目标的成本估计。它作为A*算法的指导,帮助优先考虑可能更接近目标的节点。启发式的准确性和计算成本可能会有所不同;更准确的启发式可能提供更好的指导,但需要更多计算。
迷宫生成方法
本项目包含丰富多样的迷宫生成算法,每种算法都创造独特的模式和挑战。以下是每种方法的详细说明:
-
扩散限制聚合(DLA):
- 描述:DLA是一个模拟粒子在介质中随机运动直到它们粘附在表面或相互粘附形成聚合体的过程。在这个算法中,粒子从随机位置开始,随机移动直到它们要么粘附到现有结构上,要么落出定义空间的边界。
- 机制:算法初始化时在网格上放置几个种子粒子。在随机位置引入新粒子并进行随机行走。当粒子遇到占据的单元格(另一个粒子)时,它会粘附在上面,从而增长聚合结构。这个过程产生复杂的树状模式,可能类似于雪花或矿物沉积等自然形成。
-
生命游戏:
- 描述:基于康威的生命游戏,这种方法使用细胞自动机规则来演化一个细胞网格,每个细胞可以是活的或死的。每个细胞的下一个状态由其当前状态和周围活细胞的数量决定。
- 机制:网格初始化为活细胞(1)和死细胞(0)的随机配置。下一代每个细胞的状态通过计算其活邻居数量来确定。正好有三个活邻居的细胞会变为活的,而少于两个或多于三个活邻居的细胞会死亡。这种演化创造了动态且不可预测的模式,通常形成具有复杂走廊和死胡同的迷宫状结构。
-
一维自动机:
- 描述:这种方法涉及对单行细胞(1D)应用简单规则,然后随时间演化形成2D迷宫模式。规则集通常表示为二进制数,根据邻居状态决定一个细胞的状态。
- 机制:随机初始化一行细胞。下一行中每个细胞的状态根据特定规则集(如规则30、规则110)由其当前状态和直接邻居的状态决定。这个过程迭代生成新行,创造出从简单到高度混沌的复杂模式,取决于所使用的规则。
-
兰顿蚂蚁:
- 描述:一个简单的图灵机,在黑白细胞网格上移动,其移动规则由它遇到的细胞颜色决定。
- 机制:蚂蚁遵循一套规则:如果遇到白色细胞,它向右转,将细胞颜色翻转为黑色,然后向前移动;如果遇到黑色细胞,它向左转,将颜色翻转为白色,然后向前移动。尽管规则简单,系统表现出复杂行为,形成高速公路和混沌区域。随着时间推移,蚂蚁的路径可以生成复杂且不可预测的模式。
-
沃罗诺伊图:
- 描述:这种方法基于一组种子点之间的距离将空间划分为区域,每个区域包含所有距离一个种子点比其他任何种子点都近的点。
- 机制:在网格上随机放置点,通过确定每个网格单元最近的种子点来计算沃罗诺伊图。不同区域之间的边界被视为墙,形成具有多边形单元的迷宫。然后细化单元之间的边界以形成通道,通常为迷宫结构创造自然、有机的感觉。
-
分形分割:
- 描述:一种递归的分割方法,通过在分割线上引入墙壁将网格分成更小的区域。
- 机制:算法从水平或垂直分割网格开始,然后在墙上添加一个开口。这个过程在生成的子区域上递归重复。这种方法也被称为递归分割算法,可以产生高度对称和自相似的模式,其中小尺度的布局与整体结构相似。
-
波函数坍缩:
- 描述:受量子力学概念启发,这种方法使用基于约束的方法根据邻近单元来确定每个单元的状态。
- 机制:算法从一个未决定的网格开始,每个单元可能有多种状态。然后根据邻近单元的约束条件坍缩 每个单元的可能性,确保模式保持一致且无矛盾。这种方法产生高度详细且美观的迷宫,其结构与预定义的规则和模式保持一致。
-
生长树:
- 描述:一种从起点扩展路径的程序化方法,使用选择策略来决定从哪个边界单元生长。
- 机制:算法从单个单元开始,迭代地将相邻单元添加到迷宫中。选择策略可以变化(如随机、后进先出、先进先出),影响整体结构。生长树方法灵活,可以生成各种外观的迷宫,从长廊道到密集网络。
-
地形基础:
- 描述:这种方法使用柏林噪声生成类似地形的高度图,然后通过阈值转换成迷宫。
- 机制:柏林噪声(一种梯度噪声)用于创建平滑连续的地形高度图。然后根据阈值将网格分为可通过和不可通过的区域。这种方法产生的迷宫resembles自然景观,有山丘和山谷,提供了具有自然外观障碍的不同挑战。
-
音乐化:
- 描述:受音乐作品启发,这种方法通过解释和声功能和波形来生成迷宫。
- 机制:算法生成一个网格,其中每个单元的值由多个不同频率和振幅的正弦波之和决定。然后对结果波形模式进行阈值处理以创建墙壁和路径,resembles节奏和波浪状结构。这种方法提供了独特的美学,反映了音乐的周期性。
-
量子启发:
- 描述:通过叠加波函数来模仿量子干涉模式,创造复杂的干涉图案。
- 机制:算法使用波函数组合来创建概率密度场。通过对这个场进行阈值处理,确定迷宫墙壁。产生的模式精细复杂,often resembles量子物理实验中看到的复杂干涉图案。这种方法提供了视觉上令人惊叹的迷宫,具有高度的对称性和复杂性。
-
艺术化:
- 描述:利用笔触和泼溅效果等艺术技巧创造抽象迷宫图案。
- 机制:算法在画布上随机放置笔触和泼溅,每个笔触影响网格上的多个单元。笔触的放置和方向是随机的,创造独特的抽象图案。这种艺术方法产生的迷宫模仿各种艺术风格,提供视觉上独特的体验。
-
细胞自动机:
- 描述:使用自定义规则来演化一个单元网格,每个单元的状态受其邻居影响。
- 机制:网格以随机状态初始化。一组规则根据邻居的状态决定每个单元的下一个状态。这个过程迭代多次,具体规则和迭代次数影响最终模式。该方法可以生成wide range的结构,从高度有序到混沌,取决于所选规则集。
-
傅里叶基础:
- 描述:将傅里叶变换应用于噪声场,选择性过滤频率以创建平滑图案。
- 机制:算法从随机噪声场开始,使用傅里叶变换将其转换到频域。然后过滤掉某些频率分量,并应用逆变换获得空间域图案。结果是一个具有平滑流动结构的迷宫,受选定频率及其组合的影响。
-
反应-扩散:
- 描述:模拟化学反应和扩散过程,创造有机和生物形态图案。
- 机制:算法模拟两种化学物质相互扩散和反应的过程。这些物质的浓度根据反应-扩散方程随时间演化。对结果图案进行阈值处理形成迷宫结构。这种方法创造具有自然、流体般结构的迷宫,类似于生物有机体和化学反应中看到的结构。
这个集合中的每种方法都提供了独特的视觉和结构风格,使得探索wide range的迷宫特征和挑战成为可能。这些迷宫适合测试各种寻路算法并生成视觉上引人入胜的可视化。
迷宫验证和调整技术
在迷宫生成中,确保所产生的结构不仅视觉上吸引人,而且在功能上可导航是至关重要的。各种技术和方法被用来验证生成的迷宫,并在不满足特定标准(如可解性、复杂性或连通性)时进行修改。本节详细介绍了这些技术背后的理念、理论和实际实现,重点关注如何确保高质量的迷宫结构。
概述
迷宫验证和调整的方法涉及多个步骤:
- 验证:生成迷宫后,我们根据预定义的标准(如连通性、可解性和结构多样性)对其进行评估。
- 修改:如果迷宫未能满足这些标准,则使用特定函数进行结构调整,如添加或移除墙壁、创建通道或确保区域间的连通性。
- 最终验证:对修改后的迷宫进行重新评估,确认其现在满足所有期望的标准。
这个过程确保每个迷宫不仅提供具有挑战性和吸引力的环境,还在复杂性和可解性之间保持平衡。
详细函数说明
-
智能开孔器
- 目的:通过战略性地移除墙壁,确保生成的迷宫从起点到终点有一条通路。
- 机制:该函数迭代选择墙壁单元并移除它们,优先考虑可能阻塞路径的区域。一旦找到可行路径就停止,以最小化对迷宫整体结构的改变。这种方法特别适用于可能有孤立区域的复杂迷宫。
-
确保连通性
- 目的:保证迷宫中所有开放区域都是连通的,防止出现孤立区域。
- 机制:此函数使用寻路算法验证重要点(如起点和终点)之间是否存在连续路径。如果发现断开的区域,函数会识别这些区域之间的最短路径并创建开口连接它们,确保迷宫完全可导航。
-
添加墙壁
- 目的:通过添加墙壁增加迷宫的复杂性,创造新的挑战并改变迷宫的可导航性。
- 机制:以受控方式在迷宫中放置额外的墙壁,以达到目标墙壁密度。该函数随机选择开放单元转换为墙壁,在增加挑战性和保持可解性之间寻求平衡。
-
移除墙壁
- 目的:通过移除墙壁简化迷宫,使其密度降低且更易导航。
- 机制:该函数根据需要将墙壁密度降低到目标百分比来选择要移除的墙壁。它确保移除不会过度简化迷宫,保持一定程度的挑战性和复杂性。
-
add_room_separators
- 目的:将大型开放空间划分为较小的独立区域,从而增加结构和复杂性。
- 机制:该函数在迷宫的大型开放区域内引入隔板或墙壁。这些隔板创造出独立的房间或区域,然后可以进行连接或进一步修改。这种技术防止了过大的开放区域,避免迷宫变得不够具有挑战性。
-
break_up_large_room
- 目的:防止过大的开放空间简化导航并降低挑战性。
- 机制:该函数识别迷宫中的大型房间,并引入额外的墙壁将其分割成较小的区域。这个过程涉及对房间大小的仔细分析,并对墙壁进行受控引入,以在开放性和复杂性之间保持平衡。
-
break_up_large_areas
- 目的:类似于分割大型房间,此函数针对迷宫中大型连续的开放区域,确保它们被分割以增加复杂性。
- 机制:该函数识别大型连接区域,并引入墙壁创建较小、可管理的区域。这有助于防止由于大型未被打断的空间而导致的导航容易,确保更具挑战性的体验。
-
create_simple_maze
- 目的:生成基本结构或填充较大迷宫中的小区域。
- 机制:此函数使用简单算法,如递归分割或随机路径生成,创建基本迷宫结构。它常与其他技术结合使用,用于填充特定区域或作为可进一步修改的基础。
-
connect_areas
- 目的:确保迷宫的所有区域都可访问且相互连接。
- 机制:该函数结合使用寻路和墙壁移除技术,连接迷宫内的不同区域。这确保没有区域被隔离,从任何起点都能完全导航。
-
connect_disconnected_areas
- 目的:专门关注连接与迷宫其他部分完全隔离的区域。
- 机制:此函数识别完全断开的区域,并创建路径将它们整合到主迷宫中。它使用广度优先搜索(BFS)等算法找到最短连接路径,确保效率和最小结构变化。
-
bresenham_line
- 目的:在网格上绘制直线,通常用于创建直接连接或墙壁。
- 机制:采用Bresenham直线算法在网格上两点间绘制直线,确保线条尽可能连续和接近真实直线。这对于创建遵循直线路径的走廊或墙壁很有用。
-
validate_and_adjust_maze
- 目的:对迷宫的结构完整 性和可导航性进行全面检查,并进行必要的调整。
- 机制:此函数根据可解性、墙壁密度和连通性等标准验证迷宫。基于评估,它应用各种调整(如墙壁添加/移除、区域连接)以确保迷宫满足所有必要条件。它作为迷宫完成前的最终质量检查。
-
generate_and_validate_maze
- 目的:使用指定算法之一生成迷宫,并确保其满足所有质量和功能标准。
- 机制:此函数整合了迷宫生成、验证和调整的整个过程。它从生成迷宫开始,运行验证检查,并根据需要应用修改。如果迷宫不符合标准,函数可以重新生成或进一步调整,直到满足所有要求。
可视化
本项目的可视化组件旨在提供迷宫生成过程和寻路算法工作的全面和交互式显示。该组件使用matplotlib
库创建详细的视觉表示,突出迷宫结构和寻路策略的复杂性和细节。可视化的关键元素包括:
迷宫结构
-
墙壁和地板:可视化使用双色方案清晰表示墙壁和地板。墙壁通常以深色(如深蓝或灰色)呈现,而地板以对比鲜明的浅色(如白色或浅灰色)显示。这种明确的区分帮助用户轻松识别迷宫中可通过和不可通过的区域。
-
颜色映射:代码允许自定义墙壁和地板颜色。这对于创建视觉主题或调整可视化以适应不同的观看条件(如色盲)特别有用。可以使用matplotlib
的LinearSegmentedColormap
为不同的迷宫元素定义自定义渐变。
寻路进展
-
探索顺序:在寻路过程中,可视化动态显示算法的探索顺序。这通过使用表示探索进程的渐变色彩来着色已探索的单元格实现。浅 色表示早期探索,深色表示后期探索阶段。使用探索色图有助于可视化寻路算法的探索策略和效率。
-
路径发现:当算法发现从起点到终点的路径时,可视化以独特的颜色(如蓝色或绿色)突出显示该路径。路径通常表示为连续的线,指示构成解决方案的单元格序列。可视化实时更新,允许观察者看到算法进行时路径如何演变。
-
起点和终点标记:起点和终点用明显的符号(如圆圈或星星)和颜色(如绿色表示起点,红色表示终点)清晰标记。这些标记在整个可视化过程中保持可见,为观察者提供一致的参考点。
可自定义颜色
-
自定义选项:可视化组件提供广泛的颜色自定义选项,允许用户调整墙壁、地板、路径、探索阶段和起点/终点标记的外观。这种自定义通过传递给可视化函数的参数实现,使用户能够根据自己的偏好或特定用例定制显示。
-
色图选择:对于探索和路径颜色,用户可以从预定义的色图中选择或使用LinearSegmentedColormap
创建自定义色图。这种灵活性确保可视化可以适应各种美学偏好或可访问性需求。
-
透明度和分层:可视化支持透明度和分层效果,特别是对于探索地图。通过调整alpha值,用户可以将探索进度叠加在迷宫结构上,而不会遮蔽底层细节。这个特性对于同时可视化探索区域和迷宫的结构布局很有用。
动画和导出
-
帧生成:通过生成捕捉每个时间步骤迷宫和寻路过程状态的帧来实现可视化动画。代码使用并发处理高效生成这些帧,利用多个CPU核心实现更快的渲染。每一帧通过绘制迷宫、探索进度和当前路径状态 创建。
-
动画回放:这些帧可以使用matplotlib.animation
的FuncAnimation
编译成动画。通过设置每秒帧数(FPS)可以调整回放速度,允许更慢或更快地可视化寻路过程。动画提供了算法操作从初始探索到最终寻路的平滑连续表示。
-
输出格式:帧可以单独保存为图像或编译成视频。每一帧都以指定的frame_format
(如PNG、JPG)保存为单独的图像。这个选项对于创建高质量图像序列或详细的单帧后处理很有用。或者,如果save_as_frames_only
设置为False
,帧将被编译成MP4等格式的动画。对于MP4导出,使用FFMpegWriter
,允许对编码参数(如比特率和编解码器设置)进行精细控制。这确保了适合演示或进一步分析的高质量视频输出。
-
资源管理:为了管理磁盘空间并避免杂乱,代码包含了在保存动画或帧序列后删除小文件或临时文件的功能。这有助于保持工作目录的整洁,并确保只保留最相关的文件。这个功能在保存单独的帧时特别有用,因为它可以帮助防止大量图像文件的累积。
使用FFmpeg将帧组装成MP4文件
如果你已将帧保存为单独的图像文件,并希望手动将它们组装成MP4视频,你可以使用FFmpeg。你可以从FFmpeg官方网站下载它,或通过包管理器安装。或者,你可以从最新版本这里下载预编译的二进制文件(推荐;注意如果这样做,你需要将二进制文件复制到/usr/bin/
并执行chmod +x ffmpeg
使其可执行。要检查你实际使用的FFmpeg版本,可以尝试which ffmpeg
)。此外,你可能需要用于计算的bc
命令,可以使用以下命令安装:
sudo apt install bc
命令示例
假设你的帧按顺序命名(例如,frame_0001.png
、frame_0002.png
等)并存储在当前目录中,你可以使用以下命令生成一个30秒的视频文件,使用x265编码:
cd /home/ubuntu/visual_astar_python/maze_animations/animation_20240805_114757/ # 切换到包含帧的目录——这只是一个示例
ffmpeg -framerate $(echo "($(find . -maxdepth 1 -type f -name 'frame_*.png' | wc -l) + 30 - 1) / 30" | bc) -i frame_%05d.png -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2,scale=3840:2160" -c:v libx265 -preset slow -crf 28 -pix_fmt yuv420p -x265-params "pools=16:bframes=8:ref=4:no-open-gop=1:me=star:rd=4:aq-mode=3:aq-strength=1.0" -movflags +faststart output.mp4
对于使用x264编码,使用:
ffmpeg -framerate $(echo "($(find . -maxdepth 1 -type f -name 'frame_*.png' | wc -l) + 30 - 1) / 30" | bc) -i frame_%05d.png -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" -c:v libx264 -crf 18 -pix_fmt yuv420p -threads 16 -movflags +faststart output_x264.mp4
选项说明
-framerate $(...)
:根据图像数量和所需视频时长(本例中为30秒)计算帧率。这确保无论帧数如何,视频都能播放正确的时长。
-i frame_%05d.png
:指定输入文件模式。%05d
表示输入文件按顺序编号,有四位数字(如frame_00001.png
、frame_00002.png
)。
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2,scale=3840:2160"