278字节。这已经不是你印象里的“微型程序”,而是汇编黑魔法层面的极限挑战。有人会觉得这纯粹是个噱头——要在这么小的空间里塞进完整的棋盘绘制、输入解码、走法搜索,根本就是不可能的任务。但AttoChess摆在那里,它不仅能在16位x86 DOS下跑起来,还能跟你下一盘完整的国际象棋。两种声音的冲突背后,藏着一次对代码固有假设的凶猛撕扯。
正方立场很扎实:一个能交互的棋盘游戏,至少要有个像样的图形模式、一套坐标转换逻辑、一处渲染缓冲区,再加一套字符串打印函数调用。这些都占地方,压缩到最后往往只能砍功能。反方拿出的实打实的产物却表明,功能非但没砍,只用278个字节就全做到了。差别在于,AttoChess把“渲染缓冲区”这道中间层彻底掀翻,让棋盘直接以可打印字节的形式躺在内存里,再配合DOS快速字符输出int 29h直接喷向屏幕。
传统做法会老老实实地走一遍棋盘格子,把每个格子的内部状态映射成可打印字符,存进缓冲区,末尾补上一个$终止符,然后请DOS函数int 21h/09h整体输出。这套链条天然附带了指针初始化、逐格复制、终止符写入,以及为了保留缓冲区而在磁盘映像里占着一整块数组。AttoChess直接把所有这些东西删了个干净。棋盘四周的边界列不再用08h填充,而是放上CR, LF, CR, LF这样的回车换行序列。因为回车换行的位掩码测试和原来的填充字节一样都不会触发异常,所以原先处理边界与颜色的分支照常工作,但那些字节本身就已经成了可直接刷新的“画框”。输出循环里,lodsb读出一个格子值,测试低位有没有棋子;没有就走快速输出直接发射回车、换行或空字符,有则做一次简单的ASCII转换后输出。从渲染缓冲区指针初始化到$终止符,再到依赖int 21h的整串打印,全都合成了一记int 29h的流式发射。这也是整版字节省出最大一块的根本操作。
启动环节同样一刀切掉了BIOS显示模式设置。原版上来就用int 10h强制切到文本模式0,而AttoChess根本不关心当前显示模式是什么——既然全程都用int 29h逐字输出,无所谓图形还是文本模式。取而代之的是两条真正需要保证的假设:方向标志DF必须为清,以及计数寄存器CX的初值要明确。代码开头直接放上一个cld和加载具体行数13的mov cx, 13,就完成了所有初始化。不依赖操作系统给什么状态,也不对硬件发多余指令,这让进入程序的路径短到只剩两条指令。
输入解码的改造则把汇编算术的折弯技巧又推高了一级。玩家输入两个字符(一个字母对应列、一个数字对应行),程序需要把它们变回棋盘上某个格子的地址。传统逻辑分阶段操作:读文件字符、加上偏移,读行字符、用and al, 0Fh截断高位,再加载12到ah后执行乘法得到行基地址,最后做减法凑出最终偏移。AttoChess直接把这些散落各处的常数全部预折叠进一个基地址里,让16位指针运算直接绕回mod 64K的地址空间。那个专门做正规化和单独设置乘法的步骤一起消失了,剩下的是用mov di, offset board_db + 123 + 0CE0h这样的预偏置地址,一次性吸收掉所有ASCII偏差。调用int 21h/01h拿到字符后,后面只需极简的处理就能得到直接可用的内存偏移。外行可能会觉得这些数字魔术只是为了压榨字节,但内行看到的是一次对“输入到底需要几步”这个问题的根本性重定义。
辩论到最后,核心不是“该不该省这几字节”,而是当你在约束中把每一层间接都打开检查时,会发现很多我们以为天经地义的中间步骤其实只是路径依赖。AttoChess保留原版的搜索内核一字未改,十个字节的缩减全部来自对搜索之外两个环节——棋盘绘制和走法解码——以及搜索循环内部一次寄存器释放的重新思考。它没有发明新算法,只是推倒了一些从来没人质疑的脚手架。开源社区里已经有人把这套手法移植到178字节的内核上,还有人尝试用更极端的着色方案再抠出几个字节。但不管怎么卷,AttoChess最先扔出的那个判断依然有效:边界条件很多时候不是真实的硬件限制,而是你对既有方案的默认接受。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.