《软件工程与开发实践1》软件设计报告题目俄罗斯方块学院计算机学院专业计算机科学与技术班级学号10109345学生姓名其他成员组长指导教师孙志海完成日期2012年6月一、软件设计概述(目的、任务、开发环境、参考资料)俄罗斯方块游戏属于经典小游戏,游戏规则简单,但又不乏趣味。而计算的一大领域也是游戏,所以,成为游戏开发者,几乎是每个编程者的梦想。经过大一和大二的学习,我们已经掌握了编程基础。为了提高我们的编程能力,我们就要不断积累编程经验。1、目的:复习和巩固C/C++编程的基本思想;掌握数据结构的核心思想;掌握C/C++中多文件的编写;初步对了解界面的设计。2、任务:完成一个可以运行的游戏。3、开发环境:C/C++控制台。4、参考资料:[1]谭浩强.C语言程序设计[M].北京:清华大学出版社.2004.6[2]孙鑫\余安萍.VC++深入详解[M].北京:电子工业出版社.2006.6二、可行性研究、需求分析及分工这是一个游戏软件,程序与用户的交流只在游戏界面上,方块的产生是随机的。三、软件设计的基本原理和采用的主要方法与技术1、方块类型以下7大类████████████████████████████每一种方块都能够变形,所以在游戏中如何正确打印出方块的类型是重点,也是难点。我采用的是“相对坐标法”,具体实现参照“实现的过程与步骤”部分。2、此游戏是简单的二维游戏,而且区域恒定不变,所以在存储游戏的信息时,二维数组是首选。用数组元素值模拟当前位置有无方块。3、流程图如下4、采用的方法在控制台下,光标是左到右,自上而下的,所以要要调用系统函数来控制光标。同理,为了界面的美观,也要调用系统函数进行颜色控制。5界面设置游戏的最大特点就是界面的美观,由此才能吸引玩家的兴趣,因此如何让界面尽最大限度美观,是每个游戏程序员努力的目标。这个程序是在VC环境下基于C/C++控制台的,由于VC下没有像TC下那样丰富的图形库,画图就要调用windowsAPI函数。但由于我对windowsAPI理解不深,所以画起图来还是比较困难。游戏不仅要求界面美观,而且还要音乐来衬托,所以在整个程序中,尽量让方块的每一个动作与特殊的音乐像对应,此外,最好加上背景音乐。四、实现的过程与步骤数据结构:1、方块的存储如下图所示,每一种方块都由四个小方块组成,可以按顺序编号①、②、③、④,在方块旋转、输出、擦出时,可以由第一个方块位置加上(减去)第二个与第一个的偏移量,从而找到第二个方块,如此可以方便遍历四个方块。由于方块属于宽字符,故在占两个字节,输出的时候占两位。设①号块的坐标为(x,y),那么第二块与它的偏移量的△x=2,△y=0,相对坐标即为(2,0)。同理,③号方块的相对坐标为(2,1),④号方块的坐标为(4,1),特别的,①号方块的相对坐标为(0,0),这样一来,只要知道每一种(7大类,19种)方块的①号方块的坐标,就可以通过②、③、④方块与①号方块的偏移量而逐个输出整个方块。明白了方块的输出,就要用一个数据结构存储方块了。19种方块都由4个小方块组成,在打印的时候最好能用一个4次的循环,这样代码也显得简单。综上以上两点,可以定义结构体typedefstruct_VARY{intvary_x[4];intvary_y[4];}VARY;存储4个小块之间x,y坐标之间的相对偏移量。在定义变量的时候初始化成VARYvary[]={{{0,2,4,6},{0,0,0,0}},{{0,0,0,0},{0,-1,-2,-3}},{{0,2,2,0},{0,0,-1,-1}},{{0,-2,-2,-4},{0,0,-1,-1}},{{0,0,2,2},{0,-1,-1,-2}},{{0,2,2,4},{0,0,-1,-1}},{{0,0,-2,-2},{0,-1,-1,-2}},{{0,0,2,4},{0,-1,0,0}},{{0,0,0,2},{0,-1,-2,-2}},{{0,0,-2,-4},{0,-1,-1,-1}},{{0,2,2,2},{0,0,-1,-2}},{{0,2,4,4},{0,0,0,-1}},{{0,-2,-2,-2},{0,0,-1,-2}},{{0,0,2,4},{0,-1,-1,-1}},{{0,0,0,-2},{0,-1,-2,-2}},{{0,2,4,2}{0,0,0,-1}},{{0,0,2,0},{0,-1,-1,-2}},{{0,-2,0,2},{0,-1,-1,-1}},{{0,0,-2,0},{0,-1,-1,-2}},};这样,7大类,共19中方块就全部存储了,在输出时只要用一个4次循环就可以了。2、控制变形→变形→12如图所示,1种方块经变形得到到2种方块。那么如何才能根据1种方块快速地找到2种方块,这是关键。有方块的存储可以联想到,在这儿能不能也用相对坐标来存储呢?可以的!如图所以,设1种块的①号小块坐标为(x,y),则2种块的①号小块坐标为(x+2,y-1),其相对偏移量为(2,-1)。这样一来在当方块1变为方块2时,只需把光标移动到点(x+2,y-1)处即可,然后可以通过一个4次循环,完整打印出2方块。明白了控制变形,就要设计数据结构。但是每一类方块通过变形产生的子类方块的目是不定的,如上类方块变形可生成两类,但是“山”字形方块变形,最多可产生4类,而“田”形的只有一类。所以在存储变形的相对位置时,还要将7大类方块变形产生方块的数目记下来。综上,可以定义如下结构体typedefstruct_CONNECTION{intsum;intconnection_x[5];intconnection_y[5];}CONNECTION;其中,sum用来存储该类变形产生的方块数,connection_x[5]和connection_y[5]用来每一个子类与大类直接的相对位置。由于每一大类变形产生的子类数目不定,故用一个能容下最大量是数组。有了结构体的定义,可以具体定义变量CONNECTIONconnection[]={{2,{-2,2},{0,1}},{1,{0},{0}},{2,{2,-2},{0,0}},{2,{-2,2},{0,0}},{4,{-2,0,4,-2},{0,0,-1,1}},{4,{-4,2,-2,4},{0,0,-1,1}},{4,{-2,2,0,0},{0,0,0,0}},};这样,在连续变形中,可以通过不断对sum去余而得到当钱方块形状。3、用户界面的存储正如前面所说,俄罗斯方块是个二维游戏,所以很容易想到用二维数组存储界面信息。用数组元素和界面建立一一对应的关系,在游戏过程中会出现销行,所以,这就要存储当前位置有无方块,如果,为了界面美观,还要存储当前位置方块的颜色。综上,可以定义一下结构体:typedefstruct_BOARD{intx;inty;intcolor;boolhaving;}BOARD;游戏的界面大小根据开发者自己定义,这儿用满格为15*25的区域,即定义界面面板BOARDboard[15][25];主要函数1、voidchoice_direction(int*prev_count)作用:实现上、下、左、右的选择。参数:旋转次数指针,用来记录当前方块旋转次数。实现:根据_kbhit()函数判断当前有无按键,若无,则方块下降;若有,则用switch()判断是哪个键,根据键值修改跟踪方块的参数。2、voidget_depth(int*pdepth)作用:计算当前方块可以下降的高度。参数:高度值。实现:由于每个方块有4个小方块,而且每个小方块的上下位置不一,而方块能够下降的高度,应该是最小高度,所以应该在4个值里面去最小值。3、voidcheck_boundary()作用:判断当前方块是否已处于左右边界。实现:同上,方块有4个小块,在判断是否到达边界时,四个小块都要判断。若没有触界,则处理左右按键引起的水平移动;若已处于边界,则对左右按键引起的参数值的改变进行复原。4、voidinvilate()作用:方块到达底部后数据记录实现:根据方块的类型、坐标,对应对数组里面的个元素赋值,包括坐标点和颜色值已经状态量。五、遇到的困难与获得的主要成果1、方块的存储和变形这个问题我想了好久。开始的时候我想着把19种方块分开了,但那样出现的问题就是:竖直下落的时候很简单,但是如果变形,新方块的位置怎么确定,鉴于这个问题,最初的方法还是放弃了。我用“相对偏移法”的灵感来自于指针和寻址。在存储的时候,第一方块的位置就相当于是基“地址”,而后面的3个小块都可以通过加上一个相对值来找到,这与指针和相对寻址不是很相似吗?2、判断有误按键开始的时候给按键开了一个进程,用来判断有误按键。但是那样也有个不足的地方,每一种进程执行的时间长度是随机的,如果不对每个进程进行控制,就会出现“在输出四个小块的时候,值输出了前两个小块,CPU就转向判断有误按键了,而如果这时恰好有按键,而且是水平位移键,出现的结果就是部分小块在左端,部分小块在右端”的情况,很显然,这是绝对不可以的。之前我不知道有_kbhit()这个函数,我也是在阅读别人的代码时发现这个函数的。有了这个函数,就不用开线程了,也不用去控制线程了,程序简洁多了。3、获取方块可以下落的高度如前面函数声明里所说,方块下落的高度要去最小值。但是随着水平按键,导致方块的左右移动,方块能够下降的高度随时都在变。也可以用另一个线程获取下降高度,但不好的情况和用线程判断按键类似,所以我的处理就是在每一次方块的位置改变时,都调用获取高度的函数。4、方块的下落方块下落的高度有获取高度函数确定。但是会有一种极端情况出现,如下所示:方块道道第3列上方时,它能下降的高度已经是0,但是如果在它到到第3列上方,随后立即按了水平右移键,那么,它能下降的高度至少是4,而不是0。因此一味得说能下降的高度是0时,它就停止下降,显然是不对的。下降高度是0时,下降函数就马上执行完了,至少是跳出了下降循环,所以就要想办法,在在下降函数执行马上结束时,再一次判定它的能够下降的高度,如果高度不是0,那就再次调用下降函数(它自己),也就是递归,如果下降高度还是0,那么这个方块就落到底部了。采用这种方法可以很好的解决在水平移动时的类似问题。但是这种方法也有个不足就是,如果当方块已经到底了,这时仍然按了水平移动键,而此时,它又满足水平移动的要求,那么他就反复递归了,递归栈越来越深,只有累计时间溢出时,下一个方块才能落下。5、多文件编写以前写程序都是一main到底,多文件的。初次试着用多文件编写,遇到的主要问题是全局变量的存放。在多文件编写中,变量,尤其的全局变量的定义和引用显得不那么精简。很多人可能会想,把全局变量放在一个*.h文件中,然后在以后的调用中只要include一下不就可以了?想想也有道理,include不就是把*.h原封不动的复制过来吗?但是,这样存在一个问题。加入定义了10个全局变量,而在具体一个*.c文件中我只用其中一个。如果用include把全局变量全都包含进来。那利用率不就才是10%吗。而且C语言常用于嵌入式,如果每个文件都include一个全局变量头文件,那么这些变量就被分配一次内存,这对嵌入式来说肯定是致命的;另外,如果有很多个文件都include了全局变量的头文件,编译器要跟踪每一个文件对其中一个变量的更改,这对编译器来说也是吃不消的。所以便有了*.c和*.h的区别。很多人都知道声明和定义的区别——一个分配内存,一个不分配内存,而且函数的声明和定义更是显而易见,但是对于变量的声明和定义,就显得有些模糊不清。由于如上所述的种种原因,C语言中全局变量放在*.c文件,而不放在*.h文件。这样一来,*.h文件好像无用武之地了,非也。我们经常见到#include****.h,但有谁见过#include***.c吗?所以*.h文件用来声明,*.c文件用来实现,在调用处用extern声明。1、普通变量定义成全局变量如果是普通类型,完全可以不用*.h文件,