资源描述
连连看游戏的设计与实现
精品文档
苏州高博软件技术职业学院
学生毕业设计(论文)报告
系 别 计算机科学技术
专 业 计算机应用
班 级 1310计应YZ
姓 名 支峰
学 号 013321018
设计(论文)题目连连看游戏的设计与实现
指导教师 贺 雪 梅
起迄日期 2015年10月16日-2016年4月25日
收集于网络,如有侵权请联系管理员删除
连连看游戏的设计与实现
摘 要 本文用VisualC++来设计与实现简单的连连看游戏的基本功能,玩家可以在游戏区域中通过键盘控制来选取相同的两个物件,采用特定的消除规则对它们进行消除的操作,当游戏区域中的所有方块对都被消除后玩家即可胜利。本次课程设计对该游戏的算法以及游戏图案的绘制进行详细的介绍。运用连线相消的方法完成了连连看游戏。
关键词:VisualC++6.0;连连看;游戏; 3D绘图
1 引 言
1.1连连看游戏介绍
游戏“连连看”顾名思义就是找出相关联的东西,它来源于街机游戏《四川麻将》和《中国龙》,是给一堆图案中的相同图案进行配对的简单游戏,在2003年,一个叫做朱俊的网友将这种形式搬到了PC上,立刻成为办公一族的新宠,并迅速传遍了世界各地。饱受工作压力的人们没有太多的时间进行复杂的游戏,而对于这种动动鼠标就能过关的游戏情有独钟。之后村子的连连看风靡版,阿达的连连看奥运版,连连看反恐版,还有敏敏连连看,水晶连连看等遍地开花,造就了一个连连看的新世界。连连看游戏有多种地图样式和道具系统、大大加强了游戏的可玩性,是一款老少皆宜的休闲佳品。
1.2课程设计的目的
网络小游戏制作的目的是满足了人们休闲的需要,在紧张工作之余休闲类的小游戏能够给人带来最大程度的放松,也可以增进人们之间的交流,沟通,通过游戏还可以认识更多的朋友,也可以到达跨省、跨市,甚至跨国间人们互相娱乐的目的。
另外也想通过本次课程设计将三年来所学的专业知识和其他方面的知识融入到实际应用中。
1.3主要问题
开始制作游戏时,主要要解决的问题有以下几个方面:如何设置整个游戏的界面;如何控制连连看游戏中随机图片的生成且每种图片必须为偶数个;游戏开始后,判断鼠标两次点击的图片能否消去,即图片是否相同且图片之间路径的判断。
1.4课题实现技术的简要说明
Visual C++是一个功能强大的可视化软件开发工具,Visual C++6.0不仅是一个C++编译器,而且是一个基于Windows操作系统的可视化集成开发环境(integrated development environment,IDE)。Visual C++6.0由许多组件组成,包括编辑器、调试器以及程序向导AppWizard、类向导Class Wizard等开发工具。 这些组件通过一个名为Developer Studio的组件集成为和谐的开发环境。Visual C++一直被认为是目前最好的软件开发工具之一,其在界面开发、执行速度、代码的移植性方面都有很强的优势。所以,实现本系统,VC++是一个相对较好的选择。
2 系统需求分析
整个游戏程序包括了进入记录,图片消去和过关结果三个阶段,在处理鼠标响应事件中伴随着3D绘图。程序通过调试运行,实现了设计目标,能够满足连连看游戏玩家的需要。
2.1 可行性分析
(1)技术可行性分析
技术上的可行性分析主要分析现有技术条件能否顺利完成开发工作,硬件、软件配置能否满足开发者的需要,各类技术人员的数量,水平,来源等。连连看系统的工作主要是在开发者和玩家之间架起一座桥梁,能相互沟通信息和处理信息。这一特点非常适合计算机特点,通过网络Internet技术,发挥计算机的信息传输速度快、准确度高的优势。计算机硬件和软件技术的飞速发展,为系统的建设提供了技术条件。
(2)社会可行性分析
社会可行性有时也称为操作可行性,主要论证新系统在玩家在游戏过程中的感受与反馈信息。在当前信息技术飞速发展的大环境下,计算机技术和软件技术的更新使得游戏更加的易掌握。
2.2 功能需求分析
关于连连看的功能描述如下:运行游戏并进行初始化工作,将整个游戏区域分成纵向和横向扩展的若干个小方块,并且这些小方块是由多种动物图案成对地分布于游戏区域的不同位置。玩家可以通过选取相同的两个物件来对它们进行消除的操作,直到将游戏区域中的所有方块对都被消除后为胜利。
2.3 性能需求分析
(1)硬件环境
l 处理器:Inter CR300或是更高。
l 内存:128MB(建议196MB)。
l 硬盘空间:20MB。
(2)软件环境
l 操作系统:Windows 98 或是Windows 2000/Windows NT Server 4.0。
3 系统设计
3.1系统功能设计
连连看系统的设计流程图如图3.1所示。
游戏框架的搭建
菜单的制作
连线相消算法的设计与实现
绘图界面的设计
连连看系统
图3.1 连连看游戏的设计流程图
3.2系统功能模块总设计
这个游戏的主要类是游戏模式类,类名为CLinkToLinkDlg。这个类主要对包括图案方块的销毁判断,游戏胜利判断以及整个游戏用户交换功能的实现。它的图如图3.2所示。
CLinkToLinkDlg
m_mem3DBkDC :CDC
m_mem3DBkBmp :Cbitmap
m_memAnimalDC :CDC
m_memAnimalBmp :Cbitmap
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() : void
IsLink(int x1,int y1,int x2,int y2) : BOOL
IsWin(void) : BOOL
X1_Link_X2(int x,int y1,int y2) : BOOL
Y1_Link_Y2(int x1,int x2,int y) : BOOL
OneCornerLink(int x1,int y1,int x2,int y2) : BOOL
TwoCornerLink(int x1,int y1,int x2,int y2) : BOOL
Ythrough(int x,int y,BOOL bAdd) : BOOL
Xthrough(int x,int y,BOOL bAdd) : BOOL
LineX(int x,int y1,int y2) : BOOL
LineY(int x1,int x2,int y) : BOOL
图3.2 游戏模式类图
4 系统详细设计与实现
4.1游戏地图设计
对于整个游戏区域,可以把它看作一个是由若干个小方块构成的地图,而且每一个小方块放置着不同的动物图案,可将其称之为图案小方块。这些图案小方块零散地分布在地图的不同位置区域,并且每一个图案小方块都有与其对应的完全一样的另外一个小方块,如图4.1所示。
图4.1 游戏地图设计图
如图4.1所示,整个游戏游戏区域被抽象成一个有坐标位置属性的平面,平面上零散地分布着若干个小方块,并且这些小方块的物种起码是成对出现的。经过前面的描述和分析后,可以把游戏区域地图用一个数组m_map来表示。m_map是把地图设计成一个动态分配的int整形一维数组,对地图中的行列数的表达,用一个转换法则即可。可以在LinkToLinkDlg类对象定义中添加地图核心数据的成员变量,具体如下:
//地图位置相关属性组
int* m_map; //动态地图数据头指针(一维数组)
int m_nRow; //地图的行数(虚拟)
int m_nCol; //地图的列数(虚拟)
上面的成员变量中定义了一个整形指针标量m_map,用于记录动态分配出来的一维数组地图空间的首地址。对于地图区域中的某个小方块的类型,可以用一个整形的ID来进行识别。这里为标识地图的行列位置分别添加m_nRow和m_nCol变量。
现在,地图的数据结构已经设计好。下面对游戏进行初始化。由于方块需要成对地出现,因此在做地图的初始化时,不仅仅是对动物种类做简单的随机取数,然后将该随机选取出来的物件放到地图区域中去就了事,而是需要成对地对物种进行成对选取,就是说地图中的小方块必须是偶数个。
前面提到过,把地图数组设置成动态分配方式,目的是让其数据空间可以根据行列数的需求动态地获取,而对于实际不同大小比例的地图可以预先定义几组关于行列数的宏来实现。当需要创建时,根据宏值的不同分配不同大小的地图空间即可。接下来在LinkToLinkDlg类的构造函数对地图数据进行相关的初始化:
#define ROWCOUNT 8 //行数
#define COLCOUNT 12 //列数
CLinkToLinkDlg::CLinkToLinkDlg(CWnd* pParent /*=NULL*/)
: CDialog(CLinkToLinkDlg::IDD, pParent)
{ ……
//初始化行列数
m_nRow=ROWCOUNT;
m_nCol=COLCOUNT;
//根据行列数动态分配内核数据数组空间
m_map=new int[m_nRow*m_nCol];
}
CLinkToLinkDlg::~CLinkToLinkDlg()
{
//释放动态数组空间
delete[] m_map;
}
在LinkToLinkDlg类对象的实现中,定义了一些关于地图行列数的宏,如ROWCOUNT和COLCOUNT,并且在LinkToLinkDlg类对象的构造函数中,进行了行列的真实确认赋值,并根据当前行列数的大小对地图数据空间进行动态创建。因为地图数据是用new在堆栈动态创建的,所以在销毁该对象时要将这些内存空间释放,如代码所示在LinkToLinkDlg类对象的析构函数中调用delete将m_map指向的所有空间都释放掉。
4.2初始化工作
接下来,再分配好的空间中放上适当的图案方块物件,对数据进行初始化。即需要对地图空间内的数据进行成对性的随机布局,因此可以将该功能的实现封装在StartNewGame( )函数里面,其代码如下:
void CLinkToLinkDlg::StartNewGame()
{
//初始化地图,将地图中所有方块区域位置置为空方块状态
for(int iNum=0;iNum<(m_nCol*m_nRow);iNum++)
{
m_map[iNum] = BLANK_STATE;
}
//部下随机种子
srand(time(NULL));
//生成随机地图
//将所有匹配成对的动物物种放进一个临时的地图中
CDWordArray tmpMap;
for(int i=0;i<(m_nCol*m_nRow)/6;i++)
for(int j=0;j<6;j++)
tmpMap.Add(i);
//每次从上面的临时地图中取走(获取后并在临时地图删除)
//一个动物放到地图的空方块上
for(i=0;i<m_nRow*m_nCol;i++)
{
//随机挑选一个位置
int nIndex=(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的宏值来表示,具体定义如下:
#define BLANK_STATE -1 //空方块(没有任何动物)
可以看到,对图案方块的布局,先用srand()函数对时间函数布下随机种子,然后调用rand()函数对具体的图案方块的种类进行随机的获取。在这里需要引入一个临时地图tmpMap,该临时地图的大小与内核数据地图的大小一致,并且先添置好4组完全一样的图案类型ID数据(0~(m_nCol*m_nRow)/4),然后再将已经安放在tmpMap中的图案作随机抽取,并放到内核地图数据中去,将取出的元素从tmpMap中除去。
4.3图案方块的连接判断
对于选中的两个方块的销毁,他们必须符合下面3个条件:
(1)选中的两个方块图案相同。
(2)选中的两个方块之间没有障碍物阻碍的情况下,可以用若干个垂直的直线线段连接起来。
(3)这些将它们连接起来的直线线段的折点不超过两个(连接线由x轴和轴的平行线组成)。
现在针对(2)和(3)进行分析,如图4-3。
图4-2所示可知道,同种物件的连接方式大致可以分成以下3种:
(1)直接方式。
(2)有一个折点的垂直线段连接。
(3)有两个折点的垂直线段连接.。
①直接连接方式
在直接连接方式中,必须要求所选定的两个方块在同一水平直线上(可以为x方向或y方向),并且两个方块之间没有任何其他图案方块。
②一个这点连接方式
所选定的两个方块如果通过折点的方式连接,那么对于折点来说,每个折点必定有且至少有一个坐标(x或y)是和其中一个目标点相同的,即折点必定在两个目标点所在的x方向或y方向的直线上。
此外,对于一个折点连接的情况,折点应该为第一个选中方块的横向线或纵向线与第二个选中方块的纵向线和横向线相交而得出。
③两个折点的连接方式
这种方式的两个折点所连成的直线与两物件的直接连线可以构成平行线,因此可以根据这个规律,将这条水平线在游戏区域允许的条件上下移动,然后通过判断整条带垂直折线点的曲线之间有无障碍物方式来确定是否可以连同。这种情况可以分为两种情况:
1)选中的两图案方块在同一直线,两折点间的直连线可在其这两个方块之间的空间位置作移动,其约束是不超过游戏边界区域。
2)选中的两图案方块不在同一直线,两折点间的直连线可在两个方块之间的空间位置作移动,其约束是两方块之间的区域。
经过上面详细的分析后,可以对选定的两方块是否可以作抵消操作可以这样设计下去。
首先,对简单的直接连情况进行判断,看其是否符合条件,假如不能,再加深一个级别的复杂度,对一个折点的情况进行判断,依次类推,如下图4.2所示。
图4.2 连线规则图
连线流程图如图4.3所示。
图4.3 连线流程图
根据如图4.3所示的流程图,可以对选定的两个方块(分别在(x1,y1)以及(x2,y2)两个区域位置,其中x,y分别代表行与列的概念)是否可以抵消作以下实现。把该功能封装在IsLink()函数里面,其代码如下所示:
// 判断选中的两个方块是否可以消除
BOOL CLinkToLinkDlg::IsLink(int x1, int y1, int x2, int y2)
{
//X直连方式
if(x1==x2)
{
if(X1_Link_X2(x1,y1,y2))
return TRUE;
}
//Y直连方式
else if(y1==y2)
{
if(Y1_Link_Y2(x1,x2,y1))
return TRUE;
}
//一个转弯直角的联通方式
if(OneCornerLink(x1,y1,x2,y2))
{
return TRUE;
}
//两个转弯直角的联通方式
else if(TwoCornerLink(x1,y1,x2,y2))
{
return TRUE;
}
return FALSE;
}
在上面的实现中,先是对直连方式中的x方向直连Y1_Link_Y2()以及y方向直连X1_Link_X2()这两种情况进行判断,如果尚未取得结果,再通过调用OneCornerLink()函数对一个折点的情况进行判断,或者更糟糕的时候调用TwoCornerLink()函数对两个这点的情况进行判断,然后得出最终结果。
下面将对上面涉及到的子功能模块进行实现,代码如下所示:
//X直接连通
BOOL CLinkToLinkDlg::X1_Link_X2(int x, int y1,int y2)
{
//保证y1的值小于y2
if(y1>y2)
{
//数据交换
int n=y1;
y1=y2;
y2=n;
}
//直通
for(int i=y1+1;i<=y2;i++)
{
if(i==y2)
return TRUE;
if(m_map[i*m_nCol+x]!=BLANK_STATE)
break;
}
//左通
if(XThrough(x-1,y1,FALSE)&&XThrough(x-1,y2,FALSE))
return TRUE;
//右通
if(XThrough(x+1,y1,TRUE)&&XThrough(x+1,y2,TRUE))
return TRUE;
return FALSE;
}
//Y直接连通
BOOL CLinkToLinkDlg::Y1_Link_Y2(int x1,int x2,int y)
{
if(x1>x2)
{
int x=x1;
x1=x2;
x2=x;
}
//直通
for(int i=x1+1;i<=x2;i++)
{
if(i==x2)
return TRUE;
if(m_map[y*m_nCol+i]!=BLANK_STATE)
break;
}
//上通
if(YThrough(x1,y-1,FALSE)&&YThrough(x2,y-1,FALSE))
return TRUE;
//下通
if(YThrough(x1,y+1,TRUE)&&YThrough(x2,y+1,TRUE))
return TRUE;
return FALSE;
}
// 是否同一直线通
//
BOOL CLinkToLinkDlg::LineX(int x,int y1,int y2)
{
if(y1>y2)
{
int y=y1;
y1=y2;
y2=y;
}
for(int y=y1;y<=y2;y++)
{
if(m_map[y*m_nCol+x]!=BLANK_STATE)
return FALSE;
if(y==y2)
return TRUE;
}
return FALSE;
}
//
// 是否同一直线通
//
BOOL CLinkToLinkDlg::LineY(int x1,int x2,int y)
{
if(x1>x2)
{
int x=x1;
x1=x2;
x2=x;
}
for(int x=x1;x<=x2;x++)
{
if(m_map[y*m_nCol+x]!=BLANK_STATE)
return FALSE;
if(x==x2)
return TRUE;
}
return FALSE;
}
// 直角接口连通
BOOL CLinkToLinkDlg::OneCornerLink(int x1, int y1,int x2, int y2)
{
if(x1>x2)
{
int n=x1;
x1=x2;
x2=n;
n=y1;
y1=y2;
y2=n;
}
if(y2<y1)
{
if(LineY(x1+1,x2,y1)&&LineX(x2,y1,y2+1))
return TRUE;
if(LineY(x2-1,x1,y2)&&LineX(x1,y2,y1-1))
return TRUE;
return FALSE;
}
else
{
if(LineY(x1+1,x2,y1)&&LineX(x2,y1,y2-1))
return TRUE;
if(LineY(x2-1,x1,y2)&&LineX(x1,y2,y1+1))
return TRUE;
return FALSE;
}
return FALSE;
}
// 直角接口连通
BOOL CLinkToLinkDlg::TwoCornerLink(int x1, int y1, int x2, int y2)
{
if(x1>x2)
{
int n=x1;
x1=x2;
x2=n;
n=y1;
y1=y2;
y2=n;
}
//右通
if(XThrough(x1+1,y1,TRUE)&&XThrough(x2+1,y2,TRUE))
return TRUE;
//左通
if(XThrough(x1-1,y1,FALSE)&&XThrough(x2-1,y2,FALSE))
return TRUE;
//上通
if(YThrough(x1,y1-1,FALSE)&&YThrough(x2,y2-1,FALSE))
return TRUE;
//下通
if(YThrough(x1,y1+1,TRUE)&&YThrough(x2,y2+1,TRUE))
return TRUE;
//右
for(int x=x1+1;x<m_nCol;x++)
{
if(m_map[y1*m_nCol+x]>-1)
break;
if(OneCornerLink(x,y1,x2,y2))
return TRUE;
}
//左
for(x=x1-1;x>-1;x--)
{
if(m_map[y1*m_nCol+x]!=BLANK_STATE)
break;
if(OneCornerLink(x,y1,x2,y2))
return TRUE;
}
//上
for(int y=y1-1;y>-1;y--)
{
if(m_map[y*m_nCol+x1]!=BLANK_STATE)
break;
if(OneCornerLink(x1,y,x2,y2))
return TRUE;
}
//下
for(y=y1+1;y<m_nRow;y++)
{
if(m_map[y*m_nCol+x1]!=BLANK_STATE)
break;
if(OneCornerLink(x1,y,x2,y2))
return TRUE;
}
return FALSE;
}
BOOL CLinkToLinkDlg::XThrough(int x, int y, BOOL bAdd)
{
if(bAdd)
{
for(int i=x;i<m_nCol;i++)
if(m_map[y*m_nCol+i]!=BLANK_STATE)
return FALSE;
}
else
{
for(int i=0;i<=x;i++)
if(m_map[y*m_nCol+i]!=BLANK_STATE)
return FALSE;
}
return TRUE;
}
BOOL CLinkToLinkDlg::YThrough(int x, int y,BOOL bAdd)
{
if(bAdd)
{
for(int i=y;i<m_nRow;i++)
if(m_map[i*m_nCol+x]!=BLANK_STATE)
return FALSE;
}
else
{
for(int i=0;i<=y;i++)
if(m_map[i*m_nCol+x]!=BLANK_STATE)
return FALSE;
}
return TRUE;
}
这里把直接连接方式分为直通,左通,右通3种情况,如4.4图所示。
图4.4 连接方式图
下面简单介绍直通和左通两种情况(右通与左通类似)
(1)直通:直通就是在选定的两个方块直连线中,没有被任何方块所阻碍。
(2)左通:左通就是选定的两个方块的直连线之间有其他方块阻碍,但是通过它的左侧可以将它们无阻碍地连通。直连方式中的此种情况跟前面分析的两个折点连接方式中的其中一点相当类似,如4-5图所示。
图4.5 两折通道图
它们之间的区别就是,左通的连接线直接在其相邻的位置连通,从而不构成垂直折点的效果,而两个折点连同方式中的其中一个类似的情况是,线的连通起码要偏移一个方块的距离来形成连通。
同理,有上通和下通,它们的情况与左通和右通类似。
4.4 游戏胜利的判断
要判断游戏的胜利,实现起来比较简单,只需对地图中的所有区域的状态进行检测就可里了,只要检测到地图中有一个图案方块还没有被抵消,则证明游戏没有结束,完成判断。其代码如下所示:
// 检测是否已经赢得了游戏
BOOL CLinkToLinkDlg::IsWin(void)
{
//检测所有是否尚有非未被消除的方块
// (非BLANK_STATE状态)
for(int i=0;i<m_nRow*m_nCol;i++)
{
if(m_map[i] != BLANK_STATE)
{ return FALSE;
}
}
return TRUE;
}
游戏胜利界面如图4.6所示。
图4.6 通关图
至此,已经完成整个游戏基本的内部相关功能模块的运算,接下来实现用户的交互部分的功能。
4.5鼠标交互功能的实现
对于用户交互的实现,这里选择鼠标交互方式。下面简单描述一下通过鼠标交互方式实现的功能。鼠标选取两个图案方块后,程序将自动判断所选定的两个方块是否能进行抵消操作,能则进行抵消操作。在游戏过程中,我们不断重复上面描述的功能,直到游戏的胜利结束。下面将鼠标事件处理工作归纳为如图4.7所示的流程。
图4.7 鼠标交互流程图
对于鼠标交互功能的实现,可以通过ClassWizard对鼠标左键被按下时触发的命令消息WM_LBUTTONDOWN进行拦截,并重写该消息的处理函数OnLButtonDown(),其程序清单如下所示:
void CLinkToLinkDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//计算鼠标点击方块的的位置
int x=point.x/FRONTWIDTH+(point.x%FRONTWIDTH?1:0)-1;
int y=point.y/FRONTHEIGHT+(point.y%FRONTHEIGHT?1:0)-1;
//在游戏区域内并且该区域还有该区域不是空的区域
if(x<m_nCol&&y<m_nRow&&m_map[y*m_nCol+x]!= BLANK_STATE)
{
//假设尚未记录第一个方块
if(m_nX1==BLANK_STATE)
{
//记录第一个方块的位置
m_nX1=x;
m_nY1=y;
//获取程序框架的设备环境
CDC *pWinDC = GetDC();
//临时绘制点中的方块外框
//只绘屏幕不载入内存位图
CPen myPen;
CPen *pOldPen;
myPen.CreatePen(PS_SOLID, 4, RGB(255,0,0));
pOldPen = pWinDC->SelectObject(&myPen);
//方块外框绘制,线条环绕绘制框架
pWinDC->MoveTo(x*FRONTWIDTH,y*FRONTHEIGHT);
pWinDC->LineTo(x*FRONTWIDTH,(y+1)*FRONTHEIGHT);
pWinDC->LineTo((x+1)*FRONTWIDTH,(y+1)*FRONTHEIGHT);
pWinDC->LineTo((x+1)*FRONTWIDTH,y*FRONTHEIGHT);
pWinDC->LineTo(x*FRONTWIDTH,y*FRONTHEIGHT);
//现场恢复
pWinDC->SelectObject(pOldPen);
}
else
{
//判断是否点击的方块非本身, 是否点击同一种动物
if((m_nX1!=x||m_nY1!=y)&&
m_map[m_nY1*m_nCol+m_nX1]==m_map[y*m_nCol+x]
)
{
//检测是否可以消除
if(IsLink(m_nX1,m_nY1,x,y))
{
//数据清理
m_map[m_nY1*m_nCol+m_nX1]=BLANK_STATE;
m_map[y*m_nCol+x]=BLANK_STATE;
}
}
//清空记录方块的值
m_nX1=BLANK_STATE;
m_nY1=BLANK_STATE;
//通知重绘
Invalidate(FALSE);
}
}
//察看是否已经胜利
if(IsWin())
{
MessageBox("恭喜您胜利闯关,即将开始新局");
StartNewGame();
}
}
按照预先设计出的鼠标事件处理流程,上面已经将它转换成具体的实现代码。下面将按照流程的子功能模块的划分方式,对整个功能模块的具体协调和实现过程进行简单的描述。
(1)首先,利用鼠标的当前坐标位置point对每个小单元方块的宽度FRONTWIDTH和高度FRONTHEIGHT分别取模,获取当前鼠标落点所在的游戏区域的具体行列数(x,y)。
(2)判断出该行列数(x,y)是否符合条件。保证运算出来的行数x和列数y的预定义区域最大的行数m_nCol和列数m_nRow内,并且点击的区域状态不是空白方块区域BLANK_STATE
(3)对判断此次鼠标书剑的选取是否与第一个方块的选取一样,只需通过用于记录第一个被选中的方块的行列数的成员变量m_nX1是否为有效即可。这里将m_nX1以及m_nY1来记录它所在的行、列数,并且每次经过判断后都会将它们的状态恢复为空白无选中状态BLANK_STATE。关于这两个用作记录第一个选中图案方块行列数的成员变量,在对话框类中的具体定义如下所示:
int m_nX1; //鼠标选中的记录方块列数
int m_nY1; //鼠标选中的记录方块行数
(4)对于本次选中的方块为第一选中的情况,先用m_nX1和m_nY1对当前的选中方块位置做记录,然后直接在屏幕的该区域绘制图像,为该方块区域添加一个红色的矩形外边框,用以提示用户当前的第1个图案方块选中所在的位置。需要注意的是,对于标记方块的加亮边框绘制是通过GetDC()函数来获取对话框窗体(屏幕)的设备环境,对绘制的图像数据没有作历史记录的方式来绘制的。
(5)在这一处理中,对该选定方块作一些判断,以便更高效地处理。判断选中的方块与前一方块是否为同一图案方块,并且此次选择不与上一次选定的方块为同一方块,然后才跳到下一步对两个选定的方块是否可以抵消的流程中去。
(6)调用前面已经实现的答功能函数IsLink()来判断当前所选定的两个图案方块是否可以抵消。
(7)如果可以抵消,对选中的两个方块在内部核心地图对应的数据状态作适当的修改,将它们的状态记作已经被销毁的空方块状态BLANK_STATE。
(8)完成第二个图案的选取与相关的功能操作后,我们需要前面已经选去第1个方块位置的记录作清理工作,以便下一个新方块的选择。
(9)最后,判断此次的鼠标操作是否已经胜利结束,如果是则给予用户提示,然后重新开适新的一关。
4.6连连看新纪录的实现
首先创建两个对话框模板,一个用作当用户胜利结束游戏并打破历史记录后弹出的签名记录对话框模板IDD_DLG_NEWRECORD,另外一个是用以显示以往最高的游戏记录的对话框模板IDD_DLG_HERO。分别如图4.8和图4.9所示。
图4.8 添加新记录图
图4.9 积分记录图
4.7绘图功能的实现
在完成以上所有的交互以及内部数据关联的运算处理后,最后要将这些数据展现到屏幕上。
(1)位图图像的准备
首先,准备好一个带有不同动物种类的位图,以及一个3D边框效果的位图,如图4.10所示。通过资源编辑器将它们引入到项目中来,并将他妈的ID分别命名为IDB_BMP_ANIMAL和IDB_BITMAP_3DFRAMES。
图4.10绘图图像
(2)绘图方案的设计
对于游戏的绘制实现,这里采用内存位图映射的方式,先将整个游戏区域的图像绘制到内存位图中,然后再一次性地将它拷贝到屏幕上予以显示,如图4.11所示。
图4.11绘图方案设计流程图
(3)绘图资源的载入与初始化
根据图3-1.所示的描述方案,可以在内存创建两个内存位图,并对它们的图像进行载入。在需要使用的时候,则可以从这些内存位图中直接去拷贝,并绘制到游戏区域内存位图中去,接下
展开阅读全文