汇编语言贪吃蛇、俄罗斯方块双任务设计实现详解(四)——双任务调度与键盘中断(完结)

详细设计:

4.双任务设计和键盘中断:

更改键盘中断的中断向量表的cs、ip的值,使其指向自定义的int9程序,在int9程序中完成模拟中断时要做的准备,包括pushf,将可屏蔽中断标志位置零,push cs,push ip,然后调用dos系统的键盘中断程序,来处理中断底层操作。然后编写针对不同的扫描码,做出不同的响应。比如按下ESC键,返回到dos命令行,按下Tab键切换运行程序……

如下图所示,左上侧为将原有init9h键盘中断例程的cs:ip存储在data段的前两个单元中,左下侧为整个程序退出时恢复原有9号键盘中断例程的cs:ip,右侧为自定义键盘中断程序。

 程序一开始运行贪吃蛇,闪烁光标在左边窗口右下角:

按下Tab键后去运行俄罗斯方块,贪吃蛇暂停,闪烁光标在右边窗口右下角:

再按下Tab键后,保存俄罗斯方块的相关数据,恢复贪吃蛇的数据,从中断前的位置继续移动:

此时按下ESC键后,退出程序,返回到dos命令行:

 5.双任务调度:

在按下Tab键后,响应左右两个程序之间相互切换。程序一开始运行左侧的贪吃蛇游戏,左侧窗口右下角边框为闪烁光标,按下Tab键后,贪吃蛇暂停移动。左侧窗口右下角边框变回“*”,使右侧窗口右下角边框变为闪烁光标,示意要运行右侧程序。然后保存按下Tab键之前运行贪吃蛇时的所有寄存器的值和堆栈中的值,去执行右侧俄罗斯方块程序。

在俄罗斯方块下落过程中,此时按下Tab键,俄罗斯方块停止下落,右侧窗口右下角边框变回“*”, 左侧窗口右下角边框变为闪烁光标示意要运行左侧程序。然后保存按下Tab键之前运行俄罗斯方块时的所有寄存器的值和堆栈中的值,恢复之前保存的贪吃蛇的所有寄存器的值和堆栈中的值,去接着中断之前的地方执行,显示为贪吃蛇从暂停的地方继续前进。

此后按下Tab键后,要做的事均为切换右下角光标,保存一个程序数据,然后恢复另一个程序数据,接着执行被恢复的程序。此中重点在于数据的存储与恢复,存储时,先利用堆栈SP、BP存储中断前的CS、IP,因为此后执行指令会更改他们,然后存储ax、ds,因为在存储其他寄存器时,需要这两个寄存器来辅助,然后将剩余所有寄存器的值存储,然后根据SP的值,将堆栈中的值一一弹出并存储。此时中断前的所有寄存器的值已被存储,堆栈已被清空,其中的值已被存储。

恢复动作时,与存储动作相反,先恢复堆栈中的值,然后将CS、IP的值存储在栈顶,然后去借用ax、ds恢复其他寄存器,恢复完毕后,执行retf去弹出CS、IP,去执行CS、IP所指向位置的指令。

