欢乐连连看小游戏制作
标签: 欢乐连连看小游戏制作 JavaScript博客 51CTO博客
2023-07-14 18:24:15 197浏览
之前完成了欢乐连连看的实验,现在来做一下总结,以实验的步骤为纲进行。
一.实验目的和要求
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;
核心类设计
- 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. 测试用例设计
界面设计
- 主界面布局设计
- 游戏界面布局设计
- 游戏地图设计:用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.消子
完整代码见
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论