GitHub – UNICORN-2887/2048-ai: 2048 AI — Expectimax + DQN on MuMu Android Emulator · GitHub
众所周知,主包的游戏水平堪比三叠纪草履虫,向来是不擅长打游戏的,因此常做逆向。
今天要讲的是2048的逆向。
下面是在像素危城游戏中的一个小游戏2048,你也可以下一个纯净版的2048,上面仓库提供的代码会有一个简单的标定过程的,你只需要把要识别的区域框进去即可。

逆向工程怎么做
通俗且片面地说,逆向工程的目的就是自动化打游戏,无论是基于规则的自动化,还是引入AI来自动化。
当然,实际上是指用CE反向抓基址、判断哪里断call、通过数据结构判断共享代码的双方如何分辨等等,这些都是方法,最终是要运用在自动化打游戏的目的之上的。
所有的逆向工程,都包含三个层次,
观察层:状态获取
这一层需要通过CV来获取游戏的状态。
第一点,是有哪些类型的状态?理论上如何获取?
一般而言,
无数值状态:使用简单的检色即可。(比如血条,只用检测绿色和黑色的比例)
![]()
有数值状态:使用OCR,个人而言,EasyOCR非常好用。其次是Tesseract的OCR。都是免费的。当然,对于一些色彩很奇怪的文字,OCR不那么好用的时候,引入yolo对10个数字做训练也是一种办法。
![]()
图像状态(比如怪物类型、标志物、物品栏识别):一般使用yolov8训练。选择Object Detection任务进行训练。

位置状态:非联网游戏用CE搜基址。联网游戏分几种情况:
对于有坐标的,那直接用CE的断call法,去尝试各个位置是否触发中断。
对于有小地图的,可以用二值图拼接+特征匹配。
对于都没有的,只能用我独家的光流法定位。这个我会在DeadMaze游戏的逆向中讲解。

第二点,是实战上如何获取?
对于CE能搜索到的,我们不做讨论。
问题是如何获取实时画面?
我们常用的方案会根据设备不同选择。
对于手游,我们会用模拟器,比如MuMu模拟器就会提供API来截图。
但是MuMu模拟器的截图速度是很低的,实测最高仅3fps,对于时间高精度要求的游戏是很糟糕的。
因此我们常用OBS Studio来实时获取游戏的视频流。可以达到60fps,对于所有游戏都是适用的。
电脑游戏一般也可以用OBS Studio来获取实时视频流。
算法层:
这一层就是如何做。
通常有两种模式,一种是基于规则的算法,是固定的。适用于简单,规则容易归纳的游戏(比如消消乐、可推理的扫雷、较为固定的跑酷)。要求动作空间小,观测空间小的游戏。
另一种是智能模式,是随动的,我常用强化学习。使用于较为复杂的,规则难以描述的游戏(大型MOBA、吃鸡、MC)。对于动作空间大、观测空间大的游戏。
操作层:
这一层就是具体如何控制游戏了。
对于手机游戏,一般都能用模拟器进行操作,我常用MuMu模拟器。当然也有一些游戏不支持模拟器,比如《我是市长》这类。之后的一切逆向工程,都会用MuMu的API来操作,你可以阅读
对于电脑游戏,基本都能调用win32的API进行后台控制。
那么根据上述思路,我们就能做出如下架构
二、技术栈分层解读
第1层:物理控制(打通真实游戏)
| MuMu 模拟器 | 运行真实 2048 App | Android 模拟器 |
| ADB | 与模拟器通信 | adb exec-out screencap -p 截图 adb shell input swipe 模拟滑动 |
| 校准系统 | 定位棋盘格子位置 | 用户手动框选 16 格 → 保存 JSON |
通俗理解:用代码代替你的手指,自动截图看棋盘、自动滑动操作。
第2层:状态识别(让 AI "看见"棋盘)
模拟器截图 → 裁剪 16 个格子 → EasyOCR 识别数字 → 构建 4×4 矩阵
EasyOCR 是一个 OCR(光学字符识别)库,能识别图片中的文字/数字。
关键问题:2048 是主游戏里的"小游戏",棋盘位置不固定 → 需要用户先框选一次做校准。
第3层:AI 决策(两种方案)
三、方案一:DQN(深度强化学习)—— 原理拆解
3.1 强化学习基本框架
Agent(AI)── 执行动作 → 环境(2048游戏)
↑ ↓
└── 获得奖励 + 新状态 ←─┘
核心循环:观察 → 决策 → 执行 → 获得反馈 → 学习
3.2 DQN 的 4 个核心组件
| Q 网络 | 一个"打分器":输入棋盘,输出 4 个方向的"预期得分" |
| 目标网络 | Q 网络的"备份",定期同步,防止训练震荡 |
| 经验回放 | 记日记:把每次经历存下来,随机抽出来复习,打破惯性思维 |
| ε-greedy | 90% 时间选最优动作,10% 随机乱走(探索新策略) |
3.3 网络结构
16个数字(4×4棋盘展平) ↓ 256 个神经元 ↓ 128 个神经元 ↓ 64 个神经元 ↓ 4个输出(上/下/左/右的Q值)
问题:全连接网络(MLP)无法理解"位置"信息。它把棋盘当成 16 个独立的数字,丢失了"相邻关系"这个关键信息。
3.4 训练流程(伪代码)
for 步数 in range(300000):
动作 = ε-greedy(当前棋盘) # 选动作
执行动作,得到 新棋盘, 得分, 是否结束
存经验 (旧棋盘, 动作, 得分, 新棋盘)
if 步数 % 16 == 0:
随机抽 256 条经验
计算 Q 值损失
反向传播更新网络
ε 逐渐衰减(从 1.0 → 0.02)
3.5 为什么 DQN 失败了?



