基于C++的扫雷游戏设计与实现.docx

上传人:b****8 文档编号:28128853 上传时间:2023-07-08 格式:DOCX 页数:29 大小:173.24KB
下载 相关 举报
基于C++的扫雷游戏设计与实现.docx_第1页
第1页 / 共29页
基于C++的扫雷游戏设计与实现.docx_第2页
第2页 / 共29页
基于C++的扫雷游戏设计与实现.docx_第3页
第3页 / 共29页
基于C++的扫雷游戏设计与实现.docx_第4页
第4页 / 共29页
基于C++的扫雷游戏设计与实现.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

基于C++的扫雷游戏设计与实现.docx

《基于C++的扫雷游戏设计与实现.docx》由会员分享,可在线阅读,更多相关《基于C++的扫雷游戏设计与实现.docx(29页珍藏版)》请在冰豆网上搜索。

基于C++的扫雷游戏设计与实现.docx

基于C++的扫雷游戏设计与实现

1引言

扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。

不久之后,“方块”被改写成了游戏“Rlogic”。

在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。

两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。

1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。

1.1开发背景

在计算机逐步渗入社会生活各个层面的今天,计算机已经成为了人们日常生活中的一部分,越来越多的人使用计算机办公、娱乐等等。

扫雷游戏是Windows操作系统自带的一款小游戏,在过去的几年里,Windows操作系统历经数次换代更新,变得越来越庞大、复杂,功能也越来越强大,但是这款小游戏依然保持原来的容貌,可见这款小游戏受到越来越多人的喜爱。

本次的毕业设计我将利用VisualC++作为开发工具,开发一款类似的“扫雷游戏”。

1.2开发的目的以及意义

经过四年的大学学习,我对理论知识已经有了一定的了解与认知,本次的毕业设计便是将书本上所学的理论知识与实际相结合,同时也是对所学知识的一种检查,希望通过本次的毕业设计使自己在程序的开发和设计上有新的认识并能有所提高。

本次毕业设计既锻炼了我们的实际动手能力,又在老师的指导下进行了一次模拟实际产品的开发,对于我们以后工作能力的培养具有重要意义。

2需求分析

2.1功能概述

扫雷游戏的游戏界面如图1所示。

在这个界面中,由众多面积均等的小方块所组成的区域称之为雷区,雷区的大小由用户设置的游戏等级决定。

图1初级雷区

游戏开始时,系统会在雷区的某些小方块中随机布下若干个地雷。

安放好地雷的小方块称之为雷方块,其他的称之为非雷方块。

部署完毕后,系统会在其他非雷方块中填充一些数字。

某一个具体数字表示与其紧邻的8个方块中有多少雷方块。

玩家可以根据这些信息去判断是否可以打开某些方块,并把认为是地雷的方块打上标识。

如果某个数字方块周围的地雷全都标记完,可以指向该方块并同时点击鼠标左右键,将其周围剩下的方块挖开。

如果编号方块周围地雷没有全部标记,在同时点击鼠标左右键时,其他隐藏或未标记的方块将被按下一次(即闪烁一下)。

当玩家将所有地雷找出后,其余的非雷方块区域都已打开,此时游戏结束。

在游戏过程中,一旦错误地打开了雷方块则立即失败,游戏结束;当玩家标识的地雷数超过程序设定,虽然打开了全部其余方块,游戏仍然不会结束。

在游戏开始后,雷区上方有两个计数器。

右边的计数器显示用户扫雷所花费的总时间,以秒为单位;左边的计数器显示当前还剩余多少个雷方块。

2.2功能需求分析

游戏需要提供一个菜单栏,上面有不同的相关选项,如游戏的开始、难度设置、退出等。

按功能将游戏区域分成两个区域:

雷区和提示区。

提示区包括两个计数器和一个按键操作结果图像提示。

游戏过程中,当玩家用鼠标点击相应的方块,程序就会作出相应的鼠标响应事件,并伴随着GDI绘图,而众多鼠标事件的处理,都是围绕着实现扫雷程序的算法而衍生的。

3总体设计

3.1游戏框架的搭建

(1)工程项目的创建

利用应用程序向导创建一个名称为Mine的工程项目。

由于不需要诸如工具栏、状态栏等功能,并且扫雷游戏的框架是不允许改变窗口大小的,所以在向导的第四步里面把所有的选项置空,然后点击“Advanced”按钮,在弹出的对话框中选中“WindowsStyles”选项卡,将“Maximizebox”项置空,其他均使用默认设置。

(2)框架的改造

通过类向导添加一个继承于CFrameWnd的类,命名为CMineWnd,删除CMineDoc、CMineView和CAboutDlg类,将CMineWnd类代替CFrameWnd,让程序启动的时候以此窗口为主窗口予以显示。

结果如图2所示。

图2框架的改造

