把1992年的经典射击游戏《毁灭战士》移植到稀奇古怪的硬件上,已经成了程序员圈子里最流行的非正式挑战。从烤面包机到充电头,甚至验孕棒,几乎没有什么设备能逃过被拿来“玩DOOM”的命运。不过,最近一位名叫Artem Lytkin的开发者把这种折腾推向了新的维度——他用正则表达式运行了《毁灭战士》。
听起来像个玩笑,但它是真的。正则表达式通常只是程序里用来查找和替换文本的一套规则语言,语法看起来像一堆乱码,被很多人戏称为“写完之后自己就看不懂”的语言。Lytkin却穷尽它的能力,搭建了一台完整的虚拟计算机,而“硬件”就是一个96MB大小的纯文本字符串。
整个系统像一台透过文字运转的CPU。这段文本里划分好了虚拟CPU的各个寄存器、一小块内存、一块帧缓冲用来输出画面、游戏本身的WAD数据,以及输入输出等杂项。当这一切启动后,正则表达式就像处理器里的电子信号一样,开始疯狂地进行文本匹配和字符替换,用这些替换动作来模拟寄存器的数值变化、内存的读写,直到跑出《毁灭战士》的完整图形。
速度有多慢?慢到每一帧画面需要大约180秒才能渲染出来。也就是你按下前进键,三分钟后角色才在屏幕上往前走一步。生成一帧画面需要执行近1400万次文本替换,引擎每秒能完成大约8万次替换。Lytkin自己形容这种体验“更像是拿着霰弹枪下通信棋,而不是在玩快节奏射击游戏”。这个比喻精准得让人发笑,也精确地描述了那种延迟:你开一枪,然后可以泡杯咖啡慢慢等结果。
争论几乎从项目公开的那一刻就开始了。一派人觉得这彻底疯了,效率低到荒谬,纯属行为艺术。另一派开发者则马上指出,这件事恰好证明了正则表达式具备图灵完备的能力——理论上它可以计算任何可计算的问题,只不过是以一种极其痛苦的方式。Lytkin面临的真正挑战从来不是“能不能做到”,而是游戏“能不能在宇宙热寂之前跑起来”。
为了实现这一点,他在内存访问的设计上耍了一点巧妙的把戏。常规的做法需要反复扫描整个96MB的文本,寻找用“#M”标记框起来的内存区域,就像在一本字典里每次从头翻找同一个词。Lytkin改用二叉树结构,通过正则表达式自带的字符跳转指令,逐级跳进树的分支,用较少的比较次数就能定位到目标位置,大幅减少不必要的替换操作。
即便有了这种优化,这台正则引擎依然慢得惊人,却有一个很强的特性让人无法忽视:Lytkin宣称它的输出结果与真正的《毁灭战士》原始执行文件逐字节完全相同。也就是说,虽然每一步都在文本的海洋里挣扎,但它始终维持了计算状态的绝对精确,没有因为性能妥协而牺牲任何逻辑的正确性。
两方的判断其实都没有错。从实用角度看,这个项目没有任何直接工程价值,没人会用它来玩游戏;但站在编程语言理论和计算极限的视角上,它把日常工具里沉睡的运算能力彻底暴露了出来。就像有人用手摇计算器解出圆周率小数点后十万位,虽然荒谬,却让旁观者重新审视了工具的边界。
更让人着迷的是操作上的某种反差。玩家仍然可以通过键盘控制角色,只不过每一次按键都在给那台文本处理器下达一批新的替换任务,然后等上几分钟让画面挪动。这种延迟带来的不是焦躁,反而让“玩DOOM”变成了一种和时间静静较劲的仪式,和通信棋的回合制节奏如出一辙——你走一步,对方(在这里是正则引擎)思考几分钟再响应。
Lytkin已经在GitHub上公开了doom-regex仓库,任何人都可以下载演示版本在自己电脑上跑一跑。项目的网页里还提供了可视化界面,能直观地看到那台正则机器如何在文本和图形两种形态间切换。当你盯着屏幕上一个个字符被替换成下一状态,看着那个96MB的字符串慢慢“呼吸”时,或许会突然理解这个项目背后的疯狂浪漫:不是让游戏跑得多快,而是证明即使在最小的语法规则里,也藏着一台通用计算机在沉睡。
热门跟贴