[微机原理]推箱子游戏设计

一、游戏功能设计

        推箱子是一款非常经典的游戏,利用汇编语言编写推箱子小程序。在能够提供游戏服务的同时增加更多的交互性以及可拓展性。


二、游戏程序架构

2.1总架构

       为了便于程序的扩展以及衔接,整体程序包含了五大模块。分别是:初始化模块、界面模块、判断模块、声音模块以及执行模块。其中初始化模块:初始化地图、数据、刷新缓存。界面模块:一次有效按键后的界面刷新、目录刷新工作。判断模块:判断有效按键、判断可否移动、判断是否胜利等……声音模块:通过频率表,延时表连续播放音乐。执行模块:执行具体的操作等。

2.2按键判断架构

        按键判断模块作为整个游戏与外界的输入对接程序起到至关重要的作用,其中通过调用21H的中断服务来进行实时的按键检测。随后通过对不同按键的检测来确定是否做出回应,做出什么回应的操作。对于有效按键,程序通过跳转不同的子程序,来实现不同的功能。

 2.3地图刷新框架

       地图刷新框架是实现游戏动态化的灵魂所在,其输入接口为通过键盘有效按键操作后更改的地图,输出为图形化的界面显示。       根据不同的物体显示不同,程序定义了路、墙、角色、箱子、目标箱、目标人这几种状态的显示。整个地图采用8*8的方块矩阵组成,而每一块的显示属性则由8*8的数字矩阵组成。通过循环的扫描不同块的状态进而实现动态的刷新地图操作。

2.4 子程序嵌套状态


三、游戏核心

3.1图形的建立

       在上千行的程序代码中,图形处理代码占了将近一半的比例。虽然行出很多,但其实是大量的“套娃”过程,即在如画点、由点画线,再到由线画实心矩形的方式,由多种形状堆砌而成的过程。

       在图形建立的过程中,也凸显了BIOS对于图形显示丰富的功能。可以由BIOS定义显示分辨率、色彩数等等设置。

       拿绘制地图中“墙”过程来做举例。乍一看非常复杂,但实际上它是由五块叠加而成的。在学习具体中断使用方法后,依次叠加图形,就可以形成相对逼真的图像了。此时的子过程便可以通过调用来在制定区域显示我们想要的图像了。墙是一个较为典型的图像合成例子,相比之下人物的绘制更为复杂,但本质上大同小异。只不过在形式上描绘更为细节,调用的子过程更多而已。

       写好图像形成的子过程,地图显示的“砖”就搭好了,下面还需坐标系的统一来为地图搭好“铁架”。

3.2坐标系建立与地图显示

        如果一个个图像显示是一块块“砖”,那么整个地图的坐标就更像大楼的“铁架”,通过一个8*8的坐标矩阵,可以让程序显示时知道,在哪里该显示“墙”,哪个地方又该显示“人物”。

       首先,对于BIOS的显示,要首先了解它的正方向。如右图所示,它的x轴以向右为正,而y轴则是以上边为0,向下延伸。因此在建图时,需要从上往下刷新。

       在了解了坐标系后,如何设置合适的坐标,让地图能够正常的显示呢?其实,对于机器并不存在矩阵这样的概念,它所做的只是在制定的开始位置显示我们之前写好的显示函数,因此所谓的坐标转换,其实就是编写一个转换器,把数据串变成一个个确切的坐标。

       最后,当有了确切坐标后,在转换为实际坐标前还有一步,就是对坐标进行线性的放大。由于一个方块的尺寸是30*30像素。因此,在依次输出地图时,还需要对坐标乘以30,才能找到对应的其实像素点。

       再有了这些基础后,程序便可以根据我们所保存的8*8数据来输出地图的样子。接下来要做的就是通过按键以及相应判断来改变地图的值了。

3.3动态的实现

        首先,要做到游戏的交互性就需要实现游戏界面的“动态”效果。其实这条核心是以核心2为基础实现的。但我认为这是一个对于游戏连续性的底层认知问题,因此还是有必要去单列一点来提出。

       对于动态的概念,实际上是建立在快速显示静态的基础上的。在游戏中最主要的逻辑就是:

       等待按键——执行按键——刷新地图——判断胜利——等待按键

        这样的循环,我们之所以看到游戏是动态的是由一个个静态画面不断刷新组成的。因此CPU的响应速度对游戏流畅性有很大的影响,此次游戏对于一次的按键仅仅执行了一次画面刷新,这种工作量无疑是极小的。对于如今的大型游戏,之所以对显卡的要求非常高其中就是由于它一次执行的像素点多、帧数多。帧数越多,则玩家玩起来感觉越流畅,游戏体验越好。

3.4整体游戏的嵌套

       一条上千行的小游戏对于初学者无疑是迈入了一个新的阶段。由于子过程变得很多、接口和回调也会变得复杂,且最重要的——JMP等转跳范围的有限性,都让我们很难把游戏主体设计在游戏的主循环中。这样做既不现实,也不便于调试、修改。

       如右图所示,可以看到整个游戏的主循环非常的简单,只是调用了初始化子过程以及开始游戏的子过程。这样做的好处是便于我们在编写时理清脉络,这样编写的程序总的会呈现一个树形的结构。非常便于在接口调试时进行单部分的修改以及新函数的拼接。

       随后,在进入开始游戏子过程后,继续调用扫描按键、播放音乐子过程,在扫描按键子过程中,再调用按键判断以及按键执行子过程……以此类推,逐级向下分叉,但各个子过程内调用的子子过程又不是绝对的毫无联系——对于执行按键后的地图刷新子过程,所调用的均为图形显示的子过程。

       这就是子过程的嵌套,它可以理清整体程序的架构,增加程序的拓展性以及鲁棒性。从而避免出现问题后无从下手的尴尬情况。

3.5按键的逻辑嵌套

        在按键判别上,与上学期单片机课设中的按键判断其实原理一致。右图的按键处理流程图看似复杂,但其实只不过是按键变得更为丰富,判断周期拉的更长罢了。其基本思路还是:在21中断有按键响应时,将输入的按键值与设置的按键值进行比较,判断有无设置按键值与其相吻合。若有,则执行相对应的子过程。若没有,则返回扫描循环,扫描也不执行。

       在这里,对于较多的按键选择,不同的判断结构会对性能产生影响。对于相同数量的按键,若逐个判断前将其分为两半,则平均下来每次判断的约为所有逐个判断次数的一半。当然,也可以把判断分为四份、八份,这取决于程序按键的多少以及按键直接嵌套的复杂程度。

       总之,采用类似于并行的按键判断结构可以很大程度的减少浪费的判断次数。


四、程序原码

        首先是地图文件,下图为地图的具体说明。使用时请将map.txt文件放置在主程序同目录下。