3.2菜单的制作

参考Windows自带的扫雷游戏,创建出“游戏”和“帮助”菜单,然后通过菜单资源编辑器设定菜单的功能选项,包括难度级别的选择、颜色和音效是否开启、扫雷英雄榜、使用手册、关于软件的信息等。

具体的菜单选项如图3所示。

图3游戏菜单

(1)难度级别的选择

不同的难度级别有不同的雷区大小和不同的布雷数目,所以通过宏定义预定义不同级别的横向方块数目、纵向方块数目和雷数。

并将该宏定义放入新建的头文件“MineDefs.h”中。

窗口除了雷区外至少还包括蓝色窗口边缘Frame_wide、白色的视觉效果区line_wide、3D的外壳边框3D_line_wide、雷区mine_area_wide等。

于是还需要定义关于位置的宏变量。

由于难度级别的不同,窗口大小也会随之改变,因此通过在CMineWnd类增加一个改变窗口大小的函数SizeWindow()去实现。

通过ClassWizard分别选择“初级”、“中级”和“高级”菜单资源ID,为它们添加处理函数OnMenuPrimary()、OnMenuSecond()、OnMenuAdvance()。

OnMenuAdvance()的实现如下,另外两个类似。

voidCMineWnd:

:

OnMenuAdvance()

{

m_uLevel=LEVEL_ADVANCE;

m_uXNum=ADVANCE_XNUM;

m_uYNum=ADVANCE_YNUM;

m_uMineNum=ADVANCE_MINENUM;

SetCheckedLevel();

InitGame();

Invalidate();

SizeWindow();

}

(2)雷区大小的自定义实现

首先新建一个自定义雷区对话框资源(IDD_DLG_CUSTOM),然后添加高度、宽度、雷数三个静态文本控件和三个对应的(IDC_HEIGHT)、(IDC_WIDTH)、(IDC_NUMBER)编辑框控件,最后将OK和Cancel按钮分别改名为“确定”和“取消”。

结果如图4。

图4自定义雷区

接着为该对话框创建CDlgCustom类,然后为三个编辑控件分别添加关联变量m_uHeight、m_uNumber、m_uWidth,最后为OK按钮创建命令消息处理函数OnOK(),代码如下所示。

voidCDlgCustom:

:

OnOK()

{

UpdateData();

if(m_uWidth<9)m_uWidth=9;

if(m_uWidth>30)m_uWidth=30;

if(m_uHeight<9)m_uHeight=9;

if(m_uHeight>24)m_uHeight=24;

if(m_uNumber<10)m_uNumber=10;

if(m_uNumber>m_uWidth*m_uHeight)m_uNumber=m_uWidth*m_uHeight-1;

CMineWnd*pMine=(CMineWnd*)AfxGetMainWnd();

pMine->SetCustom(m_uWidth,m_uHeight,m_uNumber);//TODO:

Addextravalidationhere

CDialog:

:

OnOK();

}

(3)使用帮助的实现

由于Windows自带有扫雷游戏,所以直接调用它的使用手。

为“使用帮助”菜单选项创建命令消息处理函数OnMemuHelpUse(),代码如下所示。

显示结果如图5所示。

voidCMineWnd:

:

OnMemuHelpUse()

{

ShellExecute(NULL,"open","1.txt",NULL,NULL,SW_SHOW);

}

 

图5使用帮助

(4)以往的记录

每一次游戏破记录则将有关信息保存下来。

显示结果如图6所示。

图6以往记录

(5)扫雷英雄榜的实现

首先创建两个对话框模板,一个用作当用户胜利结束游戏并打破历史记录后弹出的签名记录对话框模板IDD_DLG_NEWRECORD,另外一个是用以显示以往最高的游戏记录的对话框模板IDD_DLG_HERO。

如图7和图8所示。

图7记录对话框

图8排行榜

然后为IDD_DLG_HERO对话框模板创建CDlgHero类,分别为编辑框控件添加关联变量m_szBHolder、m_szBRecord、m_szEHolder、m_szERecord、m_szIHolder、m_szIRecord,并将Cancel按钮的ID和标题分别改为IDC_RESET和重新计分,三个静态文本标题设置为初级记录、中级记录、高级记录,最后为重新计分按钮创建命令消息处理函数OnReset()和其他成员函数。

对IDD_DLG_NEWRECORD对话框模板类似处理。

3.3布雷,扫雷核心算法的设计与实现

(1)算法的设计

把整个雷区看成一个二维数组,a[i][j]周围的雷个数是由如下8个雷区决定的(如果超出边界,应该再加以判断):

a[i-1][j-1],a[i-1][j],a[i-1][j+1],

a[i][],a[i][j+1],

a[i+1][j-1],a[i+1][j],a[i+1][j+1],

在被展开时,检查周围的雷数是否与周围标示出来的雷数相等,如果相等则展开周围未标示的雷区。

