汇编网首页登录博客注册
youthangel的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
文章收藏

[2012-11-10 12:33] 手把手教你写汇编版贪吃蛇(第三期)

时隔多日,迟迟未更新。因为我遇到了难题,控制这一块不太好写(毕竟是第一次用汇编写这么大的程序)。我所谓的不好写不是我的汇编基础知识不够,而是用汇编描述算法的能力或者经验还有所欠缺。相信各位有时候也会遇到类似的情况。C或者java的语法和基础知识我都掌握了但还是写不出东西来。这就好比我们都是中国人,汉字都认识了好几千。但我们却写不出像莫言那样的文章。这就是语言表达能力和经验的问题了。要想解决这个问题,就得多写多练。量的积累,才会有质的飞跃!唉,我怎么又开始扯闲篇了……开始正题!

       先解决上次我们留下的问题,第一个问题什么不是模除256或者199(如果读者不知道我在说什么,请查看上期的内容),因为蛇可以活动的范围是255*200,计算机是从0开始数的,所以范围变成了254*199。假如现在我们得到一个组随机数(253,197),我们会把这个数做为某个蛇节的左上角的坐标。请问,这个蛇节的右下角坐标是多少呢?答案是(258,202)。可是,这俩个数已经超出了254*199的范围。所以得到的两个随机数应该分别摸除249和194。

       接下来,第二个问题:怎么保证食物的坐标都是5的倍数(这样蛇才能找到食物)。①我们可以得到随机数分别摸除249和194。然后检测,如果出现不能被5整除的就丢掉,继续获取一个新的数。②得到随机数分别摸除249和194,然后除以5再乘以5(这个是我的室友想到的)。显然第二种方法的效率会高一些。

3、  控制(核心代码的集中地,往往比较复杂)

由于这一块比较复杂,我们还得分析一下整个流程,把任务细化。

①     让蛇自己动起来

②     通过键盘控制蛇去吃掉食物,蛇节加一

③     让计时和计分显示跑起来

④     如果蛇撞到墙和自己 或者时间到 gameover!

首先让蛇跑起来,往哪个方向跑呢?我们需要设置一个方向标志drect。

       snack:  db        150,150,150,155,150,160,'$'         ;以行列的方式存,方便调

drect:  db        48h      ; 48h代表’↑’50h代表’↓’ 4bh代表’←’4dh代表’→’、先取得方向标志,然后switch……case(当然汇编里没有这么高级的语句)

       让后根据方向修改蛇头的位置,蛇身只需要把前一蛇节的坐标复制给下一个蛇节。(瞎子走路,后跟前)

       move:

              push      ax

              push      bx

              push      ds

              push      dx

              

              mov       ax,data     ;指向数据段

              mov       ds,ax

              

              ;取得方向标志,修改所有节点的坐标

              mov       bx,drect

              mov       ah,[bx]

              mov       bx,snack    ;取出蛇头准备移动

              mov       dx,[bx]     ;保存蛇头

              ;进行比较

              cmp       ah,48h

              je        up          ;上

              cmp       ah,50h

              je        down        ;下

              cmp       ah,4bh

              je        left        ;左

              cmp       ah,4dh

              je        right       ;右

         up:

              mov       ax,[bx]     ;取出来

              sub       al,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

       down:

              mov       ax,[bx]     ;取出来

              add       al,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

       left:

              mov       ax,[bx]     ;取出来

              sub       ah,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

      right:

              mov       ax,[bx]     ;取出来

              add       ah,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

              ;移动每个节点,第一个节点已经移动

  show_next:

              add       bx,2

              mov       ax,[bx]     ;取出下一个节点坐标

              cmp       al,'$'

              je        retnext

              xchg      dx,ax       ;跟上一个节点坐标交换

              mov       [bx],ax     ;在写回去

              jmp       show_next

    retnext:

              pop       dx

              pop       ds

              pop       bx

              pop       ax

              ret

如果你运行一下程序的话,会看到一个绿色条从屏幕上一闪而过。没有动的效果!(O_O)

因为我们没有加延时,而且也没擦出移动前的影子

那在组织一下:

①     显示蛇身

