《程序设计综合课程设计》报告
——基本MFC类框架的俄罗斯方块游戏设计
设计目的
PC游戏早已在IT行业形成了庞大的产业链,很多IT企业都凭借着自己雄厚的游戏开发技术而占据电子游戏娱乐市场。俄罗斯方块是一款风靡全球的PC机游戏和掌上机游戏,它造成的轰动与创造的经济价值可以说是游戏史上的一件大事。这款游戏看似简单却变化无穷。通过该游戏设计,达到以下目的:
- 了解Visual C++ 6.0下可视化编程的特点,熟悉其相应的各种操作和技巧。
- 了解随机函数的使用、动态存储空间的分配和回收、位图资源的引入和操作。
- 掌握MFC类家族中,键盘消息、菜单、工具栏、定时器、画笔画刷的使用。
- 掌握俄罗斯方块游戏开发的基本原理,强化基本编程能力和游戏开发技巧。
- 学会PC游戏设计的重要理念,区分开游戏逻辑代码和游戏地图逻辑坐标。
任务内容
本设计要求采用Visual C++6.0 下可视化编程,充分利用MFC类家族中的类和库函数,实现游戏方块预览、游戏方块控制、游戏显示更新、游戏分数、游戏等级、游戏帮助等功能。对该游戏系统功能模块进行详细分析,写出详细设计说明文档,编写程序代码,代码量要求不少于300行。调试程序使其能正确运行。
设计要求
具体要求如下:
- 屏幕中央有一个矩形“容器”,选择“开始”菜单或“开始”工具按钮后,俄罗斯方块的部件随机产生并在容器中自由下落。
- 游戏过程中,当在矩形“容器”中,出现一个游戏方块时,必须在游戏方块的预览区域中出现下一方块,这样利于游戏玩家控制游戏的策略。
- 有七种标准俄罗斯方块部件,通过各种判断,实现游戏方块并随着键盘上的上键顺时针旋转90度,随着下键加速下落,随着左、右各键分别左、右移动。
- 当部件下落达“容器”底部或已停止的部件时,停止下落;当同一行部件完整拼接上时,该行消失,其他行向下移动,在适当位置显示当前累计分数。
- 当部件总行数超过矩形容器的高度或没有足够的空间产生新的部件时,提示“游戏结束”信息并停止游戏运行。
- 游戏计分原则:一次消去一行加10分,同时消去2行、3行、4行分别加30分、50分、70分。该游戏设置有3个级别,分别为1——简单、2——普通、3——较难,难度越大,方块下落的速度越快。
- 游戏帮助功能:玩家进入游戏,可以通过菜单中的“帮助”菜单或工具栏上的“?”工具按钮,获得游戏如何操作的友情提示。
设计说明
- 需求分析
基本思路是采用MFC AppWizard应用程序的SDI程序框架,设计该游戏,实现方块预览、方块控制、显示更新、游戏记分、游戏等级、游戏帮助等功能。方块控制,直接由玩家通过键盘控制,游戏区域的方块部件根据玩家具体键盘的操作左右移动、旋转、加速下落。游戏地图即矩形“容器”,根据具体数据,直接通过画笔画刷绘制。
主要的设计难点:
□ 如何用内部数据结构表示当前部件的状态;
□ 如何旋转方块、判断部件放置位置及使满行消失;
□ 如何实现方快的预览功能;
□ 怎样动态地分配内存。
- 游戏运行主要流程分析
如 图 1 所示
图 1 游戏运行主要流程
- 设计难点的解决方案:
- 所有的部件及已停止的部件均用小方格来表示。整个游戏区域对应一个二维数组,数组元素为0时,表示空白;为1时,表示已有方格。该数组存储所有已经不能移动的部件。部件采用一维数组表示,这些一维数组实际表是一个n×n的矩阵。如表示一个方块使用一个2×2的矩阵,实际存储{1,1,1,1};如表示一个长条使用一个4×4的矩阵,实际存储{0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0}。
- 实际显示的时候,先画出已停止的方块,然后计算出正在下落的部件的正确坐标位置并其将画出,同时在固定位置画出预览方块。
- 旋转部件时,只需变换存储部件的矩阵即矩阵的转置,使其对应为转换后的形态,并将其显示出即可。
- 判断部件是否可以下落、旋转、左移、右移时,将表示部件的数组对应到游戏区域的二维数组中,再判断是否允许操作。当部件无法再操作时,将部件数组中对应填入游戏区域中对应的二维数组。
- 使一层消失可以通过判断游戏区域中的二维数组是否某一行全为1;如果该数组的第一行全为1或已没有足够的空白使新的部件可以加入到游戏区域中,则游戏结束。
- 数据结构设计
□游戏方块Componet结构体
typedef struct tagComponet
{
int intComID; //部件的ID号
int ntDimension; //存储该部件所需的数组维数
int* pintArray; //指向存储该部件的数组
}Componet;
Componet结构体表示某个游戏方块具体的属性,每个游戏方块对应有一个编号,且有对应存储该部件的数组,用指针指向该数组,并设定存储该部件所需数组(n×n)的维数。
□Componet结构体数组
初始化游戏方块,即定义MAXCOM个Componet类型的结构体,并初始化(MAXCOM为7)。例如
/ //6
m_Componets[6].intComID=6;
m_Componets[6].intDimension=4;
m_Componets[6].pintArray=new int[16];
m_Componets[6].pintArray[0]=0;
m_Componets[6].pintArray[1]=1;
m_Componets[6].pintArray[2]=0;
m_Componets[6].pintArray[3]=0; //0 1 0 0
m_Componets[6].pintArray[4]=0; //0 1 0 0
m_Componets[6].pintArray[5]=1; //0 1 0 0
m_Componets[6].pintArray[6]=0; //0 1 0 0
m_Componets[6].pintArray[7]=0;
m_Componets[6].pintArray[8]=0;
m_Componets[6].pintArray[9]=1;
m_Componets[6].pintArray[10]=0;
m_Componets[6].pintArray[11]=0;
m_Componets[6].pintArray[12]=0;
m_Componets[6].pintArray[13]=1;
m_Componets[6].pintArray[14]=0;
m_Componets[6].pintArray[15]=0;
以上代码是对长条这个方块的定义和初始化。其余形状的6种方块的定义见附录2的程序清单
CELSblockView::CELSblockView()该析构函数中。
- 功能模块设计
□游戏方块预览
新游戏方块将在如图4所示的右边4×4的正方形小方块中预览。使用随机函数rand( )来产生0~6之间的游戏方块编号,其中的正方形小方块的大小为SIZE×SIZE。SIZE为设定的方块像素大小。
□游戏方块控制
- 左移的实现过程:
- 判断在矩形“容器”中能否左移。这一判断必须满足如下两个条件:游戏方块整体左移一位后,游戏方块不能超越“矩形”容器的左边线,否则越界;并且在游戏方块有值(值为1)的位置,“矩形”容器必须是没有被占用的(占用时,值为1)。若满足这两个条件,则执行左移动作,否则不执行左移动作.
- 清除左移前的游戏方块。
- 在左移一位的位置,重新显示此游戏方块。(具体如何清除和显示方块的函数将在功能函数设计中阐述)
- 右移的实现过程:
- 判断在矩形“容器”中能否右移。这一判断必须满足如下两个条件:游戏方块整体右移一位后,游戏方块不能超越“矩形”容器的右边线,否则越界;并且在游戏方块有值(值为1)的位置,“矩形”容器必须是没有被占用的(占用时,值为1)。若满足这两个条件,则执行右移动作,否则不执行右移动作。
- 清除右移前的游戏方块。
- 在右移一位的位置,重新显示此游戏方块。
3、 向下加速下落(下移)的实现过程:
- 判断在当前的“矩形”容器中,能否下移。这一判断必须满足以下两个条件:游戏方块整体下移一位后,游戏方块不能超过“矩形”容器的底边线,否则越界;并且在游戏方块有值(值为1)的位置,“矩形”容器必须是没有被占用的(占用时,值为1)。若满足这两个条件,则执行下移动作,否则不执行下移动作。
- 清除下移前的游戏方块。
- 在下移一位的位置,重新显示此游戏方块。
4、 旋转的实现过程:
- 判断在当前的“矩形”容器中,能否旋转。这一判断必须满足以下两个条件:游戏方块整体旋转后,游戏方块不能超过“矩形”容器的左边线、、右边线、底边线,否则越界;并且在游戏方块有值(值为1)的位置,“矩形”容器必须是没有被占用的(占用时,值为1)。若满足这两个条件,则执行旋转动作,否则不执行旋转动作。
- 清除旋转前的游戏方块。
- 在“矩形”容器中显示区域不变的位置,显示旋转后的游戏方块。
□游戏显示更新
当游戏方块左右移动、下落、旋转时,要先清除先前的游戏方块,重新绘制游戏方块。当消除满行时,不能移动的方块下移后,进行重新绘制。
清除游戏方块的过程为:用刷新函数刷新相应的游戏区域,清除先前显示的方块,执行相应的操作后,再次调用刷新函数刷新此区域,显示将要显示的游戏方块。
□游戏帮助功能
在帮助对话框中,直接添加静态文本即可。
- 功能函数设计
在MFC视图类中添加功能构造函数,主要的功能函数设计如下:
□ CELSblockView():
函数原型:
CELSblockView::CELSblockView()
CELSblockView()初始化游戏的各项数据,“矩形”容器(游戏地图)、游戏级别、当前部件的ID、游戏分数、随机函数。定义并初始化MAXCOM个方块部件信息(MAXCOM为7),且在对应的析构函数中释放回收内存。
□ OnDraw():
函数原型:void CELSblockView::OnDraw(CDC* pDC)
OnDraw()导入背景图片,画游戏区域、不能移动的方块、下落的部件,显示得分、级别、下一个方块。
□ OnKeyDown():
函数原型:void CELSblockView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
OnKeyDown()对键盘信息处理,读取键盘操作值,对当前部件,调用CanLeft()、CanRight()、CanRotate()、CanDown(),并实现左移、右移、旋转、加速下落。
□ OnTimer():
函数原型:void CELSblockView::OnTimer(UINT nIDEvent)
OnTimer()是定时器消息处理函数,显示预览部件,调用CanDown()判断能否下移,实现方块自由下落,调用Disappear()消去满行,调用CheckFail()判断游戏是否结束,调用NewComponet()产生新部件。
□ NewComponet():
函数原型:void
CELSblockView::NewComponet(void)
NewComponet()产生新部件的函数,并将新部件的信息拷贝到当前部件的结构体m_CurrentCom中。
□ CanDown():
函数原型:bool CELSblockView::CanDown(void)或false。
□ CanLeft() :
函数原型:bool CELSblockView::CanLeft(void)
CanLeft()判断游戏方块是否可以左移,并返回值true或false。
□ CanRight():
函数原型:bool CELSblockView::CanRight(void)
CanRight()判断游戏方块是否可以右移,并返回值true或false。
□ CanRotate():
函数原型:bool CELSblockView::CanRotate(void)
CanRotate()判断游戏方块是否可以旋转,并返回值true或false。
□ CanNew():
函数原型:bool CELSblockView::CanNew(void)
CanNew()判断是否可以在“矩形”容器中产生新的游戏方块,并返回值true或false。
□ CheckFail():
函数原型:bool CELSblockView::CheckFail(void)
CheckFail()判断游戏是否结束,并返回值true或false。
□ Disappear():
函数原型:CELSblockView::Disappear(void)
Disappear()判断是否有满行,且消去满行,记录消去行数,并按规则增加和显示游戏分数。
□ MyInvalidateRect():
函数原型:Void
CELSblockView::MyInvalidateRect(POINT ptStart,int intDimension)
MyInvalidateRect()刷新了一个以ptStart为坐上角,长度为intDimension且不超出游戏区域的正方形区域。
□ OnGameStart():
函数原型:void
CELSblockView::OnGameStart()
OnGameStart()游戏开始的菜单和工具栏消息函数。
□ OnGameEnd( ):
函数原型:void CELSblockView::OnGameEnd()
OnGameEnd( )游戏结束的菜单和工具栏消息函数。
其他相关的功能实现函数:
□PreCreateWindow( )
函数原型:BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
PreCreateWindow( )设置生成主窗体的大小、风格。
一些常见的消息函数,再此略去,详细,请见附录2:程序清单。
- 主要函数调用流程
如 图 2 所示
图 2 主要函数调用流程图
- 编码和实现:
- 在Visual C++6.0平台下,利用App Wizard生成一个名为ELSblock的SDI程序框架,其他选项均可用缺省设置。
- 在“Resources View”菜单中,选取Menu资源项中菜单ID“ID_MAINFRAME”中,添加“游戏”和“设置”菜单,再增加相应的子菜单,并设置其相应的属性,见表1,在Toolbar资源项中,编辑工具栏,并设置相应的属性;拷贝文件clxj.bmp到res文件夹下,引入Bitmap资源,引用res文件夹下clxj.bmp,生成背景位图。然后建立类向导,添加相应的消息函数和位图引入代码。
ID | Caption | Prompt |
ID_GAME_START | 开始 | 游戏开始 |
ID_GAME_END | 结束 | 游戏结束 |
ID_SET_LEVEL1 | 简单 | 游戏级别1 |
ID_SET_LEVEL2 | 普通 | 游戏级别2 |
ID_SET_LEVEL3 | 较难 | 游戏级别3 |
表1 菜单属性
- 在“ClassView” 菜单中,在CELSblockView类中,“Add Windows Message Handler”对话框,“添加Windows消息/时间”窗口中添加WM_KEYDOWN键盘和WM_TIMER定时器消息事件。
- 在生成的程序框架中CELSblockView类的头文件和源文件中添加相应的代码。具体代码,见附录2,程序清单。
- 调试及运行:
所有代码录入完成后,按F5编译,调试运行,生成的可执行程序如图3所示。游戏运行时的界面如图 4 所示。
在代码的输入过程中,主要以输入一个函数,编译一次的方式进行,所以在执行的时候基本没有输入的错误。
调试的过程中,出现的主要问题是,逻辑坐标的计算失误,导致预览方块不在视图窗口预定位置显示和分数无法刷新。刷新过程中,起初调用了Invalidate()函数,导致屏幕显示闪烁,后来改调用InvalidateRect()函数避免此此问题。同时,对游戏方块的清除和显示,采用自定义的MyInvalidateRect()刷新函数,使屏幕显示更加稳定。
调试的过程中,还发现,一些还没有解决的问题,游戏级别的更新和分数的更新之间没有相互关联的关系和消去满行的函数,不能够实现,这样的情况——如果矩形容器底部,有未满的行,但是中间有满行,这些满行无法正常消去。由于时间仓促,多次修改不得的情况下,暂时没有能找到合适的解决方案。
在前期的设计中,还有背景音乐的设计和网格提示等功能,由于用到关于多媒体技术等相关知识,时间仓促,也没有去实现,
图 3 生成的可执行程序图标
图 4 游戏运行时截图
游戏操作说明
1、玩家可以通过游戏菜单中的开始和结束菜单或工具栏上“开始”“结束”按钮开始和结束游戏。
2、玩家可以通过设置菜单中的游戏级别和工具栏“1” “2” “3”按钮设置不同的游戏级别。
3、游戏中,玩家通过键盘上的上、下、左、右键分别控制方块的旋转、加速下落、左移、右移。
4、玩家还可以通过帮助菜单中的帮助菜单和工具栏上的“?”按钮获得游戏操作的友情提示和制作者的相关信息。
课程设计心得与体会
俄罗斯方块游戏,这款一直风靡全球的游戏,80后的我们一点不陌生,一个看似很简单的游戏,却拥有着变幻无穷的魅力。用自己所学的知识去实现这样一款可堪称经典的游戏,真是一种快乐。
此次课程设计历时近25天,主要是前期的准备工具花去了大量的时间,包括对游戏的需求分析、可行性分析、数据结构设计、功能函数设计、算法设计及一些资料和资源的收集工作。主要的详细设计用了大约3天的时间,编写和修改代码总共用了3天的时间,书写和修改书面报告用来2天的时间。由于时间仓促,原先很多的想法和设计,在具体设计的时候,被我删掉。我会在寒假的时候,继续去完善它。
此次我的设计,依旧采用我传统的思想——“分而治之”。分而治之,即指在软件的开发过程中,将该软件模块化和对其功能细化,设计的过程中,逐个击破,但是这个过程中值得注意的是,不要忽略了一些变量的定义和各部分的接口是否一致,在这些工作完成后,我们做的就是堆积木般地去组装好软件。对于俄罗斯方块游戏,首先我对功能模块进行了如下划分:游戏预览功能、游戏显示功能、游戏方块操作、游戏帮助功能、游戏等级分数更新功能。其中最主要的是游戏的显示功能和游戏方块操作。这部分的设计,是该游戏设计的重点和难点。在进行详细的分析和逻辑坐标的计算之后,我又把这些功能的实现,分解成一个一个的子函数,通过对相关函数设计和函数间的相互调用去实现一个一个功能和游戏中具体操作。
这款游戏的设计过程中,我深感,实现其的方法真的很多。但是,我选择了一种易于初学者理解和掌握的方法,利用Visual C++ 6.0可视化编程的特点,结合MFC类家族丰富的库函数,去实现了游戏显示这一难题,然后结合库函数构造了一些自己的函数,去实现具体的一些判断和操作。最后,处理和美化界面,包括利用随机函数,让方块可以随机变换颜色,并且在下落过程中,方块出现闪烁下降的特效等。
在这段时间里,我才真正的感觉到,一个想在软件行业发展的热血青年,需要什么?不仅仅是知识,的确,知识是对技术的支持,技术源于何处?技术就是在不断实践地过程中的结晶。一个合格的软件人,没有书写代码量的积累,是不可能有质的飞跃。一个优秀的程序员,没有十几万行的代码书写经验,也不能称得上优秀的程序员。我是一个没有背景的青年,我想走的就是技术路线,将来以技术立家。
看着行业的前辈们,走过的辛酸路,我才知道我还在成功的天际,还没有走到成功的门口,我用自己的双手在键盘上创造了自己的世界,我将用自己的心去丰富这个属于我的世界,未来的路看似很远,我会用自己的努力去将其缩减到最短。“风萧萧易易水寒,壮士一去不复返,”“路漫漫其修远兮,”吾将追求技术而上下求索,问软件江湖,谁能笑傲其中,唯有身怀绝技者。
憧憬着我的未来,追求着技术,在键盘上敲击出自己的音乐,唱响我的世界,我的生命,我的未来。
附录1:
参考文献
王明福 主编.Visual C++ 程序设计.北京:高等教育出版社,2003
谭浩强 编著.C++程序设计.北京:清华大学出版社,2004
罗建军等 编著.大学Visual C++程序设计案例教程.北京:高等教育出版社,2004
孙鑫,余安萍 著.VC++深入详解.北京:电子工业出版社.2006
古万荣 著.Visual C++ 完全自学手册.北京:机械工业出版社,2009
附录2:
程序清单
视图类头文件中的定义宏、类成员变量和自定义成员函数
#define MAXCOM 7 //部件数
#define WIDE 12 //游戏区域宽
#define HIGH 21 //高
#define SIZE 14 //组成游戏区域的方格大小
#define TOP 50 //游戏左上角坐标
#define LEFT 50
#define EASY 500 //游戏难度
#define NORMAL 300
#define HARD 200
typedef struct tagComponet
{
int intComID; //部件的ID号
int intDimension; //存储该部件所需的数组维数
int* pintArray; //指向存储该部件的数组
}Componet;
class CELSblockView : public CView
{
private:
int m_intComID; //当前下落部件
int m_intState[HIGH][WIDE]; //当前状态
Componet m_Componets[MAXCOM];//所有部件的内部表示
int m_intScore; //分数
int m_intLevel; //难度
int m_GameLevel; //游戏级别
Componet m_CurrentCom; //当前部件
POINT ptIndex; //部件数组在全局数组中的索引
void NewComponet(void); //产生一个新部件
bool CanDown(void); //是否可以下落
void MyInvalidateRect(POINT ptStart,int intDimension);//刷新函数
void Disappear(void) ; //消去行
bool CheckFail(void); //判断游戏是否结束
bool CanRotate(void); //是否还可以旋转
bool CanLeft(void); //是否可以左移
bool CanRight(void); //是否可以右移
bool CanNew(); //检查是否有足够的空位产生新的部件,否则游戏结束
}
视图类中的所有构造函数和析构函数
CELSblockView::CELSblockView()
{
for(int i=0;i for(int j=0;j m_intState[i][j]=0; m_intLevel=NORMAL; //初始化难度 m_GameLevel=1; //初始化游戏级别 srand((unsigned)time(NULL));//初始化随机数 m_intScore=0; m_CurrentCom.intComID=-1; m_CurrentCom.intDimension=0; m_CurrentCom.pintArray=NULL; //初始化7个部件 //0:方块 m_Componets[0].intComID=0; m_Componets[0].intDimension=2; m_Componets[0].pintArray=new int[4]; for(i=0;i<4;i++) m_Componets[0].pintArray[i]=1; //1 1 //1 1 //1 m_Componets[1].intComID=1; m_Componets[1].intDimension=3; m_Componets[1].pintArray=new int[9]; m_Componets[1].pintArray[0]=0; m_Componets[1].pintArray[1]=1; m_Componets[1].pintArray[2]=0; m_Componets[1].pintArray[3]=1; //0 1 0 m_Componets[1].pintArray[4]=1; //1 1 1 m_Componets[1].pintArray[5]=1; //0 0 0 m_Componets[1].pintArray[6]=0; m_Componets[1].pintArray[7]=0; m_Componets[1].pintArray[8]=0; //2 m_Componets[2].intComID=2; m_Componets[2].intDimension=3; m_Componets[2].pintArray=new int[9]; m_Componets[2].pintArray[0]=1; //1 0 0 m_Componets[2].pintArray[1]=0; //1 1 0 m_Componets[2].pintArray[2]=0; //0 1 0 m_Componets[2].pintArray[3]=1; m_Componets[2].pintArray[4]=1; m_Componets[2].pintArray[5]=0; m_Componets[2].pintArray[6]=0; m_Componets[2].pintArray[7]=1; m_Componets[2].pintArray[8]=0; //3 m_Componets[3].intComID=3; m_Componets[3].intDimension=3; m_Componets[3].pintArray=new int[9]; m_Componets[3].pintArray[0]=0; m_Componets[3].pintArray[1]=0; m_Componets[3].pintArray[2]=1; m_Componets[3].pintArray[3]=0; //0 0 1 m_Componets[3].pintArray[4]=1; //0 1 1 m_Componets[3].pintArray[5]=1; //0 1 0 m_Componets[3].pintArray[6]=0; m_Componets[3].pintArray[7]=1; m_Componets[3].pintArray[8]=0; //4 m_Componets[4].intComID=4; m_Componets[4].intDimension=3; m_Componets[4].pintArray=new int[9]; m_Componets[4].pintArray[0]=1; m_Componets[4].pintArray[1]=0; m_Componets[4].pintArray[2]=0; m_Componets[4].pintArray[3]=1; //1 0 0 m_Componets[4].pintArray[4]=1; //1 1 1 m_Componets[4].pintArray[5]=1; //0 0 0 m_Componets[4].pintArray[6]=0; m_Componets[4].pintArray[7]=0; m_Componets[4].pintArray[8]=0; //5 m_Componets[5].intComID=5; m_Componets[5].intDimension=3; m_Componets[5].pintArray=new int[9]; m_Componets[5].pintArray[0]=0; m_Componets[5].pintArray[1]=0; m_Componets[5].pintArray[2]=1; m_Componets[5].pintArray[3]=1; //0 0 1 m_Componets[5].pintArray[4]=1; //1 1 1 m_Componets[5].pintArray[5]=1; //0 0 0 m_Componets[5].pintArray[6]=0; m_Componets[5].pintArray[7]=0; m_Componets[5].pintArray[8]=0; //6 m_Componets[6].intComID=6; m_Componets[6].intDimension=4; m_Componets[6].pintArray=new int[16]; m_Componets[6].pintArray[0]=0; m_Componets[6].pintArray[1]=1; m_Componets[6].pintArray[2]=0; m_Componets[6].pintArray[3]=0; //0 1 0 0 m_Componets[6].pintArray[4]=0; //0 1 0 0 m_Componets[6].pintArray[5]=1; //0 1 0 0 m_Componets[6].pintArray[6]=0; //0 1 0 0 m_Componets[6].pintArray[7]=0; m_Componets[6].pintArray[8]=0; m_Componets[6].pintArray[9]=1; m_Componets[6].pintArray[10]=0; m_Componets[6].pintArray[11]=0; m_Componets[6].pintArray[12]=0; m_Componets[6].pintArray[13]=1; m_Componets[6].pintArray[14]=0; m_Componets[6].pintArray[15]=0; } CELSblockView::~CELSblockView() { //释放内存 for(int i=0;i delete [] m_Componets[i].pintArray; delete [] m_CurrentCom.pintArray; } void CELSblockView::OnDraw(CDC* pDC) { CELSblockDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //导入背景图片 CBitmap bitmap; CDC dcMemory; bitmap.LoadBitmap(IDB_BITMAP1); dcMemory.CreateCompatibleDC(pDC); dcMemory.SelectObject(&bitmap); pDC->BitBlt(0,0,410,510,&dcMemory,0,0,SRCCOPY); //画游戏和预览区域 CBrush brushBK(RGB(120,255,255)); CBrush* pbrushOld=pDC->SelectObject(&brushBK); pDC->Rectangle(LEFT,TOP,LEFT+WIDE*SIZE,TOP+HIGH*SIZE); int BrushColor=rand()%255; //造成方块闪烁 //画不能移动的方块 CBrush brushStick(RGB(BrushColor,BrushColor,255)); pDC->SelectObject(&brushStick); for(int i=0;i for(int j=0;j if(m_intState[i][j]==1) pDC->Rectangle(LEFT+SIZE*j,TOP+SIZE*i,LEFT+SIZE*(j+1),TOP+SIZE*(i+1)); //画预览部件和下落的部件 if(m_CurrentCom.intComID>=0) { CBrush brushCom(RGB(255,BrushColor,BrushColor)); pDC->SelectObject(&brushCom); int intDimension=m_CurrentCom.intDimension; for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=3+i/intDimension; //找出部件对应整体数组的位置 int n=WIDE+5+(i%intDimension); pDC->Rectangle(LEFT+SIZE*n,TOP+SIZE*m,LEFT+SIZE*(n+1),TOP+SIZE*(m+1)); m=ptIndex.x+i/intDimension; //找出部件对应整体数组的位置 n=ptIndex.y+(i%intDimension); pDC->Rectangle(LEFT+SIZE*n,TOP+SIZE*m,LEFT+SIZE*(n+1),TOP+SIZE*(m+1)); } } } //显示“级别 得分 下一方块” CString strLevel,strOUT,strNext; strNext.Format("下一个方块",NULL); pDC->TextOut(LEFT+WIDE*SIZE+50,TOP,strNext); strLevel.Format("级别:%d",m_GameLevel); pDC->TextOut(LEFT+WIDE*SIZE+50,TOP+200,strLevel); strOUT.Format("得分:%d",m_intScore); pDC->TextOut(LEFT+WIDE*SIZE+50,TOP+250,strOUT); } void CELSblockView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { int intDimension=m_CurrentCom.intDimension; switch(nChar) //left 37 ,right 39,up 38,down 40 { case 37: if(CanLeft()) { //擦除 MyInvalidateRect(ptIndex,intDimension); //左移 ptIndex.y--; //显示新位置上的部件 MyInvalidateRect(ptIndex,intDimension); } break; case 39: if(CanRight()) { //擦除 MyInvalidateRect(ptIndex,intDimension); //右移 ptIndex.y++; //显示新位置上的部件 MyInvalidateRect(ptIndex,intDimension); } break; case 38: if(CanRotate()) { //擦除 MyInvalidateRect(ptIndex,intDimension); //转动 int* pintNewCom=new int[intDimension*intDimension]; for(int i=0;i { int intR=intDimension*(intDimension-(i%intDimension)-1)+(i/intDimension); pintNewCom[i]=m_CurrentCom.pintArray[intR]; } for(i=0;i { m_CurrentCom.pintArray[i]=pintNewCom[i]; } delete [] pintNewCom; //显示新位置上的部件 MyInvalidateRect(ptIndex,intDimension); } break; case 40: if(CanDown()) { //擦除 MyInvalidateRect(ptIndex,intDimension); //加速下落 ptIndex.x++; //显示新位置上的部件 MyInvalidateRect(ptIndex,intDimension); } } CView::OnKeyDown(nChar, nRepCnt, nFlags); } void CELSblockView::OnTimer(UINT nIDEvent) { int intDimension=m_CurrentCom.intDimension; if(CanDown()) //可以下落 { //擦除 MyInvalidateRect(ptIndex,intDimension); //下落 ptIndex.x++; //显示新位置上的部件 MyInvalidateRect(ptIndex,intDimension); } else { for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=ptIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=ptIndex.y+(i%intDimension); m_intState[m][n]=1; } } MyInvalidateRect(ptIndex,intDimension); Disappear(); //消去行 if(CheckFail()) //游戏结束 { m_CurrentCom.intComID=-1; KillTimer(1); MessageBox("游戏结束!"); } else { NewComponet();//新部件 } } CView::OnTimer(nIDEvent); } void CELSblockView::NewComponet(void) { int intComID=rand()%7; //产生随机数 m_CurrentCom.intComID=intComID; int intDimension=m_Componets[intComID].intDimension; m_CurrentCom.intDimension=intDimension; delete [] m_CurrentCom.pintArray; // 删除上一个部件的内存分配 m_CurrentCom.pintArray=new int[intDimension*intDimension]; //拷贝部件 for(int i=0;i m_CurrentCom.pintArray[i]=m_Componets[intComID].pintArray[i]; ptIndex.x=0; ptIndex.y=5; //检查是否有足够的空位产生新部件,否则游戏结束 if(CanNew()) { //显示预览部件 InvalidateRect(CRect(LEFT+WIDE*SIZE+55,TOP+35,LEFT+(WIDE+5)*SIZE+55,TOP+5*SIZE+35)); //显示新部件 MyInvalidateRect(ptIndex,intDimension); } else { m_CurrentCom.intComID=-1; KillTimer(1); MessageBox("************\n**游戏结束**\n************"); } } //是否可以下落 bool CELSblockView::CanDown(void) { bool boolDown=true; POINT intNewIndex=ptIndex; intNewIndex.x++; int intDimension=m_CurrentCom.intDimension; for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=intNewIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=intNewIndex.y+(i%intDimension); if(m>=HIGH||m_intState[m][n]==1) //被挡住或出游戏区域 boolDown=false; } } return boolDown; } //是否可以左移 bool CELSblockView::CanLeft(void) { bool boolLeft=true; POINT ptNewIndex=ptIndex; ptNewIndex.y--; int intDimension=m_CurrentCom.intDimension; for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=ptNewIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=ptNewIndex.y+(i%intDimension); if(n<0||m_intState[m][n]==1) //被挡住或出游戏区域 boolLeft=false; } } return boolLeft; } //是否可以右移 bool CELSblockView::CanRight(void) { bool boolRight=true; POINT ptNewIndex=ptIndex; ptNewIndex.y++; int intDimension=m_CurrentCom.intDimension; for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=ptNewIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=ptNewIndex.y+(i%intDimension); if(n>=WIDE||m_intState[m][n]==1) //被挡住或出游戏区域 boolRight=false; } } return boolRight; } //是否可以转动 bool CELSblockView::CanRotate(void) { bool boolRotate=true; POINT ptNewIndex=ptIndex; int intDimension=m_CurrentCom.intDimension; //假设可以转动 //新的矩阵存储转动后的部件 int* pintNewCom=new int[intDimension*intDimension]; //顺时针旋转并判断 for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int intR=intDimension*(intDimension-(i%intDimension-1)+(i/intDimension)); pintNewCom[i]=m_CurrentCom.pintArray[intR]; if(pintNewCom[i]==1) { int m=ptNewIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=ptNewIndex.y+(i%intDimension); if(n<0||m_intState[m][n]==1||m>=HIGH) //被挡住或出游戏区域 boolRotate=false; } } } delete [] pintNewCom; return boolRotate; } //可以产生新的部件 bool CELSblockView::CanNew(void) { bool boolNew=true; POINT ptNewIndex=ptIndex; int intDimension=m_CurrentCom.intDimension; for(int i=0;i { if(m_CurrentCom.pintArray[i]==1) { int m=ptNewIndex.x+i/intDimension; //找出部件对应整体数组的位置 int n=ptNewIndex.y+(i%intDimension); if(m_intState[m][n]==1) //被挡住或出游戏区域 boolNew=false; } } return boolNew; } //判断游戏是否结束 bool CELSblockView::CheckFail(void) { bool boolEnd=false; for(int j=0;j if(m_intState[0][j]==1) boolEnd=true; return boolEnd; } void CELSblockView::Disappear(void) //消去行函数 { int intLine=0; //一次消去行数 bool boolLine=true; for(int i=HIGH-1;i>=0;i--) { for(int j=0;j if(m_intState[i][j]==0) boolLine=false; if(boolLine) //行可以消去 { intLine++; //向下移动 for(int m=i;m>0;m--) for(int n=0;n m_intState[m][n]=m_intState[m-1][n]; for(int n=0;n m_intState[0][n]=0; //最顶层清除 } } if(intLine>0) { m_intScore+=(intLine-1)*20+10; InvalidateRect(CRect(LEFT+WIDE*SIZE+50,TOP+250,LEFT+WIDE*SIZE+200,TOP+350)); } InvalidateRect(CRect(LEFT,TOP,LEFT+WIDE*SIZE,TOP+HIGH*SIZE)); } //刷新函数 void CELSblockView::MyInvalidateRect(POINT ptStart,int intDimension) { //刷新了一个以ptStart为坐上角,长度为intDimension的正方形区域 //同时注意判断不超出游戏区域 int x1=LEFT+ptStart.y*SIZE; x1=x1>LEFT?x1:LEFT; int y1=TOP+ptStart.x*SIZE; y1=y1>TOP?y1:TOP; int x2=LEFT+(ptStart.y+intDimension)*SIZE; x2=x2>LEFT+WIDE*SIZE?LEFT+WIDE*SIZE:x2; int y2=TOP+(ptStart.x+intDimension)*SIZE; y2=y2>TOP+HIGH*SIZE?TOP+HIGH*SIZE:y2; InvalidateRect(CRect(x1,y1,x2,y2)); } void CELSblockView::OnGameStart() { for(int i=0;i for(int j=0;j m_intState[i][j]=0; m_intScore=0; InvalidateRect(CRect(0,0,400,500)); NewComponet(); SetTimer(1,m_intLevel,NULL); } void CELSblockView::OnGameEnd() { KillTimer(1); } void CELSblockView::OnSetLevelEasy() { m_intLevel=EASY; m_GameLevel=1; } void CELSblockView::OnSetLevlelNormal() { m_intLevel=NORMAL; m_GameLevel=2; } void CELSblockView::OnSetLevelHard() { m_intLevel=HARD; m_GameLevel=3; } 框架类中的构造函数 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { cs.style = WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; //风格 cs.cy = 510; //视图窗口大小 cs.cx = 410; cs.y = 200; //逻辑位置在屏幕上 cs.x = 400; return CFrameWnd::PreCreateWindow(cs); }