4440011100000131000001011111112023113024111111121000001310000011100322011110000100110001430100013321100102201001000010010000100111111046300111100001331000110311001002310110200111001220110040001111111115310011111100100001111222011402330110233311111100100001111000000000864111111101332331013313310102220101002001010222010100140101111111035600000000001111001113010010300111100022011103024101111111000000004130111110001040111110120011053030110022011111013100010001000111110

        下面是游戏主程序,有详细的注释。

;推箱子详细注释版
;zhangheyao,2021.11.14
;========================================================================
;                               数据定义                                 ;
;========================================================================
;移动键定义
ROAD		EQU	48	
WALL		EQU	49
BOX	    	EQU	50
AIM	    	EQU	51
ROLE		EQU	52
BoxInAim	EQU	53
RoleInAim	EQU	54
WALL_LENGTH	EQU	30
WALL_DEPTH	EQU	8

;功能键定义
ESCAPE		EQU	27
UP			EQU	72
DOWN		EQU	80
LEFT		EQU	75
RIGHT		EQU	77
ReGame		EQU	48
LevelUp		EQU	49
LevelDown	EQU	50
MusicOn		EQU	51

DATA	SEGMENT
	MUS_FREG 	DW 330,294,262,294,3 dup (330)     ;频率表
            	DW 3 dup (294),330,392,392
               	DW 330,294,262,294,4 dup (330)
               	DW 294,294,330,294,262,-1
    MUS_TIME	DW 6 dup (25),50                   ;节拍表
               	DW 2 dup (25,25,50)
              	DW 12 dup (25),100
	MUS_FREG1	DW 384,426,384,426,384,-1
	MUS_TIME1	DW 4 DUP(20),60
	MusicFlag	DB	1
	FILE_NAME	DB	'map.txt' , 0
	HANDLE		DW	?	;保存文件号
	TEMP_DB		DB	?	;用来临时存放不需要读的字节
	LevelFlag	DB	10 dup(0)	
	LEVEL_MAX	DB	7
	LEVEL		DB	1
	BOX_MAX	 	DB	4
	ROLE_POS	DB	4,4	
	DATA_ARRAY 	DB      48,48,49,49,49,48,48,48
				DB      48,48,49,51,49,48,48,48
				DB      48,48,49,48,49,49,49,49
				DB      49,49,49,50,48,50,51,49
				DB      49,51,48,50,52,49,49,49
				DB      49,49,49,49,50,49,48,48
				DB      48,48,48,49,51,49,48,48
				DB      48,48,48,49,49,49,48,48
DATA	ENDS

EXTRA1	SEGMENT
	VICTORY		DB	"Good job! Press Enter to next level!"	
	SCORE		DB	"Score:"
	SCORE_NUM	DB	"00","10","20","30","40","50","60","70","80","90"
	OBSCATLE	DB	"Come on bro! I hit the wall!"
	MENU_EXIT	DB	"Exit:Esc",7 dup(' ')			;后面print子函数cx统一都是15,凑空格保证15
	MENU_UP		DB	"Up:",24,11 dup(' ')
	MENU_DOWN	DB	"Down:",25,9 dup(' ')
	MENU_LEFT	DB	"Left:",26,9 dup(' ')
	MENU_RIGHT	DB	"Right:",27,8 dup(' ')
	MENU_REGAME	DB	"Regame:0",7 dup(' ')
	MENU_LEVEL_UP	DB	"Level up:1",5 dup(' ')
	MENU_LEVEL_DOWN	DB	"Level down:2",3 dup(' ') 
	MENU_MUSIC		DB	"Music(on/off):3"
	MENU_LEVEL		DB	"Level:",' ','/',7 dup(' ')
	MENU_WARNING1	DB	"In order to ensure a better game experience"
	MENU_WARNING2	DB	"keep press Ctrl+F12 to ensure the speed of the CPU is at least 100000 cycles!"
;========================================================================
;                                                                       ;
;========================================================================
EXTRA1	ENDS



CODE	SEGMENT
;========================================================================
;                               主体循环                                 ;
;========================================================================
	ASSUME CS:CODE,DS:DATA,ES:EXTRA1
START:
	MOV	AX,	DATA		;分配寄存器
	MOV	DS,	AX
	MOV	AX,	EXTRA1
	MOV	ES,	AX

	CALL	GAME_INIT	;游戏初始化,显示地图、目录,进入按键扫描循环	
	CALL	GAME_START	
EXIT:					;主动退出循环
	MOV	AH,	4CH
	INT	21H
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               本地读取                                 ;
;========================================================================
;对本地文件进行读操作
;入口参数:DI:文件绝对路径的偏移量,SI:需读取数据的地址偏移量,
;AX:从第AX个字节开始读,CX:传送字节数
READ_FILE	PROC
	PUSH	AX			;保护现场
	PUSH	BX
	PUSH	CX
	PUSH	DX

	PUSH	CX			;保存读的字节数
	MOV	CX,	AX			;将不需读的字节个数送至CX
	;打开文件
	MOV	DX,	DI			;文件绝对路径的偏移量送往DX
	MOV	AH,	3DH			;3DH功能调用,AX=文件号
	MOV	AL,	2			;以读/写方式打开
	INT	21H

	MOV	HANDLE,	AX		;将文件号保存至HANDLE
	;写文件
	MOV	BX,	AX			;将文件号送至BX
	CMP	CX,	0
	JE	NO_TEMP
READ_TEMP_DB:			;每次读取1个字节,读取跳过字节的次数
	PUSH	CX
	MOV	CX,	1
	LEA	DX,	TEMP_DB		;将不需读取的字节读至TEMP_DB
	MOV	AH,	3FH			;3FH功能调用
	INT	21H
	POP	CX
	LOOP	READ_TEMP_DB
	

NO_TEMP:
	POP	CX				;取出需要读取的字节数
	MOV	DX,	SI			;需读取数据的地址偏移量送往DX
	MOV	AH,	3FH			;3FH功能调用
	INT	21H
	
	;关闭文件
	MOV	BX,	HANDLE		;取出句柄
	MOV	AH,	3EH			;3EH功能调用
	INT	21H
	POP	DX				;恢复现场
	POP	CX		
	POP	BX
	POP	AX
	RET
READ_FILE	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               声音模块                                 ;
;========================================================================
;功能:计数器产生方波送至扬声器发声
;入口参数:DI:频率表首地址,BX:节拍表首地址
BEEP	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	DI
	MOV	AL,	10110110B
	OUT	43H,	AL	;计数器2用于产生方波送至扬声器发声
	MOV	DX,	12H
	MOV	AX,	348CH
	DIV	DI			;计数初值为12348C/频率
	OUT	42H,AL		;送低8位
	MOV	AL,AH
	OUT	42H,AL		;送高8位

	IN	AL,61H		;读取8255B端口
	MOV	AH,AL		;存在AH
	OR	AL,03H
	OUT	61H,AL		;输出至8255的B端口,使扬声器发声
;========================================================================
G7:
	MOV	CX,	1FFFH	;3314	
	CALL	DELAY	
	DEC BX			;BL的值为控制长短声,BL=6(长),BL=1(短)
	JNZ	G7
