贪吃蛇AI实战:从状态编码到DQN训练,避开绕圈与撞墙的坑
简介这份资源是一套用 C/C 编写的贪吃蛇游戏人工智能实现面向对游戏 AI、路径搜索与强化学习感兴趣的开发者与学习者帮助理解如何让蛇在自动寻路中尽可能吃满整张地图。压缩包共 57 个文件以 34 个 Python 源码为主辅以 gif 演示、png 截图、yml 配置、md 文档及许可证等整体约 1.52MB结构上分为 solver、gui、base、util、tests、docs 等模块便于按功能阅读与调试。项目综合运用最短路径、最长路径与人工智能算法核心目标是在保证生存的前提下最大化食物获取直至填满地图。已有 1635 人学习下载说明其在同类小项目中具备一定参考价值。读者可从中获得完整的算法实现思路、模块化目录组织方式、可视化运行界面以及测试与文档支持适合作为课程设计、算法练习或 AI 入门项目的参考素材。1. 贪吃蛇 AI 到底在做什么从“蛇撞墙”到“蛇自己找路”很多人第一次听到“贪吃蛇游戏的人工智能”脑子里浮现的是训练一个神经网络让蛇自己学会吃食物。但真动手做过一轮就会发现这件事的核心根本不是“让蛇变聪明”而是把游戏状态变成可计算的输入把移动方向变成可优化的输出。蛇每走一步你都要回答三个问题现在蛇头在哪、食物在哪、哪些方向会立刻撞死。这三个问题答清楚了剩下的才是选算法。我见过不少新手一上来就搭深度强化学习结果蛇在 20×20 的格子上连 10 分都拿不到。原因很简单状态表示没做对奖励函数没设计好蛇在“绕圈”和“追食物”之间反复横跳。贪吃蛇 AI 适合两类人一类是想学强化学习但找不到轻量级练手项目的人另一类是已经会写游戏逻辑、想看看 AI 怎么接管决策的人。它不需要 GPU 集群一台普通笔记本就能跑但里面的坑一个不少。这一篇不讲空泛的“AI 原理”而是按我实际做过的路径从状态编码、算法选型、训练循环到避坑排查把一条能复现的路子讲清楚。你跟着走至少能让蛇稳定吃到 30 分以上你想再往上推后面也有调参和进阶技巧。2. 状态怎么编码蛇才能“看见”世界2.1 用 11 维特征还是网格图像两种输入方案的取舍贪吃蛇 AI 的第一道分水岭是状态表示。常见做法有两类一类是手工提取低维特征另一类是把整个棋盘当成图像喂给卷积网络。我一般会先用手工特征跑通 baseline因为它的调试成本低得多。手工特征最经典的是 11 维向量蛇头四个方向是否危险、当前移动方向、食物相对蛇头的四个方位。这个表示在 20×20 以内的棋盘上足够用训练几百轮就能看到蛇开始追食物。缺点是蛇变长以后它“看不到”自己身体中段的分布容易把自己围死。网格图像方案是把棋盘转成 H×W×C 的张量C 可以是 3 通道蛇头、蛇身、食物或 4 通道再加墙。这种表示保留了全部空间信息但网络参数量大训练慢早期容易陷入“原地转圈”的局部最优。我的建议是先用 11 维特征把流程跑通再换网格图像做对比实验。import numpy as np def get_11d_state(snake, food, direction, board_size): snake: list of (x, y), snake[0] 是蛇头 food: (x, y) direction: 当前移动方向, 0上,1右,2下,3左 board_size: (w, h) head snake[0] # 四个方向是否立即危险 danger [] moves [(-1,0),(0,1),(1,0),(0,-1)] # 上右下左 for dx, dy in moves: nx, ny head[0]dx, head[1]dy if nx 0 or ny 0 or nx board_size[0] or ny board_size[1]: danger.append(1) elif (nx, ny) in snake[1:]: danger.append(1) else: danger.append(0) # 当前方向 one-hot dir_onehot [0,0,0,0] dir_onehot[direction] 1 # 食物相对方位 food_rel [0,0,0,0] if food[0] head[0]: food_rel[0] 1 elif food[0] head[0]: food_rel[2] 1 if food[1] head[1]: food_rel[3] 1 elif food[1] head[1]: food_rel[1] 1 return np.array(danger dir_onehot food_rel, dtypenp.float32)这段代码里danger的顺序必须和动作空间顺序严格一致否则蛇学到的策略会“左右颠倒”。food_rel用的是相对方位而不是绝对坐标这样蛇在棋盘任何位置都能泛化。board_size传(宽, 高)别传反传反了蛇会以为墙在错误的位置。2.2 动作空间与奖励函数为什么你的蛇总在绕圈动作空间通常设成 3 个直行、左转、右转。不要设成 4 个绝对方向因为蛇不能瞬间掉头绝对方向会让它产生“自杀式”动作。3 动作空间下蛇的转向是相对于当前方向的这样物理上更合理。奖励函数是第二个大坑。很多人只给“吃到食物 1撞死 -1”结果蛇学会的是“活着但不吃”因为绕圈不得分也不扣分。我一般会加一个小的步数惩罚比如每步 -0.01再给“靠近食物”一个微小正奖励。这样蛇有动力去追食物而不是原地转。def compute_reward(prev_dist, curr_dist, ate_food, dead): if dead: return -1.0 if ate_food: return 1.0 # 靠近食物给正奖励远离给负奖励 if curr_dist prev_dist: return 0.02 else: return -0.02注意这里的0.02不能太大否则蛇会为了“靠近”而反复横跳不真正吃食物。步数惩罚我一般放在环境 step 里不放在这个函数里方便单独调。2.3 棋盘尺寸与蛇的初始长度别一上来就 20×20新手最容易犯的错是直接上 20×20 棋盘、蛇长 3。这个配置下状态空间很大随机探索很难碰到食物。我建议从 8×8 或 10×10 开始蛇长 2食物随机放。等蛇能稳定吃 10 个食物再逐步扩大棋盘。扩大棋盘时不要只改尺寸还要同步调整网络隐藏层大小和训练轮数。8×8 下 128 隐藏单元够用20×20 下建议 256 起步。训练轮数也要从几千轮加到几万轮否则蛇还没探索到有效策略就停了。3. 选 DQN 还是 Q-table小棋盘和大棋盘的算法分界3.1 Q-table 在 10×10 以内还能打再大就爆内存Q-table 的思路是给每个状态-动作对存一个值。8×8 棋盘、蛇长 2 时状态数大约几千Q-table 完全放得下。我早期用 Q-table 跑 8×8蛇能稳定吃到 15 分左右。但棋盘一到 15×15状态数指数上升Q-table 内存直接爆掉而且大部分状态永远访问不到查表全是零。所以我的分界线是10×10 以内可以用 Q-table 做 baseline超过 10×10 直接上 DQN。DQN 用神经网络拟合 Q 值不需要存所有状态泛化能力也更强。import torch import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim11, action_dim3, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) ) def forward(self, x): return self.net(x)state_dim11对应前面 11 维特征action_dim3对应直行、左转、右转。隐藏层用 128 是 8×8 到 12×12 的通用值再大可以加到 256。激活函数用 ReLU 就够了别用 sigmoid容易梯度消失。3.2 经验回放与目标网络DQN 稳定的两个关键件DQN 直接训练会震荡因为前后两步的 Q 值互相影响。解决办法是经验回放池加目标网络。回放池存(state, action, reward, next_state, done)训练时随机采样一批打破时间相关性。目标网络每隔几百步同步一次参数让 Q 值目标稳定。from collections import deque import random class ReplayBuffer: def __init__(self, capacity10000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones zip(*batch) return (np.array(states), np.array(actions), np.array(rewards), np.array(next_states), np.array(dones)) def __len__(self): return len(self.buffer)回放池容量我一般设 10000 到 50000。太小了样本不够太大了早期旧策略会拖后腿。目标网络同步步数设 200 到 500太小了不稳定太大了学得慢。3.3 训练循环从随机撞墙到稳定吃食物要多少轮训练循环的核心是用 epsilon-greedy 选动作存经验采样训练定期同步目标网络。epsilon 从 1.0 线性降到 0.1让蛇先探索再利用。def train_step(online_net, target_net, optimizer, buffer, batch_size64, gamma0.99): if len(buffer) batch_size: return states, actions, rewards, next_states, dones buffer.sample(batch_size) states torch.FloatTensor(states) next_states torch.FloatTensor(next_states) actions torch.LongTensor(actions) rewards torch.FloatTensor(rewards) dones torch.FloatTensor(dones) q_values online_net(states).gather(1, actions.unsqueeze(1)).squeeze(1) with torch.no_grad(): next_q target_net(next_states).max(1)[0] target rewards gamma * next_q * (1 - dones) loss nn.MSELoss()(q_values, target) optimizer.zero_grad() loss.backward() optimizer.step()gamma0.99表示蛇看重长期回报适合贪吃蛇这种需要规划路径的场景。batch_size64是通用起点显存不够可以降到 32。8×8 棋盘下我一般跑 3000 到 5000 轮能看到蛇稳定吃食物20×20 需要 20000 轮以上。4. 避坑排查蛇不吃饭、原地转、越训越傻的 5 个原因4.1 蛇一直绕圈不吃食物现象训练几千轮后蛇能活很久但分数停在 0 到 2 分。原因奖励函数里“活着”的隐性收益大于“吃食物”或者步数惩罚太小。解决把每步惩罚从 -0.01 调到 -0.05同时把吃食物奖励从 1.0 提到 2.0。如果还不行检查食物相对方位特征是不是写反了写反了蛇会往反方向追。4.2 蛇刚开局就撞墙现象每局不到 5 步就死epsilon 降到 0.1 后依然如此。原因危险检测特征里蛇头四个方向的顺序和动作空间顺序不一致。解决打印danger数组和实际移动结果逐方向核对。我一般会在环境里加一个断言如果蛇撞墙但danger对应位是 0直接报错。4.3 训练 loss 震荡不下降现象loss 在 0.5 到 2.0 之间反复跳Q 值越来越大。原因目标网络同步太慢或者 gamma 太接近 1。解决把目标网络同步步数从 1000 降到 200gamma 从 0.99 降到 0.95。如果还震荡检查奖励有没有做归一化奖励范围太大也会导致 Q 值爆炸。4.4 蛇越训越傻分数倒退现象前 2000 轮分数上升之后突然掉到 0 并再也上不去。原因回放池里旧策略样本太多新策略被带偏或者学习率太大把已学到的策略冲掉了。解决把回放池容量从 50000 降到 10000学习率从 1e-3 降到 1e-4。我一般还会保存历史最优模型分数倒退时回滚。4.5 换了棋盘尺寸后完全学不会现象8×8 能跑换成 16×16 后蛇连食物都找不到。原因状态特征没有归一化棋盘变大后食物相对距离的数值范围变了。解决把食物相对方位改成归一化坐标或者把棋盘尺寸作为额外特征输入。更简单的做法是扩大隐藏层并增加训练轮数别指望小网络直接泛化到大棋盘。5. 进阶技巧用优先经验回放和 n-step 回报把分数再推一截5.1 优先经验回放让蛇多复习“差点撞死”的样本普通回放池均匀采样但贪吃蛇里“撞墙前一步”的样本价值远高于“空旷处直行”。优先经验回放按 TD 误差给样本加权误差大的样本被采样概率高。实现上可以用 SumTree 或者简单的概率数组。class PrioritizedBuffer: def __init__(self, capacity10000, alpha0.6): self.capacity capacity self.alpha alpha self.buffer [] self.priorities np.zeros(capacity, dtypenp.float32) self.pos 0 def push(self, transition, td_error): priority (abs(td_error) 1e-5) ** self.alpha if len(self.buffer) self.capacity: self.buffer.append(transition) else: self.buffer[self.pos] transition self.priorities[self.pos] priority self.pos (self.pos 1) % self.capacity def sample(self, batch_size, beta0.4): probs self.priorities[:len(self.buffer)] / self.priorities[:len(self.buffer)].sum() indices np.random.choice(len(self.buffer), batch_size, pprobs) samples [self.buffer[i] for i in indices] weights (len(self.buffer) * probs[indices]) ** (-beta) weights / weights.max() return samples, indices, weightsalpha0.6控制优先级强度0 就是均匀采样。beta0.4是重要性采样修正训练后期可以升到 1.0。这个改动一般能让蛇在 20×20 上多拿 20% 到 30% 的分数。5.2 n-step 回报让蛇提前看到三步后的奖励单步 DQN 只看下一步奖励蛇在长路径上学习慢。n-step 回报把连续 n 步奖励累加让蛇更快传播“吃到食物”的价值。实现上可以在回放池里存 n 步的累积奖励。def compute_nstep_target(rewards, next_q, gamma0.99, n3): rewards: 连续 n 步奖励列表 next_q: 第 n 步后的 Q 值 target 0 for i, r in enumerate(rewards): target (gamma ** i) * r target (gamma ** n) * next_q return targetn 一般取 3 到 5。太小了没效果太大了方差大。我一般先用 n3 跑一轮如果分数波动大就降到 2。5.3 验证方法固定随机种子跑 100 局看均值和方差训练完别只看一局分数那可能是运气。我一般固定随机种子跑 100 局看平均分和标准差。平均分高但标准差大说明策略不稳定需要继续调 epsilon 或奖励。平均分低但标准差小说明策略保守需要加大探索。def evaluate(env, agent, episodes100, seed42): scores [] for i in range(episodes): state env.reset(seedseedi) done False while not done: action agent.act(state, epsilon0.0) state, reward, done, info env.step(action) scores.append(info[score]) return np.mean(scores), np.std(scores)epsilon0.0表示评估时完全不探索只用学到的策略。seedi保证每局初始状态不同但可复现。我一般要求平均分达到棋盘面积的 1/5 才算及格比如 20×20 棋盘平均分 80 以上。最后说个我自己的习惯每次改奖励函数或网络结构一定先跑 500 轮看趋势别一上来就几万轮。贪吃蛇 AI 的坑大多不在算法本身而在状态编码和奖励设计的细节里。把 11 维特征写对、把危险检测顺序对齐、把步数惩罚调好蛇自然就学会找路了。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