②     延时1s

③     擦出影子

④     移动蛇节

⑤     调至①

①     和④我们已经实现。接下来做个延时。

有的朋友的会想到空转50000条不就行了,这个可以实现延时。但放到不同的机子上延时效果是不一样的。因为人家主频高0.1s就转完了。你的机子少低端,转了0.5s还没转完。这样效果就不一样了,移植性太低。

       要不我们用1ch时钟中断吧,这个真可以用……而且移植性也挺好。就是……实现起来有些麻烦。

       熟悉硬件的朋友可能知道80X86系列机还有一种延时的方法:检测61H端口的PB4位,使PB4位每15.8us触发一次。我们可以通过检测这一位变化的次数进行计时

       delay:

              

              push      ax

              push      bx

              push      cx

              mov       bx,4

              ;执行完33144次大概0.5s

      again:  mov       cx,33144

      waitf:

              in        al,61h      ;跟61h端口的pb4有关

              and       al,10h

              cmp       al,ah

              je        waitf

              mov       ah,al

              loop      waitf

              dec       bx

              jnz       again

              pop       cx

              pop       bx

              pop       ax

              ret

下面擦出影子,这一步也很简单。我们可以用绿色的显示蛇身,当然也可以用其他颜色去显示蛇身。相信你已经想到了:屏幕的黑色。

       Mov        ch,0;0代表黑色

       Call        show_snack

我们还可以通过清屏在显示、显示不同页的方法实现动的效果,这两种方法留给读者自行实验。

用键盘控制蛇的移动方向
       如果没有键盘输入一直方向标志位移动,有键盘就按键盘修改方向标志位(把显示、控制、数据分开。我们院的老教授告诉我的)

       首先,我们需要一个检测键盘状态(是否有输入)的子程序,dos中断中提供好了

       Mov        ah,0bh

       Int          21h

       如果有输入(al)=0ffh,没有(al)=00h

有输入,我们在调用

       Mov        ah,0

       Int   16h

读取键盘缓冲区(al)=键盘值的ASCII,(ah)=键盘值的扫描码

其中扫描码中48h代表’↑’、 50h代表’↓’、 4bh代表’←’、 4dh代表’→’、

先来体验一下键盘控制的感觉吧

;#mode=dos

.model        small

        data  segment

      snack:  db        150,150,150,155,150,160,'$'         ;以行列的方式存,方便调用

      drect:  db        4dh           ;0上 1下 2左 3右

 

       food:  db        8 dup(0)

 

      times:  db        "time",'$'

       time:  db        "00:00",'$' ;上限99s

 

      marks:  db        "mark",'$'

       mark:  db        "0000",'$'  ;一节一分,60分过关

        data  ends

.code

      start:

              call      init        ;初始化

       goon:

              call      delay;延时

              mov       ch,0

              call      show_snack;擦出影子

              call      move;移动

              mov       ch,2

              call      show_snack;显示

              mov       ah,0bh

              int       21h

              cmp       al,0        ;检测是否有输入

              je        goon        ;没有的话继续

              call      get_key     ;有的话进行处理              

              jmp       goon

           

       ;暂停           

              mov       ah,0

              int       16h

        ;返回16色文本模式      

              mov       ax,0003h

              int       10h

       ;退出              

              mov       ax,4c00h

              int       21h

;-------------获取按键子程序----------------------

;子程序的功能:

;      根据键盘值的上下左右修改方向标志

;--------------------------------------------------     

    get_key:

              push      ax

              push      bx

              push      ds

              mov       ax,data

              mov       ds,ax

              mov       bx,drect

                   

              mov       ah,0

              int       16h

              mov    al,[bx]

              cmp     al,ah

              je retsw

              

              cmp       ah,50h

              je        down1       ;下

              cmp       ah,4dh

              je        right1      ;右

              cmp       ah,48h

              je        up1         ;上

              cmp       ah,4bh

              je        left1       ;左

              

      retsw:

              pop       ds

              pop       bx

              pop       ax

              ret

        up1:

              mov       al,[bx]     ;取出来

              mov       al,48h        ;修改

              mov       [bx],al     ;写回去

              jmp       retsw

      down1:

              mov       al,[bx]     ;取出来

              mov       al,50h        ;修改

              mov       [bx],al     ;写回去

              jmp       retsw

              

     right1:

              mov       al,[bx]     ;取出来

              mov       al,4dh        ;修改

              mov       [bx],al     ;写回去

              jmp       retsw

              

      left1:

              mov       al,[bx]     ;取出来

              mov       al,4bh        ;修改

              mov       [bx],al     ;写回去

              jmp       retsw

     

