嵌入式系统课程设计
连连看游戏设计与分析
作 者 姓 名:
专业、班级 :
学 号 :
指 导 教 师:
完 成 日 期:
目录
引言……………………………………………………………………………………..
1. 系统分析与总体设计…...........................................................................................1
1.1 项目概述……………………………………………………………………….
1.1.1 项目概要…………………………………………………………………..
1.1.2项目的意义………………………………………………………………...
1.2.3主要问题…………………………………………………………………...
1.2 总体设计……………………………………………………………………….
1.2.1总体结构图………………………………………………………………...
1.2.2系统功能…………………………………………………………………...
1.2.3系统结构及相关模块……………………………………………………..
2. 系统的详细设计………………………………………………………………….
2.1初始化游戏界面…………………………………………………………….
2.2 图片的选择…………………………………………………………………….
2.3 图片的判断与消除…………………………………………………………….
2.4 游戏的结果…………………………………………………………………….
3.系统的实现与调试………………………………………………………………….
3.1 调试过程……………………………………………………………………….
3.2运行结果………………………………………………………………………..
1、 系统分析与总体设计
1.1项目概述
1.1.1 项目概要
"连连看"是近几年流行于网络的一种益智类休闲小游戏。它主要通过在游戏界面内连接相同图案来进行消除,以最终完全消除游戏界面内的图案为游戏完成的标准。这项小游戏操作简单,只需用鼠标进行点击操作即可,并且对玩家观察力、判断力、反应能力起到很好的煅炼作用,故一经推出就受到了广大玩家的喜爱。
1.1.2 项目的意义
随着社会节奏的加快,平常工作的竞争强度也加大,生活的压力也跟着加重。所以休息时或下班后大家都需要个休闲的娱乐工具,来放松自己,缓解自己的压力,消除紧张的疲劳状态。连连看游戏是一种意趣休闲的小游戏,不仅能调节精神,还能在游戏中锻炼我们的眼力视野,注意力的专心的程度,开发我们的思维能力,同时可以充实自己,向高等级挑战,培养我们面对困难时自己所具有攻克困难的信心。
当今基于EVC4.0编程技术的游戏开发十分流行,被游戏开发者所普遍应用。而“连连看”是近来网络上非常流行的一种休闲游戏,它对电脑配置要求不高,娱乐性强,简单易玩。目前大学生课程涉及基于EVC编程技术游戏开发方向的相对较少,这次通过连连看游戏的设计开发将对理解EVC技术以及EVC技术在游戏方面的应用起到很大的帮助作用,同时为EVC++编程技术及游戏设计打下坚实的基础。
1.1.3 主要问题
开始制作游戏时,主要要解决的问题有以下几个方面:如何设置整个游戏的界面;如何控制连连看游戏中随机图片的生成且每种图片必须为偶数个;游戏开始后,判断鼠标两次点击的图片能否消去,即图片是否相同且图片之间路径的判断.
1.2 总体设计
1.2.1 总体结构图
图1.1
1.2.2 系统功能
(1) 初始化游戏界面
该部分主要由执行窗口创建函数及位图加载函数来实现。通过数据的初始化及位图资源的加载为用户呈现一个游戏初始的界面。
(2) 图片的选择
该功能主要由鼠标来完成。在OnLButtonDown()事件函数中通过鼠标的点击事件选取所要消除的两个相同图片。
(3) 图形的判断与消除
对于第(2)步所选的两个图片,对其连通性进行判断:如果两图片直线连通,则相互消去;如果两图片连接为一个拐点且相通,则相互消去;如果两图片连接为两个拐点且相通,则相互消去;否则,不能消去。
(4) 判断游戏是否死锁或结束
如果所有的图片全部消去,则提示“游戏结束!”的信息。当游戏玩家不可能在消去任意两个图片时,游戏进入死锁状态。此时提示相关信息。
1.2.3 系统的结构及相关模块
图1.2
2、 系统的详细设计
2.1初始化游戏页面
首先调用CenterWindow()函数创建一个居中的窗口,再执行m_bitmap.LoadBitmap()函数来加载指定的位图资源,然后通过调用m_MemDC.CreateCompatibleDC() 创建一个与指定设备兼容的内存设备上下文环境(DC),再调用CreateCompatibleBitmap()函数创建与指定的设备环境相关的设备兼容的位图;最后调用FillMemDC()函数填充所创建的窗
口。该部分的实现代码下:
BOOL CPocketLianDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
//创建一个居中的窗口
CenterWindow(GetDesktopWindow());
//加载指定的位图资源
m_bitmap.LoadBitmap(IDB_BITMAP_129);
CClientDC dc(this);
//创建一个与指定设备兼容的内存设备上下文环境
m_MemDC.CreateCompatibleDC(&dc);
CBitmap bitmap;
//创建与指定的设备环境相关的设备兼容的位图
bitmap.CreateCompatibleBitmap(&dc,m_nCol*FRONTWIDTH+5,m_nRow*FRONTHEIGHT+5);
m_MemDC.SelectObject(&bitmap);
int n=GetTickCount();
srand(n);//time(NULL));
CDWord arPlayer;
for(int i=0;i<(m_nCol*m_nRow)/4;i++)
for(int j=0;j<4;j++)
arPlayer.Add(i);
for(i=0;i
{
int nIndex=(int(rand()*0.1+rand()*0.01+rand()))%arPlayer.GetSize();
m_Player[i]=arPlayer.GetAt(nIndex);
arPlayer.RemoveAt(nIndex);
}//填充所创建的窗口
FillMemDC();
return TRUE;
}
2.2 图形的选择
该部分主要由鼠标控制来实现。鼠标选取两个图片后,程序将自动判断所选定的两个方块是否能进行抵消操作,能则进行抵消操作。在游戏过程中,我们不断重复上面描述的功能,直到游戏的胜利结束。下面将鼠标事件处理工作归纳为如图1.7所示的流程。
图2.1
对于鼠标交互功能的实现,可以通过ClassWizard对鼠标左键被按下时触发的命令消息WM_LBUTTONDOWN进行拦截,并重写该消息的处理函数OnLButtonDown(),其程序清单如下所示。
void CPocketLianDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//1.计算鼠标点击方块的的位置
int x=point.x/FRONTWIDTH+(point.x%FRONTWIDTH?1:0)-1;
int y=point.y/FRONTHEIGHT+(point.y%FRONTHEIGHT?1:0)-1;
//2.在游戏区域内并且该区域还有该区域不是空的区域
if(x
{
//3.假设尚未记录第一个方块
if(m_nX1==BLANK_STATE)
{
//4.记录第一个方块的位置
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
{
//5.判断是否点击的方块非本身, 是否点击同一种动物
if((m_nX1!=x||m_nY1!=y)&&
m_map[m_nY1*m_nCol+m_nX1]==m_map[y*m_nCol+x]
)
{
//6.检测是否可以消除
if(IsLink(m_nX1,m_nY1,x,y))
{
//7.数据清理
m_map[m_nY1*m_nCol+m_nX1]=BLANK_STATE;
m_map[y*m_nCol+x]=BLANK_STATE;
}
}
//8.清空记录方块的值
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)最后,判断此次的鼠标操作是否已经胜利结束,如果是则给予用户提示,然后重新开适新的一关。
2.3图形的判断与消除
对于选中的两个图片的销毁,它们必须符合下面3个条件:
(1)选中的两个图片图案相同。
(2)选中的两个图片之间没有障碍物阻碍的情况下,可以用若干个垂直的直线线段连接起来。
(3)这些将它们连接起来的直线线段的折点不超过两个(连接线由x轴和y轴的平行线组成)。
现在针对(2)和(3)进行分析,同种物件的连接方式大致可以分成以下3种:
(1) 直接方式
(2) 有一个折点的垂直线段连接。
(3) 有两个折点的垂直线段连接.。
1. 直接连接方式
在直接连接方式中,必须要求所选定的两个方块在同一水平直线上(可以为x方向或y方向),并且两个方块之间没有任何其他图案方块。
2.一个这点连接方式
所选定的两个方块如果通过折点的方式连接,那么对于折点来说,每个折点必定有且至少有一个坐标(x或y)是和其中一个目标点相同的,即折点必定在两个目标点所在的x方向或y方向的直线上。
此外,对于一个折点连接的情况,折点应该为第一个选中方块的横向线或纵向线与第二个选中方块的纵向线和横向线相交而得出。
3.两个折点的连接方式
这种方式的两个折点所连成的直线与两物件的直接连线可以构成平行线,因此可以根据这个规律,将这条水平线在游戏区域允许的条件上下移动,然后通过判断整条带垂直折线点的曲线之间有无障碍物方式来确定是否可以连同。这种情况可以分为两种情况:
(1)选中的两图案方块在同一直线,两折点间的直连线可在其这两个方块之间的空间位置作移动,其约束是不超过游戏边界区域。
(2)选中的两图案方块不在同一直线,两折点间的直连线可在两个方块之间的空间位置作移动,其约束是两方块之间的区域。
经过上面详细的分析后,可以对选定的两方块是否可以作抵消操作可以这样设计下去。
首先,对简单的直接连情况进行判断,看其是否符合条件,假如不能,再加深一个级别的复杂度,对一个折点的情况进行判断,依次类推,如下图所示。
图2.2
图2.3
根据如图1.6所示的流程图,可以对选定的两个方块(分别在(x1,y1)以及(x2,y2)两个区域位置,其中x,y分别代表行与列的概念)是否可以抵消作以下实现。把该功能封装在Pass()函数里面,其代码如下所示:
// 判断选中的两个方块是否可以消除
BOOL CPocketLianDlg::Pass(int x1, int y1, int x2, int y2)//消去图片
{
//X直连方式
if(x1==x2)
{
if(X1EqualX2(x1,y1,y2))
return TRUE;
}
//Y直连方式
else if(y1==y2)
{
if(Y1EqualY2(x1,x2,y1))
return TRUE;
}
//一个拐点的联通方式
if(OnePlumb(x1,y1,x2,y2))
{
return TRUE;
}
//两个拐点的联通方式
else if(TwoPlumb(x1,y1,x2,y2))
{
return TRUE;
}
return FALSE;
}
在上面的实现中,先是对直连方式中的x方向直连Y1EqualY2()以及y方向直连X1EqualX2()这两种情况进行判断,如果尚未取得结果,再通过调用OnePlumb()函数对一个折点的情况进行判断,或者更糟糕的时候调用TwoPlumb()函数对两个这点的情况进行判断,然后得出最终结果。
下面将对上面涉及到的子功能模块进行实现,代码如下所示:
//x直连通
BOOL CPocketLianDlg::X1EqualX2(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_Player[i*m_nCol+x]>-1)
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 CPocketLianDlg::Y1EqualY2(int x1,int x2,int y)
{
// 保证x1的值小于x2
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_Player[y*m_nCol+i]>-1)
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 CPocketLianDlg::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_Player[y*m_nCol+x]>-1)
return FALSE;
if(y==y2)
return TRUE;
}
return FALSE;
}
// 是否同一直线通
BOOL CPocketLianDlg::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_Player[y*m_nCol+x]>-1)
return FALSE;
if(x==x2)
return TRUE;
}
return FALSE;
}
//一个拐点连通
BOOL CPocketLianDlg::OnePlumb(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
{
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 CPocketLianDlg::TwoPlumb(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
{
if(m_Player[y1*m_nCol+x]>-1)
break;
if(OnePlumb(x,y1,x2,y2))
return TRUE;
}
//左
for(x=x1-1;x>-1;x--)
{
if(m_Player[y1*m_nCol+x]>-1)
break;
if(OnePlumb(x,y1,x2,y2))
return TRUE;
}
//上
for(int y=y1-1;y>-1;y--)
{
if(m_Player[y*m_nCol+x1]>-1)
break;
if(OnePlumb(x1,y,x2,y2))
return TRUE;
}
//下
for(y=y1+1;y
{
if(m_Player[y*m_nCol+x1]>-1)
break;
if(OnePlumb(x1,y,x2,y2))
return TRUE;
}
return FALSE;
}
这里把直接连接方式分为直通,左通,右通3种情况,如1.7图所示。
下面简单介绍直通和左通两种情况(右通与左通类似)
(1)直通:直通就是在选定的两个方块直连线中,没有被任何方块所阻碍。
(2)左通:左通就是选定的两个方块的直连线之间有其他方块阻碍,但是通过它的左侧可以将它们无阻碍地连通。直连方式中的此种情况跟前面分析的两个折点连接方式中的其中一点相当类似,如1.8图所示。
它们之间的区别就是,左通的连接线直接在其相邻的位置连通,从而不构成垂直折点的效果,而两个折点连同方式中的其中一个类似的情况是,线的连通起码要偏移一个方块的距离来形成连通。
图1.7 图1.8
同理,有上通和下通,它们的情况与左通和右通类似。
2.4 游戏结束或死锁的判断
要判断游戏的结束,只需对地图中的所有区域的状态进行检测就可以了,若检测到地图中所有的图片都被消除,则证明游戏结束。当区域中出现任意不可能消除的两个图片时,游戏进入死锁状态。
//检测游戏是否结束
BOOL CPocketLianDlg::IsWin(void)
{
//检测所有是否尚有非未被消除的方块
for(int i=0;i
{
if(m_map[i] != BLANK_STATE)
{
return FALSE;
}
}
return TRUE;
}
//死锁状态如下图所示:
3、系统的实现与调试
3.1 调试过程
程序调试过程中出现的错误信息如下:
--------------------Configuration: PocketLian - Win32 (WCE ARMV4I) Debug--------------------
Compiling resources...
Compiling...
PocketLian.cpp
StdAfx.cpp
Linking...
uafxwced.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in coredll.lib(COREDLL.dll)
uafxwced.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined in coredll.lib(COREDLL.dll)
uafxwced.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in coredll.lib(COREDLL.dll); second definition ignored
uafxwced.lib(afxmem.obj) : warning LNK4006: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined in coredll.lib(COREDLL.dll); second definition ignored
Creating library ARMV4IDbg/PocketLian.lib and object ARMV4IDbg/PocketLian.exp
corelibc.lib(pegwmain.obj) : error LNK2019: unresolved external symbol WinMain referenced in function WinMainCRTStartup
ARMV4IDbg/PocketLian.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.
PocketLian.exe - 4 error(s), 2 warning(s)
解决方法如下:
(1)点击project目录下的project Setting 进入相关页面后,选择Link,在下面的Category栏中选择Input,然后将object/library modules下的commctrl.lib coredll.lib 及Ignore libraries下的$(CENoDefaultLib)删除
(2)将Category栏中类容选择为:Output ,然后将Entry-point symbol 下的Entry-point symbol的内容改为:wWinmainCRTStartup 。
经过以上两步的修改后,程序可以正常运行。
3.2 系统运行的结果
(1)初始界面
(2) 运行时界面
(4) 游戏结束时界面
本文来源:https://www.2haoxitong.net/k/doc/a77c6004ba1aa8114431d9b7.html
文档为doc格式