c++课程设计Word文档格式.docx
《c++课程设计Word文档格式.docx》由会员分享,可在线阅读,更多相关《c++课程设计Word文档格式.docx(58页珍藏版)》请在冰豆网上搜索。
m_mem3DBkDC:
CDC
m_mem3DBkBmp:
CBitmap
m_memAnimalDC:
CDC
m_memAnimalBmp:
m_MemDC:
CDC
m_memBitmap:
CBitmap
m_map:
int
m_nRow:
int
m_nCol:
int
m_nX1:
int
m_nY1:
int
GameDraw(CDC*pDC):
void
StartNewGame():
PreTranslateMessage(MSG*pMsg):
BOOL
IsLink(intx1,inty1,intx2,inty2):
IsWin(void):
X1_Link_X2(intx,inty1,inty2):
Y1_Link_Y2(intx1,intx2,inty):
OneCornerLink(intx1,inty1,intx2,inty2):
TwoCornerLink(intx1,inty1,intx2,inty2):
YThrough(intx,inty,BOOLbAdd):
XThrough(intx,inty,BOOLbAdd):
LineX(intx,inty1,inty2):
LineY(intx1,intx2,inty):
这些成员函数和成员变量的算法分析与设计以及核心算法中有详细介绍。
3.2.框架的搭建
从图1.1可以看出,整个运行的界面由菜单栏和游戏区域的图形显示组成,因此,需要用对话框模板的方式去搭建整个框架再添加一个菜单栏。
首先,创建一个基于对话框模板的项目,如图1.2所示。
图1.2
在创建过程的第一步“MFC应用程序向导—步骤1”中应该选择“基本对话框”选项,确保应用程序的创建是对话框模板,如图1.3所示。
图1.3
然后添加菜单栏为对话框增加菜单栏并为对话框创建一个菜单资源IDR_MENU1。
图1.4
在对话框的属性对话框中在Menu属性中选择刚创建的菜单资源IDR_MENU1。
此时保存编译后,在对话框中就可看到菜单。
图1.5
点击快捷键Ctrl+W,在“AddaClass”对话框中选择已存在的对话框类“Selectanexistingclass”,打开“SelectClass”对话框。
图1.6
选择对话框类,本例中的类名为CMyDlg,选中进入下一步。
图1.7
进入MFCClassWizard对话框,在消息映射属性页中,为菜单增加消息处理函数,为子菜单增加COMMAND处理函数函数名为默认函数名。
如图1.8所示:
图1.8
给各个子菜单添加消息映射,同上所述。
然后编辑函数代码,实现自己定义的功能,添加的代码如下:
voidCMyDlg:
:
Onexit()
{
//TODO:
Addyourcommandhandlercodehere
CDialog:
OnCancel();
}
Onnewgame()
StartNewGame();
Onathour()
MessageBox("
11物41陈晓丽"
);
}
Onabout()
本游戏连连由C++MFC设计实现,是给一堆图案中的相同图案进行配对的简单游戏"
编译运行后,点击各子菜单,将弹出对话框。
4.算法分析与设计
在对算法进行分析前,应先抽象出游戏的基本是具结构,而对连连看游戏来说,核心的部分应该是整个游戏区域的地图数据。
那么,下面将分析游戏特性,然后设计出标识地图的数据结构。
4.1游戏地图数据设计
对于整个游戏区域,可以把它看作一个是由若干个小方块构成的地图,而且每一个小方块放置着不同的动物图案,可将其称之为图案小方块。
这些图案小方块零散地分布在地图的不同位置区域,并且每一个图案小方块都有与其对应的完全一样的另外一个小方块,如图1.8所示。
图1.9
如图1.9所示,整个游戏游戏区域被抽象成一个有坐标位置属性的平面,平面上零散地分布着若干个小方块,并且这些小方块的物种起码是成对出现的。
经过前面的描述和分析后,可以把游戏区域地图用一个数组m_map来表示。
m_map是把地图设计成一个动态分配的int整形一维数组,对地图中的行列数的表达,用一个转换法则即可。
可以在CMyDlg类对象定义中添加地图核心数据的成员变量,具体如下:
//地图位置相关属性组
int*m_map;
//动态地图数据头指针(一维数组)
intm_nRow;
//地图的行数(虚拟)
intm_nCol;
//地图的列数(虚拟)
上面的成员变量中定义了一个整形指针标量m_map,用于记录动态分配出来的一维数组地图空间的首地址。
对于地图区域中的某个小方块的类型,可以用一个整形的ID来进行识别。
这里为标识地图的行列位置分别添加m_nRow和m_nCol变量。
现在,地图的数据结构已经设计好。
下面对游戏进行初始化。
由于方块需要成对地出现,因此在做地图的初始化时,不仅仅是对动物种类做简单的随机取数,然后将该随机选取出来的物件放到地图区域中去就了事,而是需要成对地对物种进行成对选取,就是说地图中的小方块必须是偶数个。
前面提到过,把地图数组设置成动态分配方式,目的是让其数据空间可以根据行列数的需求动态地获取,而对于实际不同大小比例的地图可以预先定义几组关于行列数的宏来实现。
当需要创建时,根据宏值的不同分配不同大小的地图空间即可。
接下来在CMyDlg类的构造函数对地图数据进行相关的初始化:
#defineROWCOUNT7//行数
#defineCOLCOUNT12//列数
......
CMyDlg:
CMyDlg(CWnd*pParent/*=NULL*/)
:
CDialog(CMyDlg:
IDD,pParent)
{……
//记录方块置为无效状态
m_nY1=BLANK_STATE;
m_nX1=BLANK_STATE;
//初始化行列数
m_nRow=ROWCOUNT;
m_nCol=COLCOUNT;
//根据行列数动态分配内核数据数组空间
m_map=newint[m_nRow*m_nCol];
~CMyDlg()
//释放动态数组空间
delete[]m_map;
在CMyDlg类对象的实现中,定义了一些关于地图行列数的宏,如ROWCOUNT和COLCOUNT,并且在CMyDlg类对象的构造函数中,进行了行列的真实确认赋值,并根据当前行列数的大小对地图数据空间进行动态创建。
因为地图数据是用new在堆栈动态创建的,所以在销毁该对象时要将这些内存空间释放,如代码所示在CMyDlg类对象的析构函数中调用delete将m_map指向的所有空间都释放掉。
4.2数据的初始化工作
接下来,再分配好的空间中放上适当的图案方块物件,对数据进行初始化。
即需要对地图空间内的数据进行成对性的随机布局,因此可以将该功能的实现封装在StartNewGame()函数里面,其代码如下:
StartNewGame()
//初始化地图,将地图中所有方块区域位置置为空方块状态
for(intiNum=0;
iNum<
(m_nCol*m_nRow);
iNum++)
{
m_map[iNum]=BLANK_STATE;
}
//部下随机种子
srand(time(NULL));
//生成随机地图
//将所有匹配成对的动物物种放进一个临时的地图中
CDWordArraytmpMap;
for(inti=0;
i<
(m_nCol*m_nRow)/6;
i++)
for(intj=0;
j<
6;
j++)
tmpMap.Add(i);
//每次从上面的临时地图中取走(获取后并在临时地图删除)
//一个动物放到地图的空方块上
for(i=0;
m_nRow*m_nCol;
{
//随机挑选一个位置
intnIndex=(int(rand()*0.1+rand()*0.01+rand()))%tmpMap.GetSize();
//获取该选定物件放到地图的空方块
m_map[i]=tmpMap.GetAt(nIndex);
//在临时地图除去该动物
tmpMap.RemoveAt(nIndex);
//更新显示
Invalidate(TRUE);
在游戏进行初始化的过程中,应该先对整个地图中的各个区域做必要的初始化操作,将它们的状态设置为BLANK_START空白方块状态(无动物图案方块),关于BLANK_START空白方块状态的定义,跟其他动物方块的物种定义表达类似,也是用整数ID来对它进行标识,不过不同的是,由于他代表该方块区域无图案,所以这里用-1的宏值来表示,具体定义如下:
#defineBLANK_STATE-1//空方块(没有任何动物)
可以看到,对图案方块的布局,先用srand()函数对时间函数布下随机种子,然后调用rand()函数对具体的图案方块的种类进行随机的获取。
在这里需要引入一个临时地图tmpMap,该临时地图的大小与内核数据地图的大小一致,并且先添置好4组完全一样的图案类型ID数据(0~(m_nCol*m_nRow)/4),然后再将已经安放在tmpMap中的图案作随机抽取,并放到内核地图数据中去,将取出的元素从tmpMap中除去。
4.3快捷方式的创建
按Ctrl+W为类添加消息映射PreTranslateMessage,双击函数名为该函数添加如下代码
BOOLCMyDlg:
PreTranslateMessage(MSG*pMsg)
Addyourspecializedcodehereand/orcallthebaseclass
if(pMsg->
message==WM_KEYDOWN)
if(pMsg->
wParam==VK_F2)
{
StartNewGame();
}
if(pMsg->
wParam==VK_F3)
CDialog:
在运行游戏时按下F2便可重新开始新游戏,按F3便可退出游戏。
5.核心算法
在完成地图数据设计后,就开始展开游戏核心的设计,该部分主要包括图案方块的销毁判断,游戏胜利判断以及整个游戏用户交换功能的实现。
5.1图案方块的连接判断
对于选中的两个方块的销毁,他们必须符合下面3个条件:
(1)选中的两个方块图案相同。
(2)选中的两个方块之间没有障碍物阻碍的情况下,可以用若干个垂直的直线
线段连接起来。
(3)这些将它们连接起来的直线线段的折点不超过两个(连接线由x轴和y轴的平行线组成)。
现在针对
(2)和(3)进行分析,同种物件的连接方式大致可以分成以下3种:
(1)直接方式
(2)有一个折点的垂直线段连接。
(3)有两个折点的垂直线段连接.。
1.直接连接方式
在直接连接方式中,必须要求所选定的两个方块在同一水平直线上(可以为x方向或y方向),并且两个方块之间没有任何其他图案方块。
2.一个这点连接方式
所选定的两个方块如果通过折点的方式连接,那么对于折点来说,每个折点必定有且至少有一个坐标(x或y)是和其中一个目标点相同的,即折点必定在两个目标点所在的x方向或y方向的直线上。
此外,对于一个折点连接的情况,折点应该为第一个选中方块的横向线或纵向线与第二个选中方块的纵向线和横向线相交而得出。
3.两个折点的连接方式
这种方式的两个折点所连成的直线与两物件的直接连线可以构成平行线,因此可以根据这个规律,将这条水平线在游戏区域允许的条件上下移动,然后通过判断整条带垂直折线点的曲线之间有无障碍物方式来确定是否可以连同。
这种情况可以分为两种情况:
(1)选中的两图案方块在同一直线,两折点间的直连线可在其这两个方块之间的空间位置作移动,其约束是不超过游戏边界区域。
(2)选中的两图案方块不在同一直线,两折点间的直连线可在两个方块之间的空间位置作移动,其约束是两方块之间的区域。
经过上面详细的分析后,可以对选定的两方块是否可以作抵消操作可以这样设计下去。
首先,对简单的直接连情况进行判断,看其是否符合条件,假如不能,再加深一个级别的复杂度,对一个折点的情况进行判断,依次类推,如下图所示。
图1.10
图1.11
根据如图1.6所示的流程图,可以对选定的两个方块(分别在(x1,y1)以及(x2,y2)两个区域位置,其中x,y分别代表行与列的概念)是否可以抵消作以下实现。
把该功能封装在IsLink()函数里面,其代码如下所示:
//判断选中的两个方块是否可以消除
IsLink(intx1,inty1,intx2,inty2)
//X直连方式
if(x1==x2)
if(X1_Link_X2(x1,y1,y2))
returnTRUE;
//Y直连方式
elseif(y1==y2)
if(Y1_Link_Y2(x1,x2,y1))
//一个转弯直角的联通方式
if(OneCornerLink(x1,y1,x2,y2))
returnTRUE;
//两个转弯直角的联通方式
elseif(TwoCornerLink(x1,y1,x2,y2))
returnFALSE;
在上面的实现中,先是对直连方式中的x方向直连Y1_Link_Y2()以及y方向直连X1_Link_X2()这两种情况进行判断,如果尚未取得结果,再通过调用OneCornerLink()函数对一个折点的情况进行判断,或者更糟糕的时候调用TwoCornerLink()函数对两个这点的情况进行判断,然后得出最终结果。
下面将对上面涉及到的子功能模块进行实现,代码如下所示:
//X直接连通
X1_Link_X2(intx,inty1,inty2)
//保证y1的值小于y2
if(y1>
y2)
//数据交换
intn=y1;
y1=y2;
y2=n;
//直通
for(inti=y1+1;
=y2;
if(i==y2)
if(m_map[i*m_nCol+x]!
=BLANK_STATE)
break;
//左通
if(XThrough(x-1,y1,FALSE)&
&
XThrough(x-1,y2,FALSE))
//右通
if(XThrough(x+1,y1,TRUE)&
XThrough(x+1,y2,TRUE))
//Y直接连通
Y1_Link_Y2(intx1,intx2,inty)
if(x1>
x2)
intx=x1;
x1=x2;
x2=x;
//直通
for(inti=x1+1;
=x2;
if(i==x2)
if(m_map[y*m_nCol+i]!
//上通
if(YThrough(x1,y-1,FALSE)&
YThrough(x2,y-1,FALSE))
//下通
if(YThrough(x1,y+1,TRUE)&
YThrough(x2,y+1,TRUE))
//是否同一直线通
//
LineX(intx,inty1,inty2)
inty=y1;
y2=y;
for(inty=y1;
y<
y++)
if(m_map[y*m_nCol+x]!
returnFALSE;
if(y==y2)
LineY(intx1,intx2,inty)
for(intx=x1;
x<
x++)
if(x==x2)
//1直角接口连通
OneCornerLink(intx1,inty1,intx2,inty2)
intn=x1;
x2=n;
n=y1;
if(y2<
y1)
if(LineY(x1+1,x2,y1)&
LineX(x2,y1,y2+1))
if(LineY(x2-1,x1,y2)&
LineX(x1,y2,y1-1))
returnFALSE;
}
else
LineX(x2,y1,y2-1))
LineX(x1,y2,y1+1))
//2直角接口连通
TwoCornerLink(intx1,inty1,intx2,inty2)
if(XThrough(x1+1,y1,TRUE)&
XThrough(x2+1,y2,TRUE))
if(XThrough(x1-1,y1,FALSE)&
XThrough(x2-1,y2,FALSE))
if(YThrough(x1,y1-1,FALSE)&
YThrough(x2,y2-1,FALSE))
if(YThrough(x1,y1+1,TRUE)&
YThrough(x2,y2+1,TRUE))
//右
for(intx=x1+1;
m_nCol;
if(m_map[y1*m_nCol+x]>
-1)
if(OneCornerLink(x,y1,x2,y2))
//左
for(x=x1-1;
x>
-1;
x--)
if(m_map[y1*m_nCol+x]!
//上
for(inty=y1-1;
y