;========================================================================
	MOV	AL,AH		;恢复8255B端口值,停止发声
	OUT	61H,AL
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET	
BEEP	ENDP
;========================================================================
;========================================================================
;功能:胜利提示音
MUS_VIC	PROC
	PUSH	AX
	PUSH	BX
	PUSH	DX
	PUSH	SI
	PUSH	DI
	PUSH	BP
	
MUS_INIT1:			;音乐频率表、节拍表首地址初始化
	XOR	AX,	AX
	XOR	SI,	SI
	XOR	BP,	BP
MUSIC2:
	MOV	DL,	BYTE PTR MusicFlag[0]	;判断音乐播放标志是否为0
	CMP	DL,	0
	JE	QUITT			;是则跳转音乐初始化
	MOV DI, mus_freg1[SI]			;否则获取频率和节拍信息
	CMP DI, 0FFFFH				;判断是否播放到音乐末尾
	JE	QUITT			;是则跳转音乐初始化
	MOV BX, mus_time1[BP]
	CALL BEEP				;调用蜂鸣器子程序播放音乐
	ADD	SI,	2			;指针移向下一个字
	ADD	BP,	2			;指针移向下一个字
	JMP	MUSIC2
QUITT:	
	POP	BP
	POP	DI
	POP	SI
	POP	DX
	POP	BX
	POP	AX	
	RET
MUS_VIC	ENDP
;========================================================================
;========================================================================
;功能:延时函数
;入口参数:CX
DELAY PROC
	PUSH	AX
	PUSH	CX
DELAY1:
	IN	AL,	61H
	AND	AL,	10H
	CMP	AL,	AH
	JE	DELAY1
	MOV	AH,	AL
	LOOP DELAY1
	POP	CX
	POP	AX
	RET
DELAY endp
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                            游戏界面初始化                              ;
;========================================================================
GAME_INIT	PROC
	CALL	DATA_INIT
	CALL	GRAPH_INIT
	CALL	DRAW_MENU
	RET
GAME_INIT	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                           字符串输出模块                                ;
;========================================================================
;功能:打印字符串子函数
;入口参数:AX:字符串地址,CX:显示字符串长度,(DH、DL)=坐标(行、列)
PRINT_STR	PROC
	PUSH	AX		;保护现场
	PUSH	BX
	PUSH	DX
	PUSH	BP
	MOV	BP,	AX		;需显示的字符串首地址
	MOV	AX,	1301H	;BIOS的AH=13功能,AL:显示方式,光标跟随移动
	MOV	BH,	00H		;显示的页面
	MOV	BL,	0CH		;显示的前景色及背景色,0:black,C:light red
	INT 10H
	POP	BP
	POP	DX
	POP	BX
	POP	AX	
	RET
PRINT_STR	ENDP
;========================================================================
;========================================================================
;显示无法前进
DRAW_OBSCATLE	PROC
	PUSH	AX
	PUSH	CX
	PUSH	DX
	MOV	CX,	28			;要显示的字符串长度
	
	MOV	DX,	1100H		;(DH、DL)=坐标(行、列)
	LEA	AX,	OBSCATLE	;要显示的字符串首地址
	CALL	PRINT_STR	;调用字符串显示程序
	POP	DX
	POP	CX
	POP	AX
	RET
DRAW_OBSCATLE	ENDP
;========================================================================
;========================================================================	
;显示菜单界面,调用PRINT_STR子函数
DRAW_MENU	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	SI
	MOV	CX,	15						;要显示的字符串长度
	MOV	DX,	022AH					;(DH、DL)=坐标(行、列)
;========================================================================
	LEA	AX,	MENU_EXIT				;显示EXIT	
	CALL 	PRINT_STR				;调用PRINT子过程
	INC	DH							;行地址加1
;========================================================================
	LEA	AX,	MENU_UP					;显示UP	
	CALL	PRINT_STR		
	INC	DH					
;========================================================================
	LEA	AX,	MENU_DOWN 				;显示down	
	CALL	PRINT_STR	
	INC	DH		
;========================================================================
	LEA	AX,	MENU_LEFT 				;显示left
	CALL	PRINT_STR	
	INC	DH		
;========================================================================
	LEA	AX,	MENU_RIGHT 				;显示right	
	CALL	PRINT_STR	
	INC	DH		
;========================================================================
	LEA	AX,	MENU_REGAME 			;显示regame	
	CALL	PRINT_STR	
	INC	DH		
;========================================================================
	LEA	AX,	MENU_LEVEL_UP 			;显示level up
	CALL	PRINT_STR	
	INC	DH		
;========================================================================
	LEA	AX,	MENU_LEVEL_DOWN 		;显示level down	
	CALL	PRINT_STR	
	INC	DH
;========================================================================
	LEA	AX,	MENU_MUSIC 				;显示music on/off	
	CALL	PRINT_STR	
	INC	DH				
;========================================================================
	MOV	AL,	BYTE PTR LEVEL[0]		;当前关卡等级
	OR	AL,	30H						;将其转化成对应的ASCII码
	MOV	BYTE PTR MENU_LEVEL[6],	AL	;存入显示字符串对应位置中	
	MOV	AL,	7						;最大关卡等级为7
	OR	AL,	30H						
	MOV	BYTE PTR MENU_LEVEL[8],	AL	
	LEA	AX,	MENU_LEVEL				;显示level?/?
	CALL	PRINT_STR				
	INC DH
;========================================================================
	MOV	CX,	6						
	LEA	AX,	SCORE					;显示score
	CALL	PRINT_STR			

	MOV	CX,	7						;获取当前关卡标志,为BX加2,BX为分数偏移地址
	MOV	SI,	0
	MOV	BX,	0

Total_Score:
	CMP	LevelFlag[SI],	1			;1代表已经通关,则BX加2,因为存在跳关键,但跳关不加分,所以设置标志位判断是否通关,根据通关数确定得分
	JNE	SKIP
	INC	SI
	ADD	BX,	2
;========================================================================
SKIP:
	LOOP	Total_Score

	MOV	DX,	0C30H			
	MOV	CX,	2			
	LEA	AX,	SCORE_NUM[BX]			;显示当前得分
	CALL	PRINT_STR			
;========================================================================
	MOV	DX,	1500H				
	MOV	CX,	43					
	LEA	AX,	MENU_WARNING1			;温馨提示第一行
	CALL	PRINT_STR			
	INC	DH
	MOV	CX,	77
	LEA	AX,	MENU_WARNING2			;温馨提示第二行
	CALL	PRINT_STR
;========================================================================
	POP	SI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
