ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

12家大模型同写一个游戏:谁更靠谱?最贵的竟翻车

12家大模型同写一个游戏:谁更靠谱?最贵的竟翻车

1. 这场“AI天梯赛”到底在比什么

第一次看到“12家大模型做同一个游戏”这个说法,我脑子里冒出来的不是技术评测,而是一个很朴素的疑问:让一堆语言模型去写同一个游戏,到底能比出什么?是比谁的代码能跑起来,还是比谁写出来的东西更好玩?后来我自己动手复现了一遍类似的流程,才慢慢摸清楚这类对比真正有价值的地方在哪里。

先说清楚这件事的本质。所谓“AI天梯”,并不是让模型直接去玩游戏,而是让它们扮演一个游戏开发者的角色,在同一个需求描述下,各自产出一份可运行的游戏工程。这个需求通常包含几个固定要素:一个明确的玩法目标、一套操作方式、一个胜负判定条件,以及一个视觉呈现的基本要求。所有模型拿到的是同一份“需求文档”,区别只在于它们背后的模型能力、上下文理解方式、代码组织习惯,以及面对模糊需求时的补全策略。

我之所以对这个话题感兴趣,是因为它把平时很难量化的东西摆到了台面上。平时我们评价一个模型写代码行不行,往往是零散地丢几个函数让它补全,或者问它一个算法题。但游戏开发是一个典型的“多模块耦合”场景:输入处理、状态管理、渲染循环、碰撞检测、分数统计,这些东西必须同时成立,游戏才能跑。任何一个环节掉链子,整个工程就是一堆跑不起来的死代码。这就比单纯的算法题更能暴露模型的真实工程能力。

还有一个容易被忽略的点:游戏需求本身是“半开放”的。你告诉模型“做一个贪吃蛇”,它可以选择用二维数组做网格,也可以用链表做蛇身;可以用定时器驱动,也可以用帧循环驱动。这些选择没有绝对的对错,但会直接影响代码的可读性、可扩展性和运行稳定性。观察不同模型在这些分岔路口怎么选,比看它们能不能写出一个能跑的版本更有意思。

所以这场天梯赛真正比的,不是“谁更聪明”,而是“谁更像一个靠谱的开发者”。靠谱体现在几个维度:需求理解是否准确、架构选择是否合理、边界情况是否考虑、代码是否可维护、遇到自己不确定的地方是否会主动收敛而不是胡乱发挥。这些维度平时很难一次性看清楚,但放在同一个游戏需求下,差异会被放大得很明显。

至于标题里说的“最贵的竟然翻车”,我一开始也以为是噱头。但实际跑下来会发现,价格高通常意味着模型更大、推理更慢、单次调用成本更高,但这并不自动等于工程能力更强。有些大模型在自然语言表达上非常流畅,可一旦进入需要严格状态同步的代码场景,反而容易“想太多”——它会自作主张地加入一些需求里根本没提的机制,结果把简单问题复杂化,最后自己把自己绕进去。这个现象在后面会展开讲。

2. 我复现这套流程时踩过的三个坑

在真正动手之前,我以为这件事很简单:把需求写清楚,丢给不同模型,把返回的代码存下来跑一遍就行了。结果第一轮就翻车了,而且翻得很有代表性。这里把踩坑过程完整记录下来,因为如果你也想自己做类似的横向对比,这几个坑大概率一个都躲不掉。

2.1 需求描述写得太“人话”,模型理解偏差极大

我第一版需求是这么写的:“做一个简单的打砖块游戏,球碰到砖块砖块消失,球掉到底部就结束。”这句话对人来说很清楚,但对模型来说,里面全是模糊点。球的速度是多少?砖块排几行几列?挡板怎么移动?球碰到挡板后反弹角度怎么算?分数怎么记?这些我都没说。

