贪吃蛇游戏AI自动游玩的强化学习模型
本文介绍如何使用深度强化学习训练贪吃蛇游戏AI,涵盖状态编码、动作设计、奖励函数优化及DQN与PPO算法对比,提供可复现的实现方案与调参经验,帮助理解智能体决策机制。
贪吃蛇游戏AI自动游玩的强化学习模型
你有没有试过盯着贪吃蛇发呆——看着那条小蛇一圈圈绕着走,明明食物就在眼前,却怎么也吃不到?😅 或者更惨:刚吃完一口,下一秒直接撞自己身上……这不怪你,毕竟人类反应有限。但如果是AI来玩呢?
别急着说“不就是个老掉牙的游戏吗?”——嘿,可别小看它!🐍 这个看似简单的像素小蛇,其实藏着不少门道:路径规划、避障决策、长期收益权衡……换句话说,它是训练AI做“聪明选择”的绝佳 playground!
最近几年,深度强化学习(DRL)火得不行🔥,而贪吃蛇就成了很多研究者手里的“小白鼠”。比起传统靠写死规则的方法(比如A*寻路 or Hamilton回路),DRL让AI从零开始自己学!不需要任何人教它“往哪儿拐”“怎么绕开身子”,只要告诉它啥事该奖励、啥事要惩罚,剩下的交给神经网络去摸索就行。
今天咱们就来干一票大的: 亲手打造一个会自己玩贪吃蛇的AI模型 。不整虚的,直接上硬核内容——状态怎么编码?动作咋设计?奖励函数怎么调才不让AI“摆烂”?DQN和PPO到底谁更适合这个任务?通通安排!
先来点直观感受:想象一下,AI控制的小蛇在屏幕上灵活穿梭,闻着“食物味儿”一路狂奔,遇到墙知道绕,快撞身时果断变向……而且越玩越强,从一开始乱冲乱撞,到后来能预判几步后的局势——是不是有点赛博生命的那味儿了?😎
要实现这一切,核心就在于四个字: 状态、动作、奖励、算法 。我们一个个拆开揉碎讲。
状态表示:让AI“看见”当前局面
首先,AI得知道自己在哪、食物在哪、周围有没有危险。但这可不是让它真去看画面(虽然也能这么做,用CNN处理图像),而是把关键信息提炼成一组数字向量——也就是“状态”。
常见的做法有三种:
- 坐标差值法 :只保留蛇头到食物的相对位置(dx, dy)
- 网格独热编码 :把地图切成N×N格子,每个格标0或1(有没有蛇/食物/墙)
- 视觉输入 :直接喂渲染好的图像给CNN(像Atari游戏那样)
对于贪吃蛇这种结构清晰的小环境,其实没必要上CNN大模型🧠。我推荐一种轻量又高效的方案: 融合相对方向 + 局部感知 。
什么意思呢?除了告诉AI“食物在左上方”,还让它感知前方、左前方、右前方有没有障碍物——就像蛇长了三只“触角”一样。这样一来,哪怕地图变大,策略也能泛化。
下面是具体实现👇:
import numpy as np
def get_state(snake, food, grid_size):
head = snake[0]
# 食物相对于蛇头的方向(归一化为-1, 0, 1)
food_dir = (np.sign(food[0] - head[0]), np.sign(food[1] - head[1]))
def is_collision(x, y):
if x < 0 or y < 0 or x >= grid_size or y >= grid_size:
return True
return (x, y) in snake # 包括身体碰撞
# 当前移动方向向量
directions = {
'up': (0, -1),
'right': (1, 0),
'down': (0, 1),
'left': (-1, 0)
}
current_dir = directions[snake.direction]
# 前方是否危险
front = is_collision(head[0] + current_dir[0], head[1] + current_dir[1])
# 左转后的方向(逆时针90度)
left_dir = (-current_dir[1], current_dir[0])
left = is_collision(head[0] + left_dir[0], head[1] + left_dir[1])
# 右转后的方向(顺时针90度)
right_dir = (current_dir[1], -current_dir[0])
right = is_collision(head[0] + right_dir[0], head[1] + right_dir[1])
# 构建12维状态向量
state = [
int(front), int(left), int(right), # 感知三方向障碍
int(food_dir[0] == 1), int(food_dir[0] == -1), # 水平方向食物
int(food_dir[1] == 1), int(food_dir[1] == -1), # 垂直方向食物
int(current_dir == (0,-1)), # one-hot 编码当前方向
int(current_dir == (1,0)),
int(current_dir == (0,1)),
int(current_dir == (-1,0))
]
return np.array(state)
这套编码方式只有12维,足够简洁,又能覆盖所有必要信息。重点是用了“相对视角”——不管蛇朝哪,它都能判断“左边危险吗?”“食物在我右下方吗?”,这对提升策略泛化能力特别有用。
💡 小贴士:记得把坐标归一化到 [-1,1] 或 [0,1] ,不然训练起来梯度飞得满天跑~
动作空间:别让AI“自杀式转弯”
接下来是动作设计。最 naive 的想法是让AI直接选“上、下、左、右”。听起来合理?错!🚨
因为一旦允许“向上时突然向下”,等于当场自杀。这种无效动作会严重拖慢学习速度。
聪明的做法是采用 相对动作系统 :
- 0 : 继续前进(不变向)
- 1 : 左转90°
- 2 : 右转90°
这样无论当前朝哪个方向,左转永远是逆时针,右转永远是顺时针。不仅避免了反向自杀,还让学到的策略更具通用性——毕竟AI不再依赖绝对方位,而是学会“看到食物在左边 → 我该左转”。
实验数据也支持这一点:在相同DQN架构下,使用相对动作比绝对方向收敛速度快约40%!⚡️
当然,你也可以加个“后退”选项?算了兄弟,那是自掘坟墓🙃。
奖励函数:别让AI变成“躺平族”
强化学习最怕啥? 稀疏奖励 。如果只有吃到食物才给+10分,其他时候全是0,那AI可能玩了几万步都还没尝过甜头,根本不知道自己在干嘛。
这就像是让你闭着眼投篮,进了才告诉你“对了!”,否则啥也不说……你能学会吗?大概率放弃吧。
所以我们得给AI一点“提示信号”,帮它建立行为与结果之间的联系。这就是所谓的 稠密奖励 (dense reward)。
来看看我的奖励设计方案:
| 事件 | 奖励 |
|---|---|
| 吃到食物 🍎 | +10 |
| 更靠近食物 ➕ | +0.1 |
| 远离食物 ➖ | -0.1 |
| 撞墙 or 自撞 💥 | -10 |
| 长时间无进展(防绕圈) | -1 |
代码实现如下:
def calculate_reward(old_state, new_state, done, ate_food):
if done:
return -10 # 死亡重罚,别想不开
# 计算到食物的距离变化
old_dist = np.linalg.norm(old_state[4:6]) # 假设第5~6位是dx, dy
new_dist = np.linalg.norm(new_state[4:6])
distance_reward = (old_dist - new_dist) * 0.1 # 靠近加分,远离扣分
if ate_food:
return 10 + distance_reward
else:
return distance_reward
这个设计妙在哪?
👉 它给了AI持续的反馈:哪怕没吃到食物,只要你在往正确方向挪,就有正反馈;反之瞎晃悠就会被轻微惩罚。
👉 加上“死亡-10”的严厉处罚,AI很快就能明白:“哦,撞墙这事不能干。”
⚠️ 注意平衡!惩罚太狠会导致AI过度保守,躲在角落不敢动;太轻又容易频繁送命。建议初期用 -10 ,后期可根据表现微调。
还可以额外加个“存活奖励”:每活一步+0.01,鼓励它多撑一会儿,防止早早结束局数影响训练效率。
算法选型:DQN vs PPO,谁才是贪吃蛇之王?
现在轮到最关键的环节:用什么算法来训练?
🟡 DQN(Deep Q-Network)
经典中的经典。适合离散动作空间,贪吃蛇的三动作正好fit。
优点:
- 结构简单,两层全连接搞定
- 收敛快,适合快速验证想法
- 经验回放 + 目标网络,稳定性不错
缺点:
- 对超参敏感(学习率、ε衰减等)
- Q值估计有时震荡,需要耐心调试
🟢 PPO(Proximal Policy Optimization)
现代主流选手,属于策略梯度方法。
优点:
- 更新稳定,不容易崩
- 样本利用率高,适合复杂任务
- 支持连续动作(虽然后者在这用不上)
缺点:
- 实现稍复杂
- 初期训练慢一点
📌 实测建议:
- 如果你是新手 👉 先上 DQN ,跑通流程,理解整个pipeline。
- 如果你想追求更高性能、更稳的表现 👉 上 PPO ,尤其是当你打算扩展到更大地图或多蛇对抗时。
下面是DQN的核心训练片段(PyTorch版):
import torch
import torch.nn as nn
import torch.optim as optim
class DQN(nn.Module):
def __init__(self, input_dim, n_actions):
super().__init__()
self.network = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, n_actions)
)
def forward(self, x):
return self.network(x)
# 初始化
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
policy_net = DQN(state_dim, 3).to(device)
target_net = DQN(state_dim, 3).to(device)
optimizer = optim.Adam(policy_net.parameters(), lr=1e-4)
criterion = nn.MSELoss()
# 训练循环(简化)
for batch in replay_buffer.sample(32):
states, actions, rewards, next_states, dones = batch.to(device)
q_values = policy_net(states).gather(1, actions.unsqueeze(1)).squeeze()
with torch.no_grad():
max_next_q = target_net(next_states).max(1)[0]
target_q = rewards + 0.99 * max_next_q * (1 - dones)
loss = criterion(q_values, target_q)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 定期更新目标网络
if step % 1000 == 0:
target_net.load_state_dict(policy_net.state_dict())
看到没?并不复杂!一个标准MLP,加上经验回放和目标网络,就能让AI从零开始学会吃豆人式的生存之道。
实际应用中那些坑,我都替你踩过了 🛠️
你以为写完代码就能坐等AI称霸贪吃蛇?Too young too simple 😏
真实训练过程中,你会遇到各种诡异行为:
🌀 无限绕圈 :AI发现原地打转不会死,还能偶尔蹭点距离奖励,干脆就不动了。
✅ 解法:加入“生存时间奖励”(+0.01/step),并检测连续状态重复,超过阈值就惩罚。
📍 贴边游走 :沿着墙走确实安全,但容易错过食物。
✅ 解法:增加“中心吸引力”奖励,或者动态调整探索率。
🎯 贪近忘远 :AI只顾眼前一口,不顾长远路径,导致把自己围死。
✅ 解法:引入更复杂的奖励 shaping,比如预测未来几步可达性,或使用分层RL。
🔧 我的最终配置建议:
| 项目 | 推荐设置 |
|---|---|
| 状态表示 | 相对方向 + 三向感知 + one-hot方向 |
| 动作空间 | 相对动作(前行/左转/右转) |
| 网络结构 | MLP(128→128)即可,无需CNN |
| 探索策略 | ε-greedy,从1.0线性衰减至0.05 |
| 训练地图 | 多尺寸混合(10×10 ~ 20×20),增强鲁棒性 |
| 评估指标 | 平均得分、最长存活步数、成功率 |
最后说点掏心窝的话 ❤️
这个项目看起来只是让AI玩个小游戏,但它背后的意义远不止于此。
你想想, 一个从未见过贪吃蛇的神经网络,仅凭试错和奖励信号,就能学会觅食、避障、规划路径 ——这不是魔法是什么?
更重要的是,这套框架完全可以迁移到真实世界的问题上:
- 机器人导航 ✅
- 自动驾驶决策 ✅
- 物流路径优化 ✅
贪吃蛇就像是一块“AI启蒙积木”,简单却不失深刻。你可以在这上面试验各种新想法:加注意力机制看看能不能更好关注食物,试试模仿学习让AI学高手操作,甚至搞个多智能体竞争版本……
所以啊,别嫌弃它老派。有时候, 最经典的,反而最能照亮未来的路 。✨
“Every expert was once a beginner.”
—— 而你的第一步,也许就从这条小蛇开始 🐍
更多推荐


所有评论(0)