DRAW_MENU	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               通关处理                                 ;
;========================================================================
;功能:通关后的显示,下一关的判断与初始化
;入口参数:无
DRAW_VICTORY	PROC
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	MOV	CX,	36			
	MOV	DX,	1100H			
	LEA	AX,	VICTORY			
	CALL	PRINT_STR			;显示通关提示
	CALL MUS_VIC
	MOV	AH,08H					;等待按键按下进入下一关
	INT 21H

	MOV	BL,	BYTE PTR LEVEL[0]	;获取当前关卡等级
	DEC	BL
	MOV	BYTE PTR LevelFlag[BX],1;通关标志置为1,flag矩阵从0开始,BL获取后减1
	MOV	BL,	BYTE PTR LEVEL[0]	
	CMP	BL,	7					;与最大关卡等级比较
	JE	RE						;等于则更新为第一关
	INC	BL						;否则关卡等级加1
	MOV	BYTE PTR LEVEL[0],BL	;更新关卡等级信息
	XOR	BX,	BX

	JMP	V_EXIT					
RE:	MOV	BYTE PTR LEVEL[0],1		
V_EXIT:							;更新地图信息与绘制界面
	CALL	DATA_INIT			;更新地图信息
	CALL	GRAPH_INIT			;绘制游戏界面
	MOV	AX,	0C00H				;AH=0CH号功能,清空键盘缓冲区
	INT	21h
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
DRAW_VICTORY	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                            地图数据初始化                              ;
;========================================================================
;功能:通过READ_FILE子过程获取地图信息,获取箱子数、人物坐标位置
;入口参数:DI:文件名偏移地址,SI:参数存储偏移地址
DATA_INIT	PROC
	PUSH	AX
	PUSH	CX
	PUSH	DI
	PUSH	SI
	
	LEA	DI,	FILE_NAME			;获取文件名称
	LEA	SI,	BOX_MAX				;获参数存储地址
	MOV	AL,	67					;获取数据长度
	MOV	AH,	BYTE PTR LEVEL[0]	;获取当前关卡等级
	DEC	AH				
	MUL	AH						;获取的内容地址为(AH-1)*67
	MOV	CX,	67
	CALL	READ_FILE			;将地图信息读入
	LEA	SI,	BOX_MAX				;SI指针指向BOX_MAX,则下一个指向ROLE_pole[1]
	MOV	CX,	3
DATA_INIT1:						;获取存储关卡箱子数量、角色坐标的地址(行、列)
	SUB	BYTE PTR [SI],30H		;将ASCII码转化为数字(-30H)
	INC	SI
	LOOP	DATA_INIT1	
	
	POP	SI
	POP	DI
	POP	CX
	POP	AX
	RET
DATA_INIT	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                              绘图子过程                                ;
;========================================================================
;功能:通过重新设置显示模式来清屏
;入口参数:无
GAME_CLEAR	PROC
	PUSH	AX		
	PUSH	BX
	MOV	AX,	4F02H	;对于超级VGA显示卡,用AX=4F02H和BX来设置其显示模式
	MOV	BX,	100H	;640*400,256色
	INT	10H
	POP	BX		
	POP	AX
	RET
GAME_CLEAR	ENDP
;========================================================================
;========================================================================
;功能:画点
;入口参数:AX:x坐标,BX:y坐标,DL颜色
POINT	PROC
	PUSH	AX		
	PUSH	CX
	PUSH	DX
	MOV	CX,	AX		;X坐标
	MOV	AL,	DL		;颜色
	MOV	DX,	BX		;Y坐标

	MOV	AH,	0CH		;0CH功能调用,写入像素点
	INT	10H		
	
	POP	DX		
	POP	CX
	POP	AX
	RET		
POINT	ENDP
;========================================================================
;========================================================================
;功能:调用画点子过程,画横线
;入口参数:AX:x坐标,BX:y坐标,DL:颜色,DI:长度
HORIZONTAL_LINE	PROC
	PUSH	AX		
	PUSH	CX
	MOV	CX,	DI
HORIZONTAL:	
	CALL POINT		;调用画点子程序
	INC	AX			;x坐标+1
	LOOP HORIZONTAL
			
	POP	CX			
	POP	AX
	RET
HORIZONTAL_LINE	ENDP
;========================================================================
;========================================================================
;功能:调用画点子过程,画竖线
;入口参数:AX:x坐标,BX:y坐标,DL:颜色:,DI:长度
VERTICA_LINE	PROC
	PUSH	BX		
	PUSH	CX
	MOV	CX,	DI
VERTICA:	
	CALL POINT		;调用画点子程序	
	INC	BX			;y坐标+1
	LOOP VERTICA
	POP	CX		
	POP	BX
	RET
VERTICA_LINE	ENDP
;========================================================================
;========================================================================
;功能:调用画点子过程,画正斜线
;入口参数:AX:x坐标,BX:y坐标,DL:颜色,DI:横向长度
OBLIQUE_LINE	PROC
	PUSH	AX		
	PUSH	BX
	PUSH	CX
	MOV	CX,	DI
OBLIQUE:	
	CALL POINT		;调用画点子程序
	INC	AX			;x坐标+1
	INC	BX			;y坐标+1
	LOOP OBLIQUE
	
	POP	CX		
	POP	BX
	POP	AX
	RET
OBLIQUE_LINE	ENDP
;========================================================================
;========================================================================
;功能:画反斜线
;入口参数:AX:x坐标,BX:y坐标,DL:颜色:,DI:横向长度
REV_OBLIQUE_LINE	PROC
	PUSH	AX		
	PUSH	BX
	PUSH	CX
	MOV	CX,	DI
REV_OBLIQUE:	
	CALL POINT		;调用画点子程序
	DEC	AX			;x坐标-1
	INC	BX			;y坐标+1
	LOOP REV_OBLIQUE

	POP	CX		
	POP	BX
	POP	AX
	RET
REV_OBLIQUE_LINE	ENDP
;========================================================================
;========================================================================
;功能:调用竖线子过程,画实心矩形
;入口参数:AX:横坐标,BX:纵坐标,DL:颜色,DI:宽度,SI:高度
SOLID_RECTANGLE	PROC
	PUSH	AX		
	PUSH	CX
	MOV	CX,	SI
RECTANGLE: 
	CALL VERTICA_LINE	;调用画点子程序
	INC	AX				;x坐标+1
	LOOP RECTANGLE

	POP	CX		
	POP	AX
	RET
SOLID_RECTANGLE	ENDP
;========================================================================
;========================================================================
;功能:调用实心矩形子过程,画墙
;入口参数:AX:横坐标,BX:纵坐标
DRAW_WALL	PROC
	PUSH	AX		
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	DI
	PUSH	SI

	;墙的底层颜色				
	MOV	DL,	011110100B	;浅灰色		
	MOV	DI,	WALL_LENGTH
	MOV	SI,	WALL_LENGTH
	CALL	SOLID_RECTANGLE
	
	;画箱子四边,利用色差达到立体效果
	PUSH	AX		;保护箱子坐标
	PUSH	BX
	
	MOV	CX,	WALL_DEPTH
	MOV	DI,	WALL_LENGTH
	MOV	DL,	01011011B	;浅白色

