《C++程序设计》课程设计报告题 目: 俄罗斯方块 学 院: 专 业: 学生姓名: 学 号: 指导教师: 2012 年 月 日目 录5. 班级通讯录 II1.前言 12.需求分析 23.概要设计 44.详细设计 65.测试 86.总结 8附录 10前言C++程序设计牵涉到面向对象程序设计的理论、C++语言的语法以及算法等3个方面的内容,其中每一方面都包含十分丰富的内容,都可以分别单独成论显然在一个程序中深入、详细地介绍以上3个方面的知识是不可能的,必须把它们有机地结合起来,综合应用不同的书对此采取不同的写法,侧重点有所不同,各有道理,也各有优缺点,适合于不同的读者需要在教学实践中检验,取长补短,不断完善作者认为:要进行C++程序设计当然需要了解面向对象程序设计的有关概念,但是本课程毕竟不是一门面向对象程序设计的理论课程,在本书中不是抽象地介绍面向对象程序设计的理论,而是在介绍C++语言的特点和应用过程中自然地引出面向对象程序设计的有关概念,通过C++的编程过程理解面向对象程序设计方法。
在介绍程序设计过程中,介绍有关的算法,引导读者思考怎样构造一个算法编写程序的过程就是设计算法的过程要用C++编程序,最基本的要求是正确掌握和运用C++由于C++语法复杂,内容又多,如果对它缺乏系统的了解,将难以真正应用,编出来的程序将会错误百出,编译出错,事倍功半本书的做法是全面而系统地介绍C++的主要特点和功能,引导读者由简而繁地学会编写C++程序有了C++编程的初步基础后,再进一步提高,掌握更多更深入的算法这样的方法可能符合大多数学习者的情况,降低了学习难度程序设计是一门实践性很强的课程,只靠听课和看书是学不好的衡量学习好坏的标准不是“懂不懂”,而是“会不会干”因此必须强调多编程,多上机实践考虑到不同学校、不同专业、不同读者对学习C++有不同的要求1. 需求分析1.1总体需求分析 剖析游戏的实质,可以发现:首先,该游戏需要一个良好且可控的界面,能够接受鼠标和键盘的响应,可以供玩家选择自己想要的游戏级别、背景颜色和是否开启背景音乐,实现个性化设置;其次,由于游戏涉及到许多不同种类的方块,所以需要来构造这些方块并且设置一种旋转规则实现方块的各种旋转;再者,在游戏过程中,必须要处理好方块的一些事件,例如自动下落,消除满行,下一个方块的显示以及游戏的自主升级。
1.2方块及各种变换需求分析本游戏需要有7种方块,而每种方块还可以进行旋转每种方块每行每列最多只有4个小方块可以将它们放在一个n*m的区域内,该区域可以看作是有许多个等面积小方块构成的区域,而这些区域的状态只有两种,被方块占据或空闲,因此,对于整个游戏区域的空间是占据或空闲,可以用一位数来标识对于7种方块和它们旋转后的形态我们可以用不同的标识进行标记对于旋转,游戏中所有方块都是按照逆时针旋转的规则进行的,而且在旋转过程中它们不会因为旋转而下降,总会保持在同一高度任何方块经过一个旋转周期还会变回原型1.3正常工作和中断操作的需求分析在无人操作时,游戏开始后会随机产生一个方块,先显示在界面右上角的显示区域,然后转到游戏区域,右上角又随机产生一个新的方块,当该方块下落到底后,新的方块再次进入游戏区域,如此循环,直到游戏结束/暂停,这就是游戏的正常工作上述过程是在无人操作时游戏自动运行的,当用户进行一定的操作交互的时候,运行程序可以根据用户的操作指示进行当前方块的控制而这些操作都是响应相关的功能键而执行的,所以这里可以把它看成一种“中断”的操作在中断过程中,可以通过键盘包括按某些键进行操作这里设置默认键向上键为变形,向下键款速下降,向左键为左移,向右键友谊,空格键为暂停, 回车键为开始。
向上键产生方块旋转操作,方块并非任何情况都能旋转,如果旋转后与小方格矩阵显示的方块有冲突或超出边界时,均不能发生旋转因此首先要判断是否有足够的空间进行旋转,然后选择是否旋转向下键产生方块加速下落操作,如果方块已经到达游戏区域底部或者已经有其他方块遮挡,则方块停止下降向左键产生下落方块左移操作首先要判断此方块是否能够发生左移,当越界或被其他显示方块阻挡时,则不能左移向右键产生下落方块右移操作首先要判断此方块是否能够发生右移,当越界或被其他显示方块阻挡时,则不能右移空格键产生方块一键到底的操作,如果方块已经到达游戏区域底部或者其下方已经有其他方块遮挡,则方块不能再下降1.4主界面需求分析主界面包括主游戏界面,预览界面和得分界面由于比较简单,没有设置主菜单,功能按钮等只在右上角显示下一个方块,右下角显示级别、分数1.5记分需求分析计分需求主要是描述游戏中的得分规则,每消一行得2分初始级别为一级,随分数增加自动升级,速度加快2. 概要设计 2.1主要功能设计根据分析,俄罗斯方块这个程序一共要实现如下几个功能,开始游戏、游戏的暂停\继续、游戏控制和退出游戏其中游戏控制最为主要和重要,它控制着整个游戏的画面和有关数据的操作,是游戏的核心部分。
暂停和退出功能做成一体,在退出的提示下不做任何操作即可实现暂停的功能程序结构如图2.1所示 图2.1 2.2程序流程图根据分析后的程序结构图设计出相应的流程图俄罗斯方块的内容主要包括游戏开始,画背景和边框,显示分数等级和下一个方块的预览图;根据速度没隔一定时间方块自动下落,当有按键操作时,根据相应按键执行动作,每次动作前要判断是否动作可以执行下落方块满一行时,消去该行,根据消去行数得到相应分数分数达到一定程度,等级提升,速度加快如图2.2所示 图2.2 3. 详细设计3.1函数声明bool rotate(sCord *lpsCord,int rType,int rNumber,bool firstRotate);void getRandom();//初始化方格形状void getNext(sCord *targ,const sCord* sur);//取出下一个方块void draw();//绘出方格void start();//开始游戏bool downAble();//能否下落bool leftAble();//能否左移bool rightAble();//能否右移bool disRows(HWND hwnd);//判断能否消行3.2函数功能说明3.2.1游戏界面游戏界面采用WndClass函数编写WNDCLASS wndclass; wndclass.style=CS_HREDRAW|CS_VREDRAW;//|~(WS_MINIMIZEBOX|WS_MAXIMIZEBOX) ; wndclass.lpfnWndProc=WndProc; wndclass.cbClsExtra=0; wndclass.cbWndExtra=0; wndclass.hInstance=hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground= (HBRUSH)GetStockObject (WHITE_BRUSH);// CreateSolidBrush(RGB(195,195,237)); wndclass.lpszMenuName= NULL; wndclass.lpszClassName= szAppName; 俄罗斯方块的游戏界面包括游戏区域边框、下落方块绘制、右部计分和预览图显示等。
游戏区域边框的绘制比较简单,循环中确定光标的位置输出特定字符,即可完成边框绘制游戏区方块的绘制,循环从数据数组中依次读出数据,根据读到的数据显示“□”,最后组成方块的形状,完成方块的绘制计分和预览图部分先画出一个矩形区域,然后控制光标在其中显示分数、等级、预览图和提示信息3.2.2生成方块本程序中生成的方块有七种形状,在程序运行生成方块时,调k=rand()%KINDS+1;语句,确定当前要显示的是哪一个方块形状而在实际运行中,第一次需要调用两次生成方块函数getRandom(),将先产生的赋给游戏当前方块,第二个赋给预览图方块以后每次产生一个方块,把预览方块赋给当前方块,把新产生的赋给预览方块3.2.3方块变形 俄罗斯方块的特点就在于通过方块的变形拼满整行来消去该行方块从而使游戏可以继续下去,很难想象不能变形的俄罗斯方块是什么样子而变形的过程就是根据当前方块形状改变方块的相对位置,这样就可以改变方块的形状了在程序中每当按下Up键,程序判断可以变形后,根据当前方块的形状序号和变化形状序号调用相应的方块数值赋给draw()函数,通过刷新重画就可以显示变化后的方块了3.2.4方块显示 以上方块的操作都是数据层面的操作,而真正要在游戏窗口中看到数据的变化,还必须把方块不断的绘制出来。
这就是draw()函数的作用把当前运动的方块对应节点存储在一个4*4数组里,变形和生成方块的过程就是更新该数组数据的过程然后在draw()函数里检测数组的各个值,并控制光标跳到一定位置,画出“■”组成方块3.2.5障碍判断 障碍判断,就是在方块运动中或者变形中判断周围是否有障碍阻碍下落、移位、变形当方块下落遇到下面有方块或者到达下边界则应停止下落并记录数据,存入背景数据数组变形时应判断这个变形是否可以进行,如果有障碍则不能变形例如当方块达到右边界,而若变形则会越过边界,那么这个变形的命令是不应执行的所有这些判断都由meet()函数进行,根据是否有障碍返回1或0,再由其他函数接收执行相应操作3.2.6消行计分 游戏玩家拼满一行后,程序消去满行,并计分中当一个方块下落停止后,程序检查方块是否充满了游戏区域,如果是结束游戏不是,则判断是否构成消行条件,从下落方块的最低点依次向上检查是否可以消行,根据消去行数分数增加分数达到一定程度,等级提升,速度加快4. 测试经过调试和修改,程序完全实现设计要求,成功模拟了俄罗斯方块的运行过程和游戏效果,只是界面略微简陋,但已从程序层面上实现了游戏,达到了这次实训的要求和目的。
程序正常生成方块,根据速度值每隔一定时间自动下落,如有操作按键按下,根据按键实现位移和变形当方块满一行后,可以消除该行,同时记录分数和等级5. 总结通过这次课程设计,我收获了很多首先把所学知识加以利用和巩固,其次在实践中遇到问题去探索和学习,更增加了新知识实践证明达到了预期的目的,积累了经验由于程序是用文本窗口模拟的图形,界面比较简陋,如果使用MFC用C++来实现,那么界面将会非常好,只是由于所学知识有限,只有下一步去探索了通过这次的学习设计,我发现我还有许许多多的不足的地方,比如c++的程序设计,源代码的书写等等,刚开始我发现我的问题后,十分紧张,感觉很绝望,没有别人的帮助,自己动手设计曾经自己想都没想过的东西,虽然很兴奋,但想想自己无从下手,后来通过上网查找资料,进图书馆查找书籍等,终于知道了俄罗斯方块游戏的设计概念,终于知道了设计的方法,于是,渐渐地我的游戏设计理念诞生了通过这次设计,我学会了很多东西,例如通过网络资料来寻求帮助,自己改正错误,加强了我自己的自己动手能力,对今后的学习和生活有很大的帮助,有助于以后的课程设计顺利完成参考文献[1]杨永国.Visual C++ 6.0实用教程.北京:清华大学出版社.2004年[2]唐俊明.Visual C++ 6.0 编程实例与技巧.北京:高等教育出版,2002年[3]潘锦平.软件系统开发技术.西安:西安电子科技大学出版社, 1997年 [4] 孙鑫.VC++深入讲解.北京:电子工业出版社,2006年附录附录关键代码或其它有需要附录的内容,代码要有基本的注释。
这是头文件data.h:#ifndef DATA_H_#define DATA_H_#includestatic const int KINDS=7;//方块种类数量static const int COLS=10;//数据列数/* * 7行10列数组 每行对应一种方块类别 * 每行的前四列为x坐标,中间四列为y坐标 * 第九列为方块类别代码,最后一列为该类型方块有几种变形 * 用一个5*5的矩阵表示 7种类别方块 共19种变形 *///每种方块的代号static const int TYPE1=1;static const int TYPE2=2;static const int TYPE3=3;static const int TYPE4=4;static const int TYPE5=5;static const int TYPE6=6;static const int TYPE7=7;//变形的种类static const int RTYPE1=1;static const int RTYPE2=2;static const int RTYPE3=4;static int rTypeNext;static int rTypeDown;//初始化方块坐标及对应的类别和变形种类static const int index[KINDS][COLS]={ {0,1,0,1,0,0,-1,-1,TYPE1,RTYPE1}, {-1,0,1,2,0,0,0,0,TYPE2,RTYPE2}, {0,0,1,1,1,0,0,-1,TYPE3,RTYPE2}, {0,0,1,1,-1,0,0,1,TYPE4,RTYPE2}, {-1,0,0,1,0,0,1,0,TYPE5,RTYPE3}, {-1,0,1,1,0,0,0,-1,TYPE6,RTYPE3}, {-1,0,1,1,0,0,0,1,TYPE7,RTYPE3}};//定时器IDstatic const int TIMER=1;////初始游戏级别对应的时间间隔static int CURRENTLEVEL=600;static int level=1;//每种图形所包含的小方块数static const int CTN=4;typedef struct { //方块形状定义int x;int y;}sCord;sCord sDown[CTN],sNext[CTN];//下一个方块的坐标static RECT rectNext[CTN];//正在下落方块的坐标static RECT rectDown[CTN];//显示区域的大小static const int cxSize=25;static const int cySize=35;//方块偏离(0,0)得位置static int offsetx;static int offsety;static int offsetxNext;static int offsetyNext;//自定义消息static const int MS_DOWN=10001;//暂停static bool go=true;//开始static bool startGame=false;//结束static bool gameOver=false;//得分static int score;RECT rt={326,81,425,455};//每个方格包含的像素static const int pelsSize=13;//显示区域大小的定义static const POINT area[]={0,455,326,455,326,0};//显示区域的表示方法 最后一列最后一行分别对应该行该列所具有的方块总数 0表示没有方块1表示有static int fillArea[cySize+1][cxSize+1];HBRUSH hBrush1=CreateSolidBrush(RGB(0,0,255));//方块颜色:蓝色HBRUSH hBrush2=CreateSolidBrush(RGB(255,0,128)); //分数区域颜色:粉色HPEN hPen1=CreatePen(PS_SOLID,0,RGB(245,245,245));//背景格颜色:灰色#endif /* DATA_H_ */这是源文件MainPro.cpp#include"Data.h"#include#includeLRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);/* * 逆时针方向旋转方格 返回能否旋转 * @param lpsCord 要旋转的方块坐标指针 * @param rType 旋转类型 * @param rNumber 旋转次数 * @param 是否第一次旋转 */bool rotate(sCord *lpsCord,int rType,int rNumber,bool firstRotate);void getRandom();//初始化方格形状void getNext(sCord *targ,const sCord* sur);//取出下一个方块void draw();//绘出方格void start();//开始游戏bool downAble();//能否下落bool leftAble();//能否左移bool rightAble();//能否右移bool disRows(HWND hwnd);//判断能否消行void getRandom() //初始化方格形状{ int k=rand()%KINDS+1; for(int i=0;i sNext[t].y ? sNext[t].y : min_y; offsetxNext=(int)(cxSize/2)*pelsSize+(int)(pelsSize/2);//x方向的中间显示 offsetyNext=(-min_y)*pelsSize+(int)(pelsSize/2);//保证置顶显示}bool rotate(sCord *lpsCord,int rType,int rNumber,bool firstRotate) // 旋转{ int tempx; int tempy; int temp; int tx=(offsetx-(int)(pelsSize/2))/pelsSize; int ty=(offsety-(int)(pelsSize/2))/pelsSize; bool ra=true; switch(rType) { case RTYPE1: ra=false; break; case RTYPE2: { if(rNumber%2!=0) { for (int j = 0; j < CTN; j++) { tempx=-lpsCord->y+tx; tempy=lpsCord->x+ty; lpsCord++; if(!firstRotate&&(fillArea[tempx][tempy]>0||tempx>24||tempx<0||tempy<0||tempy>34)) { ra=false; } } lpsCord-=4; } if(ra) { if (rNumber % 2 != 0) for (int k = 0; k < CTN; k++) { temp = -lpsCord->x; lpsCord->x = lpsCord->y; lpsCord->y = temp; lpsCord++; } } } break; case RTYPE3: for(int k=0;ky+tx; tempy=(-lpsCord->x)+ty; lpsCord++; if(!firstRotate&&(fillArea[tempx][tempy]>0||tempx>24||tempx<0||tempy<0||tempy>34)) { ra = false; } } lpsCord-=4; } if(ra) for (int i = 0; i < rNumber; i++) { for (int j = 0; j < CTN; j++) { temp = -lpsCord->x; lpsCord->x = lpsCord->y; lpsCord->y = temp; lpsCord++; } lpsCord=lpsCord-4; } break; } return ra;}void getNext(sCord *targ,const sCord* sur) //取出下一个方块{ rTypeDown=rTypeNext; offsetx=offsetxNext; offsety=offsetyNext; for(int i=0;ix=sur->x; targ->y=sur->y; sur++; targ++; } getRandom();}void draw(HWND hwnd,const sCord* shape,RECT *rect,HBRUSH hBrush,int offsetx,int offsety) //绘出方格 { HDC hdc=GetDC(hwnd); SelectObject(hdc,hBrush); SelectObject(hdc,hPen1);for(int i=0;ix-(int)(pelsSize/2)+offsetx,pelsSize*shape->y-(int)(pelsSize/2)+offsety, pelsSize*shape->x+(int)(pelsSize/2)+offsetx+2,pelsSize*shape->y+(int)(pelsSize/2)+offsety+2); shape++;} ReleaseDC(hwnd,hdc);}void start() //开始游戏{ if(!startGame) { for (int i = 0; i < cySize + 1; i++) for (int j = 0; j < cxSize + 1; j++) fillArea[i][j] = 0; startGame=true; go=true; score=0; }}bool downAble() //能否下落{ bool da=true; int x=(offsetx-(int)(pelsSize/2))/pelsSize; int y=(offsety-(int)(pelsSize/2))/pelsSize; int xtemp; int ytemp; for(int i=0;i0||ytemp>34) { da=false; break; } } if (!da) { for (int k = 0; k < CTN; k++) { xtemp = sDown[k].x + x; ytemp = sDown[k].y + y; fillArea[ytemp][xtemp] = 1; fillArea[ytemp][cxSize]++; fillArea[cySize][xtemp]++; } } return da;}bool leftAble() //能否左移{ bool la = true; int x = (offsetx - (int) (pelsSize / 2)) / pelsSize; int y = (offsety - (int) (pelsSize / 2)) / pelsSize; int xtemp; int ytemp; for (int i = 0; i < CTN; i++) { xtemp = sDown[i].x + x-1; ytemp = sDown[i].y + y; if (fillArea[ytemp][xtemp] > 0 || xtemp <0) { la = false; break; } } return la;}bool rightAble() //能否右移{ bool ra = true; int x = (offsetx - (int) (pelsSize / 2)) / pelsSize; int y = (offsety - (int) (pelsSize / 2)) / pelsSize ; int xtemp; int ytemp; for (int i = 0; i < CTN; i++) { xtemp = sDown[i].x + x+1; ytemp = sDown[i].y + y; if (fillArea[ytemp][xtemp] > 0 || xtemp > 24) { ra = false; break; } } return ra;}bool disRows(HWND hwnd) //消行{ HDC hdc=GetDC(hwnd); bool da=false; int row[CTN];//可以消除的行 for (int ii = 0; ii < CTN; ii++) row[ii] = 0; int number = 0;//可连续消的行数 static int levelScore; SelectObject(hdc,hPen1); for (int i = 0; i < cySize; i++) { if (fillArea[i][cxSize] == cxSize) row[number++] = i; } if (number > 0)//可以消行 { da=true; levelScore+=(number + 1) * number / 2; score += (number + 1) * number / 2; if(levelScore>2)//增加游戏级别 { levelScore=0; CURRENTLEVEL=(int)CURRENTLEVEL*2/3; SetTimer(hwnd,TIMER,CURRENTLEVEL,NULL); for(int i=0;i<15;i++) { if((int)CURRENTLEVEL*3*(i+1)/2>=600) { level=i+2; break; } } } InvalidateRect(hwnd,&rt,true); for (int k = 0; k < number; k++) { for(int i=row[k];i>0;i--) { for(int j=0;j0) { Rectangle(hdc,k*pelsSize,t*pelsSize,(k+1)*pelsSize+1,(t+1)*pelsSize+1); } } if(startGame) { draw(hwnd,sNext,rectNext,hBrush1,369,44); draw(hwnd,sDown,rectDown,hBrush1,offsetx,offsety); } FillRect(hdc,&rt,hBrush2); WCHAR ss[20]; WCHAR ll[20]; wsprintf(ss,TEXT("score:%d"),score); wsprintf(ll,TEXT("level:%d"),level); TextOut(hdc,331,300,ll,lstrlen(ll)); TextOut(hdc,331,320,ss,lstrlen(ss)); if(gameOver) { TextOut(hdc,330,200,TEXT("game over!"),strlen("game over!")); } SelectObject(hdc, GetStockObject(BLACK_PEN)); Polyline(hdc, area, 3);//绘制一个矩形 MoveToEx(hdc, 325, 80, NULL); LineTo(hdc, 425, 80); EndPaint(hwnd, &ps); return 0; } case WM_KEYDOWN: switch(wParam) { case VK_UP: { if(go&&startGame) { down=false; draw(hwnd,sDown,rectDown,(HBRUSH)GetStockObject(WHITE_BRUSH),offsetx,offsety); rotate(sDown,rTypeDown,1,false); draw(hwnd,sDown,rectDown,hBrush1,offsetx,offsety); } } return 0; case VK_DOWN: { if(go&&startGame) { down=false;draw(hwnd,sDown,rectDown,(HBRUSH)GetStockObject(WHITE_BRUSH),offsetx,offsety); int k=(offsety-(int)(pelsSize)/2)/pelsSize; while(k