这样新的雷区展开又触发这个事件,就这样递归下去,一直蔓延到不可展开的雷区。

(2)核心算法的实现

整个游戏程序包含3个阶段:

布雷、扫雷过程和结果(并不是操作结果展示,而是在扫雷过程中,玩家通过与游戏交互后的操作结果展示)。

首先定义雷方块的数据结构,具体描述如下所示。

typedefstruct

{

UINTuRow;//所在雷区二维数组的行

UINTuCol;//所在雷区二位数组的列

UINTuState;//当前状态

UINTuAttrib;//方块属性

UINTuOldState;//历史状态

}MINEWND;//雷方块结构体

然后定义雷方块的状态类别和属性类别。

A布雷

随即获取一个状态为非雷的点,将它的属性标志为雷,重复这样的工作,直到布下足够的雷为止,其流程如图9所示。

在CMineWnd类中添加游戏的布雷模块的处理函数,该函数的实现如下。

voidCMineWnd:

:

LayMines(UINTrow,UINTcol)

{

//埋下随机种子

srand((unsigned)time(NULL));

UINTi,j;

for(UINTindex=0;index

{

//取随即数

i=rand()%m_uYNum;

j=rand()%m_uXNum;

if(i==row&&j==col)continue;

if(m_pMines[i][j].uAttrib!

=ATTRIB_MINE)

{

m_pMines[i][j].uAttrib=ATTRIB_MINE;//修改属性为雷

index++;

}

}

}

B扫雷

鼠标左击事件

其流程如图10所示。

 

图10游戏流程

当鼠标左键点击雷区域,并且该区域不是雷方块,需要进行打开以及拓展工作。

流程如图11所示。

鼠标左键点击事件的关键代码如下所示。

voidCMineWnd:

:

OnLButtonUp(UINTnFlags,CPointpoint)

{

//笑脸图按钮所在的区域

CRectrcBtn(m_uBtnRect[1],15,m_uBtnRect[2],39);

//雷区所在的区域

CRectrcMineArea(MINE_AREA_LEFT,MINE_AREA_TOP,

MINE_AREA_LEFT+m_uXNum*MINE_WIDTH,

MINE_AREA_TOP+m_uYNum*MINE_HEIGHT);

if(rcBtn.PtInRect(point))

{//点击笑脸图

Invalidate();

InitGame();

}

elseif(rcMineArea.PtInRect(point))

{//点击雷区域

CStringvalue;

UINTaround=0;

//根据不同的游戏状态作处理

switch(m_uGameState)

{

//游戏进行状态

caseGS_WAIT:

caseGS_RUN:

//firstgettheMINEWNDwhichifpushingdown

m_pOldMine=GetMine(point.x,point.y);

if(!

m_pOldMine)

{

ReleaseCapture();

return;

}

//检测判断当前状态是否为左右鼠标同时按下

if(m_bLRBtnDown)

{

m_bLRBtnDown=FALSE;

OnLRBtnUp(m_pOldMine->uRow,m_pOldMine->uCol);

if(m_uGameState==GS_WAIT)

{

m_uBtnState=BUTTON_NORMAL;

Invalidate();

ReleaseCapture();

return;

}

//假若周围已经标识的雷=周围真正的雷数,拓展

if(m_pOldMine->uState!

=STATE_FLAG)

{

OpenAround(m_pOldMine->uRow,m_pOldMine->uCol);

}

if(ErrorAroundFlag(m_pOldMine->uRow,m_pOldMine->uCol))

{

Dead(m_pOldMine->uRow,m_pOldMine->uCol);

ReleaseCapture();

return;

}

}

else

{

//如果游戏尚未开始,点击左键启动游戏

if(m_uGameState==GS_WAIT)

{

if(m_uTimer)

{

KillTimer(ID_TIMER_EVENT);

m_uTimer=0;

}

m_uSpendTime=1;

Invalidate();

if(m_bSoundful)

{

sndPlaySound((LPCTSTR)LockResource(m_pSndClock),SND_MEMORY|SND_ASYNC|SND_NODEFAULT);

}

//启动定时器

m_uTimer=SetTimer(ID_TIMER_EVENT,1000,NULL);

//布雷

LayMines(m_pOldMine->uRow,m_pOldMine->uCol);

//改变游戏状态为"运行/GS_RUN"

m_uGameState=GS_RUN;

}

if(m_pOldMine->uOldState==STATE_NORMAL)

{//当该雷区域为正常未作标记才打开

//如果该区域为雷,则死亡

if(IsMine(m_pOldMine->uRow,m_pOldMine->uCol))

{

Dead(m_pOldMine->uRow,m_pOldMine->uCol);

ReleaseCapture();

return;

}

//不是雷的时候,获取其周围的雷数目

around=GetAroundNum(m_pOldMine->uRow,m_pOldMine->uCol);

//如果为空白区域,拓展,否则打开该区域(显示周围有多少雷数)

if(around==0)ExpandMines(m_pOldMine->uRow,m_pOldMine->uCol);

elseDrawDownNum(m_pOldMine,around);

}

elseif(m_pOldMine->uOldState==STATE_DICEY)

{

//标志为“?

”问号的时候

m_pOldMine->uState=STATE_DICEY;

}

//判断是否为胜利

if(Victory())

{

Invalidate();

ReleaseCapture();

return;

}

}

break;

caseGS_VICTORY:

caseGS_DEAD:

ReleaseCapture();

return;

default:

break;

}

m_uBtnState=BUTTON_NORMAL;

Invalidate();

}

else

{//点击非雷区域

if(m_uGameState==GS_WAIT||m_uGameState==GS_RUN)

{

m_uBtnState=BUTTON_NORMAL;

InvalidateRect(rcBtn);

}

}

ReleaseCapture();

CWnd:

:

OnLButtonUp(nFlags,point);

}

 

在函数体的开始部分,先用rcBtn和rcMineArea两个矩形变量存储游戏的用户提示区域位置中的笑脸图区域以及雷区域的位置。

利用接口函数PtInRect()判断当前鼠标的位置(由参数point携带鼠标当前位置信息)是否在这两个区域内,如果检测到鼠标左键点击并释放在笑脸图的按钮区域rcBtn上,则调用初始化函数重新开始游戏,如果检测到鼠标左键点击并释放在雷区域rcMineArea,假若当前游戏状态处于已初始化完成但尚未开始的状态GS_WAIT时,则打开计时器,并且调用LayMines()函数进行布雷,然后修改游戏状态为GS_RUN进入游戏。

接着判断点击在小方块的状态是否被用于通过右键标记(可以标记为雷或者未知,此时游戏规则规定左键点击不生效),如果未标记,该状态为普通状态STATE_NORMAL时,先通过IsMine()检测是否点中地雷而失败地结束游戏,如果是,则调用函数Dead()来进行失败后的工作处理,反之对它进行打开显示与拓展操作。

先通过GetAroundNum()函数获取当前小方块相邻的8个位置的雷数。

如果当前小方块相邻区域的雷数为0,则可以向8个方向进行拓展,并显示该方块区域,直到不可拓展为止;如果当前小方块相邻区域的雷数不为0,则显示该方块区域的相邻雷数,用作提供用户对其他位置的信息判断的提示。

拓展操作的实现代码如下。

voidCMineWnd:

:

ExpandMines(UINTrow,UINTcol)

{

UINTi,j;

UINTminRow=(row==0)?

0:

row-1;

UINTmaxRow=row+2;

UINTminCol=(col==0)?

0:

col-1;

UINTmaxCol=col+2;

UINTaround=GetAroundNum(row,col);

//显示该区域的方块状态

m_pMines[row][col].uState=15-around;

m_pMines[row][col].uOldState=15-around;

//“打开”该区域,重绘

DrawSpecialMine(row,col);

//对周围一个雷都没有的空白区域

if(around==0)

{

for(i=minRow;i

{

for(j=minCol;j

{//对于周围可以拓展的区域进行的规拓展

if(!

(i==row&&j==col)&&

m_pMines[i][j].uState==STATE_NORMAL

&&m_pMines[i][j].uAttrib!

=ATTRIB_MINE)

{

if(!

IsInMineArea(i,j))continue;

ExpandMines(i,j);//递归拓展操作

}

}

}

}

}

经过打开或拓展后,最后通过Victory()判断游戏是否已经胜利结束,如果是则作胜利处理。

鼠标右击事件

其流程如图12所示。

图12右击事件

其实现代码如下所示。

voidCMineWnd:

:

OnRButtonDown(UINTnFlags,CPointpoint)

{

//笑脸图按钮所在的区域

CRectrcBtn(m_uBtnRect[1],15,m_uBtnRect[2],39);

//雷区所在的区域

CRectrcMineArea(MINE_AREA_LEFT,MINE_AREA_TOP,

MINE_AREA_LEFT+m_uXNum*MINE_WIDTH,

MINE_AREA_TOP+m_uYNum*MINE_HEIGHT);

m_bLRBtnDown=FALSE;

if(rcMineArea.PtInRect(point))

{//点击雷区域

if(m_uGameState==GS_WAIT||m_uGameState==GS_RUN)

{

m_pNewMine=GetMine(point.x,point.y);

if(!

m_pNewMine)return;

//检测判断当前状态是否为左右鼠标同时按下

if(nFlags==(MK_LBUTTON|MK_RBUTTON))

{

m_bLRBtnDown=TRUE;

OnLRBtnDown(m_pNewMine->uRow

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 初中作文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1