0. 问题重定义:从小游戏到动力系统
我最开始以为 Serpent 是一个“贪吃蛇变体”:吃东西,避障碍,尽量活久一点。这个理解没有错,但它太浅。
当 Greedy、A*、Lookahead、NoGrow、TailGuard、Depth-2、MinLength 一层层跑出来之后,这个游戏真正暴露出来的不是“哪个 AI 分数高”,而是它的状态空间里有一套非常清楚的动力结构:分数在增长,长度在增长,size 在扰动几何压力,墙和危险物在提供修剪,timeout 在强迫系统周期性进食。
所以本文的核心不是“怎么打高分”,而是:Serpent 的长期最优控制对象到底是什么?
最终答案不是 length,也不是 size,而是:
1. 游戏规则的数学抽象
1.1 状态变量
设当前游戏状态为:
| 符号 | 含义 |
|---|---|
N(t) | 蛇的节数,也就是 length。 |
q(t) | 蛇的粗细 size,取值 1、2、3。 |
S(t) | 当前分数。 |
τ(t) | 距离上次吃普通食物或大奖励的时间。 |
M(t) | 地图状态,包括食物、大奖励、危险物、障碍物和蛇身形状。 |
代码里的关键常量可以直接翻译成数学约束:MAX_SIZE = 3,无进食死亡时间是 NO_PROGRESS_TIMEOUT = 30000ms,普通食物 +10,大奖励 +50,危险物 -20,障碍物扣分为 -5/-10/-15,砍节为 1/1/2。
1.2 行为变量
每个 tick,AI 直接控制的是方向:
但从策略层面看,它选择的是行为类型:
| 行为 | 记号 | 效果 |
|---|---|---|
| 吃普通食物 | F | +10 分, +1 节, τ 重置 |
| 吃大奖励 | B | +50 分, +1 节, size 可能 +1, τ 重置 |
| 撞脆墙 | W1 | -5 分, -1 节,至少保留 1 节 |
| 撞砖墙 | W2 | -10 分, -1 节,至少保留 1 节 |
| 撞铁墙 | W3 | -15 分, -2 节,至少保留 1 节 |
| 吃危险物 | H | -20 分, -2 节,至少保留 1 节,不重置 τ |
| 等待/绕行 | Ø | 分数不变,长度不变,但消耗 timeout 余量。 |
lastFoodEaten。所以危险物不是续命资源,只是减压资源。这一点会直接影响长期控制策略。2. 基础资源方程
2.1 长度方程
设某段时间内吃普通食物 F 次,吃大奖励 B 次,撞三类墙分别为 W1/W2/W3 次,吃危险物 H 次。名义长度变化是:
但游戏里有一个非常关键的下限:
所以更准确地说:
这就是为什么一节蛇撞墙不会继续被砍死。它不是一个小实现细节,而是长期小长度稳态能成立的重要原因之一。
2.2 分数方程
如果把障碍物类型看作随机变量,障碍生成概率为:
那么一次随机撞墙的期望扣分是:
一次随机撞墙的期望砍节是:
所以每修掉 1 节的期望成本约为:
于是:
关键结论:只要能持续把增长出来的 length 修掉,普通食物和大奖励都是正收益资源。
这一步把 Serpent 从“避障游戏”变成了“资源账游戏”。墙不是失败;墙是付费修剪。
2.3 timeout 约束
30 秒无进食死亡,所以必须满足:
长期策略里,食物不是单纯得分源,它也是续命时钟。如果平均进食周期是 T_food,必须有:
实际策略要留安全余量,所以一般要把目标压到:
危险物和撞墙不重置 τ,所以长期主循环必须包含普通食物或大奖励。墙和危险物只能修剪,不能续命。
3. 几何压力:真正该控制的是 pressure
早期我们只看 N = length。这是必要的,但不够。因为 Serpent 里每节蛇的实际占地会随 size 增长。
| size | 每节占格 |
|---|---|
| 1 | 1×1 = 1 |
| 2 | 2×2 = 4 |
| 3 | 3×3 = 9 |
所以定义近似几何压力:
更准确的版本是:
因为粗蛇相邻节会重叠,Nq² 是一个上界或近似指标。但它非常有用,因为它揭示了:同样的 length,在不同 size 下完全不是同一个状态。
3.1 墙在不同 size 下的几何修剪效率
撞一次随机墙,期望砍节是 1.15。如果 size 为 q,期望清理几何占地约为:
期望成本仍约 8 分,所以每清理 1 个几何格的成本:
| size | 期望清理占地 | 每占地格成本 |
|---|---|---|
| 1 | 1.15 | 8 / 1.15 ≈ 6.96 |
| 2 | 4.60 | 8 / 4.60 ≈ 1.74 |
| 3 | 10.35 | 8 / 10.35 ≈ 0.77 |
反直觉结论:变粗后,墙作为几何减压阀反而更高效。
但同时,变粗后更容易误撞、更难寻路、可达空间下降。这就是 Serpent 的核心张力:size 提高了修剪效率,也放大了几何风险。
3.2 稳态条件
成熟的长期目标不是 N ≤ 常数,而是:
例如可以设:
| size | 建议 length 上限 | pressure |
|---|---|---|
| 1 | N ≤ 5 | P ≤ 5 |
| 2 | N ≤ 3 | P ≤ 12 |
| 3 | N ≤ 2 或 3 | P ≤ 18 或 27 |
控制器目标变成:
4. L1–L17:策略演进与算法实现
下面每个策略都不是孤立版本号,而是一次问题重定义。Serpent AI 的进步不是“代码越来越复杂”,而是目标函数越来越接近游戏真实结构。
Greedy 与 A*:最短路问题
baseline / pathfindingL1 Greedy 直接追最近或最高收益目标:
它只优化眼前收益,没有考虑 future reachability、self-collision、pressure,也没有考虑 timeout margin。所以它是 baseline,不是可持续策略。
L2 A* 把问题升级成真正的路径搜索:
Implementation notes
open_set: priority queue ordered by f(n)
g(n): current path length
h(n): Manhattan distance to target
blocked: snake body + hard obstacles
neighbors: four directions, with wrap-around if game rules allow
A* 比 Greedy 强,因为它不会只看曼哈顿距离,而是真正避开静态阻挡。但它仍然只解决“当前蛇头到目标是否有路径”,不解决“吃完以后是否还有出口”。
seed0:460 分。适合作为 baseline,不是最优录像。
Lookahead / Chase Tail:安全寻路
reachability-constrained planningL3a 的关键变化是:吃食物前先模拟路径,吃完以后检查能不能追到尾巴。
尾巴是一个移动的出口。如果新蛇头还能到达新蛇尾,说明这条路径至少没有立即把自己封死。
Algorithm
def safe_to_eat(game, food):
path = astar(game.head, food.position, game.blocked_cells)
if not path:
return False
future = simulate(game, path)
return bfs_reachable(future.head, future.tail, future.blocked_cells)
它的局限也很清楚:它只看一颗食物,不看第二颗食物;它检查尾巴可达,但不最大化剩余空间。
seed0:460 分。这个 seed 不够理想,但能展示“吃完检查出口”的思想。
Economic Decision:墙不是障碍,是价格
resource-cost optimization这是第一个真正的认知跳跃。普通玩家把墙当作不可通行障碍;L4 把墙当作带价格的通道。
普通食物撞脆墙:+10 - 5 = +5。大奖励撞铁墙:+50 - 15 = +35。从资源账看,墙不是失败,而是修剪成本。
Implementation notes
def cell_cost(cell):
if cell.is_empty:
return 1
if cell.is_fragile_wall:
return 1 + wall_weight * 5
if cell.is_brick_wall:
return 1 + wall_weight * 10
if cell.is_iron_wall:
return 1 + wall_weight * 15
return INF
这一步把 Serpent 从“避障寻路”变成了“资源经济”。但它没有成为最强,因为它低估了变粗后的几何风险。
seed0:600 分。用于展示“墙可通行”的策略转向。
大奖励为什么会变成负债
score-rush / q=1 meta从纯分数账看,大奖励是高收益资源。但从几何账看,大奖励可能是压力冲击。
所以 NoGrow-Strict 的目标是固定 q(t)=1。在这个约束下,几何压力退化成 P=N,寻路和生存难度大幅下降。
Implementation notes
def food_score(food, game):
if food.type == "big_reward":
return -INF # NoGrow-Strict: treat bonus as forbidden
return 10 - distance_penalty(food)
短期刷分中,保持 size=1 的灵活性大于大奖励的 +50。这不是因为大奖励数学上亏,而是因为短期 score-rush 没有足够强的 pressure recovery。
seed0:1500 分。正文核心证据之一。
Space:吃完之后,剩多少空间
future feasible volumeL5 不再只问“我能不能吃到这个食物”,而是问:“吃完以后,我还剩多少可达空间?”
Flood fill implementation
def reachable_area(game, start):
queue = [start]
visited = {start}
while queue:
cell = queue.pop()
for nxt in neighbors(cell):
if nxt not in visited and not blocked(nxt):
visited.add(nxt)
queue.append(nxt)
return len(visited)
数学上,这一步接近“保持可控域”:不只是拿分,还要最大化未来可行状态空间。
seed0:1500 分。适合展示空间指标如何抬高上限。
TailGuard 与 Depth-2:score-rush 顶点
hard safety constraint + shallow planningL6 TailGuard 把“能追到尾巴”从加分项变成硬约束:
这是从软偏好到安全约束的变化。L6 seed0 跑到 2190,是当前 score-rush 线的视觉高潮。
L6 pseudo-code
for food in visible_foods(game):
path = astar(game.head, food.position, game)
future = simulate_path(game, path)
if can_reach_tail(future):
candidates.append(score(food, path, future))
return best(candidates)
L7 Depth-2 再往前看一步:当前食物安全之后,下一颗可见食物是否仍然有机会继续吃。
它不是完整动态规划,因为新食物随机补充,不能完全预知;但它补上了 L6 的一个盲点。
seed0:2190 分。正文最重要回放之一。
seed0:1660 分。展示二层前瞻。
失败分支:TailStall 与 CycleCruise
negative evidence失败策略必须保留。它们说明:局部安全不等于长期稳定,理论巡航也可能被 timeout 杀死。
L11 TailStall
假设是:如果 stall 时追尾巴,头跟着移动尾巴走,就能形成安全循环。
失败原因是:尾巴可达不是充分条件。追尾路径仍然可能把身体折叠成坏拓扑,最后 self collision。
L12 CycleCruise / Hamiltonian
空棋盘贪吃蛇里,Hamiltonian cycle 可以保证不撞自己。但 Serpent 有 30 秒 timeout、动态障碍、危险物、大奖励和随机食物。固定巡航的进食周期可能是:
而游戏要求 T_food < 30s。所以它理论优雅,但不适合当前规则。
seed0:1010 分,最后 self。失败证据。
seed0:30 分,timeout。失败证据。
长期控制线:从长蛇控速到小长度稳态
endurance-control长期控制线的起点是一个简单但强的等式:
所以理论上存在:
L8 尝试固定目标长度;L9 把严格稳态放宽成低正增长;L10/L13 做 hybrid;L14 以后出现真正的转折:不要在危险长蛇里求稳,而是让系统始终待在小长度安全集。
L16/L17 进一步推翻了早期 NoGrow 的强假设:不需要永远 size=1。只要 pressure 小,size=3 也可以继续稳态。
Long-control state machine
if timeout_soon:
seek_safe_food_or_bonus()
elif pressure_high:
trim_by_wall_or_hazard()
elif pressure_low:
take_profitable_food()
else:
safe_wander_or_follow_tail()
5000 tick 截断片段。展示小胖蛇稳态。
长跑策略片段。不是完整 106 分钟录像,而是稳态证据。
5. Hazard 纳入后的修正
危险物的效果是:
所以每节成本是:
| 修剪资源 | 每节成本 |
|---|---|
| 脆墙 | 5 |
| 随机障碍 | 6.96 |
| 铁墙 | 7.5 |
| 砖墙 | 10 |
| Hazard | 10 |
所以 hazard 不是优质修剪资源。但它有两个价值:一次砍 2 节,适合高压应急;如果附近没有墙,它是备用减压阀。
6. L18:PressureControl-Profit 的理论设计
现在 L18 不该叫 MinLength-Profit,更准确应该叫:
目标函数:
6.1 状态机
LOW_PRESSURE:
可以吃普通食物
如果大奖励收益很高且路径安全,也可以吃
MEDIUM_PRESSURE:
优先吃普通食物
避免大奖励,除非 timeout 压力很高
HIGH_PRESSURE:
停止吃奖励
优先找脆墙/铁墙修剪
hazard 可作为应急修剪
TIMEOUT_PRESSURE:
不管 pressure,先吃最近安全食物/大奖励续命
RECOVERY_AFTER_BONUS:
如果 size 刚变大,立刻降低 pressure
6.2 每步评分函数
其中 λ 应该随 pressure 增大:
pressure 越高,越重视修剪;pressure 低时,允许多吃、多赚钱。
6.3 动作估值
吃普通食物
吃大奖励
撞墙
吃 hazard
hazard 只有在 λ 很大,也就是 pressure 很高时才值得吃。
7. 思路演进:九次问题重定义
| 阶段 | 问题看法 | 对应模型 | 关键变化 |
|---|---|---|---|
| 1 | 贪吃蛇 AI 问题 | minimize distance to food | Greedy / A* |
| 2 | 安全寻路问题 | Reachable(head', tail') = true | 吃完后仍要有出口 |
| 3 | 经济问题 | Net = reward - cost | 墙是付费修剪 |
| 4 | NoGrow meta | q=1 fixed | 短期刷分拒绝大奖励 |
| 5 | 稳态控制问题 | E[ΔN]=0, E[ΔS]>0 | 撞墙抵消进食增长 |
| 6 | 低正增长准稳态 | 0≤E[ΔN]≤ε | 不必完全抵消 |
| 7 | 小长度安全集 | A={N small} | 不要在长蛇里求稳 |
| 8 | pressure 控制 | P=Nq² | 控制几何压力 |
| 9 | 吸收大奖励 | q∈{1,2,3}, P≤Pmax | 不怕变胖,只怕 pressure 失控 |
这轮研究的真正价值在于:我没有停在“哪个 AI 分数高”,而是不断追问“这个游戏的长期动力系统是什么”。
8. 当前能说的结论,和不能说的强结论
8.1 弱命题:Serpent 存在长期正收益压力受控策略
在以下条件下:
- AI 能在
τ<30s内找到普通食物或大奖励; - AI 能以有限代价找到障碍物或 hazard 作为修剪资源;
- AI 使用 pressure 控制,使
P=Nq²保持在某个上界附近; q≤3,即 size 有上限;- 障碍物/危险物不会形成永久不可达死局。
则有模拟证据和正期望资源账支持存在策略,使得:
更直观地说:只要能持续把几何压力修回安全区,分数就可以长期正增长。
8.2 现在不能声称的强定理
不能严格声称:
原因很硬:地图随机生成;食物/大奖励/危险物位置可能短期极差;AI 寻路不是完美动态规划;timeout 是硬约束;size=3 后可达性下降;真实 occupied cells 比 Nq² 更复杂;撞墙结算一次只处理一个障碍,规划估值仍需校准。
9. 录像引用校正
我重新整理了正文里的回放引用:不是把 22 个录像全部塞进正文,而是按证据功能筛选。
| 正文用途 | 回放 | 判断 |
|---|---|---|
| baseline | astar_seed0.json | A* seed0 分数不高,但适合做基线。 |
| 墙经济 | economic_seed0.json | 用于展示墙可通行,不强调最高分。 |
| NoGrow 阶段性顶点 | no_grow_strict_seed0.json | 1500,正文核心证据。 |
| 空间评分 | l5_space_seed0.json | 1500,适合正文。 |
| score-rush 顶点 | l6_tail_guard_seed0.json | 2190,正文最重要回放。 |
| Depth-2 前瞻 | l7_depth2_seed0.json | 1660,能展示策略逻辑。 |
| 失败分支 | l11_tail_stall_seed0.json / l12_cycle_seed0.json | 对应准确,适合说明失败原因。 |
| pressure 稳态 | l16_min_length_all_size_seed0.json / l17_endurance_seed1.json | 作为稳态片段,不当成完整局。 |
其余录像保留在回放库作为附录。像 L8/L9/L10/L13 这类长跑或截断录像,不适合在正文里当“最终分数证据”,更适合作为策略库补充。