WALL1:	
	CALL HORIZONTAL_LINE
	INC	AX			;从左往右画
	INC	BX
	SUB	DI,	2		;每次竖线的长度减2,拼成一个下边为斜边的直角梯形
	LOOP WALL1
	
	POP	BX			;恢复箱子坐标
	POP	AX	
	
	PUSH	AX		;保护箱子坐标
	PUSH	BX
	
	MOV	CX,	WALL_DEPTH
	MOV	DI,	WALL_LENGTH
	MOV	DL,	01011011B

WALL2:	
	CALL VERTICA_LINE
	INC	AX
	INC	BX
	SUB	DI,	2		
	LOOP WALL2
		
	POP	BX			;恢复箱子坐标
	POP	AX
	
	PUSH	AX		;保护箱子坐标
	PUSH	BX
	
	ADD	AX,	WALL_LENGTH
	MOV	CX,	WALL_DEPTH
	MOV	DI,	WALL_LENGTH
	MOV	DL,	11110111B	;浅灰色	

WALL3:	
	CALL VERTICA_LINE
	DEC	AX			;从右往左画
	INC	BX
	SUB	DI,	2	
	LOOP WALL3
	
	POP	BX			;恢复箱子坐标
	POP	AX
	
	ADD	BX,	WALL_LENGTH
	MOV	CX,	WALL_DEPTH
	MOV	DI,	WALL_LENGTH
	MOV	DL,	11110111B	;浅灰色	
	
WALL4:	
	CALL HORIZONTAL_LINE
	INC	AX
	DEC	BX
	SUB	DI,	2	
	LOOP WALL4
	
	POP	SI		
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
DRAW_WALL	ENDP
;========================================================================
;========================================================================
;功能:画叉号
;入口参数:AX:横坐标,BX:纵坐标,DL:颜色,DI:横向长度
DRAW_CROSS	PROC
	PUSH	AX		;保护现场
	PUSH	BX
	PUSH	CX

	PUSH	AX		;保护坐标
	PUSH	BX
	MOV	CX,	5		;叉号线条的宽度为5
	ADD	AX,	5		
CROSS1: 
	CALL OBLIQUE_LINE
	DEC	AX
	INC	BX	
	LOOP CROSS1
	
	POP	BX		;恢复坐标
	POP	AX
		
	MOV	CX,	5
	ADD	AX,	25
CROSS2: 
	CALL REV_OBLIQUE_LINE
	INC	AX
	INC	BX	
	LOOP CROSS2
	
	POP	CX		;恢复现场
	POP	BX
	POP	AX
	RET
DRAW_CROSS	ENDP
;========================================================================
;========================================================================
;功能:调用画叉号程序,画目标位置
;入口参数:AX:横坐标,BX:纵坐标,DL:颜色,DI:横向长度
DRAW_AIM	PROC
	PUSH	DX		
	PUSH	DI
	
	MOV	DI,	25		;DI表示斜线横向长度,由于宽度为5,则长度为30-5=25
	MOV	DL,	1010B	;选取颜色
	CALL DRAW_CROSS
	
	POP	DI			
	POP	DX
	RET
DRAW_AIM	ENDP
;========================================================================
;========================================================================
;功能:画箱子
;入口参数:AX:横坐标,BX:纵坐标
DRAW_BOX	PROC
	PUSH	DX		;保护现场
	PUSH	DI
	PUSH	SI
	
	MOV	DL,	00000110B	;箱子底色
	MOV	DI,	30
	MOV	SI,	30
	CALL	SOLID_RECTANGLE
	
	MOV	DI,	25
	MOV	DL,	00000000B	;叉子颜色
	CALL	DRAW_CROSS
		
	POP	SI		
	POP	DI
	POP	DX
	RET
DRAW_BOX	ENDP
;========================================================================
;========================================================================
;功能:画角色
;入口参数:AX:横坐标,BX:纵坐标
DRAW_ROLE	PROC
	PUSH	AX		;保护现场
	PUSH	BX
	PUSH	DX
	PUSH	DI
	PUSH	SI

	CALL DRAW_ROAD	;maozi
	MOV DL, 0110B
	ADD	AX,	12
	MOV DI, 1
	MOV SI, 6
	CALL SOLID_RECTANGLE

	MOV DL, 0000B
	ADD BX, 1
	MOV DI, 2
	MOV SI, 6
	CALL SOLID_RECTANGLE

	MOV DL, 0110B
	SUB AX, 3
	ADD BX, 2
	MOV DI, 2
	MOV SI, 12
	CALL SOLID_RECTANGLE

	MOV	DL,	1001B	;face
	MOV	DI,	6
	MOV	SI,	6
	ADD AX, 3
	ADD	BX,	2
	CALL	SOLID_RECTANGLE

	MOV	DL,	0000B	;eye
	MOV DI, 2
	ADD AX, 1
	ADD BX, 1
	CALL 	VERTICA_LINE
	MOV DI, 2
	ADD AX, 2
	CALL 	VERTICA_LINE

	MOV	DL,	1001B	;neck
	MOV	DI,	2
	SUB	AX,	1
	ADD	BX,	5
	CALL	VERTICA_LINE
	ADD	AX,	1
	CALL	VERTICA_LINE
	
	MOV DL, 0110B
	MOV	DI,	9		;body
	MOV	SI,	6
	SUB	AX,	3
	ADD	BX,	2
	CALL	SOLID_RECTANGLE
	
	MOV DL, 0000B
	ADD AX,2
	MOV DI, 9
	MOV SI, 1
	CALL	SOLID_RECTANGLE

	SUB AX, 2
	MOV	DL,	1001B
	MOV	DI,	5		;arm
	ADD	BX,	2
	CALL	REV_OBLIQUE_LINE
	ADD	AX,	6
	CALL	OBLIQUE_LINE
	
	MOV	DL,	0001B	;LEG
	MOV	DI,	9
	MOV SI, 2
	SUB	AX,	2
	ADD	BX,	7
	CALL	SOLID_RECTANGLE
	SUB	AX,	4
	CALL	SOLID_RECTANGLE
	
	MOV	DL,	0000B	;SHOES
	ADD	BX,	7
	MOV	DI,	1
	MOV SI, 3
	SUB AX, 1
	CALL	SOLID_RECTANGLE
	ADD AX, 4
	CALL	SOLID_RECTANGLE

	POP	SI		;恢复现场
	POP	DI
	POP	DX
	POP	BX
	POP	AX
	RET
