设计内容
游戏中玩家可以用键盘的上,下,左,右键控制小人的移动位置,游戏规则也十分简单,玩家在一个有限空间内,将木箱放在指定的位置。箱子只能推不能拉,而且一次智能推动一个箱子,胜利的条件是把所有箱子都推到目的地。
需求分析
经典的推箱子是一个来自日本的古老游戏,目的是在训练人的逻辑思考能力。在一个狭小的仓库中,要求把木箱从开始位置推放到指定的位置。在仓库有障碍物,稍不小心就会出现箱子无法移动或者通道被堵住的情况,而且箱子只能推,不能拉,所以需要巧妙的利用有限的空间和通道,合理安排移动的次序和位置,才能顺利的完成任务。本游戏一共7关,由易到难,拥有初始化、按键处理、重置、播放音乐、选关及退出功能。
1.初始化包括屏幕初始化和每一关卡的初始化,屏幕被初始化宽640×400×56色的图形模式。
2.按键处理包括移动小人和移动箱子,通过移动W、S、A、D键来控制小人的移动,从而推动箱子,以把箱子推到指定的目的地为过关。
3.每一关都可以重置,按0键可以重置当前关。
4.在每一关都可跳关或选择上一关卡,按1键进行跳关,按2键退回上一关卡。
5.播放音乐可通过按3键进行处理,默认开启状态,按一下关闭,再按开启。
6.按Esc键可以在任何时候退出游戏。
备注:按CTRL+F12键加速游戏,建议调节CPU速度到100000以上,按CTRL+F11键减速游戏,按ALT+ENTER键进入全屏模式,以此获得更好的游戏体验。
概要设计
-
方案设计
-
模块功能说明
本程序采用模块化设计,包括6个模块,分别是初始化模块、绘图模块、人物移动模块、输入控制模块、音乐播放模块和文件操作模块。各个模块的功能描述如下:1) 初始化模块:本模块的主要任务是将游戏中的墙、目标位置、菜单信息、箱子、人物输出到游戏窗口上。此模块通过map中的偏移地址,然后根据坐标绘制各个信息,同时其还会显示菜单信息。
2) 绘图模块:此模块用于绘制路、墙、人物、目的地等图形。
3) 人物移动模块:本模块的主要任务是控制人物的正确移动。接收到人物移动的输入指令后,判断人物是否能移动,可以移动更新地图中各个位置的信息。此模块分为两个部分,先判断前方是否是箱子,此功能实现在子程序GO中,若是箱子,判断箱子能否移动,此功能实现在子程序AHEAD中,若箱子能移动,更新地图信息,否则不执行操作。
4) 输入控制模块:本模块的主要任务是接收玩家输入的按键执行对应的操作。游戏中需要通过接收玩家输入的按键,控制游戏重新开始,游戏音效的开启与关闭,关卡选择,游戏退出以及控制人物移动等重要游戏功能。游戏中本功能的主要实现在子程序GET_CHAR,在获取到用户输入的按键后与游戏中的操作按键进行逐一比对。比对游戏中已经设定的按键即可确定用户输入的按键,进而跳转到按键对应的功能实现程序段中,继续执行相应操作,从而保证人物的正常移动、游戏跳转以及游戏功能的实现。
5) 音乐播放模块:此模块用于通过蜂鸣器播放音乐。
6) 文件操作模块:此模块用于将地图信息从本地文件中读取出来,以达到游戏的可拓展性。
详细设计
- 流程图
- 主要子程序调用
运行截图
游戏初始界面,这是第一关,左上角8×8个小方格,白色是路,灰色是墙,褐色是箱子,绿色叉号是目标位置,蓝色是角色,右边是菜单,分别是按ESC退出,按↑向上移动,按↓向下移动,按←向左移动,按→向右移动,按0键重新开始这关,按1跳到下一关卡,按2跳到上一关卡,按3进行背景音乐的开和关,level后面的是当前关卡/最高关卡,Score后面是当前分数。最下面显示的是为了游戏的更好体验,请将DOS BOX 的CPU速度调制100000 cycles以上。
绿色箱子表示箱子推到目的地,由于按了↑,而上方是墙,不能移动,游戏会提示你的前方有障碍物,不能直行。
当把箱子推到目的地后会有提示信息:恭喜!你将进入下一关!
按任意键进入下一关,右边level后的当前关卡信息也随之变为第二关。
连按两下1键,会跳两关,变为第4关。
按2键会回到上一关,即第3关。
按↑、↓、←、→可以移动人物位置。
按0键会回到当前关卡初始界面,按3键可进行音乐的开关。
调试情况
遇到的问题:
- JMP跳转超出范围
刚开始写程序时,有时代码写的过长使用JMP语句时,就会出现ADDRESS OUT OF RANGE XX的错误,之后使用了间接跳转的方法解决了这个问题。刚开始为了传参数方便大多都用的宏指令,但发现宏指令嵌套使用很容易出现JMP跳转范围不够的错误,即使使用间接跳转的方法也常出现范围不够的错误,最终将宏指令全部改为子程序解决了这个问题。 - 读取文件不能随机读取
在从本地文件读取地图数据时,发现只能从文件开始读取,但我们想要的数据需要随机读取,因此我们想到可以先将前面的数据读取出来,直到读到我们想要的数据起始地址,这样就达到了随机读取的目的。为此我们专门分配了1个字节的内存,每次把不需要的数据读取到这里,直到读到我们想要的数据。 - 清屏太慢
可能是CPU速度不够的原因,图形界面绘图时速度很慢,也没有专门的清屏中断,若用一种颜色画一个640×400的矩形进行清屏操作,又太费时间,之后设置显示窗口的方法达到了清屏的目的。 - 背景音乐播放
由于播放音乐时需要用到延时函数,若直接将一首歌曲的播放携程子程序放入主程序的循环中,音乐函数的延时会将程序挂起来,导致无法正常运行。因此我们先判断是否有按键按下,当无按键按下时播放一个频率,然后继续查询,按键按下时去执行按键操作,从而解决了这个问题。
源代码
- 地图文件(名为pushbox.txt,与可执行文件需同目录)
4440011100000131000001011111112023113024111111121000001310000011100322011110000100110001430100013321100102201001000010010000100111111046300111100001331000110311001002310110200111001220110040001111111115310011111100100001111222011402330110233311111100100001111000000000864111111101332331013313310102220101002001010222010100140101111111035600000000001111001113010010300111100022011103024101111111000000004130111110001040111110120011053030110022011111013100010001000111110
- 源代码
;名称:推箱子
;完成日期:2019-12-22
;需在dosbox下运行,另有名为pushbox.txt的地图文件与其同目录,地图文件如上
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
MusicFlag DB 1
FILE_NAME DB 'pushbox.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 "Congratulations! You will enter the next level!"
SCORE DB "score:"
SCORE_NUM DB "00","10","20","30","40","50","60","70","80","90"
OBSCATLE DB "There are obstacles in front of you. You can't go straight!"
MENU_EXIT DB "exit:esc",7 dup(' ')
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_WARNING DB "In order to ensure a better game experience, please ensure that the CPU speed 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:需传送数据的地址偏移量,CX:传送字节数
WRITE_FILE PROC
PUSH AX ;保护现场
PUSH BX
PUSH CX
PUSH DX
;打开文件
MOV DX, DI ;文件绝对路径的偏移量送往DX
MOV AH, 3DH ;3DH功能调用
MOV AL, 2 ;以读/写方式打开
INT 21H
MOV HANDLE, AX ;将文件句柄保存至HANDLE
;写文件
MOV BX, AX ;将文件句柄送至BX
MOV DX, SI ;需传送数据的地址偏移量送往DX
MOV AH, 40H ;40H功能调用
INT 21H
;关闭文件
MOV BX, HANDLE ;取出句柄
MOV AH, 3EH ;3EH功能调用
INT 21H
POP DX ;恢复现场
POP CX
POP BX
POP AX
RET
WRITE_FILE ENDP
;对本地文件进行读操作
;入口参数: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功能调用
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
;功能:延时函数
;入口参数: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
;功能:打印字符串
;入口参数:AX:字符串地址,CX:显示字符串长度,(DH、DL)=坐标(行、列)
PRINT_STR PROC
PUSH AX
PUSH BX
PUSH DX
PUSH BP
MOV BP, AX ;需显示的字符串首地址
MOV AX, 1301H ;AH:功能调用,AL:显示方式
MOV BH, 00H ;显示的页面
MOV BL, 0CH ;显示的前景色及背景色
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, 59 ;要显示的字符串长度
MOV DX, 1100H ;(DH、DL)=坐标(行、列)
LEA AX, OBSCATLE ;要显示的字符串首地址
CALL PRINT_STR ;调用字符串显示程序
POP DX
POP CX
POP AX
RET
DRAW_OBSCATLE ENDP
;功能:显示菜单界面
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;要显示的字符串首地址
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_UP ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_DOWN ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_LEFT ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_RIGHT ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_REGAME ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_LEVEL_UP ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_LEVEL_DOWN ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
LEA AX, MENU_MUSIC ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
MOV AL, BYTE PTR LEVEL[0] ;获取当前关卡等级
OR AL, 30H ;将其转化成对应的ASCII码
MOV BYTE PTR MENU_LEVEL[6], AL ;将其存入待显示字符串对应的位置中
MOV AL, BYTE PTR LEVEL_MAX[0] ;获取当前最大关卡等级
OR AL, 30H ;将其转化成对应的ASCII码
MOV BYTE PTR MENU_LEVEL[8], AL ;将其存入待显示字符串对应的位置中
LEA AX, MENU_LEVEL ;要显示的字符串首地址
INC DH ;行地址加1
CALL PRINT_STR ;调用字符串显示程序
MOV CX, 6 ;待显示字符串长度
MOV DX, 0C2AH ;(DH、DL)=坐标(行、列)
LEA AX, SCORE ;要显示的字符串首地址
CALL PRINT_STR ;调用字符串显示程序
MOV CX, 7 ;获取当前关卡标志,为1BX加2,BX为分数偏移地址
MOV SI, 0
MOV BX, 0
CONTINUE_LEVEL:
CMP LevelFlag[SI], 1
JNE CONTINUE_LEVEL_ERGODIC
INC SI
ADD BX, 2
CONTINUE_LEVEL_ERGODIC:
LOOP CONTINUE_LEVEL
MOV DX, 0C30H ;(DH、DL)=坐标(行、列)
MOV CX, 2 ;待显示字符串长度
LEA AX, SCORE_NUM[BX] ;要显示的字符串首地址
CALL PRINT_STR ;调用字符串显示程序
MOV DX, 1500H ;(DH、DL)=坐标(行、列)
MOV CX, 104 ;待显示字符串长度
LEA AX, MENU_WARNING ;要显示的字符串首地址
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
LEA AX, VICTORY ;要显示的字符串首地址
MOV CX, 47 ;待显示字符串长度
MOV DX, 1100H ;(DH、DL)=坐标(行、列)
CALL PRINT_STR ;调用字符串显示程序
MOV AH,08H ;等待按键按下,不回显
INT 21H
MOV BL, BYTE PTR LEVEL[0] ;获取当前关卡等级
DEC BL
MOV BYTE PTR LevelFlag[BX], 1 ;当前关卡标志置为1
MOV BL, BYTE PTR LEVEL[0] ;获取当前关卡等级
CMP BL, BYTE PTR LEVEL_MAX[0] ;与最大关卡等级比较
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 ;清空键盘缓冲区
INT 21h
POP DX
POP CX
POP BX
POP AX
RET
DRAW_VICTORY ENDP
;功能:从文件中读取地图数据
;入口参数:无
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 ;获取的内容地址为(AH-1)*67
MUL AH
MOV CX, 67
CALL READ_FILE ;将地图信息读入
LEA SI, BOX_MAX ;获取存储关卡箱子数量、角色坐标的地址,将ASCII码转化为数字
MOV CX, 3
DATA_INIT1:
SUB BYTE PTR [SI], 48
INC SI
LOOP DATA_INIT1
POP SI
POP DI
POP CX
POP AX
RET
DATA_INIT ENDP
;功能:游戏数据、图形界面初始化
;入口参数:无
GAME_INIT PROC
CALL DATA_INIT
CALL GRAPH_INIT
CALL DRAW_MENU
RET
GAME_INIT ENDP
;功能:清屏,将显示模式重新设置来清屏
;入口参数:无
GAME_CLEAR PROC
PUSH AX ;保护现场
PUSH BX
MOV AX, 4F02H ;640*400 256色的图形模式
MOV BX, 100H ;用来设定显示模式的服务程序
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 ;写入像点
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
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
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
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
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, 00000100B
MOV DI, 30
MOV SI, 30
CALL SOLID_RECTANGLE
MOV DI, 25
MOV DL, 00000101B
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
MOV DL, 1001B
MOV DI, 4
MOV SI, 4
ADD AX, 13
ADD BX, 2
CALL SOLID_RECTANGLE
MOV DI, 2
ADD AX, 1
ADD BX, 4
CALL VERTICA_LINE
ADD AX, 1
CALL VERTICA_LINE
MOV DI, 10
MOV SI, 6
SUB AX, 3
ADD BX, 2
CALL SOLID_RECTANGLE
MOV DI, 5
ADD BX, 2
CALL REV_OBLIQUE_LINE
ADD AX, 6
CALL OBLIQUE_LINE
MOV DI, 8
SUB AX, 1
ADD BX, 8
CALL VERTICA_LINE
SUB AX, 4
CALL VERTICA_LINE
POP SI ;恢复现场
POP DI
POP DX
POP BX
POP AX
RET
DRAW_ROLE ENDP
;功能:画目标位置的人
;入口参数:AX:横坐标,BX:纵坐标
DRAW_RoleInAim PROC
CALL DRAW_AIM
CALL DRAW_ROLE
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, 0FH
MOV DI, 30
MOV SI, 30
CALL SOLID_RECTANGLE
POP SI ;恢复现场
POP DI
POP DX
RET
DRAW_ROAD 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 ;外层循环次数
LEA SI, DATA_ARRAY
MOV BX, 0 ;X坐标
OUT_LOOP:
PUSH CX
MOV CX, 8 ;内层循环次数
MOV AX, 0 ;Y坐标
IN_LOOP:
COMPARE:
CMP BYTE PTR [SI], ROAD ;当前位置是路
JNZ A1 ;否则跳转
CALL DRAW_ROAD ;调用画路子程序
B1:
JMP CONTINUE_IN_LOOP ;继续下一循环
A1:
CMP BYTE PTR [SI], WALL ;当前位置是墙
JNZ A2 ;否则跳转
CALL DRAW_WALL
JMP B1 ;继续下一循环
A2:
CMP BYTE PTR [SI], BOX ;当前位置是箱子
JNZ A3 ;否则跳转
CALL DRAW_BOX
JMP B1 ;继续下一循环
A3:
CMP BYTE PTR [SI], AIM ;当前位置是目标位置
JNZ A4 ;否则跳转
CALL DRAW_AIM
JMP B1 ;继续下一循环
A4:
CMP BYTE PTR [SI], ROLE ;当前位置是角色
JNZ A5 ;否则跳转
CALL DRAW_ROLE
JMP B1 ;继续下一循环
A5:
CMP BYTE PTR [SI], BoxInAim ;当前位置是在目的地的箱子
JNZ A6 ;否则跳转
CALL DRAW_BoxInAim
INC DX ;如果箱子推到目的地,DX++
JMP B1 ;继续下一循环
A6:
CMP BYTE PTR [SI], RoleInAim ;当前位置是在目的地的角色
JNZ B1 ;继续下一循环
CALL DRAW_RoleInAim
CONTINUE_IN_LOOP:
INC SI ;指向地图数据的指针后移
ADD AX, 30 ;x坐标+30
LOOP IN_LOOP
POP CX
ADD BX, 30 ;y坐标+30
LOOP OUT_LOOP
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:数组二维下标,BL:数组一维下标
;出口参数:SI:一维数组下标
CALC_POS PROC
PUSH AX ;保护现场
PUSH BX
MOV AL, 8 ;SI=BH*8+BL
MUL BH
AND BX, 00FFH
MOV SI, BX
ADD SI, AX
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
JMP CALC_END
DOWN_2:
CMP DH, DOWN
JNE LEFT_2
INC BH
JMP CALC_END
LEFT_2:
CMP DH, LEFT
JNE RIGHT_2
DEC BL
JMP CALC_END
RIGHT_2:
CMP DH, RIGHT
JNE CALC_END
INC BL
CALC_END:
POP AX ;恢复现场
RET
PRE_POS ENDP
;功能:按键对角色进行操作
;入口参数:BH:数组二维下标,BL:数组一维下标
GET_CHAR PROC
PUSH AX ;保护现场
PUSH BX
PUSH CX
PUSH DX
PUSH SI
MOV BL, ROLE_POS[1] ;取角色下标
MOV BH, ROLE_POS[0]
CALL CALC_POS
XOR DX, DX ;DX清零
MOV AH, 08H ;读键盘输入到AL中无回显
INT 21H
CMP AL, 70 ;判断是方向键还是功能键,方向键ASII码值大于70
JB ESCAPE_1 ;小于跳转到功能键比较
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 ;将下键ASII码值移入DH
JMP GO_AHEAD ;跳转判断可否前行
LEFT_1:
CMP AL, LEFT ;左键
JNE RIGHT_1 ;否则跳转
MOV DH, LEFT ;将左键ASII码值移入DH
JMP GO_AHEAD ;跳转判断可否前行
RIGHT_1:
CMP AL, RIGHT ;右键
JNE TEMP_END ;否则跳转
MOV DH, RIGHT ;将右键ASII码值移入DH
JMP GO_AHEAD ;跳转判断可否前行
ESCAPE_1:
CMP AL, ESCAPE ;ESC键
JNE REGAME_1
CALL GAME_CLEAR ;清屏
JMP EXIT ;退出游戏
REGAME_1:
CMP AL, ReGame ;0键
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 ;判断前方是否可行
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 ;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 ;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