原理图如下,首次table启动teris的初始化,后续按下table键键入右侧的循环过程。

 存储动作如下图所示:首先将存储在堆栈中的寄存其值弹出保存,然后为如左下角堆栈区,剩余堆栈中的其他数据,将这些值也弹出并保存后,堆栈变为空堆栈,供另一个游戏使用。右侧为存储部分代码。

 恢复动作如下图所示:恢复一开始堆栈为空,然后将原有堆栈数据恢复,将数据段中存储的寄存赋值给相应寄存器,把将要执行的程序的当前位置的cs:ip放在栈顶,最后通过retf跳转指令完成将栈顶的cs:ip值赋值给cs、ip,完成恢复。

 键盘中断和任务调度部分代码如下:

	;新的int 9中断服务程序
	int9:
	push ax
	push bx
	push ds
	push dx
	push bp

	mov ax,data
	mov ds,ax

	;接收键盘中断所得到的扫描码
	in al,60h

	;模拟中断程序的调用准备工作
	
	;模拟标志寄存器入栈
	pushf

	;
	pushf
	pop bx
	and bh,11111100b
	push bx
	popf

	;模拟push cs、push ip
	call dword ptr ds:[0]
	
	;ESC按键检测
	ESC_key_check:
	cmp al,1
	jne Tab_key_check
	jmp near ptr program_end

	;Tab按键检测
	Tab_key_check:
	cmp al,0fh
	;若不是table按键则去判断是否是其他按键
	jne to_select_response_keys
	jmp tab_key_response
	to_select_response_keys:
	jmp near ptr select_response_keys
	
	tab_key_response:
	;置光标,并修改程序调度状态码
	call sclbsc
	;判断是否是第一次按table键
	mov ax,data
	mov ds,ax
	inc word ptr ds:[6]
	cmp word ptr ds:[6],1
	;若不是第一次按table键,则去存储当前程序数据,然后恢复被中断之前程序数据
	jne program_dispatch
	
	;若是第一次按table键
	mov ax,snake_tempporary_storage
	mov ds,ax
	mov bp,sp
	;存储中断前ip
	mov ax,[bp+10]
	mov ds:[0],ax
	;存储中断前cs
	mov ax,[bp+12]
	mov ds:[2],ax
	;存储中断前ax
	mov ax,[bp+8]
	mov ds:[4],ax
	;存储中断前ds
	mov ax,[bp+4]
	mov ds:[6],ax
	
	;堆栈恢复到调用键盘中断程序int 9之前
	pop bp
	pop dx
	;弹出ds
	popf
	pop bx
	pop ax
	;弹出ip
	popf
	;弹出cs
	popf
	;弹出标志寄存器
	popf
	
	;存储中断前通用寄存器
	mov ds:[8],bx
	mov ds:[10],cx
	mov ds:[12],dx
	;存储中断前段寄存器
	mov ds:[14],es
	mov ds:[16],ss
	
	;存储中断前其他寄存器
	mov ds:[18],si
	mov ds:[20],di
	mov ds:[22],sp
	mov ds:[24],bp
	
	;存储中断前标志寄存器
	pushf
	pop ax
	mov ds:[26],ax
	
	;准备存储中断前栈中数据
	mov ax,128
	sub ax,sp
	mov bl,2
	div bl
	mov ah,0
	
	;(cx)=中断前堆栈中数据个数
	mov cx,ax
	cmp cx,0
	;(cx) <= 0则跳过存储堆栈数据动作
	jna skip_storage_data_in_stack_0
	
	;前28字节存储14个寄存器,往后存储堆栈中数据
	mov bx,28
	storage_data_in_stack_0:
	pop ds:[bx]
	add bx,2
	loop storage_data_in_stack_0
	
	skip_storage_data_in_stack_0:
	
	;第一次Tab到右边窗口时,先去启动俄罗斯方块
	jmp near ptr play_teris
	
	program_dispatch:
	;判断按table键前正在运行的是哪个游戏
	mov ax,data
	mov ds,ax
	cmp word ptr ds:[4],1
	jne is_right
	;按table键之前运行的是贪吃蛇
	mov ax,snake_tempporary_storage
	jmp storage_this_grogram
	
	;按table键之前运行的是俄罗斯方块
	is_right:
	mov ax,teris_tempporary_storage
	jmp storage_this_grogram
	
	storage_this_grogram:
	
	;利用ds、ax、sp先存储ip、cs,因为存储其他寄存器和数据时,选用ds、ax来作为辅助,故ds、ax先存储
	mov ds,ax
	mov bp,sp
	;存储中断前ip
	mov ax,[bp+10]
	mov ds:[0],ax
	;存储中断前cs
	mov ax,[bp+12]
	mov ds:[2],ax
	;存储中断前ax
	mov ax,[bp+8]
	mov ds:[4],ax
	;存储中断前ds
	mov ax,[bp+4]
	mov ds:[6],ax
	
	;堆栈恢复到调用键盘中断程序int 9之前
	pop bp
	pop dx
	;弹出ds
	popf
	pop bx
	pop ax
	;弹出ip
	popf
	;弹出cs
	popf
	;弹出标志寄存器
	popf
	
	;存储中断前通用寄存器
	mov ds:[8],bx
	mov ds:[10],cx
	mov ds:[12],dx
	;存储中断前段寄存器
	mov ds:[14],es
	mov ds:[16],ss
	
	mov ds:[18],si
	mov ds:[20],di
	mov ds:[22],sp
	mov ds:[24],bp
	
	;存储中断前标志寄存器
	pushf
	pop ax
	mov ds:[26],ax
	
	;存储中断前栈中数据
	mov ax,128
	sub ax,sp
	mov bl,2
	div bl
	mov ah,0
	
	;(cx)=中断前堆栈中数据个数
	mov cx,ax
	cmp cx,0
	;(cx) <= 0则跳过存储堆栈数据动作
	jna skip_storage_data_in_stack
	mov bx,28
	storage_data_in_stack:
	pop ds:[bx]
	add bx,2
	loop storage_data_in_stack
	
	skip_storage_data_in_stack:
	
	;判断按table键前正在运行的是哪个程序,去恢复另一个程序
	mov ax,data
	mov ds,ax
	
	;查看Tab后要运行哪个游戏
	cmp word ptr ds:[4],1
	jne to_recover_snake
	
	;等于一时表示要运行俄罗斯方块
	mov ax,teris_tempporary_storage
	jmp to_recover_program_data

	;不等于1时表示要运行贪吃蛇
	to_recover_snake:
	mov ax,snake_tempporary_storage
	jmp to_recover_program_data
	
	to_recover_program_data:
	mov ds,ax
	;恢复中断前ss
	mov ax,ds:[16]
	mov ss,ax
	mov sp,128
	;读出中断前sp
	mov bx,ds:[22]
	mov ax,128
	;算出中断前堆栈所占字节数
	sub ax,bx
	mov bx,ax
	add bx,26
	;恢复中断前堆栈数据,顺便恢复了sp
	recover_stack:
	cmp bx,26
	je recover_stack_end
	push ds:[bx]
	sub bx,2
	jmp recover_stack
	
	recover_stack_end:
	
	;将ip、cs放在栈顶
	push ds:[2]
	push ds:[0]
	
	;恢复中断前标志寄存器
	mov ax,ds:[26]
	push ax
	popf
	
	mov bp,ds:[24]
	mov di,ds:[20]
	mov si,ds:[18]
	
	;恢复中断前es
	mov ax,ds:[14]
	
	;恢复通用寄存器
	mov dx,ds:[12]
	mov cx,ds:[10]
	mov bx,ds:[8]
	
	;读出ax
	mov ax,ds:[4]
	push ax
	;恢复ds
	mov ax,ds:[6]
	mov ds,ax
	;恢复ax
	pop ax
	
	retf

	;根据程序调度码选择性响应键盘中断
	select_response_keys:
	
	;ds指向data段
	push ax
	mov ax,data
	mov ds,ax
	pop ax
	
	;查看要运行哪个游戏,去恢复那个程序数据
	mov dx,ds:[4]
	cmp dx,0
	jne to_teris_response_keys
	
	;等于表示要运行贪吃蛇,去恢复贪吃蛇数据
	jmp near ptr snake_response_keys

	;不等于0则表示要运行俄罗斯方块,去恢复俄罗斯方块
	to_teris_response_keys:
	jmp near ptr teris_response_keys
	
	;贪吃蛇响应按键
	snake_response_keys:

	;贪吃蛇游戏状态:(游戏中\已结束)
	mov dx,snake
	mov ds,dx
	mov dx,ds:[10]
	cmp dx,0
	jne R_key_check

	;得到蛇头的方向存到dx(snake:4和snake:6存储了蛇头的行、列所对应的段地址和偏移地址
	;,根据此段地址和偏移地址得到蛇头的显示字符和显示属性,属性又代表了方向)
	mov dx,snake
	mov ds,dx
	mov dx,ds:[4]
	mov bx,ds:[6]
	mov ds,dx
	mov dx,ds:[bx]

	;查看扫描码是不是W按键的扫描码
	W_key_check:
	cmp al,11h
	;若不是W键则跳转去检测是不是A键
	jne A_key_check
	;蛇头朝下时,按W键无效
	cmp dh,2
	je to_int9ret
	;若不是朝下,则更改蛇头方向为上(01h表示朝上、02h表示朝下、03h表示朝左、04h表示朝右)
	mov word ptr ds:[bx],012ah
	jmp near ptr int9ret

	;查看扫描码是不是A按键的扫描码
	A_key_check:
	cmp al,1eh
	;若不是A键则跳转去检测是不是S键
	jne S_key_check
	;蛇头朝右时,按A键无效
	cmp dh,4
	je to_int9ret
	;若不是朝下,则更改蛇头方向为左
	mov word ptr ds:[bx],032ah
	jmp near ptr int9ret

	;查看扫描码是不是S按键的扫描码
	S_key_check:
	cmp al,1fh
	jne D_key_check
	cmp dh,1
	je to_int9ret
	mov word ptr ds:[bx],022ah
	jmp near ptr int9ret

	;查看扫描码是不是D按键的扫描码
	D_key_check:
	cmp al,20h
	jne R_key_check
	cmp dh,3
	je to_int9ret
	mov word ptr ds:[bx],042ah
	jmp near ptr int9ret

	;R按键检测
	R_key_check:
	cmp al,13h
	jne to_int9ret
	
	;贪吃蛇游戏状态置0
	mov ax,snake
	mov ds,ax
	mov word ptr ds:[10],0
	
	;准备退出本子程序
	pop bp
	pop dx
	pop ds
	pop bx
	pop ax
	popf
	popf
	popf
	;跳转去重新运行贪吃蛇
	jmp near ptr play_snake
	
	to_int9ret:
	jmp near ptr int9ret

	;俄罗斯方块响应按键
	teris_response_keys:
	
	;俄罗斯方块游戏状态:(游戏中\已结束)
	mov dx,teris
	mov ds,dx
	mov dx,ds:[2]
	cmp dx,0
	jne Enter_key_check
	
	;上键按键检测
	up_key_check:
	cmp al,48h
	;若不是上键,则检测是不是下键扫描码
	jne down_key_check
	;清除屏幕上当前俄罗斯方块
	call clean_this_teris
	;旋转此俄罗斯方块坐标
	call revolve_teris
	;显示旋转后的俄罗斯方块
	call show_this_teris
	jmp int9ret
	
	;下键按键检测
	down_key_check:
	cmp al,50h
	jne left_key_check
	
	;检测是否已到底部,若到达底部,则按下键无作为
	mov ax,0100h
	call try_to_move
	cmp al,2ah
	je int9ret
	
	call clean_this_teris
	;向下移动一行
	mov ax,0100h
	call move_teris
	call show_this_teris
	jmp int9ret
	
	;左键按键检测
	left_key_check:
	cmp al,4bh
	jne right_key_check
	
	;检测是否还能左移,若不能,则按左键无作为
	mov ax,00ffh
	call try_to_move
	cmp al,2ah
	je int9ret
	
	call clean_this_teris
	;向左移动一列
	mov ax,0ffh
	call move_teris
	call show_this_teris
	jmp int9ret
	
	;右键按键检测
	right_key_check:
	cmp al,4dh
	jne Enter_key_check
		
	;检测是否还能右移,若不能,则按右键无作为
	mov ax,0001h
	call try_to_move
	cmp al,2ah
	je int9ret
	
	call clean_this_teris
	;向右移动一列
	mov ax,1
	call move_teris
	call show_this_teris
	jmp int9ret

	;Enter按键检测
	Enter_key_check:
	cmp al,1ch
	jne int9ret
	
	;俄罗斯方块游戏状态置0
	mov ax,teris
	mov ds,ax
	mov word ptr ds:[2],0
	;俄罗斯方块游戏得分归零
	mov word ptr ds:[14],0
	
	;准备退出本子程序
	pop bp
	pop dx
	pop ds
	pop bx
	pop ax
	popf
	popf
	popf
	;跳转到重新启动俄罗斯方块
	jmp near ptr play_teris
	
	int9ret:
	pop bp
	pop dx
	pop ds
	pop bx
	pop ax
	iret

上一篇:汇编语言贪吃蛇、俄罗斯方块双任务设计实现详解(三)——俄罗斯方块详细设计

完整代码:https://download.csdn.net/download/gduyt_gduyt/10924302

猜你喜欢

转载自blog.csdn.net/GDUYT_gduyt/article/details/86537837
今日推荐