DRAW_ROLE	ENDP
;========================================================================
;========================================================================
;功能:画目标位置的人
;入口参数:AX:横坐标,BX:纵坐标
DRAW_RoleInAim	PROC
	CALL	DRAW_AIM

	PUSH	AX		;保护现场
	PUSH	BX
	PUSH	DX
	PUSH	DI
	PUSH	SI

	MOV DL, 0110B
	ADD	AX,	12
	MOV DI, 1
	MOV SI, 6
	CALL SOLID_RECTANGLE

	MOV DL, 0000B
	ADD BX, 1
	MOV DI, 2
	MOV SI, 6
	CALL SOLID_RECTANGLE

	MOV DL, 0110B
	SUB AX, 3
	ADD BX, 2
	MOV DI, 2
	MOV SI, 12
	CALL SOLID_RECTANGLE

	MOV	DL,	1001B	;face
	MOV	DI,	6
	MOV	SI,	6
	ADD AX, 3
	ADD	BX,	2
	CALL	SOLID_RECTANGLE

	MOV	DL,	0000B	;eye
	MOV DI, 2
	ADD AX, 1
	ADD BX, 1
	CALL 	VERTICA_LINE
	MOV DI, 2
	ADD AX, 2
	CALL 	VERTICA_LINE

	MOV	DL,	1001B	;neck
	MOV	DI,	2
	SUB	AX,	1
	ADD	BX,	5
	CALL	VERTICA_LINE
	ADD	AX,	1
	CALL	VERTICA_LINE
	
	MOV DL, 0110B
	MOV	DI,	9		;body
	MOV	SI,	6
	SUB	AX,	3
	ADD	BX,	2
	CALL	SOLID_RECTANGLE
	
	MOV DL, 0000B
	ADD AX,2
	MOV DI, 9
	MOV SI, 1
	CALL	SOLID_RECTANGLE

	SUB AX, 2
	MOV	DL,	1001B
	MOV	DI,	5		;arm
	ADD	BX,	2
	CALL	REV_OBLIQUE_LINE
	ADD	AX,	6
	CALL	OBLIQUE_LINE
	
	MOV	DL,	0001B	;LEG
	MOV	DI,	9
	MOV SI, 2
	SUB	AX,	2
	ADD	BX,	7
	CALL	SOLID_RECTANGLE
	SUB	AX,	4
	CALL	SOLID_RECTANGLE
	
	MOV	DL,	0000B	;SHOES
	ADD	BX,	7
	MOV	DI,	1
	MOV SI, 3
	SUB AX, 1
	CALL	SOLID_RECTANGLE
	ADD AX, 4
	CALL	SOLID_RECTANGLE

	POP	SI		;恢复现场
	POP	DI
	POP	DX
	POP	BX
	POP	AX
	RET
DRAW_RoleInAim	ENDP
;========================================================================
;========================================================================
;功能:画到达目标点的箱子
;入口参数:AX:横坐标,BX:纵坐标
DRAW_BoxInAim	PROC
	PUSH	DX		;保护现场
	PUSH	DI
	PUSH	SI
	
	MOV	DL,	1010B
	MOV	DI,	30
	MOV	SI,	30
	CALL	SOLID_RECTANGLE
	
	MOV	DL,	1000B
	MOV	DI,	25
	CALL	DRAW_CROSS
		
	POP	SI		;恢复现场
	POP	DI
	POP	DX
	RET
DRAW_BoxInAim	ENDP
;========================================================================
;========================================================================
;功能:画路
;入口参数:AX:横坐标,BX:纵坐标
DRAW_ROAD	PROC
	PUSH	DX		
	PUSH	DI
	PUSH	SI
	
	MOV	DL,	00000111B	;路的颜色,银色
	MOV	DI,	30
	MOV	SI,	30
	CALL 	SOLID_RECTANGLE
	
	POP	SI		
	POP	DI
	POP	DX
	RET
DRAW_ROAD ENDP
;========================================================================
;========================================================================
;功能:画竖框架
;入口参数:AX:横坐标,BX:纵坐标
DRAW_CLASS	PROC
	PUSH	DX		
	PUSH	DI
	PUSH	SI
	
	MOV	DL,	00000110B	;路的颜色,银色
	MOV	DI,	30
	MOV	SI,	10
	CALL 	SOLID_RECTANGLE
	
	POP	SI		
	POP	DI
	POP	DX
	RET
DRAW_CLASS ENDP
;========================================================================
;========================================================================
;功能:画横框架
;入口参数:AX:横坐标,BX:纵坐标
DRAW_HCLASS	PROC
	PUSH	DX		
	PUSH	DI
	PUSH	SI
	SUB AX, 240
	MOV	DL,	00000110B	;路的颜色,银色
	MOV	DI,	10
	MOV	SI,	250
	CALL 	SOLID_RECTANGLE
	ADD AX, 240
	POP	SI		
	POP	DI
	POP	DX
	RET
DRAW_HCLASS ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               地图绘制                                 ;
;========================================================================
;功能:利用上面的各种图形绘制子过程,通过注意坐标判断进行地图的刷新。
;每一次操作都会刷新一次地图,因此还有通关的判断。
;入口参数:无
GRAPH_INIT	PROC

	PUSH	AX		
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	SI
	
	XOR	DX,	DX					;DX初始化为0,用来计算推到目的地的箱子	

	CALL	GAME_CLEAR			;先清屏
	MOV	CX,	8					;外层循环次数,x从0-7
	LEA	SI,	DATA_ARRAY	
	MOV	BX,	0					;X坐标从0开始
;========================================================================
OUT_LOOP:
	PUSH CX
	MOV	 CX,8					;内层循环次数,y从0-7
	MOV	 AX,0					;Y坐标
;========================================================================
IN_LOOP:										
COMPARE:						;开始根据矩阵所给值逐一判断是什么
	CMP	BYTE PTR [SI],ROAD		;当前位置是路
	JNZ	JUDWALL					;否则跳转下一个判断
	CALL	DRAW_ROAD			;调用画路子程序,并扫描下一个坐标
SKIPout:	
	JMP	CONTINUE_IN_LOOP	
;========================================================================
JUDWALL:	
	CMP	BYTE PTR [SI],WALL		;当前位置是墙
	JNZ	JUDBOX			
	CALL	DRAW_WALL
	JMP	SKIPout	
;========================================================================				
JUDBOX:	
	CMP	BYTE PTR [SI],BOX		;当前位置是箱子
	JNZ	JUDAIM			
	CALL	DRAW_BOX
	JMP	SKIPout		
;========================================================================
JUDAIM:	
	CMP	BYTE PTR [SI],AIM		;当前位置是目标位置
	JNZ	JUDROLE			
	CALL	DRAW_AIM
	JMP	SKIPout		
;========================================================================
JUDROLE:	
	CMP	BYTE PTR [SI],ROLE		;当前位置是角色
	JNZ	JUDABOX			
	CALL	DRAW_ROLE
	JMP	SKIPout		
;========================================================================
JUDABOX:	
	CMP	BYTE PTR [SI],BoxInAim 	;当前位置是在目标地的箱子
	JNZ	JUDAROLE			
	CALL	DRAW_BoxInAim
	INC	DX						;如果箱子推到目的地,DX++
	JMP	SKIPout		
;========================================================================
JUDAROLE:	
	CMP	BYTE PTR [SI],RoleInAim ;当前位置是在目的地的角色
	JNZ	SKIPout			
	CALL	DRAW_RoleInAim
