1 目标和意义
为了巩固汇编语言与接口技术课程所学的理论知识,理解计算机的基本系统结构,理解处理器的工作过程,探究数据和指令的内部表述,特选择本实验。
此实验,能训练运用显示器编程的能力和运算操作的能力,训练延时和图形重绘的能力,训练汇编键盘输入控制的能力,训练汇编语言编写函数和函数间调用的能力。同时训练一般游戏的设计能力。
在此基础上完成具有特定功能的较复杂的汇编语言经典游戏打砖块的设计与编程。
2 专题实验
2.1 选题描述
经典游戏打砖块是很多人曾经玩过的经典游戏,也是一段令人难忘的经历。选择这个课题一方面是为了揭开自己曾经难忘的游戏后台,另一方面也是将理论与实践结合极好的机会。
这个游戏的核心在于砖块和小球的控制,难度适中但有很大的发挥余地,而且需要在汇编语言中不断变换处理焦点,对汇编语言的核心把握要求相对较高,基本上满足了本次专题实验的难度要求。另外,这个游戏的可扩展性很高,突出在自己可以主动或被动的添加各种道具,从而丰富游戏过程,加大设计难度。
2.2 问题分析
- 砖块:易碎砖块、普通砖块、硬质砖块。
- 易碎砖块:所有砖块中最容易被打击掉的砖块。默认强度为1,即任何类型的球只需击打一次此类砖块,它都会被打碎
- 普通砖块:默认强度为2。是在普通游戏难度中最多的砖块。普通球需要击打2次方可以击碎此类砖块
- 硬质砖块:默认强度为4。普通球需要击打4次方可以击碎此类砖块。在难度较大的情况下,此类砖块的比例会有所增加
- 道具:奖生命、奖分数、大球拍、小球拍、球减速、球加速、普通炸弹、超级炸弹
- 奖生命:每个玩家都是有一定的生命值的,如果玩家的生命值为零,那么玩家就结束了当次游戏。如果玩家可以在游戏过程中,接到生命值,玩家的生命值将会加1。对玩家来说,奖生命值是最好的奖励
- 奖励分数:每个玩家在进行游戏时,都会进行一个分数叠加。每当玩家打掉砖块或者接到一定的道具时,玩家的分数就会增加
- 大球拍:这个道具的功能可以使玩家的球拍变大。当玩家接到这个道具时,如果玩家的球拍还未达到最大球拍宽度,那么玩家的球拍将会变宽;如果玩家的球拍已经达到最大的宽度,那么这个道具将不会对玩家的球拍起作用
- 小球拍:这个道具的功能可以使玩家的球拍缩小。当玩家接到这个道具时,如果玩家的球拍还未达到最小球拍宽度,那么玩家的球拍将会变窄;如果玩家的球拍已经是最窄的宽度,那么这个道具将不会对玩家的球拍起作用。因为这个道具对玩家的防守力不利,因此玩家应该尽可能避免接到此道具
- 球减速:这个道具可以减缓玩家的球的速度,玩家最多可以通过两次减速使自己的球的速度降到最低,以此来增加自己的防守力
- 球加速:这个道具可以加速玩家的球的速度。玩家醉倒可以通过两次加速来使自己的球的速度加到最大。对于比较熟练的玩家,这个道具可以快速将砖块打完,但却降低了自己的防守能力
- 普通炸弹:当玩家接到这个道具的时候,当前游戏界面中断最低3层将会被请客。在游戏的设置上,砖块在倒数第三排全部为硬质砖块,因此如果玩家再出现这个道具而没有接到时,玩家将不得不画很长的时间来将倒数第三排的硬质砖块打掉
- 超级炸弹:如果玩家接到这个道具,那么当前屏幕中的所有砖块都会消失,玩家直接完成任务,可以进入下一关
- 飘动档板:为了增加游戏的复杂性,在游戏界面的中间处增加了两个飘动档板。它们的生命值为无穷,因此将会一直在游戏的过程中存在。由于飘动档板的存在,使得小球不一定每次都能击到砖块
- 球拍变化:球拍的变化主要包括球拍的的加宽和变窄。假设球拍的最初始的宽度为1,那么球拍的最宽宽度可以达到5/3,最窄将会缩窄到2/3。在游戏过程中,玩家可以通过两次累加“大球拍”道具而时球拍加宽到5/3,也可能因为接到“小球拍”道具两次而变为宽度为2/3的球拍
2.3 实验程序设计
游戏进入界面
游戏过程界面
在游戏的过程中,主要有以下的键可以用来控制:
游戏中的各个过程(函数)
;******************************************************************
;当接到炸弹后,调用这个函数来实现特效
bomb_show proc near ;函数一;******************************************************************
;没什么好解释的吧,很简单的延时
waitf proc near;函数二;******************************************************************
;当将所有的砖块都打完后,执行这个函数
cmpgamefun proc near;函数三;******************************************************************
;这个函数是当小球死光光了以后调用的
balldeadfun proc near ;函数四;******************************************************************
;replayfun这个函数是用来处理当用户按下r或者R时,进行内存数据重新赋值
replayfun proc near ;函数五;******************************************************************
;waitfun这个函数是当用户按下空格时,游戏处于暂停状态
waitfun proc near ;函数六;******************************************************************
;通过对系统中断向量的重置,可以使得程序以每秒18.2次的速度对小球和砖块进行处理。
zhongduan proc near ;函数七;******************************************************************
;ballstep这个方法是用来处理小球运动函数
ballstep proc near ;函数八;******************************************************************
;getpos这个方法是通过对当前的 球的位置 以及 的运动向量 和球的速度
;而得到新的 运动向量 和新的位置
;这个方法是在当前的运动向量的前提下,得到小球的下一个位置。当与墙壁或
; 者砖块相撞时,这个方法也要对运动向量进行一定的修改。
getpos proc near ;函数九;******************************************************************
;这个方法是用来计算小球与砖块的碰撞的,是与哪一个砖块碰撞,它的抗击能力将减一
;如果为0,则砖块消失,并加分
hitbrick proc near ;函数十;******************************************************************
;这个函数是用来处理两个飘动挡板的
wave_brick_fun proc near ;函数十一;******************************************************************
;这个方法是用来画道具的,其中道具的左上角的x和y的位置已经存储在propx和propy中
;另外,道具的颜色也有n种,根据不同的道具,选择不同的颜色
printprop proc near ;函数十二;******************************************************************
;setball这个方法是用来画出小球
;左上角的位置和颜色都在内存中ballx和bally以及color中存储
;小球的大小是固定不变的
setball proc near ;函数十三;******************************************************************
;这个方法是用来填充一个矩形的颜色。其中,矩形的左上角的坐标和右下角
;坐标以及颜色值存储在内存变量中huaqidianx\huaqidiany\huazhongdianx
;huazhongdiany\color
setarea proc near; 函数十四;******************************************************************
;这个方法是用来将分数写入屏幕的
score_screen proc near ;函数十五;******************************************************************
;这个函数是对道具在屏幕中的运动进行处理的,即道具从上落下,直到落地或者被球拍接住
prop_screen proc near ;函数十六;******************************************************************
;是用来画出球拍的位置,其中球拍的左端位置以及球拍的宽度由内存记录
mouse_racket proc near ;函数十七;******************************************************************
;inmenufun这个函数是进入画面
inmenufun proc near ;函数十八;******************************************************************
;init这个方法是在程序开始运行时,画出界面用的
;每次程序运行或者玩家重新开始时会调用一次
init proc near ;函数十九;******************************************************************
;以下的代码是用来发声
GENSOUND PROC NEAR ;函数二十
MUSIC2 PROC NEAR ;函数二十一
游戏中的数据段代码(部分)
;此处输入数据段代码
;这些变量是用来给setarea传递参数,函数十四
qidianx dw 0 ;起点x
qidiany dw 0 ;起点y
zhongdianx dw 10 ;终点x
zhongdiany dw 3 ;终点y
color db 10
racket_color db 0111b;球拍颜色—白色
frame_color db 0110b;边框颜色—棕色
ball_color db 1110b;球的颜色—黄色
brick_color1 db 1111b;易碎砖块的颜色—高亮白色
brick_color2 db 1010b;普通砖块的颜色—浅绿色
brick_color3 db 0101b;硬质砖块的颜色—品红色
inmenu_color db 1110b; 内部菜单的颜色—黄色
wave_brick_color db 1001b;飘动档板的颜色—浅蓝色
;*********************************************************
exitflag dw 1;用来记录玩家什么时候要退出游戏,当为0时退出
stopflag dw 0;用于记录什么时候暂停,如果是1则暂停
replayflag dw 0;用于记录是否重新开局,1为有效
propflag db -1;用来判断当前屏幕中是否有道具,如果没有则为-1,否则为相应的第0到n个道具
speedflag dw 0;用来判断是否要加速,如果为1则为加速
cmpgameflag db 0;用来记录游戏中的所有砖块是否打完,如果打完则为1
inmenuflag db 1;这个变量只是在最开始进入画面时用到,为了防止中断打断,因此init中不需要对它初始化,1为有效
balldeadflag db 0;判断小球的生命值是不是都打完了,如果为1,那么就意味着小球死光光了
barflag db 1;用于记录是第几关,一共有5关
bomb_flag db 0;用于显示当接到bomb后的特效,当为0时说明没有特效
;**********************************************************
;一些要显示的字符
brick_msg db 'BRICK$'
mader_msg db 'MADE BY: XJTUSE$'
play_msg db 'Press space to play!$'
score_msg db 'Score:$'
life_msg db 'Life:$'
prop_msg db 'PROP$'
none_msg db 'NULL $'
hint_msg1 db ' HINT$'
hint_msg2 db '<Esc>:exit $'
hint_msg3 db 'Space:pause$'
hint_msg4 db '<R>: replay$'
;这些msg是用在道具的提示上的
addlife_msg db 'LIFE $' ;奖生命
addscore_msg db 'SCORE $' ;奖分数
speed_up_msg db 'SPEED UP $' ;球加速
speed_down_msg db 'SPEEDDOWN$' ;球减速
large_racket_msg db 'L RACKET $' ;大球拍
small_racket_msg db 'S RACKET $' ;小球拍
bomb_msg db 'BOMB $' ;普通炸弹
bigbomb_msg db 'SUP BOMB $' ;超级炸弹stop_msg db 'Press space to continue!$'; 当暂停的时候要调用这个
con_msg db ' $';继续玩的时候,将之前的那个字体给涂成黑色
lose_msg db 'You lose! Play again?(Y/N)$';当生命值为0时要用到这个msg
con1_msg db ' $';继续玩的时候,将之前的那个字体给涂成黑色 congratulation_msg db 'Congratulation!!You win!!$'
youscore_msg db 'Your score is: $';分数显示 Your score is: $
playagain_msg db 'Play next level?(Y/N)$'
;*********************************************************
;要记录的数值
wave_brick1 dw 15;第一个挡板的最左边的位置
wave_brick2 dw 170;第二个挡板的最右边的位置
wave_brick1_pos db 1;1为向右移动,记录第一个档板的运动方向
wave_brick2_pos db 0;0为向左移动,记录第二个档板的运动方向
count db 0;这个变量是用来记录次数的
bomb_count db 0;用于炸弹的特效
life_num db 3;记录生命值
score_num db 0;得分总和不能大于255
mouse_last dw 80;用于记录上一次的的鼠标的位置,只记录x轴
racket_width dw 30;用于记录球拍的宽度
ballx dw 90;用来记录球的左上角的x轴坐标
bally dw 183;用来记录球的左上角的y轴坐标
brick_num dw 100;用来记录砖块的数量
x dw 1;用于记录运动向量
y dw -1;用于记录运动向量
propx dw 0;这个变量是用来画道具时,储存道具的左上角的x轴信息
propy dw 0;这个变量是用来画道具时,储存道具的左上角的y轴信息
;这个是道具的颜色,它们的顺序与下面道具的顺序一一对应
prop_color db 0001b ;增加生命的道具db 0010b ;增加分数的道具db 0011b ;加速的道具db 0100b ;减速的道具db 1000b ;大球拍的道具db 1001b ;小球拍的道具db 1010b ;普通炸弹的道具 db 1011b ;超级炸弹的道具
bomb_color db 1100b ;
游戏的流程说明
- 调用inmenufun函数实现进入画面
利用机器画像素的延迟性,分步对屏幕中的不同的位置涂色,从而显示出动画的效果。最终将显示出游戏名称和游戏制作者等信息 - 调用waitfun函数等待输入空格
利用dos的int 21h中断,当无键盘(空格)输入时,一直等待键盘的中断。从而达到暂停的效果 - 调用ballstep函数
并对相应的flag位进行操作:先判断小球是否与其它对象是否相撞,如果相撞,那么就改变小球的运动向量,然后得到小球的下个位置。将上一帧的小球擦掉,再将刚得到的小球画出来,从而实现小球的运动。再判断小球是否是与砖块相撞,如果是与砖块相撞,那么判断那个小球的抗击力减一,如果那个小球的抗击力为0,那么将那个小球的颜色涂为背景色。再这个函数中还要对相应的flag位赋值:- 小球落地:balldeadflag置为有效
-
- 砖块打完:cmpgameflag置为有效
- 有道具产生:propflag置为有效
- 小球落地并且小球生命为0:exitflag置为有效
- 调用prop_screen函数
当propflag无效时,这个函数什么都不做。当propflag有效时,这个函数将之前的道具的擦掉,然后在将道具的位置下移1,再在新的位置画出道具,从而实现道具的下落过程
2.4 程序运行测试报告
游戏的进入画面(有音乐,自动暂停):
游戏的初始界面或R键重新开始界面(暂停状态):
游戏进行过程中(出现加速道具):
一些关键的问题及其解决方法
如何判断小球的撞击。因为这里涉及到小球与墙壁、小球与砖块、小球与道具、小球与球拍、小球与飘动档板等多中可能撞击,而道具的位置和飘动砖块的位置都是不可预料的,那么如果判断小球与这么多的对象是否相撞呢?刚开始准备用坐标的方式进行判断,但当小球与飘动档板和道具相撞时,老是无法识别。后来我采用了对小球的下个位置的颜色值进行读取,如果是背景颜色,那么说明那个位置是没有其它对象的,那么小球就可以移动到那个位置。如果下个位置的颜色值不是背景颜色,那么说明小球将要与其它的对象进行碰撞,那么就需要对小球的方向向量进行改变。
另一个问题是如何通过小球与球拍的碰撞,而试小球产生出不同的方向向量呢?为了使得游戏更具有娱乐性,必须使得小球可以拥有不同的方向向量。但到底如何产生不同呢,我采用了球拍三分法,将球拍分为左、中、右三个部分,如果是小球与球拍在中间区域发生碰撞,那么小球将会发生的是正碰;如果小球与球拍在左区域发生碰撞,那么产生的方向向量将会偏左(与正碰比较);如果小球与球拍在右区域发生碰撞,那么产生的方向向量将会偏右(与正碰比较)。
程序存在的一些问题
汇编语言的焦点问题。由于汇编语言对多线程处理采用的是中断处理,在编程过程中也注意到了这一点,但是在后期测试的时候,多多少少还是会存在这类问题,比如提示字符串“Press space to play!”出现的位置会有变化。
中断次数问题。由于机器和模式的原因,本程序采用的是每秒中断18.2次,应该来说正常情况下视觉上是没有问题的,但是程序采用的是鼠标控制,所以时常会出现鼠标操作速度超过了中断次数导致视觉界面出现停留。
小球的方向变化问题。首先小球是正方形的,只不过让其显示出一个近似圆形的形状,所以在小球碰撞改变方向的时候,视觉上会略有偏差;其次小球在和下方球拍碰撞的时候,由于是不同位置应该会反射不同的角度,但是实际操作体验貌似感觉不是很明显;最后,小球在与砖块碰撞时,有时会出现貌似碰上了而且改变方向了,但易碎砖块没有任何变化。
道具出现时机。计划中应该是在打碎某个砖块时出现道具,结果在某些情况下会无缘的从屏幕某个地方出现道具。
3 结论
3.1 总结
经过这次的汇编专题实验之后,我感觉自己对汇编语言的理解更加深刻了,上学期的课内实验虽然在一定程度上促进了理论与实践的结合,但是却很有限,而这次的专题实验无论是规模上、难度上,还是老师和我的重视程度上,都是课内实验所不能比拟的。
实验开始时的选题报告,我经过对多个实验项目进行综合比较,结合自己的实际情况和兴趣爱好,最终选择了经典游戏打砖块。然而在对实验的具体方向和目标特色方面不是很清楚,经过马老师认真细致的指导,帮助我深入认识了自己的主题,如何能够展现出自己的特色,突出自己与别人不一样的地方,而且就实验中的一些细节与我展开了深入的对话。这个交流从根本上改变了我对自己项目的认识,也明确了从何处入手,把何处作为重点,如何促进实验的成功等很多具体详实的细节。
实验过程中,应该说困难相当多。最大的困难不是技术不熟悉,不是知识点没掌握,而是战线拉的太长导致的遗忘。由于汇编语言的特点,jmp的不断频繁出现,所以在一定时间之后,很容易模糊自己上次的努力方向和方法,然后就会花费大量时间来回顾和总结,结果在进度上有所延后。在中后期,我注意到了这个问题,所以几乎将程序的每一行都加了注释,每一小部分都描述了功能而且彼此隔开,每一大部分都独立成一体,除此之外,我还用文档的形式记录项目的进展、项目主要函数的功能,以方便查阅。
在后期的汇总过程中,突出的问题是发现了问题却无能为力。因为汇编语言特有的特点,项目完成后修改错误变的极为困难,有时即使可以修改也不敢去动,而只是在其他方面做些相对必要的修缮,这是因为汇编程序牵一发而动全身,一旦出错,只能回滚重来,高级语言程序的测试改错策略对汇编语言基本没用,汇编语言程序要想做的好,必须在构思、设计阶段做好充分的工作,而不能指望后期的测试。
3.2 展望
这次实验总体上是成功的,我从中也学到了不少东西,但是发现的问题仍然很多,在此次专题实验结束之后,有些问题或许需要更长的时间来品味和思索,留给我去探索的路还很漫长。一次实验是有结束期的,而我对未来对前景的追求是没有结束的,如何更好的运用此次实验中获得的经验,如何在将来的生涯中避免出现此次实验中的问题,仍然有很长的路要走。
这次实验带给我观念上的转变是巨大的。汇编语言的特点给我在思想上做了一个较大的修正,改变了以往高级语言的思维,使我对计算机底层的认识更加深入,也是我懂得了高级语言背后的本质。汇编语言的核心我似有涉及,但是完全不深入,对汇编的认识也停留在高级语言的替代方面,因此在以后的学习生活中,如何把握汇编的核心也需要慢慢去体会。