GDAL 2.3.1 Windows预编译包使用指南:环境配置、格式转换与避坑实践

GDAL 2.3.1 Windows预编译包使用指南:环境配置、格式转换与避坑实践

简介:本资源为自行编译的 GDAL 2.3.1 Windows 64 位版本压缩包,面向从事 GIS、遥感影像处理与空间数据转换的开发者及研究人员。GDAL 是支持 GeoTIFF、JPEG、HDF4、HDF5、ECW、MrSID 等多种栅格与矢量格式的开源地理空间库,自行编译便于按需选…

2026/10/13 13:35:16 阅读更多 →
内网自建Git服务实战:CentOS 8 + Apache HTTP协议搭建轻量代码托管

内网自建Git服务实战:CentOS 8 + Apache HTTP协议搭建轻量代码托管

1. 为什么要在内网自建 Git 服务1.1 从一次代码托管迁移说起前段时间帮一个做嵌入式开发的小团队整理代码仓库,他们之前一直把代码放在公共托管平台上,项目本身不涉及什么敏感内容,但问题是每次拉取和推送都要走外网,办公室的网络…

2026/10/13 13:35:16 阅读更多 →
MySQL分区表详解:从原理到实操,一次讲透分区裁剪与选型

MySQL分区表详解:从原理到实操,一次讲透分区裁剪与选型