;========================================================================	
CONTINUE_IN_LOOP:				;内循环完成一轮判断后的参数改变
	
	INC	SI						;指向地图数据的指针后移
	ADD	AX,	30					;x坐标+30
	LOOP	IN_LOOP
;========================================================================
	CALL DRAW_CLASS	
;========================================================================
	POP	CX						;外循环完成一轮判断后的参数改变
	ADD	BX,	30					;y坐标+30
	LOOP	OUT_LOOP
;========================================================================
	CALL DRAW_HCLASS
;========================================================================
	CMP	DL,	BOX_MAX[0]			;比较到达目标点的箱子是否等于总箱数
	JNE	PEND					;不等则跳转
;========================================================================
	CALL	DRAW_VICTORY		;相等则执行胜利子程序

PEND:	
	POP	SI			
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	RET
GRAPH_INIT	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               坐标换算                                 ;
;========================================================================
;功能:计算一维数组下标
;入口参数:BH:y坐标,BL:x坐标
;出口参数:SI:一维数组下标
CALC_POS	PROC
	PUSH	AX	
	PUSH	BX

	MOV	AL,	8		
	MUL	BH
	AND	BX,	00FFH
	MOV	SI,	BX
	ADD	SI,	AX			;SI=BH*8+BL,之所以是这个关系式是由于地图矩阵的定义,8个为一行所以BH*8可以看成是第几行,再加BL相当于第几列。

	POP	BX	
	POP	AX
	RET
CALC_POS	ENDP
;========================================================================
;========================================================================
;功能:计算一维数组下标(移动的下一个方向)
;入口参数:BH:数组二维下标,BL:数组一维下标,DH:四个数表示上、下、左、右四个方向
;出口参数:BH:前进后的数组二维下标,BL:前进后的数组一维下标
PRE_POS	PROC
	PUSH	AX	
	CMP	DH,	UP			;是否为前进
	JNE	DOWN_2
	DEC	BH				;Y-1
	JMP	CALC_END
;========================================================================
DOWN_2:
	CMP	DH,	DOWN		;是否为后退
	JNE	LEFT_2
	INC	BH				;Y+1
	JMP	CALC_END
;========================================================================
LEFT_2:
	CMP	DH,	LEFT		;是否为左拐
	JNE	RIGHT_2
	DEC	BL				;X-1
	JMP	CALC_END
;========================================================================
RIGHT_2:
	CMP	DH,	RIGHT		;是否为右拐
	JNE	CALC_END
	INC	BL				;X+1
;========================================================================
CALC_END:
	POP	AX	
	RET
PRE_POS	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               按键控制                                 ;
;========================================================================
;功能:按键对角色进行操作
;入口参数:BH:y坐标,BL:x坐标
GET_CHAR	PROC
	PUSH	AX		
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	SI
	
	MOV	BL,	ROLE_POS[1]			;取角色下标x
	MOV	BH,	ROLE_POS[0]			;取角色下标y
	CALL	CALC_POS
	XOR	DX,	DX	
			
	MOV	AH,	08H					;读键盘输入到AL中无回显
	INT	21H		
	
	CMP	AL,	70					;判断是方向键还是功能键,方向键值大于70
	JB	FUNCTBUT				;小于跳转到功能键比较
;========================================================================
	CMP	AL,	UP					;上键
	JNE	DOWN_1					;否则跳转
	MOV	DH,	UP					;将上键ASII码值移入DH
	JMP	GO_AHEAD				;跳转判断可否前行
;========================================================================
DOWN_1:	
	CMP	AL,	DOWN				;下键
	JNE	LEFT_1		
	MOV	DH,	DOWN	
	JMP	GO_AHEAD	
;========================================================================
LEFT_1:	
	CMP	AL,	LEFT				;左键
	JNE	RIGHT_1		
	MOV	DH,	LEFT	
	JMP	GO_AHEAD	
;========================================================================
RIGHT_1:
	CMP	AL,	RIGHT				;右键
	JNE	TEMP_END				;都不是不予理睬
	MOV	DH,	RIGHT	
	JMP	GO_AHEAD		
;========================================================================
FUNCTBUT:
	CMP	AL,	ESCAPE				;ESC键
	JNE	REGAME_1
	CALL	GAME_CLEAR			;清屏
	JMP	EXIT					;退出游戏
;========================================================================
REGAME_1:
	CMP	AL,	ReGame				;Regame键
	JNE	LEVEL_UP_1
	CALL	GAME_INIT			;初始化程序
	JMP	TEMP_END				;跳转到功能按键结束处理
;========================================================================
LEVEL_UP_1:
	CMP	AL,	LevelUp				;1键
	JNE	LEVEL_DOWN_1
	MOV	BL,	BYTE PTR LEVEL[0]	;取当前关卡等级
	CMP	BL,	BYTE PTR LEVEL_MAX[0]	
	JE	RE1						;相等则跳转到RE1,将其置为1
	INC	BL						;否则关卡等级+1
	MOV	BYTE PTR LEVEL[0],	BL
	JMP	LEVEL_END				;跳转进行新地图初始化
RE1:	
	MOV	BYTE PTR LEVEL[0],1		;将当前关卡等级置为1
	JMP	LEVEL_END			
TEMP_END:						;中间跳转,防止跳转范围不够
	JMP	END1
;========================================================================
LEVEL_DOWN_1:
	CMP	AL,	LevelDown			;2键
	JNE	MUSIC_ON_1		
	MOV	BL,	BYTE PTR LEVEL[0]	;取当前关卡等级
	CMP	BL,	1					;与最初关卡等级比较
	JE	RE2						;相等则跳转,将其置为最大
	DEC	BL						;否则关卡等级减1
	MOV	BYTE PTR LEVEL[0],	BL
	JMP	LEVEL_END				;跳转进行新地图初始化
RE2:	
	MOV	CL,	BYTE PTR LEVEL_MAX[0]	
	MOV	BYTE PTR LEVEL[0],	CL
	JMP	LEVEL_END				;跳转进行新地图初始化
;========================================================================
MUSIC_ON_1:				
	CMP	AL,	MusicOn					;3键	
	JNE	END1						;都不是不予理睬
	XOR	BYTE PTR MusicFlag[0],01H	;对当前音乐状态取非
	JMP	END1						;结束
;========================================================================
LEVEL_END:
	CALL	DATA_INIT			;游戏地图数据初始化	
	CALL	GRAPH_INIT			;游戏界面初始化
	JMP	END1					;结束
;========================================================================
;障碍处理,下面程序的跳转	
END0:	
	CALL	DRAW_OBSCATLE			
	JMP	END1
;========================================================================
GO_AHEAD:
	CALL	GO								;判断前方是否可行,DL=1则说明可行
	CMP	DL,	1			
	JNE	END0								;不可行跳转障碍处理
	
	CMP	BYTE PTR DATA_ARRAY[SI],ROLE		;否则判断当前位置是否是角色
	JNE	GO_AHEAD1							;不是将当前位置置为目的地
	MOV	BYTE PTR DATA_ARRAY[SI],ROAD		;否则置为路
	JMP	S_POS								;跳转去保存当前人物坐标
