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

一.实验目的和要求

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)暂停游戏:游戏过程中可以暂停计时,并且将游戏地图遮盖,按钮显示为继续游戏。选择继续游戏,计时继续。

欢乐连连看小游戏制作_位图_02

 

二.分析与设计

欢乐连连看项目采用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. 主界面布局设计

 

欢乐连连看小游戏制作_应用程序_03

 

 

 

  1. 游戏界面布局设计

     

欢乐连连看小游戏制作_应用程序_04

 

  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()函数,将位图显示在主界面上。

欢乐连连看小游戏制作_欢乐连连看_05

位图的绘制流程

 

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.程序结构调整:按三层结构的思路,对程序的结构进行设计和修改:表示层、业务逻辑层、数据存储层

欢乐连连看小游戏制作_完整代码_06

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.主界面:程序启动时,出现系统的主界面

 

欢乐连连看小游戏制作_欢乐连连看_07

2.进入基本模式

欢乐连连看小游戏制作_位图_08

3.开始游戏界面

欢乐连连看小游戏制作_欢乐连连看_09

4.消子

欢乐连连看小游戏制作_连连看_10

完整代码见