欢乐连连看小游戏制作

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

一.实验目的和要求

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

猜你喜欢

转载自blog.csdn.net/gyx1549624673/article/details/82252352