SCP: Secret Laboratory 贪吃蛇彩蛋自动游玩机器人。项目通过只读本地游戏进程内存感知 横向 18×11 棋盘,再用正常方向键驱动本机蛇。2.2 版加入可覆盖在游戏上方的原生 Windows 悬浮小窗,并针对游戏后期持续加速重做了轮询、按键调度和算法截止机制。
- 进程权限仅为
PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; - 只调用
ReadProcessMemory,不写内存、不注入 DLL、不创建远程线程; - 输出仅为本机方向键,SCPSL 不在前台时会拒绝发送;
- 仅面向本地彩蛋玩法,不提供竞技功能。使用者仍需自行遵守游戏服务条款。
- 默认基准棋盘从实验用 8×8 改为游戏实际的 18×11 横向棋盘(宽 18、高 11,共 198 格);
- 最大内存轮询间隔从 30ms 降至 5ms,并根据观察到的蛇步周期动态收紧到最低 1ms;
- 去掉 50ms 方向键冷却。主循环本来就只在蛇头移动后决策一次,额外冷却只会漏掉高速转向;
- Hamilton 正常后继/安全捷径直接依赖回路顺序不变量,不再每步重复执行 BFS,正常热路径为
O(1); - Temporal/Adaptive 除节点上限外新增墙钟预算,默认每次搜索 3ms,避免状态爆炸阻塞下一帧;
- 完整 GUI 增加占用率、实时 Hz、方向/食物、AI 平均/最慢耗时、性能余量、移动/按键数、 PID、运行时长与食物计数,信息密度更高但仍按运行优先级分组;
- 完整窗棋盘画布按 18:11 横向比例固定高度,日志移到棋盘下方,不再用竖向大画布制造空白;
- 新增 680×300 无边框悬浮小窗:精选棋盘、分数、蛇长/占用率、步频、AI 延迟和路线信息, 默认 88% 透明、始终置顶,可拖动并完整覆盖在游戏画面上方;
- 完整窗和小窗共享实时状态,可在 55%–98% 范围调节透明度;全局快捷键无需游戏失焦即可 启停、切换小窗、显隐和调整透明度;
- GUI 使用独立机器人子进程和 JSONL 遥测,停止按钮通过控制管道请求优雅退出,界面不会被 内存扫描或 AI 搜索卡住;子进程管道固定为 UTF-8,避免中文日志在 GBK Windows 上乱码。
要求 Windows 10/11、Python 3.10+。
pip install -r requirements.txt
copy config.example.toml config.toml
python run.pypython run.py 现在默认打开 GUI。启动机器人后需把 SCPSL 切回前台;GUI 可以留在后台监控,
游戏失去前台时程序会继续读取状态但拒绝发键。
GUI 快捷键:
| 操作 | 窗口内 | 全局(游戏前台时也有效) |
|---|---|---|
| 启动 / 停止机器人 | F5 |
Ctrl+Alt+S |
| 完整窗 / 悬浮小窗 | F8 |
Ctrl+Alt+F8 |
| 显示 / 隐藏窗口 | — | Ctrl+Alt+H |
| 提高 / 降低透明度 | — | Ctrl+Alt+↑ / Ctrl+Alt+↓ |
若某个组合已被其它软件占用,GUI 会在运行日志中明确报告注册失败;其它快捷键仍可使用。
命令行与 TUI 仍完整保留:
scpsl-auto-snake-gui
scpsl-auto-snake run --config config.toml
scpsl-auto-snake run --strategy hamilton --no-ui
scpsl-auto-snake benchmark --jobs 4核心配置:
[runtime]
poll_interval = 0.005 # 最大轮询间隔,后期会按步频自动缩短至最低 1ms
key_cooldown = 0.0 # 兼容上限;非零值也会按当前步周期自动压低
wait_between_scans = 2.0
decision_budget_ms = 4.0 # 整次策略决策告警阈值
[ai]
strategy = "hamilton" # hamilton | adaptive | temporal | astar | greedy
hamilton_shortcut = true
temporal_max_expanded = 4000
temporal_time_budget_ms = 3.0
adaptive_switch_occupancy = 0.45decision_budget_ms 是监控阈值;temporal_time_budget_ms 是时序搜索的实际截止条件。截止检查
每 16 个展开节点进行一次,所以整次策略决策可能略高于搜索预算。
18×11 至少一边为偶数,可以构造覆盖全部 198 格的 Hamilton 回路。策略初始化时比较正向与 反向回路,选择与当前蛇身方向兼容的一侧。正常移动沿回路后继前进;安全捷径必须同时满足:
- 当前蛇身从头到尾仍符合回路循环顺序;
- 新头不会到达或越过尾巴;
- 新头不会越过当前食物;
- 新方向不是反向且不会立即撞身。
这些条件本身就是顺序安全证明,因此不再附加 BFS 复核。初始化 O(V),兼容状态下
每步 O(1),V=W×H。只要回路不变量成立,偶数边棋盘可保证最终填满,是 18×11 高速实战
默认策略。
因此“食物看起来就在旁边”不一定代表 Hamilton 可以直接吃:该方向可能是反向移动、可能在
回路顺序上位于蛇头后方,或会到达/越过尾巴。捷径还会在蛇身暂时不符合回路顺序时整体停用。
遇到这些情况,策略会继续沿安全回路前进,视觉上可能绕完整个棋盘再吃;这是换取填满保证的
预期行为。希望路线更直接可选 temporal/greedy,但它们不保证填满;adaptive 则是保留
Hamilton 安全顺序的折中方案。
将当前蛇身视为静态障碍,用 BFS 找到食物最短路;整条路径会真实模拟至吃下食物,并验证 吃完后头尾仍连通。找不到安全食物路时,按头尾连通、可达空间、出口数和食物距离兜底。
它简单、延迟低,但静态障碍模型看不到即将让出的尾巴,也没有全局完成不变量。在 18×11 后期常进入“不会死、也不再得分”的安全循环。
状态、安全验证和兜底与 Greedy 相同,只将 BFS 换成环面曼哈顿启发式的 A*。理论复杂度为
O(V log V);它通常减少无关格展开,但堆操作会抵消一部分收益,也无法修复静态身体模型的
根本限制。因此 A* 是寻路效率对照,而不是完成保证算法。
搜索节点为 (完整蛇身元组, 当前方向)。每次展开真实执行新头插入、增长、尾巴让出、反向
禁入和环形回绕,能找到静态 BFS/A* 看不到的“等待尾巴让路”路径。到达食物后还要验证新的
头尾连通。
状态数最坏近似指数增长。18×11 默认同时受 4000 节点和 3ms 墙钟限制;预算耗尽即用
生存评分兜底。它在大棋盘得分高,但独立使用没有填满保证,且几乎持续吃满实时预算。
Adaptive 从开局就建立 Hamilton 回路,时序 A* 只允许展开仍保持回路顺序不变量的状态;占用率
达到 adaptive_switch_occupancy 后完全交回 Hamilton。即使搜索超时,回路后继仍是安全退路。
它保留偶数边棋盘完成保证,并尝试在前期缩短取食路径。18×11 实测步数与 Hamilton 接近, 平均决策约高一个数量级,属于“有安全证明的实验型加速”,而非高速场景默认项。
| 策略 | 状态模型 | 18×11 填满保证 | 实时延迟 | 后期高速适配 | 主要价值 | 主要风险 |
|---|---|---|---|---|---|---|
hamilton |
固定回路 + 顺序安全捷径 | 是 | 最低且最稳 | 最佳 | 默认实战,稳定填满 | 平均取食路线偏长 |
adaptive |
Hamilton 不变量 + 限时时序 A* | 是 | 中等,前期有 3ms 尾部 | 好 | 安全证明下尝试捷径 | 本轮步数优势不稳定 |
temporal |
动态完整蛇身 A* | 否 | 最高,常触及预算 | 一般 | 处理移动尾巴,得分高 | 状态爆炸、超时后可能循环 |
greedy |
静态身体 BFS | 否 | 低 | 好 | 最简单可靠的基线 | 后期安全循环 |
astar |
静态身体 A* | 否 | 低 | 好 | 比较启发式寻路效率 | 大棋盘仍无全局策略 |
复杂度中的 V=W×H,S 为时序搜索实际展开状态数:
| 策略 | 单次新规划时间 | 额外空间 |
|---|---|---|
| Hamilton | 初始化 O(V),正常每步 O(1) |
回路/序号 O(V) |
| Greedy | O(V+E),四邻域下为 O(V) |
O(V) |
| A* | O(V log V) |
O(V) |
| Temporal | O(S log S),另受墙钟截止 |
O(S×蛇长) |
| Adaptive | 前期限时 Temporal,后期 Hamilton | 前期 O(S×蛇长) |
结论很直接:18×11 横向棋盘且游戏后期越来越快时选择 Hamilton。Adaptive 可用于实验;Temporal 适合离线分析或奇×奇棋盘;Greedy/A* 只适合作为低延迟基线,不能用“零死亡”替代完成率。
环境:Windows、Python 3.13.9;日期 2026-08-05;每策略 3 局;食物模式 scan;配对种子
20260805+i;单局最多 15000 步;--jobs 4;Temporal/Adaptive 为 4000 节点、3ms 搜索
预算。满分为 198-3=195。
| 策略 | 完成率 | 平均得分 | 平均步数 | 死亡 | 超时 | 平均决策 | p99 | 最慢 |
|---|---|---|---|---|---|---|---|---|
| Hamilton | 100.0% | 195.0 | 8759.7 | 0 | 0 | 0.043ms | 0.137ms | 1.40ms |
| Adaptive | 100.0% | 195.0 | 8922.0 | 0 | 0 | 0.392ms | 3.660ms | 5.37ms |
| Temporal | 0.0% | 179.0 | 15000.0 | 0 | 3 | 2.890ms | 3.904ms | 6.61ms |
| Greedy | 0.0% | 137.7 | 15000.0 | 0 | 3 | 0.095ms | 0.449ms | 1.69ms |
| A* | 0.0% | 136.3 | 15000.0 | 0 | 3 | 0.102ms | 0.429ms | 2.08ms |
三点需要分开看:
- 五种算法均为 0 次死亡,但只有 Hamilton/Adaptive 完成,说明存活率不能代表算法质量;
- Adaptive 本轮平均多 162.3 步(约 1.9%),样本量不足以说明稳定差异,延迟则明确更高;
- “最慢”包含四进程并发调度和 Windows 抢占抖动;p99 与平均值更能代表持续实时负载。
复现命令:
scpsl-auto-snake benchmark --strategies hamilton,greedy,astar,temporal,adaptive `
--games 3 --size 18x11 --max-steps 15000 --seed 20260805 `
--food-mode scan --jobs 4 --temporal-max-expanded 4000 --temporal-budget-ms 3
scpsl-auto-snake benchmark --jobs 4 --jsonscan 复刻游戏的“随机起点 + 外层 x/内层 y 回绕扫描”落点分布,但 Python RNG 不与
.NET System.Random 逐位相同;不同走法也会从第二个食物起改变空格扫描结果。
SnakeEngine 是 IL2CPP 对象。定位优先级为:
- 验证过的
GameAssembly.dll base + klass RVA缓存; - 上次实例地址验证;
ChaosKeycardItem.static_fields → SnakeSessions指针链;- metadata 定位 klass;
- 只读堆扫描兜底。
缓存永远不直接信任:代码会验证 klass 名、对象 klass、_deltaSender、棋盘范围、食物与蛇身
坐标。单帧读取先批量抓取固定字段,再读取 List<Vector2Int>;List._version 变化或前后
对象快照不一致时会整帧重试,拒绝把跨帧撕裂状态交给 AI。
当前核心偏移均为游戏版本相关实现细节:
| 结构 | 字段 | 偏移 |
|---|---|---|
SnakeEngine |
_areaSize / _segments |
0x18 / 0x30 |
_gameoverSw / _deltaSender |
0x40 / 0x48 |
|
_curMovDir / _foodPosition / Score |
0x50 / 0x58-0x5C / 0x68 |
|
List<T> |
_items / _size / _version |
0x10 / 0x18 / 0x1C |
| IL2CPP 数组 | 首元素 | 0x20 |
游戏更新后仍需在真实客户端做只读复核;缓存/RVA 验证失败会回退发现流程,而不是继续解析 错误地址。
pip install -e ".[dev]"
python -m pytest
python -m ruff check auto_snake tests
python -m ruff format --check auto_snake tests测试覆盖 18×11 Hamilton 构造与完整对局、墙钟搜索截止、动态轮询、GUI 遥测、透明度与快捷键、路径首步假安全
反例、尾巴让路、增长碰撞、配置边界及模拟 ReadProcessMemory 的一致性快照。
auto_snake/
├── gui.py / hotkeys.py # 完整 GUI、置顶悬浮小窗、全局快捷键
├── telemetry.py # 子进程控制所用实时 JSON 遥测
├── realtime.py # 随游戏步频自动收紧的轮询调度
├── state.py # 状态、碰撞、统一转移
├── strategies/ # 五种策略、拓扑缓存、限时时序搜索
├── benchmark.py # 配对种子、多进程、质量与延迟统计
├── snake.py / memreader.py # 只读定位、一致性快照、Win32 内存接口
├── cli.py / config.py # GUI/TUI/基准入口与配置校验
└── ui.py / recorder.py # rich TUI、JSONL 录制与回放
tests/ # pytest 回归套件
tools/ # 只读逆向与探针工具
反编译链路显示:持卡本地客户端持有 _deltaSender,本地执行 Move() 并产生同步消息;服务端
只中转消息,不维护该玩家的 SnakeEngine。本地实例对入站蛇消息会早退,因此服务端无法可靠
操控正在玩的本机蛇。这也是项目采用本地只读感知 + 正常键盘输入,而不是服务器插件或内存
写入的原因。
MIT License © 2026 FXDYJ