GO_AHEAD1:
	MOV	BYTE PTR DATA_ARRAY[SI],AIM			;将当前位置置为目的地

S_POS:	
	CALL	PRE_POS							;计算人物移动后的位置
	MOV	ROLE_POS[0],	BH					;保存角色下标
	MOV	ROLE_POS[1],	BL
	
	
	MOV 	AX,	0C00H						;清空键盘缓冲区
	INT	21H
	CALL	GRAPH_INIT

END1:	
	POP	SI		
	POP	DX	
	POP	CX	
	POP	BX
	POP	AX
	RET
GET_CHAR	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               移动判断                                 ;
;========================================================================
;功能:前面人前面是否可走
;入口参数:BH:数组二维下标,BL:数组一维下标
;出口参数:DL:为0时表示不可以走,为1时表示可以走
GO	PROC

	PUSH	AX		
	PUSH	BX
	PUSH	CX
	PUSH	SI
	XOR	DL,	DL	

	CALL	PRE_POS							;将BX置为移动后的坐标
	CALL	CALC_POS						;计算移动后在地图数组中的偏移量
	
	CMP	BYTE PTR DATA_ARRAY[SI],ROAD		;判断是否是路
	JNE	WALL_1								;不是则跳转
	MOV	DL,	1								;是则可以移动
	MOV	BYTE PTR DATA_ARRAY[SI],ROLE		;当前位置置为角色
	JMP	END2								;跳转结束,默认直接走
;========================================================================
WALL_1:		
	CMP	BYTE PTR DATA_ARRAY[SI],WALL		;判断是否是墙
	JNE	BOX_1								;不是则跳转
	JMP	END2								;跳转结束,默认不可以移动
;========================================================================
BOX_1:
	CMP	BYTE PTR DATA_ARRAY[SI],BOX			;判断是否是箱子
	JNE	AIM_1								;不是则跳转
	CALL	AHEAD							;判断箱子能否移动
	CMP	DL,	1
	JNE	END2								;不能移动,跳转结束
	MOV	BYTE PTR DATA_ARRAY[SI],ROLE		;可以移动将当前位置置为角色
	JMP	END2								;跳转结束
;========================================================================
AIM_1:	
	CMP	BYTE PTR DATA_ARRAY[SI],AIM			;判断是否是目的地
	JNE	BoxInAim_1							;不是则跳转
	MOV	DL,	1								;可以移动
	MOV	BYTE PTR DATA_ARRAY[SI],RoleInAim 	;将当前位置置为角色在目的地
	JMP	END2								;跳转结束
;========================================================================
BoxInAim_1:
	CMP	BYTE PTR DATA_ARRAY[SI],BoxInAim 	;当前位置是箱子在目的地
	JNE	END2								;否则跳转
	CALL	AHEAD							;判断箱子能否移动
	CMP	DL,	1		
	JNE	END2								;不能移动,跳转结束
	MOV	BYTE PTR DATA_ARRAY[SI],RoleInAim 	;可以移动将当前位置置为角色在目的地
;========================================================================
END2:	POP	SI				
	POP	CX
	POP	BX
	POP	AX
	RET
GO	ENDP
;========================================================================
;========================================================================
;功能:判断箱子前方是否能走
;入口参数:BH:数组二维下标,BL:数组一维下标
;出口参数:DL:为0时表示不可以走,为1时表示可以走
AHEAD	PROC
	PUSH	BX			
	PUSH	SI
	XOR	DL,	DL	

	CALL	PRE_POS							;将BX置为移动后的坐标
	CALL	CALC_POS						;计算移动后在地图数组中的偏移量
	
	CMP	BYTE PTR DATA_ARRAY[SI],ROAD		;当前为位置是路
	JNE	WALL_2								;否则跳转
	MOV	DL,	1								;可以移动
	MOV	BYTE PTR DATA_ARRAY[SI],BOX			;将当前位置置为箱子
	JMP	END3								;跳转结束
;========================================================================
WALL_2:	
	CMP	BYTE PTR DATA_ARRAY[SI],WALL		;当前位置是墙
	JNE	BOX_2								;否则跳转
	JMP	END3								;跳转结束
;========================================================================
BOX_2:
	CMP	BYTE PTR DATA_ARRAY[SI],BOX			;当前位置是箱子
	JNE	AIM_2								;否则则跳转
	JMP	END3								;跳转结束
;========================================================================
AIM_2:	
	CMP	BYTE PTR DATA_ARRAY[SI],AIM			;当前位置是目标位置
	JNE	BoxInAim_2							;否则跳转
	MOV	DL,	1
	MOV	BYTE PTR DATA_ARRAY[SI],BoxInAim 	;将当前位置置为箱子在目的地
	JMP	END3								;跳转结束
;========================================================================
BoxInAim_2:
	CMP	BYTE PTR DATA_ARRAY[SI],BoxInAim 	;当前位置是箱子在目的地
	JNE	END3								;跳转结束
;========================================================================
END3:	POP	SI			
	POP	BX
	RET
AHEAD	ENDP
;========================================================================
;                                                                       ;
;========================================================================



;========================================================================
;                               主体循环                                 ;
;========================================================================
;功能:开始游戏
;入口参数:无	
GAME_START	PROC
	PUSH	AX
	PUSH	BX
	PUSH	DX
	PUSH	SI
	PUSH	DI
	PUSH	BP
	
MUS_INIT:							;音乐频率表、节拍表首地址初始化
	XOR	AX,	AX
	XOR	SI,	SI
	XOR	BP,	BP
LOOP1:		
	MOV	AH,	0BH						;是否有按键按下
	INT	21H
	CMP	AL,	00H						;无按键则跳转播放音乐
	JE	MUSIC1	
	
	CALL	GET_CHAR				;有按键,转至按键处理	
	CALL	DRAW_MENU				;绘制菜单
MUSIC1:
	MOV	DL,	BYTE PTR MusicFlag[0]	;判断音乐播放标志是否为0
	CMP	DL,	0
	JE	MUS_INIT					;是则跳转音乐初始化
	MOV DI, MUS_FREG[SI]			;否则获取频率和节拍信息
	CMP DI, 0FFFFH					;判断是否播放到音乐末尾
	JE	MUS_INIT					;是则跳转音乐初始化
	MOV BX, MUS_TIME[BP]
	CALL BEEP						;调用蜂鸣器子程序播放音乐
	ADD	SI,	2						;指针移向下一个字
	ADD	BP,	2						;指针移向下一个字
	JMP	LOOP1						;继续循环
	
	POP	BP
	POP	DI
	POP	SI
	POP	DX
	POP	BX
	POP	AX	
	RET
GAME_START	ENDP
;========================================================================
;                                                                       ;
;========================================================================

CODE	ENDS

	END	START

猜你喜欢

转载自blog.csdn.net/weixin_45555522/article/details/121583592