九月初写博客时,我说最近想改一个习惯:先做一个小而完整的版本,再考虑往上堆东西。话说得挺轻巧,真正拿自己的老项目开刀时,还是有点舍不得。毕竟当年为了毕业设计熬过的夜,都实打实地留在代码里。
前几天我把那个四叉树路径规划项目重新做了一遍,叫 QuadPath Lab。现在它已经能在主站的实验室里直接打开;这篇不打算再写一份按钮说明书,主要想记一下这次为什么重做、做的时候想通了什么,以及几个让我反复返工的地方。
当年到底做了什么
最早选这个题目,是觉得它同时有算法和可视化:在地图上放障碍,四叉树把空间切开,再看 A* 和 Dijkstra 从起点走到终点。如果把搜索过程画出来,原本比较抽象的东西应该能看懂。
那时白天实习,晚上回来继续写。可我犯了个很典型的错误:为了让它看起来像一个“完整项目”,给算法演示加了账号注册、邮箱验证、地图管理、开放社区和登录日志,后端也配上 Koa2、MySQL、Sequelize。功能列表很长,真正最想看的寻路过程反而没有被照顾好。
这些功能不是不能做,而是它们没有回答这个项目最重要的问题。如果只是想拖动障碍、比较两种搜索,一个人在浏览器里就能完成,为什么非要先注册一个账号?我以前会把“做了很多”当成完成度,现在更在意别人打开之后能不能立刻明白、立刻上手。
所以 v2 做的第一件事不是加功能,是删。后端整个拿掉,地图改为本地 JSON 导入导出,保留四叉树、寻路和回放。毕业设计原版也没有丢,归档在仓库分支和标签里,权当给那段时间留个位置。
四叉树和寻路算法,其实各管一段
我之前讲这个项目时,有时会把“四叉树、A*、Dijkstra”并排放在标题里。这样写容易让人以为它们是三种寻路算法,实际不是。
一张普通栅格地图里,每个格子都可以成为一个节点。简单是简单,但大片空地也被拆成同样密密麻麻的格子。四叉树做的是空间表示:从整张地图开始,把同时有空地和障碍的区域继续四分;完全相同的区域就停下。空旷处用大块概括,障碍边缘保留小块。
接下来才能谈寻路。Lab 会取出可通行的叶节点,把共享一段边界的节点连起来;起点和终点落到各自的叶节点上。Dijkstra 和 A* 跑的是同一张图,区别在于决定下一步先看谁:Dijkstra 只看从起点走到现在的代价,A* 还加上对终点距离的估计。
这个关系想清楚之后,界面也好设计了:地图、四叉树、邻接拓扑、搜索回放,应该是一条能顺着看的链,而不是四个互不相干的功能区。现在可以在地图上画障碍,打开四叉树检查器看区域是怎么分的,再通过拓扑视图看叶节点之间的连接,最后切换算法看扩展顺序。
有个细节必须说清楚:这里的“最短路径”是四叉树叶节点构成的图上的最短路径。路径用叶节点中心连线表示,它不等于在连续平面上贴着障碍走的几何最短线。压缩空间会让图变小,也会带来这种表示上的取舍。把这点写明白,比画出一条看起来很漂亮的线更重要。
为什么坚持让两种算法用同一张图
如果把 A* 和 Dijkstra 分开做成两套演示,各自挑一张效果最明显的地图,截图会很好看,但比较本身就没什么意义了。地图大小、障碍位置、图的连接方式只要变一项,扩展节点数的差异就说不清是算法带来的,还是输入本来就不一样。
现在的做法是先确定一张四叉树叶节点图,再把同一组起终点和边权交给两种算法。边权用叶节点中心之间的距离;A* 的启发式也用到终点的几何距离,Dijkstra 则不加这项估计。这样看回放时,至少能把“它先去哪里找”与算法的决策联系起来。当然,浏览器里跑出来的毫秒数会受机器和当时环境影响,我更看重路径长度和扩展轨迹,耗时只作为一项参考。
拓扑视图也有一个容易误会的地方。为了让节点关系看得清楚,D3 会把节点排成便于观察的样子;这个排布不是地图上的真实坐标。拓扑图画的是谁和谁相连,地图上的路径画的是从哪里走到哪里。 两张图回答不同问题,不能看见拓扑视图里两点挨得近,就认为真实地图里它们也近。把这个区别想明白后,我在项目介绍里也专门补了一段说明。
真正花时间的是让过程可读
如果只看最后那条路径,A* 和 Dijkstra 的差别很容易被压成一句“前者更快”。这话太粗了。搜索范围、启发式、地图结构都会影响结果;我更想让人自己看见它们分别扩展了哪些节点。
于是做了逐步回放、暂停、前后单步和速度控制。内置的场景也不只放一张“刚好能跑通”的地图:有错位走廊、房间、开阔区域。切换同一个场景,再分别播放两种算法,可以直观看到搜索会不会往四周铺开、什么时候朝终点收拢。面板里同时放路径长度、扩展节点数和耗时,避免只凭动画下结论。
不过这些动画一加,界面马上变拥挤了。起初地图、参数、统计、拓扑信息都想挤进一屏,结果每个地方都能看一点,又都不好用。后来重排工作区、调整编辑工具位置,把四叉树检查器和拓扑图做成按需打开的视图。地图编辑时鼠标坐标和 SVG 网格对不齐,也单独修过一次——算法写得再对,落点偏一格照样让人怀疑人生。
这几天的提交记录里,许多并不是“发明新算法”,而是这种不太显眼的工作:回放速度、控件选中态、暗色模式、下拉菜单、中英文文案、不同屏幕宽度下的排列。一个实验台有没有用,往往就卡在这些地方。
还有一种返工更不显眼:改完界面之后重新检查算法结果。地图尺寸允许调整后,起终点映射、障碍边界和叶节点邻接都更容易在极端情况里出错。于是给空间划分、共享边判断和搜索结果补了测试;每次准备收工,先跑测试和构建,再去浏览器里看回放是不是和数字对得上。测试不能保证一切,但至少能阻止“视觉修好了,底层悄悄走错了”这种尴尬。
从本地玩具到网站上的一个入口
做完 Lab 之后,我又纠结它该放在哪里。把整个工作台塞进普通博客文章显然不合适:地图和拓扑需要空间,缩进正文宽度里只能变成一张看不清的图。
最后的结构是三层:主站作品列表有卡片和项目介绍,实验室有入口,真正的交互应用单独占用 /lab/quadpath/。主站还是 Astro,Lab 是 Vue + TypeScript + Vite,各自负责自己的部分。为了能挂在子路径下,构建时还要处理资源的基础路径;本地能打开,不代表放进主站后图片、脚本和样式还能找到。
发布前做了一次比较踏实的检查:两边项目先跑测试和构建,再看主站里的浅色、暗色界面,确认前一天改的暗色选中态确实进入了嵌入版本。服务器上用新目录承接构建产物,确认页面都在后再切换,旧版留作回滚。这个流程有点啰嗦,但上次博客搬家时修过一批图片路径之后,我对“本地看着没问题”已经没那么放心了。
这次还顺手给作品页写了四叉树与两种算法的详细说明。它偏项目文档,讲原理和操作;这里则留给过程,以及当时为什么这么选。
现在做到了哪一步
目前地图编辑、四叉树划分、A* / Dijkstra 对比、搜索回放、JSON 导入导出和中英文界面都能用。测试覆盖了空间划分、邻接关系和寻路结果的一些关键情况,线上版本也已能从主站进入。
它还不是一个通用的路径规划工具。比如路径展示仍以叶节点中心为代表,拓扑一复杂,线条不一定是人走路时最自然的样子;性能对比也只是当前地图与浏览器环境下的一次观察,不能拿一次耗时就宣布哪种算法“永远更好”。这些限制我暂时不想藏起来,因为它们正好说明了这个实验台在演示什么、没有演示什么。
接下来如果继续做,值得先盯着两个方向。一个是让路径更贴近人对“路线”的直觉,比如在不改变搜索结果的前提下,想办法处理叶节点中心线带来的折线感;另一个是把实验场景和说明补得更扎实,让第一次打开的人不用猜每个数字是什么意思。至于账号、排行榜、在线社区这些功能,至少目前没有再加回来的理由。真遇到一个具体需求,再决定要不要做,比先把功能清单写满靠谱一些。
回头看,这次重做最值的地方不是把老项目换了一套更漂亮的 UI,而是终于敢把当年辛苦堆出来的东西删掉。删完之后,题目反而清楚了:四叉树负责把空间组织起来,两种算法负责在同一张图上搜索,界面负责让这个过程被看见。
如果你想自己试,可以打开 QuadPath Lab:先选一个内置场景,分别跑 A* 和 Dijkstra,再拖动回放进度条看扩展顺序。有什么别扭的地方,也欢迎告诉我。反正这个站和这个实验台一样,都还在施工中。
记录于 2026-09-23。图中的搜索扩展示意用于解释策略差异,并非某次运行的实测截图;实际路径和指标请以 Lab 中的地图与运行结果为准。