本文来自微信公众号: 把科学带回家(ID:steamforkids),作者:七君,头图来自:东方IC
玩过吃豆人吧。通关过吗?
通关是不可能的,这辈子都不可能的。因为吃豆人根本不可能通关。
不可能通关的秘密,都藏在最后一关里。
吃豆人的最后一关,第256关吧,是不可解的。
基本上,你玩到这一关,就会看到这样的东西——
你没有注意到这个问题,可能是你平时太爱学习了。有些有编程经验的小朋友可能会马上反应过来,是不是256这个数字有问题啊?
的确,这个问题和256有关,不过不是你想的这种关系。
是这样的,在吃豆人游戏刚出来的时候,也就是80年代的时候,大多数微芯片的处理器是8位的。
计算机是2进制的,所以1就是1,2就是10,3就是11,4就是100,以此类推。
8位的意思是,处理器有8个“格子”,每个“格子”只能表示0或1,最多能表示8位的二进制数字。
所以,它能显示的最大数字就是二进制的 1111 1111,也就是十进制的255。
那么此时我让它再加1会发生什么事呢?
因为最多只有8位,不能进1位变成9位,所以处理器就会变成0000 0000,也就是0。这就是整数溢出了。换言之,在8位处理器里,能够表达的最大的数字是255。
可是,吃豆人的死忠粉把它的源代码调了出来,他们发现问题不在于关数上,而是出在了显示的过程中。
是这样的,吃豆人的程序员利用了0来表示第1关,所以第一关的代码实际上是0,第二关是1,第三关是2,…,所以第256关的代码实际上是255,并没有发生整数溢出的问题。
但是,水果有问题。
吃豆人的源代码显示,当初那个程序员可能是最早意识到枸杞保温杯不加班,胜过可乐加冰又加薪的程序员之一。
TA想要在每一关的底部画水果,用水果的个数和种类代表关数。
具体来说,第一关解锁的水果是樱桃,所以第一关底部是1个樱桃。
第二关解锁的草莓,底部是1个樱桃+1个草莓。
第三关和第四关是桃子,第3关的底部是1个樱桃+1个草莓+1个桃子;第4关的底部是1个樱桃+1个草莓+2个桃子。
5和6是苹果,7和8是蜜瓜,9和10是星际战舰,11和12是铃铛,13关及以上是钥匙。
在第7关及以下,显示过去所有关卡解锁的所有水果。
到了第8关及以上,显示的是最近7关获得的手办。
总之这些水果和手办的作用就是显摆你通的关多,不能吃也不能拿来+1up命。
那么,这位养生系的程序员是怎么挑选水果的呢?
TA把所有关卡能够解锁的水果还有手办做成了一个这样的包含20个项目的表格,储存在内存里。
然后在每一关一开始,养生程序员是这样设定的:
查看一下本关关数代码 A。我们刚才说过,第1关的二进制代码是0000 0000,第2关是0000 0001,第256关是1111 1111。
好,把 A+1,变成真正的关卡数 L。
判断一下 L 是否小于8。
如果答案为是,则从水果表格的第1个画到第 L 个为止。
如果答案为否,则从表格的第L-6个画到第 L 个为止(从第19关开始画7把钥匙)。
所以呢,第1关就从表格的第1个画到第1个,也就是画1个水果就可以了。第2关就画2个水果。
那么第256关呢?
第256关储存的代码,也就是A是1111 1111。但是要把 A 加1变成 L 的时候,就出问题了,因为此时就会产生整数溢出的问题,L 变成了0。
既然第256关的 L 是0,所以机器判断,要从水果表格的第1个画到...第0个...
这可怎么画?
实际上,画水果的程序具体是这样的执行的:
先从第一个水果开始画,画好一个+1得到 F,F 再和 L 对比一下。
如果 F = L,就停止不画了。
所以,如果 F = 0,要画多少个水果呢?
其实很简单,因为 F 也会发生整数溢出问题,所以画到256,F 也变成0了。因此在第256关,画256个水果就刚好了。
可是我们还记得,记录水果的表格只有20个项目,没有256这么多啊。那怎么办?
原来,水果表格被储存在内存的$3B08这个地方,往后的数据是背景音乐什么的。
表格里的水果和手办画完以后,机器就开始画内存后面储存的东西,所以画出来的东西就是乱七八糟的,这就好比计算机把它脑子里的胡话全部打印到屏幕上一样。
这就导致,吃豆人在256关吃不完所有的豆子,因为一部分豆子没有显示出来。因为没有办法吃掉所有的豆子,所以这关是永远过不了的。
玩家会在这一关兜兜转转,直到有社恐的圆脸小黄人被四色小妖怪逼到墙角摸来摸去最后摸死了,喵的咪的。
2010年5月21日的谷歌涂鸦就还原了这个梗,玩到第255关结束,屏幕上就会跳出“Game Over”。
也因为第256关永远过不了,所以吃豆人也有了官方认证的理论最高分——3333360。
第一个拿下这个分数的,是一位叫做 Billy L Mitchell 的选手,记录创建时间为1999年7月3日。
比利同志也被吃豆人的制作方——南梦宫创始人中村雅哉颁发了“世纪玩家”的头衔。
南梦宫创始人中村雅哉(左男)和 Billy L Mitchell(右男)
比利小哥为什么能拿满分?人家的手速为1秒的160分之一。拥有这个手速是什么样的体验,大家可以自行感受一下——
Billy L Mitchell 的手速为1/160秒,@greatbigstories
顺便说一句,其实当时程序员可以在内存里分配更多的空间来储存关卡数(关卡的计数器),你看总分就被分配了21位,所以可以计到3百万嘛。
那么这位养生程序员为什么没有这么做呢?大概TA根本瞧不起碳基生物的手速,认为正常人类不可能打到256关吧,毕竟比利发现第256关秘密的那年,离吃豆人首发已经过去了整整19年。
实际上,比利小哥后来在接受采访时表示,南梦宫是收到了他发过去的截图后,才知道原来自家游戏的第256关长这样。原来你们不仅瞧不起游戏宅的手速,还根本就没有debug过啊,坟蛋!
其实,不光吃豆人的第256关有整数溢出问题,我们还可以用整数溢出问题的设定去吓唬里面那个粉红色的小鬼Pinky。
在官方设定里,Pinky 会走到吃豆人前方埋伏玩家,所以 Pinky 的官方的名称叫做“埋伏者”。
从源代码来看,Pinky 的设定是走到吃豆人前方16个像素,也即是2个身位的地方伏击它。
可是 Pinky 也会遇到整数溢出的问题。如果吃豆人朝上,Pinky 就会走到吃豆人左边4个身位+上方4个身位的地方。而且,因为 Pinky 的设定是走在吃豆人前面伏击,所以如果吃豆人向着它跑,它就会逃走。
利用这一点,就可以调戏 Pinky 了。
你大概觉得整数溢出问题只存在于二维世界。但实际上,整数溢出问题也存在于三维世界。
比如...在瑞士,法律规定火车不能有256根车轴。
为什么呢?
因为瑞士联邦铁路(SBB)使用的计轴器,就是在铁路上计算经过的火车的车轴数量的仪器也是8位的,它记到了256就会溢出变成0。
0轴就是没有火车通过,所以这个傻乎乎的计轴器就会报告明明有256根车轴的小火车不存在。emmm,原本开往幼儿园的车可能就会开往天堂。
计轴器,@wikipedia
总之,在机器寿命大大超过人均寿命的瑞士,大家并不想换计轴器,那就干脆立法规定不允许火车有256根车轴好了,棒呆!(๑•̀ㅂ•́)و✧
瑞士联邦铁路规定,火车不能有256根车轴。从2012年7月1日开始执行。图片来源:Ausführungsbestimmungen zu den, Fahrdienstvorschriften, AB FDV Infrastruktur, Neuausgabe
看完吃豆人的故事我们明白,如果你觉得生活欺骗了你,生活中充满了bug永远也看不到获胜的希望,可能是这个宇宙的程序员没有料到你这么能打,还能来到这个关卡吧。恭喜你啦!
本文来自微信公众号: 把科学带回家(ID:steamforkids),作者:七君