不是所有智能都需要大模型:俄罗斯方块 AI 的启示
我最近给俄罗斯方块写了几个 AI 玩家:一个可以调参的贪心 AI、一个追求极致高分的「最佳实践」AI,还讨论过一个只停留在纸面上的"大模型玩家"。同一个游戏、同一套规则,三种完全不同的"智能"——这个过程让我想通了一个被大模型热潮掩盖的话题:智能是有层次的,规则和算法在特定领域依然是王者,大模型应该去做它真正擅长的事,而最高级的形态是让它们组合起来。
一、先看事实:200 行算法打爆"通用智能"
「最佳实践」AI 的全部代码不到 200 行:一个两层搜索加一个评估函数。它在仿真里一局能打约 5000 块、消上千行、拿下千万分,3 行和 4 行的消行占比是贪心版的 18 倍。
如果换大模型来玩会怎样?把棋盘编码成文字喂给它,让它决定落点——每步一次 API 调用(几百毫秒延迟),棋力大概率远不如这两百行代码(大模型的空间精确推理是众所周知的弱项),还按 token 烧钱。
在俄罗斯方块这个领域,"智能"的桂冠属于搜索算法,不属于大模型。 这不是大模型不行,而是赛道不对。
二、智能的本质:在环境里做出好决策
把"智能"这个词从神坛上请下来,它的工程定义朴素得很:感知环境,做出让目标最大化的决策。 至于用什么实现,取决于问题的性质:
| 问题的性质 | 适合的"智能"实现 | 例子 |
|---|---|---|
| 状态可精确枚举、规则确定、目标明确 | 搜索 + 评估函数 | 棋类、俄罗斯方块、路径规划、排班调度 |
| 有明确数学结构、可求导或可递推 | 优化算法 | 推荐排序、广告竞价、物流装箱 |
| 规则能写成清晰的条件 | 规则引擎 | 风控拦截、报销审批、工单路由 |
| 语义开放、需要知识和语言、输入模糊 | 大模型 | 理解意图、写文案、总结、对话、代码生成 |
判断标准可以浓缩成三个问题:能枚举吗?目标能写成公式吗?环境确定吗? 三个"是",就别请大模型——一把精尺量得准的事,不需要请一位博学的教授来目测。
大模型的真正强项在另一侧:它把人类语言和知识变成了可编程的接口。理解一句模糊的投诉、把需求翻译成方案、在没见过的领域给出常识判断——这些"开放世界"任务里它无可替代,但它不擅长也不该擅长大规模精确计算。
三、1 + 1 > 2:组合才是现代答案
实践中最有生命力的从来不是"谁取代谁",而是分工组合。几个已经被验证的模式:
1. 大模型理解意图,算法负责执行。 用户说"帮我把上个月华北大区的销量拉出来"——大模型把这句话翻译成受控 SQL,数据库(几十年沉淀的查询算法)毫秒级返回。让大模型直接"算"出答案?又慢又不可靠。智能客服里也是同理:大模型理解问题,关键词规则决定是否转人工。
2. 大模型生成候选,算法验证筛选。 AI 编程领域最成熟的闭环:大模型写代码,编译器和测试用例当考官,不过关就打回重写。生成的"创造力"加上验证的"严谨",各自做擅长的一半。
3. 算法产生精彩,大模型负责讲述。 想象给搜索 AI 配一个解说员:算法每落一子,大模型实时讲解"它留这一列井是因为在等 I 块凑 Tetris"。算法保证棋力,大模型提供叙事——精确性与可理解性,各取所长。
4. 检索算法 + 语言模型 = RAG。 最流行的组合形态:向量检索(算法)从知识库里找到相关资料,大模型照着资料组织答案。一个管"找得准",一个管"说得好"。
你会发现一个规律:组合的粘合剂往往是一个清晰的接口——就像下面要讲的,我的俄罗斯方块架构里那条"这块放哪"的窄接口。
四、「最佳实践」模式是怎么实现的(架构篇)
4.1 第一刀:把"游戏"和"玩家"分开
整个实现最重要的决定不是任何一行算法,而是一刀架构切分:游戏引擎是纯逻辑,不知道谁在玩;决策器是纯函数,不知道游戏长什么样。 看左图的分层:
- 引擎层:棋盘、碰撞、旋转、消行、计分——一堆纯函数,
move / rotate / hardDrop / tick,给它指令它就推进,谁给指令一律平等; - 玩家层:可插拔的决策器。人类键盘是一个玩家(按方向键产生指令),贪心 AI 是一个玩家(评估函数产生指令),最佳实践 AI 是一个玩家(两层搜索产生指令)。三者共用同一个引擎、同一套计分——成绩才有可比性。
两个玩家之间的接口窄到只有一句话:"这个局面下,这块放哪?" 输入棋盘和方块,输出目标旋转和目标列。因为接口够窄,换玩家、测玩家、给玩家做仿真,都不用碰引擎一行代码。
4.2 第二刀:两层搜索——在 1600 个平行世界里挑最好的
贪心 AI 的短视在于"只看当前这一块"。最佳实践 AI 的解法直白得近乎暴力(看右图):
- 枚举平行世界:当前块有若干种旋转、十来个落点;下一块(引擎本来就预知)同样如此。两两组合约 1600 种"两步下法",每一种都在棋盘的内存副本上完整落定、消行、结算——就像同时推开 1600 扇门看一眼门后的世界;
- 给每个世界打分:两步的真实消行得分,加上终局局面的评估——高度低加分、无空洞加分、表面平加分、留一口单列井加分(那是给 I 块预留的 Tetris 通道);
- 回到现实:执行分数最高的那个世界的第一步。下一块真的来临时,重新搜索。
为什么它就能"忍一手等四行"?因为消行分是超线性的(4 行 800 分,是 4 个单行的 8 倍),两层搜索又真的算得出"下一块是 I、现在留井能拿 800"。它不是学会了策略,而是算穿了策略。
还有一处工程上的"保险丝":当堆高接近警戒线,消行的评估权重自动放大 5 倍——紧急泄洪,防止 AI 为了囤一个完美的 Tetris 把自己囤死。
4.3 核心代码走读(不到 40 行的精髓)
评估函数——把四种直觉写成一行加权求和:
function bestEval(board) {
const f = boardFeatures(board) // 高度、洞、起伏、井深、最高列
return -0.04 * f.agg // 总高度:堆得越低越好
+ -3.0 * f.holes // 空洞:重罚,尽量不埋洞
+ -0.15 * f.bump // 起伏:表面越平越好
+ 1.4 * Math.min(f.well, 4) // 单列井:留给 I 块的通道,封顶4格
+ -0.6 * f.maxH // 最高列:防囤死
}
两层搜索主循环——四个 for 循环,一场平行世界巡游:
function bestPlacement2(engine) {
// 高压时消行收益 ×5,紧急泄洪
const pts = boardFeatures(engine.board).maxH >= 11 ? 0.03 * 5 : 0.03
let best = null
for (const r1 of rotations(engine.piece)) // 当前块每种旋转
for (const x1 of allColumns()) { // 每个落点
const s1 = simulateDrop(engine.board, r1, x1) // 平行世界①:落定+消行
if (!s1) continue
let innerBest = -Infinity
for (const r2 of rotations(engine.next)) // 下一块每种旋转
for (const x2 of allColumns()) { // 每个落点
const s2 = simulateDrop(s1.board, r2, x2) // 平行世界②
if (!s2) continue
// 终局局面分 + 下一块的真实消行分
innerBest = Math.max(innerBest, bestEval(s2.board) + LINE_PTS[s2.cleared] * pts)
}
// 当前这步的得分 = 下一层最优 + 当前步真实消行分
const total = innerBest + LINE_PTS[s1.cleared] * pts
if (!best || total > best.score) best = { score: total, ...r1, targetX: x1 }
}
return best
}
simulateDrop 在棋盘的副本上模拟一次落子并结算消行——真棋盘毫发无损,这就是"平行世界"的全部实现。每块决策约 1600 次模拟、不到 5 毫秒,浏览器里毫无压力。
4.4 这套架构给了我们什么
- 公平:多种玩家共用一个引擎一个计分,成绩才有含义;
- 可测:决策器是纯函数,不启动页面也能在 Node 里一夜跑完上万局仿真——本文开头的战绩就是这么调出来的;
- 可扩展:想加大模型玩家?写一个"输入棋盘文字描述、输出落点"的决策器插上去即可,引擎和其他玩家一行不改。
五、结语:智能是分层的,选对层比追新更重要
回到开头的问题。俄罗斯方块教给我们的不是"算法比大模型强",而是一个更完整的图景:
- 反应层用规则——毫秒级、零成本、绝对可靠;
- 优化层用搜索与算法——在可枚举的世界里逼近最优;
- 认知层用大模型——处理语言、知识与开放世界的模糊性。
一个成熟的系统往往三层俱全:智能客服(规则转人工 + 检索算法 + 大模型回答)、AI 编程助手(大模型生成 + 编译验证)都是活例子。判断一个任务该用哪层,比学会调用哪个模型 API 更接近智能的本质。
下次面对需求,先问那三个问题:能枚举吗?目标能写成公式吗?环境确定吗?三个"是",写 200 行算法;出现了模糊、语义、开放世界——那才是大模型出场的时候。而当你需要两者兼备时,记得那条窄接口的启示:把系统切开,让每种智能只做它最擅长的事。