MySQL 分区表这功能,早年在社区里的评价一直两极分化。一边是"一张大表拆成小表,查询快到飞起"的爽文故事,另一边是"分区表性能还不如普通表、坑多得防不胜防"的劝退帖。两种说法我都见过,自己也踩过不少。做…

2026/10/13 13:35:16 阅读更多 →

最新新闻

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

1. 为什么“会用AI工具”正在变成一项硬技能 这两年我身边做开发的朋友、做设计的朋友、甚至做运营和行政的朋友,都在聊同一件事:AI工具怎么用才能真正落到工作里。不是那种“打开网页问一句、复制答案”的浅层玩法,而是把它当成一个能持续产…

2026/10/13 15:08:40 阅读更多 →
TNT Unicode Controls 2.3.0 解决Delphi乱码

TNT Unicode Controls 2.3.0 解决Delphi乱码

简介:这是一套基于 Delphi 开发的第三方 Unicode 控件套装,核心价值在于补足 VCL 组件对字符集支持不足的缺陷,使中文、日文、韩文乃至欧洲多语言文本能在同一界面中流畅呈现,特别适合承担国际化改造任务的桌面程序项目。压缩包共…

2026/10/13 15:08:40 阅读更多 →
Video.js源码级改造:实现RTMP-WS与HLS双协议统一播放

Video.js源码级改造:实现RTMP-WS与HLS双协议统一播放

简介:本资源是一套基于Video.js实现RTMP推流与HLS直播播放的轻量级HTML5原生播放器实践方案,面向前端开发者、音视频初学者及Web直播功能集成需求者,解决在不依赖大型框架前提下快速搭建兼容性好、可定制性强的网页端流媒体播放能力问题。压缩…

2026/10/13 15:08:40 阅读更多 →
多传感器融合的电力系统入侵检测:Python多级融合实战

多传感器融合的电力系统入侵检测:Python多级融合实战

简介:面向具备一定Python编程基础并熟悉数据处理与机器学习算法的科研人员,以及电力系统和工业控制安全方向的研究生、工程师,该资源围绕多源数据融合技术,给出针对电力系统网络入侵的检测方案。方案覆盖数据预处理、特征提取、融…

2026/10/13 15:08:40 阅读更多 →
全流程供应链系统源码拆包:从采购到结算链路与二次开发避坑指南

全流程供应链系统源码拆包:从采购到结算链路与二次开发避坑指南

简介:这是一套面向零售与供应链领域开发者、技术团队及企业信息化选型人员的高度可定制供应链中台基础系统,全部源码开放,可用于商用目的。系统覆盖零售管理、电子商务、供应链、财务、车队、仓库、人员、产品、订单、会员、连锁与加盟等业务…

2026/10/13 15:08:40 阅读更多 →
【CStackGUI 实战】我用自研的 Win32 GUI 框架写了个「工具箱启动器」:8 页签、真注册表操作、右键菜单管理

【CStackGUI 实战】我用自研的 Win32 GUI 框架写了个「工具箱启动器」:8 页签、真注册表操作、右键菜单管理

文章目录一、这是什么二、功能介绍1. 八页签工具启动器 全局搜索2. 我的工具:改一个 ini 就能加工具,不用重编译3. 右键菜单管理:扫描、批量禁用、备份删除、一键还原4. 成对开关:模式照抄原版,落点绝不越界5. 日志与…

2026/10/13 15:07:39 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →