欢乐连连看小游戏制作

论坛 期权论坛 脚本     
匿名技术用户   2020-12-28 20:10   22   0

之前完成了欢乐连连看的实验,现在来做一下总结,以实验的步骤为纲进行。

一.实验目的和要求

1. 目的

通过连连看项目,达到如下目标:

(1)了解业务背景,调研与连连看同类型游戏,了解连连看游戏的功能和规则等。

(2)掌握C++开发工具和集成开发环境(Microsoft Visual Studio 2015)

(3)掌握C++面向对象的编程思想和C++的基础编程。

(4)了解MFC基本框架,包括MFC Dialog应用程序和GDI编程。

(5)了解线性结构,重点掌握数组和栈操作,数组遍历、消子和胜负判断等算法。

(6)了解项目开发流程,了解系统需求分析和设计,应用迭代开发进行项目开发。

(7)养成良好的编码习惯和培养软件工程化思维,综合应用“C++编程、MFC Diaolog、算法、线性结构”等知识,开发“连连看游戏”桌面应用程序,达到掌握和应用线性结构核心知识的目的。

2. 要求

实现基本功能:开始游戏、暂停游戏、消子、判断胜负、提示、重排、计时等。

(1)主界面:设计“欢乐连连看”项目的主界面,在主界面上添加一个背景图片,并在适当的地方添加“基本模式”、“休闲模式”、“关卡模式”、“帮助”、“设置”、“排行榜”按钮。

(2)开始游戏:当玩家在主界面选择“基本模式”时,出现基本游戏界面,并隐藏主界面,玩家点击“开始游戏”按钮,生成游戏地图。

(3)消子:对玩家选中的两张图片进行判断,判断是否符合消除规则。符合一条直线连通、两条直线连通、三条直线连通这三种情况之一就可以消除。如果可以消除,从游戏地图中提示连接路径,然后消除这两张图片。如果不能消除,则保持原来的游戏地图。

消子规则

(4)判断胜负:在基本模式下如果将游戏地图中的所有的图片都消除,则提示玩家获胜,并且可以重新开始新游戏。

(5)提示:可以提示界面上能够消除的一对图片。

(6)重排:根据随机数,重新排列游戏地图上的图片。

(7)计时:设定一定的时间来辅助游戏是否结束。

(8)暂停游戏:游戏过程中可以暂停计时,并且将游戏地图遮盖,按钮显示为继续游戏。选择继续游戏,计时继续。

二.分析与设计

欢乐连连看项目采用MFC框架,软件采用三层结构。使用二维数组来保存游戏地图中的数据,基本实现了连连看的核心功能。

1. 数据结构设计

//保存游戏地图中的一个点的信息

typedef struct tagVertex

{

int row; //

int col; //

int disa; //信息类

}Vertex;

核心类设计

  1. CGameLogic类

数据成员:

static int s_nRows; //游戏行数

static int s_nCols; //游戏列数

static int s_nPicNum; //图片数

int PicNum;

Vertex m_avPath[4]; //保存在进行连接判断时所经过的顶点

int m_nVexNum; //顶点数

成员函数:

int **InitMap();

void ReleaseMap(int ** &pGameMap);

bool IsLink(int ** pGameMap, Vertex V1, Vertex V2); //判断是否连通

void Clear(int ** pGameMap, Vertex V1, Vertex V2); //消子

int GetVexPath(Vertex avPath[4]); //得到路径,返回的是顶点数

bool IsBlank(int **pGameMap);

bool SearchValidPath(int** pGameMap);

void ResetGraph(int** pGameMap);

protected:

bool LinkInRow(int ** pGameMap,Vertex V1,Vertex V2); //判断横向是否连通

bool LinkInCol(int ** pGameMap, Vertex V1, Vertex V2); //判断纵向是否连通

bool OneCornerLink(int ** pGameMap, Vertex V1, Vertex V2); //一个拐点连通判断

bool LineY(int ** pGameMap, int nRow1, int nRow2, int nCol); //直接连通Y轴

bool LineX(int ** pGameMap, int nRow, int nCol1, int nCol2); //直接连通X轴

void PushVertex(Vertex V); //添加一个路径顶点

void PopVertex(); //取出一个顶点

void ClearStack(); //清除栈

bool TwoCornerLink(int ** pGameMap, Vertex V1, Vertex V2); //三条直线消子判断

  1. CGameDlg类

数据成员:

HICON m_hIcon;

CDC m_dcMem;

CDC m_dcBG;

CDC m_dcElement;

CDC m_dcMask;

CPoint m_ptGameTop;

CSize m_sizeElem;

CRect m_rtGameRect;

bool m_bFirstPoint;

CGameControl m_GameC;

CGameControl *m_GameControl;

CGameLogic *m_GameLogic;

int static GameTime;

bool m_bPlaying;

int nTime;

MCIDEVICEID m_DeviceID;

CProgressCtrl mProcess;

int GameType;

int Count;

成员函数:

void InitElement();

DECLARE_MESSAGE_MAP();

public:

afx_msg void OnPaint();

void UpdateWindow();

void InitBackground();

void UpdateMap();

CGameDlg *m_cGame;

virtual BOOL OnInitDialog();

void DrawTipFrame(int nRow, int nCol);

void DrawTipLine(Vertex asvPath[4], int Vexnum);

afx_msg void OnBnClickedSetting();

afx_msg void OnBnClickedStart();//开始游戏

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);//

afx_msg void OnBnClickedTip();//提示

afx_msg void OnTimer(UINT_PTR nIDEvent);

afx_msg void OnBnClickedStop();//暂停

afx_msg void OnBnClickedRepeat();//重排

afx_msg void OnBnClickedHelp();//帮助

afx_msg LRESULT CGameDlg::OnMciNotify(WPARAM wParam, LPARAM lParam);

afx_msg void OnNMCustomdrawProgress1(NMHDR *pNMHDR, LRESULT *pResult);

  1. CGameControl类

数据成员:

CGameLogic m_GameLogic; //游戏逻辑操作对象

int ** m_pGameMap; //游戏地图数组指针

Vertex m_svSelFst; //选中的第一个点

Vertex m_svSelSec; //选中的第二个点

static int s_nRows;

static int s_nCols;

static int s_nPicNum;

成员函数:

void StartGame();

int GetElement(int nRow,int nCol);

bool Link(Vertex avPath[4], int &nVexnum, bool flag); //消子判断

bool IsWin();

void Reset( void );

void Help(Vertex tiPath[4], int &tiVexnum);

//不足

void SetFirstPoint(int nRow,int nCol); //设置第一个点

void SetSecPoint(int nRow, int nCol); //设置第二个点

2. 核心算法设计

//游戏地图消子算法

void CGameDlg::OnLButtonUp(UINT nFlags, CPoint point)

{

if (m_bPlaying == false)//如果游戏不在运行不执行鼠标响应

return;

bool bSuc;

int nRow = (point.y - m_ptGameTop.y) / m_sizeElem.cy;

int nCol = (point.x - m_ptGameTop.x) / m_sizeElem.cx;

//判断鼠标点击的区域

if (point.y<m_rtGameRect.top + m_ptGameTop.y || point.y>m_ptGameTop.y + CGameLogic::s_nRows*m_sizeElem.cy || point.x<m_rtGameRect.left + m_ptGameTop.x || point.x>m_ptGameTop.x + CGameLogic::s_nCols*m_sizeElem.cx || !m_bPlaying)

{

return CDialogEx::OnLButtonUp(nFlags, point);

}

if (m_GameC.m_pGameMap[nRow][nCol] >= 0)

DrawTipFrame(nRow, nCol);

if (m_bFirstPoint)

{

if (m_GameC.GetElement(nRow, nCol) != BLANK)

{

m_GameC.SetFirstPoint(nRow, nCol);

}

}

else {

if (m_GameC.GetElement(nRow, nCol) != BLANK)

{

m_GameC.SetSecPoint(nRow, nCol);

int nVexnum = 0;

Vertex avPath[4];

//连子判断

bSuc = m_GameC.Link(avPath, nVexnum, true);

if (bSuc == true)

{

//画提示线

DrawTipLine(avPath, nVexnum);

Sleep(150);

//更新地图

UpdateMap();

}

InvalidateRect(false);

}

if (m_GameC.IsWin())

{

m_bPlaying = false;

CString str1, str2;

str1.Format(_T("游戏结束"));

if (GameType != 2)

{

KillTimer(1);

mProcess.SetPos(GameTime);

CString str;

str.Format(_T("%d"), nTime);

GetDlgItem(IDC_STATIC)->SetWindowTextW(str);

str2.Format(_T("恭喜您!通关成功!用时%d秒!"), CGameDlg::GameTime - nTime);

if (GameType == 3)

{

str2.Format(_T("恭喜您!通关成功!用时%d秒!请进入下一关!"), CGameDlg::GameTime - nTime);

Count++;

m_GameC.m_GameLogic.PicNum++;

}

nTime = CGameDlg::GameTime;

}

else

str2.Format(_T("恭喜您!通关成功"));

MessageBox(str2, str1, MB_ICONINFORMATION);

GetDlgItem(IDC_Start)->EnableWindow(true);

}

}

m_bFirstPoint = !m_bFirstPoint;

}

//连子判断

bool CGameControl::Link(Vertex avPath[4], int & nVexnum, bool flag)

{

//判断是否同一张图片

if (m_svSelFst.row == m_svSelSec.row&&m_svSelFst.col == m_svSelSec.col)

{

return false;

}

//判断图片是否相同

if (m_pGameMap[m_svSelFst.row][m_svSelFst.col] != m_pGameMap[m_svSelSec.row][m_svSelSec.col])

{

return false;

}

//判断是否连通

if (m_GameLogic.IsLink(m_pGameMap, m_svSelFst, m_svSelSec))

{

//消子

if (flag)

m_GameLogic.Clear(m_pGameMap, m_svSelFst, m_svSelSec);

//返回路径顶点

nVexnum = m_GameLogic.GetVexPath(avPath);

return true;

}

return false;

}

bool CGameControl::IsWin()

{

if (m_GameLogic.IsBlank(m_pGameMap))

{

return true;

}

return false;

}

//重排核心算法

void CGameDlg::OnBnClickedRepeat()

{

// TODO: 在此添加控件通知处理程序代码

//获取地图大小和花色

int nRows = CGameLogic::s_nRows;

int nCols = CGameLogic::s_nCols;

int nPicNum = m_GameC.m_GameLogic.PicNum;

//设置种子

if (m_bPlaying)

{

srand((int)time(NULL));

//随机任意交换两个数字

int nVertexNum = nRows * nCols;

for (int i = 0; i < nVertexNum; i++)

{

//随机得到两个坐标

int nIndex1 = rand() % nVertexNum;

int nIndex2 = rand() % nVertexNum;

//交换两个数值

if (m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1 % nCols] != BLANK && m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2 % nCols] != BLANK)

{

int nTmp = m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1%nCols];

m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1%nCols] = m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2%nCols];

m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2%nCols] = nTmp;

}

}

m_dcMem.BitBlt(m_ptGameTop.x, m_ptGameTop.y, m_sizeElem.cx*nCols, m_sizeElem.cy*nRows, &m_dcMem, m_ptGameTop.x, m_ptGameTop.y, SRCINVERT);

m_dcMem.BitBlt(m_ptGameTop.x, m_ptGameTop.y, m_sizeElem.cx*nCols, m_sizeElem.cy*nRows, &m_dcBG, m_ptGameTop.x, m_ptGameTop.y, SRCPAINT);

UpdateMap();

}

}

3. 测试用例设计

界面设计

  1. 主界面布局设计

  1. 游戏界面布局设计

  1. 游戏地图设计:用int类型的二维数组存储地图中元素图片的编号,起始点在客户区的左上角,X轴向右为正,Y轴向下为正。

三.实验结果

1.创建解决方案和工程:VS项目通常包括解决方案和工程。使用Visual Studio 2015开发工具,创建一个空的解决方案,解决方案名为LinkGame.sln。利用MFC应用程序向导,创建一个基于MFC对话框(Dialog)工程,工程名为LLK。

修改主界面对话框的属性:

1.在使用应用程序向导工程时,选择添加最小化按钮

2.修改对话框的标题为“欢乐连连看”

3.用自定义的ico文件替换默认的文件,以修改对话框的图标

调试对话框的运行过程

2.主界面设计:选择一张符合条件的BMP图片作为背景,考虑主界面按钮位置的摆放

1)位图导入

(1)将位图资源文件放到物理磁盘工程目录下的res文件夹中。

(2)将位图资源导入到工程中。

(3)修改位图资源为IDB_MAIN_BG。

2)绘制窗口背景

(1)创建一个内存DC。