结果就是,12个模型给出了12种完全不同的东西。有的把球速设得极快,游戏三秒就结束;有的砖块排了二十行,屏幕根本放不下;还有的干脆把“球掉到底部就结束”理解成了“球掉到底部就重新开始”,做成了一个无限循环。这些差异不是模型能力问题,而是我的需求描述本身有歧义。

后来我调整了策略:需求文档里必须包含一份“最小可运行规格”,把那些不写就会导致理解偏差的参数全部固定下来。比如画布尺寸、初始球速、挡板宽度、砖块行列数、得分规则、结束条件。这些参数不需要很精细,但必须存在,否则对比就失去了基准。

提示:如果你要做类似的模型对比,需求文档的详细程度直接决定了结果的可比性。宁可写得啰嗦,也不要留模糊空间。

2.2 直接复制模型返回的代码,格式问题比逻辑问题还多

第二个坑更隐蔽。很多模型返回的代码是带 Markdown 代码块标记的,而且不同模型的标记习惯不一样。有的用html,有的用javascript,还有的干脆不写语言标记。如果你直接把这些内容存成文件,文件开头就会多出几行 ``` 符号,浏览器打开就是一片空白。

更麻烦的是,有些模型会在代码前后加解释性文字,比如“这是为您生成的游戏代码:”或者“您可以将以下代码保存为 HTML 文件运行”。这些文字混在代码里,轻则导致语法错误,重则让整个文件无法解析。我一开始手动清理了五六个文件,后来实在受不了,写了一个简单的脚本,用正则把代码块标记和前后说明文字剥掉,只保留<html>到</html>之间的内容。这一步虽然简单,但能省掉大量重复劳动。

还有一个细节:部分模型返回的代码里混用了全角符号,比如中文引号、全角分号。这些在 JavaScript 里都是非法字符,但肉眼很难一眼看出来。我的做法是跑一遍基础语法检查,把报错位置定位出来再手动替换。这个坑不致命,但很烦人。

2.3 运行环境不统一,导致“翻车”判断失真

第三个坑是我自己造成的。第一轮测试时,我有的文件用本地浏览器直接打开,有的用了一个简易的本地服务器。结果发现,某些模型生成的代码在直接打开时能跑,走服务器就报跨域错误;另一些则相反。这就导致我一开始误判了几个模型的表现。

后来我统一了标准:所有游戏都用同一个本地静态服务器托管,浏览器版本固定,屏幕分辨率固定,并且每次测试前清空缓存。只有这样,跑出来的结果才有可比性。否则你很难分清一个游戏跑不起来,到底是模型写错了,还是你的环境有问题。

这三个坑总结下来就是一句话:对比模型的前提,是先把你自己这边的变量控制住。需求要统一、代码提取要干净、运行环境要一致。这三件事做到位之后,后面观察到的差异才是模型之间的真实差异。

3. 12个模型交出来的东西,大致分成了四类

把环境统一之后,我拿到了12份可运行或不可运行的游戏工程。按照最终表现,大致可以分成四类。这个分类不是按模型名字来的,而是按它们交出来的东西的实际状态来的。你会发现,同一类里的模型,往往在代码组织方式上也有相似之处。

3.1 第一类:能跑,而且玩法完整

这一类大概有三到四个模型。它们交出来的游戏,打开就能玩,球会动、挡板会跟手、砖块会消失、分数会累加、结束条件会触发。代码结构也比较清晰,通常会把游戏状态放在一个对象里,用requestAnimationFrame驱动主循环,碰撞检测用简单的矩形相交判断。

我印象比较深的是其中一个版本,它甚至考虑到了“球在挡板边缘反弹时角度要变化”这个细节。需求里我只写了“球碰到挡板反弹”,没提角度变化,但它自己补上了。这个补全不算越界,因为它让玩法更合理,而且没有引入额外的复杂度。这类模型的特点是:在需求模糊的地方,它们会选择“最符合直觉且实现成本最低”的方案,而不是炫技。

从代码量来看,这一类通常在 200 到 400 行之间,没有多余的依赖,纯原生 JavaScript 加 Canvas。这种规模对于一个小游戏来说刚刚好,既不会太简陋,也不会让人读不下去。

3.2 第二类:能跑,但玩法有明显缺陷

这一类数量最多,大概有五六个。它们的共同点是:代码能运行,画面能出来,但玩起来总觉得哪里不对。常见的缺陷包括:球速恒定不变导致后期过于简单、砖块消失后没有分数反馈、挡板移动没有边界限制导致可以移出屏幕、游戏结束后没有重新开始的方式。

这些问题单独看都不致命,但组合在一起,就会让游戏显得“半成品”。我仔细看了其中几个的代码,发现它们并不是写错了,而是在需求没有明确要求的地方,选择了最省事的做法。比如需求里没提“游戏结束后要能重开”,它们就真的不做重开,游戏结束就是一片死寂。

这类模型的特点是:它们能理解需求的主干,但在分支和边界上缺乏主动性。你让它做 A,它就只做 A,不会去想“做了 A 之后用户可能还需要 B”。这在某些场景下是优点,因为不会自作主张;但在游戏开发这种需要完整体验的场景下,就显得不够周到。

3.3 第三类:能跑,但代码结构混乱到无法维护

这一类有两三个。它们的游戏确实能玩,但代码读起来非常痛苦。典型表现是:所有逻辑都堆在一个函数里,变量命名随意,状态散落在各处,改一个参数要翻遍整个文件。有一个版本甚至把球的坐标、速度、砖块数组、分数全部定义成了全局变量,然后在主循环里用一长串 if-else 处理所有碰撞。

这种代码能跑,是因为逻辑本身不复杂。但如果我想在此基础上加一个“道具掉落”功能,基本等于重写。这类模型的问题不在于能力,而在于缺乏工程意识。它们把代码当成一次性脚本在写,而不是当成一个可以迭代的项目在写。

有意思的是,这类模型在自然语言对话中往往表现得很“聪明”,能跟你聊很多架构设计的原则。但一到实际写代码,这些原则就全忘了。这说明“知道”和“做到”之间,确实存在一条不小的鸿沟。

3.4 第四类:根本跑不起来

这一类有一到两个。打开就是白屏,控制台报错。常见的错误包括:引用了不存在的函数、变量声明顺序错误导致初始化失败、Canvas 上下文获取失败但没有做错误处理。有一个版本甚至在 HTML 里写了<script src="game.js">,但它根本没有生成 game.js 文件,所有代码都内联在 HTML 里。

这类“翻车”往往发生在那些在对话中表现得最自信的模型身上。它们会用非常肯定的语气告诉你“以下代码可以直接运行”,然后你打开一看,连画布都没渲染出来。这种反差感,大概就是标题里说的“最贵的竟然翻车”的来源之一。

4. 为什么“贵”不等于“稳”:几个反直觉的观察

做完这轮对比之后,有几个现象和我原本的预期不太一样。我原本以为,模型越大、越贵,写出来的代码应该越靠谱。但实际结果并不是简单的线性关系。这里把几个比较反直觉的观察整理出来,供你参考。

4.1 大模型更容易“过度设计”

有一个价格较高的模型,在接到“打砖块”需求后,没有直接写游戏逻辑,而是先定义了一套“实体组件系统”的雏形。它把球、挡板、砖块都抽象成实体,给每个实体挂上位置组件、速度组件、渲染组件。然后写了一个简单的系统调度器来更新这些组件。

这套架构本身没有问题,甚至可以说是“更专业”的做法。但问题是,需求只是一个小游戏,用这套架构写出来的代码量是其他版本的三倍,而且因为抽象层太多,调试起来非常麻烦。更关键的是,它在实现过程中引入了一个 bug:组件更新顺序错了,导致球在碰到挡板时先被渲染再被更新位置,视觉上出现了明显的抖动。

这个例子说明,模型的能力越强,越容易在简单问题上“用力过猛”。它知道很多设计模式,于是忍不住要用上,但忽略了场景的规模。对于一个小游戏来说,一个 300 行的过程式脚本,可能比一个 900 行的“架构示范”更合适。

4.2 便宜模型反而更“听话”

有几个价格较低的模型,交出来的东西反而更接近“能直接用的半成品”。它们的代码通常比较直白,没有太多花哨的抽象,变量命名也偏实用。虽然功能上可能少一些细节,但至少不会给你挖坑。

我分析了一下原因:这些模型在训练时可能接触了更多“短平快”的代码片段,所以它们更倾向于用最直接的方式解决问题。而大模型因为见过的“高级写法”更多,反而容易在简单场景下选择不合适的方案。

这给我的启发是:选模型和选人一样,不是越资深越好,而是要看匹配度。一个资深架构师去写一个静态页面,可能还不如一个刚入行的前端写得快、写得好。模型也是同理。

4.3 翻车往往发生在“自信的补全”上

我统计了一下所有跑不起来的版本,发现一个共同点:它们在需求没有明确要求的地方,做了大量的“自信补全”。比如需求里没提“暂停功能”,它自己加了一个暂停按钮,但按钮的事件绑定写错了,导致点击后整个游戏卡死。又比如需求里没提“音效”,它自己加了一段音频播放代码,但音频文件路径是它虚构的,加载失败后阻塞了后续逻辑。

这些补全本身不是坏事,如果做对了,确实能提升体验。但问题在于,它们在补全时没有做“失败兜底”。音频加载失败就让它失败,不应该影响游戏主循环;暂停功能如果没把握,就不应该加。在工程里,一个不确定的功能,不如没有功能。这个道理模型显然还没完全学会。

5. 如果让我重新设计这个对比,我会怎么改

经过这一轮折腾,我对“模型写游戏”这件事有了更具体的认识。如果让我重新设计一次对比流程,我会在几个地方做调整,让结果更有参考价值。

5.1 需求文档拆成“必做”和“选做”两层

第一版需求把所有东西混在一起,导致模型分不清哪些是核心、哪些是锦上添花。改进的做法是:把需求拆成“必做项”和“选做项”。必做项是游戏能跑起来的最低要求,比如画布渲染、球和挡板的基本运动、碰撞消失、结束判定。选做项是加分项,比如分数显示、重新开始、难度递增。

这样一来,评价标准就清晰了:先看必做项是否全部完成,再看选做项完成了多少。而不是像之前那样,因为一个模型加了音效但音效坏了,就把它和什么都没加的模型混在一起评价。

5.2 给每个模型两次机会

第一轮我只让每个模型生成一次。但模型输出有随机性,一次翻车不代表它永远翻车。更合理的做法是:同一个需求,让每个模型生成两次,取表现较好的一次作为最终成绩。这样能过滤掉一部分随机波动,让对比更接近模型的真实水平。

当然,这也带来一个问题:成本翻倍。但如果你真的想得到一个靠谱的结论,这点成本是值得的。否则你很容易因为一次偶然的失败,就对一个模型下过于严厉的判断。

5.3 增加一个“可维护性”评分维度

之前我只关注“能不能跑”和“好不好玩”,但忽略了代码本身的质量。后来我想加一个功能,发现大部分版本都很难改。所以下一轮我会加入一个简单的可维护性评分:变量命名是否清晰、函数职责是否单一、状态管理是否集中、有没有明显的重复代码。

这个评分不需要很精确,哪怕只是“好改”“勉强能改”“不想改”三档,也能提供很多信息。毕竟在实际工作中,代码写出来是要给人看的,不是给机器跑的。

6. 从游戏延伸到通用代码生成:几条实用的经验

虽然这篇文章的由头是“AI做游戏”,但里面暴露出来的问题,其实适用于所有让模型生成代码的场景。不管你是让它写一个爬虫、一个数据处理脚本,还是一个简单的网页,下面这几条经验都能用上。

6.1 需求里必须包含“验收标准”

模型不知道你心里的“好”是什么样。你必须把验收标准写出来。比如“游戏必须能连续玩三分钟不崩溃”“分数必须实时更新”“结束必须能重开”。这些标准写出来之后,模型在实现时就有了目标,你验收时也有了依据。

我试过在需求里加一句“代码必须能在不修改的情况下直接运行”,结果那一轮所有模型都主动去掉了需要额外依赖的写法,全部用原生 API 实现。这说明模型是能理解约束的,前提是你得把约束说出来。

6.2 对“额外功能”保持警惕

模型很喜欢加戏。你让它写个计算器,它可能给你加一个历史记录面板;你让它写个待办列表,它可能给你加一个拖拽排序。这些功能如果做对了,确实好用;但如果做错了,就会变成负担。

我的做法是:在需求里明确写一句“只实现以下功能,不要添加任何未提及的特性”。这句话能挡掉大部分自作主张的补全。如果某个模型还是加了,那至少说明它没有认真读需求,这本身也是一个有用的信号。

6.3 代码提取和运行环境要标准化

这一点前面已经说过,但值得再强调一次。如果你要做多个模型的横向对比,一定要把代码提取和运行环境标准化。写一个简单的脚本,自动从模型返回内容里提取代码块,统一保存为固定文件名,然后用同一个服务器托管。这些准备工作花不了多少时间,但能让你后面的观察省心很多。

6.4 不要只看“能不能跑”,要看“改起来难不难”

“能跑”只是最低标准。一个游戏能跑,不代表它写得好。你要试着去改它,比如把球速调快一点、把砖块颜色换一下、加一个简单的道具。如果改一个参数要翻三个文件,那这个代码的质量就是有问题的。

这个测试方法很土,但非常有效。它模拟的是真实开发中“需求变更”的场景。一个模型如果只能写出“一次性代码”,那它在实际项目中的价值就会大打折扣。

7. 我在这轮对比里学到的几件小事

最后说几个比较零散但我觉得挺有用的观察,不一定成体系,但都是实际跑下来才注意到的。

第一个是关于“注释”的。有些模型生成的代码注释非常多,几乎每行都有;有些则几乎不写注释。我原本以为注释多的更好,但后来发现,注释太多反而会干扰阅读,尤其是当注释和代码不一致的时候。最舒服的版本是:只在关键逻辑处写一两句说明,比如“这里用矩形相交判断碰撞”,其他地方靠命名自解释。

第二个是关于“错误处理”的。大部分模型生成的游戏代码都没有任何错误处理。Canvas 获取失败怎么办?音频加载失败怎么办?它们默认一切都会成功。这在演示场景下没问题,但如果你想把这个代码放到一个真实的产品里,这些缺失的兜底逻辑就是隐患。

第三个是关于“命名”的。有些模型喜欢用a、b、temp这种命名,读起来非常费劲。另一些则用ball、paddle、brick这种一看就懂的名字。这个差异看起来很小,但对代码可读性的影响非常大。我在评价一个模型时,会特别留意它的命名习惯,因为这反映了它有没有“为读者考虑”的意识。

第四个是关于“依赖”的。有几个模型生成的代码引用了外部库,比如某个物理引擎或者某个动画库。但需求里并没有要求用这些库,而且这些库的引入方式往往是错的——要么版本不对,要么路径不对。相比之下,那些纯原生实现的版本,虽然代码量稍多,但至少不会因为依赖问题跑不起来。这让我更加确信:在需求没有明确要求的情况下,少依赖比多依赖更安全。

这轮对比做下来,最大的感受是:模型写代码的能力确实在进步,但“工程判断力”仍然是稀缺品。它们能写出正确的语法,能实现基本的功能,但在“什么时候该简单、什么时候该复杂”“哪些地方要兜底、哪些地方可以省略”这些问题上,还远远不如一个有经验的开发者。这大概也是为什么,即使模型能生成代码,我们仍然需要人去审查、去调整、去为最终结果负责。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进