;--------------------------------------------------

;子程序的功能:

;      移动每个节点

;--------------------------------------------------              

       move:

              push      ax

              push      bx

              push      ds

              push      dx

              

              mov       ax,data     ;指向数据段

              mov       ds,ax

              

              ;取得方向标志,修改所有节点的坐标

              mov       bx,drect

              mov       ah,[bx]

              mov       bx,snack    ;取出蛇头准备移动

              mov       dx,[bx]     ;保存蛇头

              ;进行比较

              cmp       ah,48h

              je        up          ;上

              cmp       ah,50h

              je        down        ;下

              cmp       ah,4bh

              je        left        ;左

              cmp       ah,4dh

              je        right       ;右

         up:

              mov       ax,[bx]     ;取出来

              sub       al,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

       down:

              mov       ax,[bx]     ;取出来

              add       al,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

       left:

              mov       ax,[bx]     ;取出来

              sub       ah,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

      right:

              mov       ax,[bx]     ;取出来

              add       ah,5        ;修改

              mov       [bx],ax     ;写回去

              jmp       show_next

              ;移动每个节点,第一个节点已经移动

  show_next:

              add       bx,2

              mov       ax,[bx]     ;取出下一个节点坐标

              cmp       al,'$'

              je        retnext

              xchg      dx,ax       ;跟上一个节点坐标交换

              mov       [bx],ax     ;在写回去

              jmp       show_next

    retnext:

              pop       dx

              pop       ds

              pop       bx

              pop       ax

              ret

 

;--------------------------------------------------

;子程序的功能延时0.5s

;--------------------------------------------------       

      delay:

              

              push      ax

              push      bx

              push      cx

              mov       bx,4

              ;执行完33144次是0.5s

      again:  mov       cx,33144

      waitf:

              in        al,61h      ;跟61h端口的pb4有关

              and       al,10h

              cmp       al,ah

              je        waitf

              mov       ah,al

              loop      waitf

              dec       bx

              jnz       again

              pop       cx

              pop       bx

              pop       ax

              ret

;----------------------------------------------------------------------------------------------------------------

;----------------------------------------------------------------------------------------------------------------

;                                      以下为各种显示子函数

;             init、bound、show_snack、show_food、rec、line、pixel

;----------------------------------------------------------------------------------------------------------------

;----------------------------------------------------------------------------------------------------------------

 

 

;----------------初始化子程序------------------------

;子程序的功能:

;      修改显示模式、显示蛇、墙体、食物、计时、分数

;参数:无

;--------------------------------------------------

       init:

       ;显示time

              mov       ax,0013h

              int       10h         ;设置图形显示模式320*200 256色VGA

              

              mov       ah,2

              mov       bh,0

              mov       dh,2        ;行

              mov       dl,34       ;列

              int       10h

                   

              mov       ax,data

              mov       ds,ax

              mov       dx,times

              mov       ah,9

              int       21h

                   

              mov       ah,2

              mov       bh,0

              mov       dh,7        ;行

              mov       dl,34       ;列

              int       10h

                   

              mov       ax,data

              mov       ds,ax

              mov       dx,time     ;时间值

              mov       ah,9

              int       21h

                   

                   ;显示mark

              mov       ah,2

              mov       bh,0

              mov       dh,90       ;行

              mov       dl,50       ;列

              int       10h

                                      

              mov       dx,marks

              mov       ah,9

              int       21h

                   

              mov       ah,2

              mov       bh,0

              mov       dh,96       ;行

              mov       dl,50       ;列

              int       10h

                                      

              mov       dx,mark     ;分值

              mov       ah,9

              int       21h

                   

              mov       ch,2

              call      show_snack

              call      show_food

              call      bound

              ret

;----------------显示边界线程序------------------------

;子程序的功能:

;      画出可用屏幕的边界线,蓝色为蛇的活动范围

;-----------------------------------------------------   

      bound:

              push      ax

              push      bx

              push      si

              push      di

              push      cx

              

              ;下

              mov       bx,0

              mov       ax,199      ;(0,199)

              mov       si,255

              mov       di,199      ;(255,199)

              mov       ch,1

              call      line

                            

              ;左

              mov       bx,0

              mov       ax,0        ;(0,0)

              mov       si,0

              mov       di,199      ;(0,199)

              call      line

              

              ;上

              mov       bx,0

              mov       ax,0        ;(0,0)

              mov       si,255

              mov       di,0        ;(255,0)

              call      line

              

              ;右

              mov       bx,255

              mov       ax,0        ;(255,0)

              mov       si,255

              mov       di,199      ;(255,199)

              call      line

              

       ;多出来的65pix

              ;右

              mov       bx,319

              mov       ax,0        ;(319,0)

              mov       si,319

              mov       di,199      ;(319,199)

              mov       ch,4

              call      line

              ;左

              mov       bx,256

              mov       ax,0        ;(255,0)

              mov       si,256

              mov       di,199      ;(256,199)

              call      line

              ;上

              mov       bx,256

              mov       ax,0        ;(256,0)

              mov       si,319

              mov       di,0        ;(319,0)

              call      line

              ;下

              mov       bx,256

              mov       ax,199      ;(256,119)

              mov       si,319

              mov       di,199      ;(319,199)

              call      line

              

              pop       cx

              pop       di

              pop       si

              pop       bx

              pop       ax

              ret

;----------------显示蛇身子程序------------------------

;子程序的功能:

;      在屏幕上显示蛇身

;参数:

;      snack地址标号的坐标数据遇,'$'结束

;      蛇身颜色,ch

;-----------------------------------------------------                               

 show_snack:

              push      ax          ;保护现场

              push      ds

              push      bx

              push      cx

              

              mov       ax,data

              mov       ds,ax

              mov       bx,0

 show_again:

              mov       ax,snack

              add       bx,ax

              mov       ax,[bx]

              cmp       al,'$'

              je        retsnack

              call      rec

              add       bx,2

              jmp       show_again

   retsnack:

              pop       cx

              pop       bx

              pop       ds          ;恢复现场

              pop       ax

              ret

 

;----------------显示食物子程序------------------------

;子程序的功能:

;      在屏幕上随机显示一个食物(不能和蛇身重合)

;参数:无

;算法:

;      利用40端口产生随机数,并把这个坐标记录放在food的结构体中

;-----------------------------------------------------   

  show_food:

              push      ax

              push      bx          ;保护现场

              push      ds

              push      cx

              

              in        ax,40h

              mov       bl,190      ;再大就要超出屏幕了

              div       bl          ;得到不大于190的余数

              mov    al,ah

              mov    ah,0

              mov    bl,5

              div      bl;用得到的不大于5的余数除以5在乘以5(我同学想到的)              

              mov    al,ah

              mov    ah,0

              mul     bl;乘以5                            

              mov       bh,al;保存得到的横坐标

              in        ax,40h

              mov       bl,250      ;再大就要超出边界了

              div       bl ;用得到的不大于5的余数除以5在乘以5(我同学想到的)              

              mov       al,ah

              mov    ah,0

              mov    bl,5

              div      bl

              mov    al,ah

              mov    ah,0

              mul     bl;得到纵坐标在al中             

              mov       ah,bh      ;行列坐标放在bx中,接下来是查找是否是蛇身的某个节点

              mov       bx,data

              mov       ds,bx

              mov       bx,food

              mov       [bx],ax     ;保存食物的位置,也可以不保存

              mov       ch,3

              call      rec

              

              pop       cx

              pop       ds

              pop       bx

              pop       ax          ;恢复现场

              ret

              

;----------------画矩形子程序------------------------

;子程序的功能:

;      在指定左上角和右下角坐标画矩形

;参数:

;      左上角坐标(ah,al)列行 < 右下角坐标(bl,bh)颜色值ch

;      (bx,ax)列行 (si,di)

;--------------------------------------------------

        rec:

              push      ax

              push      bx

              push      cx          ;保护现场

              push      dx

              push      si

              push      di

              mov       bl,ah

              mov       bh,al

              add       bx,0505h    ;算出右下角的坐标

       ;以左上角为主

              push      ax

              push      bx

              ;左线(ah,al) (ah,bh)       

              mov       dx,bx       ;保存bx

              mov       bx,0

              mov       bl,ah

              mov       ah,0        ;开始坐标准备完毕

              

              mov       si,bx

              push      dx

              mov       cl,dh

              mov       dx,0

              mov       dl,cl

              mov       di,dx

              pop       dx          ;结束坐标准备完毕

              push      ax

              push      bx          ;下面要用ax,bx

              call      line

              pop       bx

              pop       ax

              ;上线

              mov       di,ax

              mov       dh,0

              mov       si,dx

              call      line

              pop       bx

              pop       ax          ;ax,bx恢复为刚进入子程序时的状态

          ;以右下角为主     

              ;右线

              mov       dx,ax

              mov       ax,0

              mov       al,bh

              mov       bh,0

              mov       si,bx

              mov       di,ax       ;结束坐标完毕

              mov       bx,si

              mov       al,dl

              push      di

              push      si

              call      line

              pop       si

              pop       di

             ;下线  

              mov       bl,dh

              mov       ax,di

              call      line

          

              pop       di          ;恢复现场

              pop       si

              pop       dx

              pop       cx

              pop       bx

              pop       ax

              ret

;----------------画线子程序------------------------

;子程序的功能:

;      在指定坐标画线,只能画水平和垂直的线

;参数:

;      起点坐标(bx,ax)列行,终点坐标(si,di)颜色值ch

;--------------------------------------------------

       line:

              push      ax

              push      bx

              push      si

              push      di

              push      cx          ;保护现场

              push      dx

              

              cmp       di,ax

              je        level       ;水平画线

              cmp       si,bx       ;求差

              je        perp        ;垂直画线

      level:                        ;水平线

              sub       si,bx       ;si代表要画的的总列数,bx代表但前要画的列(行在ax中),颜色在ch中

              inc       si          ;相减会少一

       putl:

              call      pixel       ;调用子程序,显示一个点

              inc       bx          ;指向下一列

              dec       si          ;列数减一

              jnz       putl        ;重复调用画点,连成线

       back:

              pop       dx          ;恢复现场

              pop       cx

              pop       di

              pop       si

              pop       bx

              pop       ax

              ret

             

       perp:                        ;垂直线

              sub       di,ax       ;di代表要画的的总列数,bx代表但前要画的列(行在ax中),颜色在ch中

              inc       di          ;相减会少一

       putp:

              call      pixel       ;调用子程序,显示一个点

              inc       ax          ;指向下一列

              dec       di          ;列数减一

              jnz       putp        ;重复调用画点,连成线

              jmp       short back

              

;----------------画点子程序------------------------

;画点子程序的功能:

;      在指定坐标画点

;参数:

;      坐标(bx,ax)列行,颜色值ch

;--------------------------------------------------

      pixel:

              push      ax

              push      bx

              push      dx

              push      di

              push      es          ;保护现场,ax,bx本来不用的,为了方便以后的调用就保护了

              

              mov       dx,0a000h   ;图形的开始内存,相当于文本模式的0b800h

              mov       es,dx

              mov       dx,320

              mul       dx          ;算出第n行的内存地址dx:ax

              add       ax,bx       ;算出n行中的m列

              mov       di,ax

              mov       es:[di],ch  ;给这个单元赋颜色值

              

              pop       es          ;恢复现场

              pop       di

              pop       dx

              pop       bx

              pop       ax

              ret

              end       start

 

The next 干掉食物!
       未完待续……
评论次数(0)  |  浏览次数(324)  |  类型(冥思苦想) |  收藏此文  | 
 
 请输入验证码  (提示:点击验证码输入框,以获取验证码