1. 左图:episode/highest_tile(最高方块数)
-
纵轴含义:每一局游戏结束时,棋盘上拼出的最大数字方块(例如 64, 128, 256…)。
-
横轴含义:训练的步伐/时间(Step)。最右边的 2124 代表当前训练进度。
【如何看趋势】
-
浅色线(原始数据):波动非常大,一会打到 200 多(比如拼出了 256 或 512),一会又掉到几十。这说明游戏前期发挥极其不稳定。
-
深色线(平滑均值):你看这条平滑线,它并没有呈现上升趋势,而是一直在 100-120 左右徘徊。 这说明你的智能体并没有在学习进步,或者遇到了瓶颈,它的水平一直卡在能拼出 128 这个方块左右。
2. 右图:episode/moves(步数/移动次数)
-
纵轴含义:每一局游戏进行了多少次操作才结束(GAME OVER)。
-
横轴含义:同左图,训练进度。
【如何看趋势】
-
浅色线(原始数据):在前半段(0 – 1000步左右),极不稳定,出现了很多次超过 500 步甚至达到上限(截断)的情况。
-
深色线(平滑均值):前期的深色线有向上的趋势(甚至涨到了400多步),但随后在 1000 步附近突然发生了“断崖式下跌”,直接掉到了 150 步左右。
-
不正常现象:如果智能体学会了策略,它的生存时间(步数)应该越来越长。这里步数突然剧烈减少,在强化学习中通常是非常危险的信号,说明策略“崩了”。
| 奖励太稀疏 | 一盘 500 步,大部分移动得 0 分,AI 不知道哪一步做对了 |
| 网络太弱 | MLP 看不懂棋盘空间结构,应该用 CNN(卷积神经网络) |
| 训练量不足 | 30 万步 vs 学术界 1000 万步起步 |
| 无效移动惩罚不够 | AI 反复尝试被堵死的方向,浪费回合 |
形象比喻:让一个没有触觉的机器人学炒菜。它只能看到"菜糊了"这个最终结果,却不知道是火太大还是油太少。反馈太少,无法学习。
四、方案二:Expectimax 搜索 —— 原理拆解
4.1 核心思想
"我走一步,想想所有可能发生的随机情况,再想我会怎么应对,再想想随机情况… 最后选最好的那一步。"
这不是"学习",而是"搜索" —— 不需要训练,直接向前模拟推演。
4.2 游戏树的交替结构
┌─────────────────────────────────────────────────┐
│ MAX 层(你的回合) │
│ 你选择:上/下/左/右 → 取期望值最高的 │
│ ↓ │
│ CHANCE 层(系统的回合) │
│ 系统随机在空位放 2(90%) 或 4(10%) │
│ 对所有可能性求"加权平均" │
│ ↓ │
│ MAX 层(你的下一回合) │
│ 你又选最优方向… │
│ ↓ │
│ CHANCE 层… │
│ ↓ │
│ … 到达搜索深度 → 用"启发式函数"打分 │
└─────────────────────────────────────────────────┘
4.3 深度 2 的搜索树规模
第1层:4 个方向
第2层:平均 12 个空位 × 2 种数字(2/4) = 24 种随机情况
第3层:又 4 个方向
第4层:约 6 个空位 × 2 种数字 = 12 种随机情况
总评估次数 = 4 × 24 × 4 × 12 = 4608 次
每次评估就是调用启发式函数打分
4.4 启发式函数 —— 这个最精彩
评估公式(5 个因素加权求和):
总分 = 2.7 × 空格数
+ 1.0 × 单调性(大数靠角,小数靠边)
+ 1.0 × 角落奖励(最大块在角上)
+ 1.0 × 可合并性(相邻相同数字)
– 0.1 × 平滑性(相邻数字差距大则扣分)
举例对比:
| 大数在角,沿边递减 | ✅高 | ✅高 | ✅高 | ✅中 | ✅高 | 高分 |
| 大数在中间 | ✅高 | ❌低 | ❌低 | ✅中 | ❌低 | 低分 |
核心策略:2048 高手的直觉被编码成了数学公式,AI 直接"学会"了人类经验。
4.5 Expectimax 的优势
| 零训练 | 开箱即用,不需要跑几十万步训练 |
| 可解释 | 每个方向的评分可以输出,知道为什么选这个方向 |
| 深度可调 | depth=2 就够用,depth=3 更强但更慢 |
| 自然处理随机 | CHANCE 层直接求期望,完美应对"随机放 2 还是 4" |
五、两种方案对比(核心总结)
| 本质 | 从经验中学习 | 向前模拟推演 |
| 需要训练 | ✅ 需要数百万步 | ❌ 零训练 |
| 人类知识 | 不需要(自己摸索) | 需要(编码启发式函数) |
| 效果 | ❌ 最高方块 128 | ✅ 最高方块 2048 |
| 速度 | 推理极快(1次前向传播) | 较慢(评估数千棋盘) |
| 适用场景 | 复杂、难编码规则的任务 | 规则明确、可编码启发式的任务 |
─────────────────────────────────────────────
Step 446 | Expectimax depth=3
[1] UP: ████ 37.9
[2] DOWN: 28.6
[3] LEFT: █████ 39.2
[4] RIGHT: █████ 40.1 ◀
─────────────────────────────────────────────
→ AI 选择: RIGHT ➡
→ 执行: RIGHT ➡
执行后棋盘 (OCR 识别):
┌──────┬──────┬──────┬──────┐
│ 1024│ 16│ 4│ 2│
├──────┼──────┼──────┼──────┤
│ · │ 4│ 128│ 16│
├──────┼──────┼──────┼──────┤
│ 2│ 256│ 64│ 32│
├──────┼──────┼──────┼──────┤
│ · │ 2│ 1│ 4│
└──────┴──────┴──────┴──────┘
─────────────────────────────────────────────
Step 447 | Expectimax depth=3
[1] UP: ██████ 33.2
[2] DOWN: 20.2
[3] LEFT: █████████ 39.3 ◀
[4] RIGHT: 无效
─────────────────────────────────────────────
→ AI 选择: LEFT ⬅
→ 执行: LEFT ⬅
执行后棋盘 (OCR 识别):
┌──────┬──────┬──────┬──────┐
│ 1024│ 16│ 4│ 2│
├──────┼──────┼──────┼──────┤
│ 4│ 128│ 16│ 4│
├──────┼──────┼──────┼──────┤
│ 2│ 256│ 64│ 32│
├──────┼──────┼──────┼──────┤
│ 2│ 16│ 4│ · │
└──────┴──────┴──────┴──────┘
合并得分: +19 | 累计: 6972 | 最高: 1024
─────────────────────────────────────────────
Step 448 | Expectimax depth=3
[1] UP: ██████ 37.2 ◀
[2] DOWN: 24.6
[3] LEFT: 无效
[4] RIGHT: █ 27.0
─────────────────────────────────────────────
→ AI 选择: UP ⬆
→ 执行: UP ⬆
执行后棋盘 (OCR 识别):
┌──────┬──────┬──────┬──────┐
│ 1024│ 16│ 4│ 2│
├──────┼──────┼──────┼──────┤
│ 4│ 128│ 16│ 4│
├──────┼──────┼──────┼──────┤
│ 4│ 256│ 64│ 32│
├──────┼──────┼──────┼──────┤
│ 2│ 16│ 4│ · │
└──────┴──────┴──────┴──────┘
合并得分: +2 | 累计: 6974 | 最高: 1024
─────────────────────────────────────────────
Step 449 | Expectimax depth=3
[1] UP: ████ 31.1
[2] DOWN: 22.5
[3] LEFT: 无效
[4] RIGHT: ██████ 34.8 ◀
─────────────────────────────────────────────
→ AI 选择: RIGHT ➡
→ 执行: RIGHT ➡
执行后棋盘 (OCR 识别):
┌──────┬──────┬──────┬──────┐
│ 1024│ 16│ 4│ 2│
├──────┼──────┼──────┼──────┤
│ 4│ 128│ 16│ 4│
├──────┼──────┼──────┼──────┤
│ 4│ 256│ 64│ 32│
├──────┼──────┼──────┼──────┤
│ 2│ 2│ 16│ 4│
└──────┴──────┴──────┴──────┘
合并得分: +2 | 累计: 6976 | 最高: 1024
─────────────────────────────────────────────
Step 450 | Expectimax depth=3
[1] UP: ████████████████████████████████████████████████████████ 25.9
[2] DOWN: -86.5
[3] LEFT: ███████████████████ -47.0
[4] RIGHT: ████████████████████████████████████████████████████████████ 34.4 ◀
─────────────────────────────────────────────
→ AI 选择: RIGHT ➡
→ 执行: RIGHT ➡
执行后棋盘 (OCR 识别):
┌──────┬──────┬──────┬──────┐
│ 1024│ 16│ 4│ 2│
├──────┼──────┼──────┼──────┤
│ 4│ 128│ 16│ 4│
├──────┼──────┼──────┼──────┤
│ 4│ 256│ 64│ 32│
├──────┼──────┼──────┼──────┤
│ 2│ 4│ 16│ 4│
└──────┴──────┴──────┴──────┘
合并得分: +2 | 累计: 6978 | 最高: 1024
─────────────────────────────────────────────
Step 451 | Expectimax depth=3
[1] UP: ████████████████████████████████████████████████████ 8.5 ◀
[2] DOWN: -96.9
[3] LEFT: 无效
[4] RIGHT: 无效
─────────────────────────────────────────────
→ AI 选择: UP ⬆
→ 执行: UP ⬆
经过一段时间的运行,你就能在游戏中断层领先了:



