
开头先放链接,防止写着写着忘了:
https://github.com/chenjunfu2/Gluttonous_snake-asm
作者有健忘症是这样的,读者要考虑的就很多了。
下面是正文
本人在很早之前学过8086汇编,但是因为弄不懂dosbox环境,甚至没写过一个汇编hello world程序。当初看完书就丢一边了。
后来在一个编程群里有人在入门8086汇编,问问题,于是帮群友解答问题的过程,我又重新学了一下8086汇编,为了帮忙调试也搭好了环境,我寻思着,既然环境什么的都弄好了,不如就写个小游戏玩玩吧。
所以就写了这个项目,就当作我8086汇编学习以来的第一个hello world吧。
运行截图(绿食物,黄蛇头,白蛇身,灰蛇尾):

不过鉴于这个项目是好几个月前的,对于具体细节已经记不太清了,只能根据我的GitHub提交记录大致复述一下自己的开发(踩坑)过程。
前言
8086汇编,属于是上古的东西了(对于计算机发展这么快的来说,很久远了),网络上资料特别少,搜东西都搜不到,问AI也没什么有用的东西,只能说开发难度是真的高,而且dos系统下面很多东西很乱,查到的资料内容都参差不齐,各说各的,弄得我心力憔悴一度想要放弃,好在最后历经万难总算是给搞出来了,唉。
历程
一开始我参考了一些网络上现成的相关项目,发现大部分都是直接用的字符输出,而且对于食物的随机生成和蛇的移动处理的不是很好,最关键的是游戏循环的延时部分(也有可能是没看到好的项目)。鉴于我之前用cpp写过一个贪吃蛇练手,就准备直接照着cpp代码写。
在复习了8086汇编基础之后,我首先想到的是,如果我不用字符画,我需要怎么改变输出,于是在各大网站搜罗资料,找到了一张表,里面有各种功能号对应的视频模式,但是一堆都是错的(CSDN垃圾站实锤),在经过了n久的试错+反复查找多个资料验证+AI询问之后,终于试出来几个dosbox能兼容的视频模式,并且绘图也是正常的,于是终于成功绘制出了第一个小正方形。
PS:在查找过程中还发现一个超级方便的网站,里面有很多关于8086中断号,功能,指令相关的东西,它帮了我莫大的忙,在此感谢一下并放出网址:
http://vitaly_filatov.tripod.com/ng/asm/
在敲完一些基础的代码之后,我遇到了第一个难题,就是如何设置游戏循环的延时,毕竟8086跟现在系统的普通程序调用api开发不一样,8086很大一部分功能是依赖中断实现的,包括程序结束也是(经典mov ax, 4c00h后int 21h),首先想到的其实就是直接用中断获取当前时间,但是查了一下后发现,直接用中断获取系统时间的精确度根本达不到毫秒级别,很多贪吃蛇小游戏的程序都是通过修改系统的一个定时中断(int 1ch,每55ms系统调用一次)让系统定时调用,但是这样就导致你的最快速度限制是中断的55ms间隔,这个间隔对我来说太大了,达不到我的要求,于是第一版本我就先跳过了这个问题,直接偷懒用了一个延迟中断,类似于Sleep那种的,不过能精确到微秒级别(缺点嘛,就是游戏循环运行时间加上固定的等待时间,直接决定了每次蛇移动的时间,会出现移动速度不均匀的情况)。
接下来就是解决按键输入的问题了,我一开始是想着说,直接通过修改按键中断例程,来自己处理按键,于是写了一个修改中断的程序,导致dosbox各种炸机,而且那个时候我还用的是普通版本的dosbox,不是高级版本的dosbox-x,没有外带调试器。
我调试程序用的还是从winXP上面搞下来的debug.exe,单步断点运行,对于这种修改中断出错的问题,程序一炸调试器dosbox跟着全炸了,啥都看不出来,特别是中断安装过程,只要一安装,因为dos程序用的就是同一套输入界面,我的程序安装中断,实际上前台的调试器的输入也会被阻挡,并且如果我打开的绘图视频模式,调试器输出也会受到影响,只能说非常难受(开发中最大的阻碍了属于是,期间也尝试过很多8086调试器,没一个满意的,那个时候还不知道dosbox-x就有好用的调试器,我开发完成之后才知道,真是气死了)。
不过好在我最终还是成功的弄出来了一个基础版本,我的想法很简单,只保留玩家按下的最后一个有效按键,每次循环检测最后一个有效的,那个时候没考虑到有可能玩家短时间内连续按两个按键,可以在接下来的两次移动中预输入拐弯,最后我还是改成了直接用自带缓冲区的键盘中断获取输入(早知今日何必当初啊,白费力气了属于是),中断艾玛真香啊。
其实我死磕中断还有一个原因,是因为我想让蛇能够在方向按键按住的情况下才加速移动,放开就恢复正常速度,原本的中断很难做到这种按键按下和放开的判断,不过后面我就妥协了,干嘛非要按住方向键加速,弄另一个专用的加速键,按一下加速,再按一下恢复不就好了嘛,别这么整自己(什么妥协大神)。
说回关于循环时间逻辑的上文,在我不懈的努力下,各种查资料,最后终于在前面提到的那个汇编网站上面,发现了一个好东西,时间事件,并且支持精确到毫秒!太好了这东西有文档记录,我们有救了。
简单解释一下,这东西就是你设置一个事件和触发时间,并且给出一个内存地址,时间到了之后,这个内存地址上的值就会被修改,然后事件失效。唉,这不就是和游戏循环判断时间差的逻辑很像吗,我开头设置一个时间,然后走完循环,再回到开头的时候循环判断当前时间事件有没有触发(就死循环硬看内存地址上的值有没有改变),等到触发之后立马再次设置事件,这样就能让游戏循环的时间稳定了,蛇每次移动的速度也大差不差了,并且如果后面我需要加速蛇的话,我只需要在下次设置事件的时候把事件时间缩短即可。唉,这还真是天才!
接下来在一点一点推进项目的时候,我又遇到了难题,一般你写cpp的时候,随机数库都是标准库直接提供的,但是8086我查了一圈发现,并没有什么随机数中断,我又搜了一下看看别的大佬做的贪吃蛇,发现直接用的时间中断,从时间生成随机数,但是这样非常不可控,短时间内时间变化不大,并且我要求均匀分布,这样会导致不均匀。
这下麻烦了,8086寄存器还是16bit的,如果要做一些随机数生成器又非常麻烦,于是在搜索了一些随机数生成器的实现方式之后,我发现了一个非常好用,并且8086平台能运行的算法xorshift,也就是用7、9、8这三个小常量就能运算,并且我为了避免生成重复和食物生成的均匀性(食物生成不能在蛇身上,如果到后期蛇很长的情况下,用随机数生成刚好能碰到空位的情况就很考验随机数生成器的均匀性,如果只是单纯的生成一个随机数,碰到蛇就一直往后递增,又会导致食物位置不能均匀的在剩余空位分布),用了一个之前在书里学到的方法,就是记录所有的可以生成的空位大小,每次生成的时候,随机数就在 0~空位个数 之间取,这样就保证了随机的食物能尽可能均匀的分布在剩余空间中,同时直接遍历地图,找到第n个空位(n就是随机数值),这样生成效果就比那些用时间值,循环判断蛇,失败就一直尝试重新生成的效果好得多。
最后就是重头戏了,也是整个开发中最难的点,首先就是无法调试,绘图过程要么完全只看绘图表现,要么不开视频模式直接调试,绘图出来的效果全靠脑补,什么脑内开发(如果绘图视频模式调试器挂上去,就会把调试器输出吃掉,太难绷了)。
而且吧,8086这东西,如果你用现在机器的绘图方式,每次绘图的时候,都遍历整个地图然后全部更新到显示屏上去,那得卡半天,所以我一开始就是想着通过只绘制更新的部分,来减少复杂度,增加运行速度,但是奈何使用中断绘图,一次只能绘制一个像素点,中断的调用速度想必大家也知道,如果我一个蛇的方块大小有10*10,每次移动需要更新蛇头*1、蛇身*1,蛇尾*1,就需要调用300次绘图中断,会有明显的闪烁效果,可以看出来蛇方块是从上到下是一点一点变化的,这就非常难受。并且如果要清空整个屏幕重开游戏,得等好几秒,这真的不能忍受一点!
于是,我灵机一动,想到屏幕内存,肯定有个映射地址,遂去网络上到处搜相关资料,但是因为我用的视频模式是超级VGA显卡模式,屏幕大小远大于8086一个寄存器所能寻址的范围(屏幕有800*600的大小),所以根本不可能直接通过【列索引*行像素数+行索引】的计算方式来直接计算内存地址的位置(那就溢出了),并且我尝试了直接填充那个映射的内存地址,发现根本只能填充从段地址0a000h开始的0~65535(16bit寄存器无符号数大小最大值)个像素,只能填充屏幕最上面的一小部分!
坏了,现在要么我赶紧收手,直接改成别的视频模式,用像素总数小于65535个的视频模式绘图,要么换回int中断绘图······
我就在想,到底屏幕绘制中断到底是怎么绘制屏幕上的任意x,y坐标点的?于是到各大平台搜索,但是无一例外毫无结果,都是一些没什么用的内容(CSDN来回搬运是这样的)。
事已至此,我只能拜托国外的朋友帮忙上stackoverflow查找相关资料,在经过了n久的搜索后,终于找到了连前面说的网站都没有记载的一个中断功能号,也就是用来切换显示器内存页面映射的功能!
只要调用这个功能,就可以把显示器拆分成的n个页面中的一个指定页面,映射到固定的内存地址(从段地址0a000h开始),这样,我只需要通过坐标,计算页面,然后再计算这个坐标在当前页面中的位置,从那开始填充一个小正方形,就可以了······吗?
好吧,是我疏忽了,没考虑到如果一个小正方形刚好在两个内存页面的边界的情况,于是又花了一堆时间,终于解决了这个问题。
然后,就有了现在这个项目。
虽然还有很多不足之处,某些功能还没做好(输赢界面显示,得分显示等,但是无伤大雅不是么),反正已经能玩了,而且那位群友8086汇编也学完了(开始学C艹了),于是项目就这样搁置了。
结尾
这是我对于之前8086汇编贪吃蛇小游戏开发困难过程的一个简化记录,实际上我花了差不多有一周多的时间才做完这些,难度真的太大了,不过做成功之后的成就感还是让我很满足的。
当然,做的不好,还请大佬轻喷,感谢理解!
如果有什么问题欢迎大佬指出,或者我的开发记录有帮到你,那就是再好不过的了。
啊啊啊啊啊
对了,最后还是不得不说一下,真是气死我了,开发了那么久,最后一版都做完了,才知道有dosbox-x这东西,能直接用外置专用的调试器,特别方便,我***的,之前花了那么多时间,合着都白搞了啊?啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,咽不下这口气啊,气死我啦!