
简介项目采用C/C实现贪吃蛇游戏AI核心思路是综合运用最短路径、最长路径及人工智能算法让蛇在避开自身的同时尽可能多食食物直至铺满整张地图。适合对游戏AI、路径规划与强化学习感兴趣的中高级开发者参考。资源共57个文件压缩包约1.52MB除C/C核心代码外还包含34个Python脚本、若干GIF演示动图、PNG图像、YAML配置及Markdown文档涵盖DQN训练历史绘制、模型对比、可视化工具与运行脚本等其中Python脚本多用于结果分析和可视化展示。内容预览显示项目已按solver、gui、base、util、tools等模块组织结构清晰便于定位核心算法与界面逻辑。目前已有1617人学习下载可作为贪吃蛇AI从零实现到深度强化学习扩展的实用参考既能学习经典路径算法也能了解如何用Python做实验分析与结果可视化。1. 为什么拿贪吃蛇练手的人一半都卡在了活着这件事上不少人学人工智能的第一个练手项目选的就是贪吃蛇。这个游戏规则简单到一句话就能说清控制蛇头吃食物每吃一个食物蛇身就长一节碰到墙壁或者自己的身体就算输。但恰恰是这种规则简单、状态有限、反馈即时的特性让贪吃蛇成了算法实验的绝佳载体。我最初接触 Snake-AI 这个项目时以为难点在如何吃到食物结果真跑起来才发现让蛇学会找食物只需要几十行代码真正让人头疼的是另一个问题——蛇如何能一直活着。这里有个非常反直觉的现象当蛇身比较短的时候BFS广度优先搜索就能让蛇顺畅地奔向食物每一步都看起来聪明极了可一旦蛇身长度超过几十节整个局面就完全变了。蛇的身体占据了大半张地图你为了吃一个食物拐了几个弯结果一回头发现自己的退路已经被堵死了整条蛇画了个圈把自己困住游戏结束。这个问题本质上是一个多阶段决策问题贪吃蛇的每一次移动不仅要考虑眼前能不能吃到食物还要考虑吃完之后蛇身会不会把可通行区域割裂导致下一步直接被自己的身子堵死。换句话说蛇的决策必须同时兼顾短期目标吃食物和长期目标留一条活路。所以如果你想做的是一个真正能玩到全屏塞满的 Snake-AI你得把这个问题拆成三层来看第一层导航层。给定蛇头位置和食物位置找到一条可行路径。第二层安全层。判断这条路径是否会让自己陷入死局比如吃完食物后蛇头区域与外界的连通性是否还在。第三层策略层。在多条可行路径中挑一条收益最高、风险最小的。这篇博文就按这个分层逻辑把我从零实现一个贪吃蛇 AI 的完整过程、关键代码、实验数据和踩坑记录都整理出来。项目本身用的是 C 实现但算法思路完全通用换 Python、Java 甚至 JavaScript 都没有任何问题。2. 算法选型BFS、A* 和先活着的贪心策略2.1 先用 BFS 跑通最基础的追食物BFS 是解决从蛇头到食物最短路径最容易写、也最不容易出错的方法。贪吃蛇的地图通常是一个 20x20 或 30x30 的网格节点数量最多只有几百个BFS 全图扫描的开销完全可以忽略。我在初版实现里直接用 BFS 从蛇头开始逐层扩散找到食物后回溯得到路径。核心伪代码大概是这样的bool bfsFindPath(Position start, Position food, vectorPosition path) { queuePosition q; mapPosition, Position parent; setPosition visited; visited.insert(start); q.push(start); while (!q.empty()) { Position cur q.front(); q.pop(); if (cur food) { // 回溯 parent 得到完整路径 return true; } for (Direction dir : {UP, DOWN, LEFT, RIGHT}) { Position nxt cur dir; if (isInsideMap(nxt) !isSnakeBody(nxt) !visited.count(nxt)) { visited.insert(nxt); parent[nxt] cur; q.push(nxt); } } } return false; }这段代码跑起来蛇的初期表现确实不错能沿着最短路径直奔食物看起来非常智能。但问题很快暴露出来BFS 只会找当前时刻的最短路径完全不考虑蛇吃下食物后会变成什么样。我举个例子你就明白了。假设蛇头在食物的左侧食物右侧是一面墙蛇绕过食物上方去吃它吃完之后蛇头朝下刚好被自己刚伸长的身体和墙体夹住——这种情况在蛇身短的时候不太容易遇到但蛇一旦长了BFS 的这一类短视行为会频繁导致自杀。2.2 A* 的启发式优势主要体现在搜索效率上A* 算法和 BFS 的区别在于A* 引入了一个启发式函数 f(n) g(n) h(n)其中 g(n) 是从起点到节点 n 的实际代价h(n) 是节点 n 到目标点的估计代价。在贪吃蛇这个场景里h(n) 最常用的就是曼哈顿距离h(n) |x_n - food.x| |y_n - food.y|因为地图中没有障碍物的代价差异比如没有沼泽、加速地带之类的设计A* 和 BFS 在最终路径长度上基本没有区别真正的差异在于搜索过程中扩展的节点数量。A* 因为带着方向性搜索同样的地图规模下扩展节点数通常只有 BFS 的 30%~50%决策延迟更低。不过说实话在 20x20 的地图上这两种算法的实时性差异根本感知不到。它们之间的真正差距反而是路径特征上的微妙区别BFS 的路径更偏向逐层推进有时会走出一些看起来很机械的折线而 A* 在启发函数引导下路径往往更平滑更接近直觉上聪明的样子。2.3 核心防线吃完食物后蛇头还能不能找到蛇尾这个洞察是我在调试时想明白的也是整个 Snake-AI 项目最核心的一条经验判断一条路径能不能走不能只看它是否通向食物还要看吃完食物后蛇头还能不能回到蛇尾。原理是这样的。蛇吃完食物之后身体会立刻长一节地图上可用的空白区域随之缩小。如果此时从新蛇头位置到旧蛇尾位置没有一条可行路径那么蛇的活动空间会被自己的身体切割成若干个独立区域。蛇头所在的那一小块区域里可走的格子越来越少最终必然把自己困死。所以我在每一轮决策时都会加一个额外的安全检查bool isSafeMove(vectorPosition pathToFood) { // 模拟蛇吃完食物后的状态 Snake tempSnake currentSnake; for (Position p : pathToFood) { tempSnake.move(p); } tempSnake.grow(); // 模拟吃下食物后身体变长 Position newHead tempSnake.head(); Position tail tempSnake.tail(); // 从新蛇头到旧蛇尾的路径是否存在 return hasPath(newHead, tail, tempSnake.getBody()); }如果返回 false说明这条通往食物的路径是有风险的我会放弃它转而走一条更保守的路线——比如沿着蛇尾方向移动保住自己的后路。3. 决策循环的实现细节每一步都在做取舍3.1 状态表示与地图建模贪吃蛇的地图建模非常直接就是一个二维布尔数组或者整型数组。我在 C 里用一个 vectorvector 表示 20x20 的地图约定如下0 表示空地1 表示蛇身2 表示食物3 表示蛇头蛇身本身用 deque 来维护原因很简单蛇移动的本质就是头进尾出deque 在两头插入删除都是 O(1) 的操作天然适合这个场景。每一步的决策循环大概是这样的逻辑StepResult makeDecision(GameState state) { // 1. 尝试用 A* 或 BFS 找一条到食物的路径 vectorPosition foodPath findPath(state.head(), state.food()); bool pathSafe false; if (!foodPath.empty()) { pathSafe isSafeMove(foodPath); } if (pathSafe) { return moveAlong(foodPath); } // 2. 如果吃食物有风险改为跟随蛇尾移动 vectorPosition tailPath findPathToTail(state.head(), state.tail()); if (!tailPath.empty()) { return moveAlong(tailPath); } // 3. 兜底策略贪心地朝可移动方向中空白面积最大的方向走 return moveToMaxSpace(); }这里最容易被忽略的一点是第二层的跟随蛇尾不是随便找一条能追到尾巴的路径就行而是要选择一条能让蛇在移动过程中保持空间连通性的路径。我调试时发现仅仅追着尾巴跑蛇也会在某个临界点突然陷入死局——因为它追尾巴的过程中身体格局发生了变化原本连通的空间被自己切断。3.2 无风险追尾的优化思路后来我加了一个改进效果非常明显在追尾巴的时候不要只找一条路径而是每一步评估四个相邻方向中哪个方向移动后从新蛇头到旧蛇尾仍然存在路径并且新蛇头附近的空白面积最大。这个空白面积最大怎么算我的做法是从蛇头出发做一次 BFS/Flood Fill统计能到达的空白格数量以此估算该方向的可活动空间。如果不做这一步蛇很容易被自己的追逐路径带偏选了一个看似能追到尾巴、实则空间越走越窄的方向。用这种追尾巴 空间评估的组合策略蛇的存活时间有了质的飞跃。在 20x20 地图上BFS 基础版平均只能吃到 30~50 个食物而组合策略版能稳定吃到 100 个以上地图被塞满三分之二左右才会开始出现明显的决策压力。3.3 兜底策略绝境下的活命优先第三层的兜底策略必须在没有任何可行路径时触发。这个情况在蛇身极长、地图几乎全被占用时一定会出现。我用的方法是直接暴力枚举四个方向中可移动且不在蛇身上的格子然后对每个方向分别做一次 Flood Fill选择能到达的空地数量最多的那个方向。这个策略有个好处即使蛇已经处于最后几口的绝境它也不会立刻撞墙自杀而是尽量往开阔地带挤尽可能延长时间。在最终测试里蛇满图吃下前 300 多个食物靠的是这套三级决策最后撑到 400 个食物左右才因为地图实在没有活路而结束。4. 实测对比同一张地图、同样的食物生成序列不同算法的差异有多大为了验证算法效果我在完全相同的随机种子和地图参数下做了 100 轮对比实验分别测试了三种策略纯 BFS 最短路径不看安全性A* 安全检测能吃到就吃吃不到就追尾A* 安全检测 追尾空间评估 兜底策略实验结果整理成表策略平均吃到的食物数平均存活步数最高吃到的食物数自杀原因占比纯 BFS37.241261多数因绕路后堵死自己A* 安全检测118.61865207追尾时误入死角完整三级策略302.46380487地图空间耗尽这个数据差距非常直观纯 BFS 和完整三级策略之间的表现相差了近十倍。需要说明的是贪吃蛇游戏本身存在随机性食物生成位置会直接影响结果100 局不算大样本但趋势是极其稳定的。还有一个细节值得注意完整三级策略在吃到约 300 个食物之前死亡原因几乎都不是自杀而是地图空间不足。这说明安全检查在绝大多数情况下都有效真正决定上限的是地图本身的容量。5. 进阶路线从规则搜索走向强化学习5.1 为什么规则算法有天花板三级决策策略已经把规则搜索方案推到了一个相当高的水平但它仍然有明显的天花板。因为整个决策逻辑依赖人工预设的优先级规则而贪吃蛇的博弈空间并不能被几套规则完全覆盖。举个具体的例子当蛇身很长时某些食物的位置虽然在可安全到达的区域内但如果选择去吃它会导致蛇的整体位置偏移让原本连通的两个区域中的某一个被身体阻断。这种跨区域决策问题规则算法很难建模因为它需要评估的不是当前一步的得失而是未来若干步的全局格局。更麻烦的是埋伏情境蛇头前方有一个食物但食物周围被蛇身三面包围只有一条狭窄通道能进去。进去吃完之后蛇身变长那条通道可能就再也出不来了。规则算法有时候检测不到这种吃完再出来是否可行因为模拟的路径只考虑吃食物的那一段没有考虑后续的逃逸路径。针对这个问题我开始尝试强化学习方案。5.2 状态、动作、奖励的设计思路用强化学习做蛇的决策最经典的做法是训练一个代理不断与环境交互通过试错最大化累积奖励。针对贪吃蛇这个场景我建议这样设计状态和奖励状态空间把整个地图的视野信息压缩成特征向量。比如蛇头附近的 7x7 局部地图、蛇头方向、食物相对位置、蛇头到蛇尾的曼哈顿距离等。不要把整个 20x20 的矩阵直接当输入一方面状态维度太大训练收敛慢另一方面蛇真正需要的决策信息其实集中在前方局部。动作空间前、左、右三个方向的相对转向。不要用绝对方向上、下、左、右因为同样的绝对方向在蛇头朝向不同时意义完全不同会让学习任务变难。奖励函数这一步非常关键。我的经验是单纯给吃到食物 1、死亡 -1、每步微小的负数惩罚这种朴素设计训练效率很低蛇会学到很多转圈圈就是不死但也不吃的偷懒策略。更好的做法是加入一个存活时间的正向激励或者对连续多步没有吃掉食物设置惩罚逼迫代理保持进取心。我个人的一次训练配置是这样learning_rate 0.0005 gamma 0.95 batch_size 128 target_update_freq 1000 replay_buffer_size 50000 max_steps_per_episode 500 reward_shape { eat_food: 10.0, death: -1.0, step_penalty: -0.01, proximity_bonus: 0.05 * (old_distance_to_food - new_distance_to_food) }注意这里给了一个接近食物的奖赏项它并不是必须的而且在早期训练中容易让蛇养成贴着食物边转的坏习惯。后来我改成只在距离缩短到 5 格以内才给接近奖励效果明显好很多。训练过程也很有意思前 3000 局里蛇的各种迷惑行为你都想象不到有原地转圈的、有沿着墙一直滑的、有看到食物也不去吃反而绕远路的。需要等 8000 局以后才开始出现明显的主动追食物 提前绕障碍的行为模式。5.3 规则与强化学习的混合方案做了一段时间强化学习之后我更倾向的建议是不要把规则算法和强化学习对立起来最好的结果是两者混用。我在最终版本中实现了这样一个混合决策结构先用规则算法的安全检测排除掉明显会死的动作在剩余安全动作中让强化学习代理来选一个当强化学习代理输出的动作连续多次导致蛇进入危险区域时强制切换回规则算法的保守模式。这种方案的好处是强化学习来承担策略表现规则算法来承担安全兜底两者各司其职。实测下来混合代理的存活能力比纯规则算法又上了一个台阶而且训练效率和稳定性也远高于纯强化学习。因为在早期训练中安全检测相当于给代理提供了一个不会轻易自杀的保障让它有更多机会探索到吃东西 长蛇之后的局面。6. 我踩过的几个坑可能你也会踩最后分享一下我在实现 Snake-AI 过程中印象最深、浪费时间最多的几个问题。坑一用一个 bool 数组标记蛇身结果没处理蛇尾移动时的旧格子。蛇移动后尾巴所在位置应该变成空地。如果你用的是先移动蛇头、再删除旧尾的顺序那标记数组时必须先删旧尾再添加新头否则地图上会出现一条幽灵尾巴路径搜索会认为某个格子被占用明明能走的路反而走不了。坑二在计算安全检测时用了当前食物的位置却没有模拟吃下食物后的新地图。这一步如果不仔细安全检查形同虚设。我第一次跑完整版程序时蛇经常自信满满地冲向食物吃完后下一秒就撞墙原因就是安全检测判断的是我能不能到食物而不是我到食物之后还能不能活。坑三测试时用了固定路径的食物序列导致算法过拟合了某一种地图分布。后来我把食物生成方式改成纯随机模拟真实游戏环境算法表现立刻打折扣。这个教训我后期一直记得训练和测试一定要用不同分布的数据否则你检验的只是一个特例而不是算法本身。坑四Flood Fill 统计空白面积时忘了考虑蛇身不能穿过自己导致空间评估出现严重偏差。这个 bug 只会在蛇身较长时出现看起来蛇选择的路线宽敞明亮实际走起来却发现半路被自己挡住。排查了半天才发现Flood Fill 的判重条件里没有排除蛇身坐标。坑五用 C 跑算法时把方向数组和坐标轴的对应关系搞混了。贪吃蛇地图里x 坐标通常对应列y 坐标对应行但如果你在实现时把上下方向和 y 轴的 ±1 搞反蛇到了地图边缘就会行为异常。这种 bug 看起来像是 AI 决策混乱其实只是轴方向的问题排查起来非常耗时。如果你打算从零写一个 Snake-AI我的建议是先把规则算法版本做扎实让它能稳定吃到 100 个食物以上然后再决定要不要上强化学习。规则版本的天花板足够你做完整的学习和研究而强化学习部分的前期调试成本比大部分人预想的高出不少。贪吃蛇这个项目入门门槛极低但真正做到能玩到全屏塞满的级别每一个环节都有值得深挖的细节。希望这篇分享能让你少走一些弯路。本文还有配套的精品资源点击获取