时隔多日,迟迟未更新。因为我遇到了难题,控制这一块不太好写(毕竟是第一次用汇编写这么大的程序)。我所谓的不好写不是我的汇编基础知识不够,而是用汇编描述算法的能力或者经验还有所欠缺。相信各位有时候也会遇到类似的情况。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 干掉食物!
未完待续……
- [cutebe] 相当牛,这个苦思冥想也值了。^_^ 11/30 00:00
- [parse] 如果忽略消息循环,那么操作系统加载的程序很快就执行完了,就像DOS程序一闪而过,所以CPU会空闲下来 06/30 09:04
- [游客] 楼主好厉害,挺一下! 01/19 08:43
- [游客] 很不错。 01/04 18:36
- [chinatree] 潜力贴留名,沙发。 11/08 12:58
- [youthangel] 恩,这次对了 10/30 18:56
- [fpamc] mov bx,18 在这条指令的上边是不是要加一条sub dx,dx? 10/30 10:03
- [fpamc] 对的 10/27 11:19
- [fpamc] 对的 10/27 09:00
- [fpamc] 哦,对不起,看错了。实验13也有一个7ch中断 10/27 08:52
- [游客] 现在急需一个汇编大作业。。。。。。可以么。。。。。如果今天之内看见留言 就加 1765496715 12/28 16:52
- [youthangel] 这算是对我学习的鼓励吗?谢谢!咱们这样交流就可以了 10/11 15:48
- [fpamc] 多日观察,你的学习积极性挺高的。可以来我们群了。群号:75916434 10/11 10:58