(2)在CLLKDlg类添加void InitBackground()函数。

(3)加载位图,创建兼容DC。

(4)在CLLKDlg::OnInitDialog()函数中调用InitBackground()函数。

(5)调用CDC::BitBlt()函数,将位图显示在主界面上。

位图的绘制流程

3)添加主界面的功能按钮:

利用工具中,对话框编辑器的Mockup Image辅助功能,进行按钮定位。

(1)给界面添加控件。

(2)修改按钮文本(Caption)和ID。

(3)通过调用MoveWindow()函数设置主界面客户区的大小。

(4)调用CenterWindow()函数,使窗口居中。

3.游戏界面设计

1)添加游戏对话框资源

2)创建并显示对话框

(1)添加游戏界面对话框类CGameDlg。

(2)创建并显示游戏对话框。

3)绘制游戏界面背景

(1)加载游戏界面背景图片。

(2)将图片选入位图内存。

(3)将图片从位图内存拷贝到视频内存。

(4)添加CGameDlg::UpdateWindow()函数,调整游戏窗口大小。

4)游戏界面布局

(1)设置游戏界面对话框标题。

(2)设置游戏界面对话框图标。

(3)添加控件。

调试运行

4.绘制游戏地图

1)加载游戏元素图片

(1)将游戏元素图片加载到程序中。

(2)添加CGameLogic类。

(3)在CGameLogic类中添加初始化游戏地图函数。

(4)在CGameLogic类中创建释放游戏地图函数

(5)调用初始化游戏地图函数,并进行异常处理。

(6)生成地图数据。

2)绘制游戏地图

(1)调用CGameControl类中的GetElement()获取相应行列位置图片的元素编号值,并将对应编号的图片区域的数据绘制到m_dcMem中的相应位置。

(2)游戏地图的起始点为客户区中的(20,50)。游戏地图分为10行16列,由CGameControl类的静态成员变量s_nRows和s_nCols得到。每格的大小和元素图片一致,每个元素大小一致。

(3)在CGameDlg类中定义UpdateMap()函数,绘制游戏界面。

(4)在绘制游戏地图之后,调用InvalidateRect()函数,更新游戏区域。

3.)消除元素图片背景

4)程序优化,将绘制游戏界面的代码和设置游戏窗口位置封装为单独的函数

5.同色消子

1)添加鼠标事件

2)选择图片

(1)判断点击位置是否在游戏地图中。

(2)计算鼠标点击位置的行号和列号。

(3)在鼠标选中的图周围绘制矩形提示框。

3)消除相同元素图片

6.程序结构调整:按三层结构的思路,对程序的结构进行设计和修改:表示层、业务逻辑层、数据存储层

1)程序结构设计

2)Vertex结构体的定义:程序中CGameLogic类和CGameControl类之间传递的信息为该结构体变量

3)编写CGameLogic类

4)编写CGameControl类

5)编写CGameDlg类

7.消子判断

1)一条直线消子

(1)添加IsLink函数进行连通判断。

(2)行号相同时,判断横向是否连通。

(3)列号相同时,判断是否纵向连通。

2)两条直线消子

(1)判断横向、纵向的线段是否能够连通。

(2)判断(nRow1,nCol1)到(nRow2,nCol2)能否连通。

(3)在CGameLogic::IsLink()中调用CGameDlg::OneCornerLink(),判断能否进行两条直线消子。

3)三条直线消子

在CGameLogic::TwoCornerLink()函数中,判断能否进行三条直线消子。

4)绘制连通线

(1)判断选择的图片是否为同一种图片。

(2)对选中的两张图片进行连通判断。

(3)获取连接路径。

(4)绘制连接线。

8.判断胜负

1)判断胜负

2)控制开始游戏按钮状态

9.提示

1)逻辑层实现提示功能

2)控制层实现提示功能

3)界面层实现提示功能

10.重排

1)随机开局

2)逻辑层实现重排功能

3)控制层实现重排功能

4)表示层实现重排功能

5)调整地图大小

11.计时

1)添加进度条

2)添加计时器

3)显示时间

4)判断胜负

5)暂停游戏

编译运行程序。

实验结果部分截图如下

1.主界面:程序启动时,出现系统的主界面

2.进入基本模式

3.开始游戏界面

4.消子

完整代码见https://download.csdn.net/download/gyx1549624673/10637456

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP