用VC++设计与实现扫雷系统_第1页
用VC++设计与实现扫雷系统_第2页
用VC++设计与实现扫雷系统_第3页
用VC++设计与实现扫雷系统_第4页
用VC++设计与实现扫雷系统_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、用VC+实现扫雷游戏程序学生姓名:尹一笑 指导老师:颜宏文摘要:本课程设计实现类似于Windows操作系统自带的扫雷游戏。在课程设计中,系统开发平台为Windows XP,程序设计语言采用Visual C+,程序运行平台为Windows 2000/XP。在程序设计中,把整个雷区看成一个二维数组,把雷方块定义为具有所在雷区二维数组的行和列、当前状态、方块属性、历史状态的结构体,采用了结构化与面向对象两种解决问题的方法。整个游戏程序包括了布雷、扫雷过程和结果三个阶段,在处理鼠标响应事件中伴随着GDI绘图。程序通过调试运行,实现了设计目标,能够同时满足扫雷游戏初学者和高手的需要。关键词:扫雷游戏程序

2、设计Visual C+GDI绘图1 概述扫雷游戏的游戏界面如图1.1所示。在这个界面中,由众多面积均等的小方块所组成的区域称之为雷区,雷区的大小由用户设置的游戏等级决定。玩家标定未知未知区周围雷数提示玩家标定地雷图1.1 游戏开始时,系统会在雷区的某些小方块中随机布下若干个地雷。安放好地雷的小方块称之为雷方块,其他的称之为非雷方块。部署完毕后,系统会在其他非雷方块中填充一些数字。某一个具体数字表示与其紧邻的8个方块中有多少雷方块。玩家可以根据这些信息去判断是否可以打开某些方块,并把认为是地雷的方块打上标识。当玩家将所有地雷找出后,其余的非雷方块区域都已打开,此时游戏结束。在游戏过程中,一旦错误

3、地打开了雷方块则立即失败,游戏结束;当玩家标识的地雷数超过程序设定,虽然打开了全部其余方块,游戏仍然不会结束。在游戏开始后,雷区上方有两个计数器。右边的计数器显示用户扫雷所花费的总时间,以秒为单位;左边的计数器显示当前还剩余多少个雷方块。2 需求分析本课程设计实现类似于Windows操作系统自带的扫雷游戏。游戏需要提供一个菜单栏,上面有不同的相关选项,如游戏的开始、难度设置、退出等。按功能将游戏区域分成两个区域:雷区和提示区。提示区包括两个计数器和一个按键操作结果图像提示。游戏过程中,当玩家用鼠标点击相应的方块,程序就会作出相应的鼠标响应事件,并伴随着GDI绘图,而众多鼠标事件的处理,都是围绕

4、着实现扫雷程序的算法而衍生的。3 总体设计3.1 游戏框架的搭建3.1.1 工程项目的创建利用应用程序向导创建一个名称为Mine的工程项目。由于不需要诸如工具栏、状态栏等功能,并且扫雷游戏的框架是不允许改变窗口大小的,所以在向导的第四步里面把所有的选项置空,然后点击“Advanced”按钮,在弹出的对话框中选中“Windows Styles”选项卡,将“Maximize box”项置空,其他均使用默认设置。3.1.2 框架的改造通过类向导添加一个继承于CFrameWnd的类,命名为CMineWnd,删除CMineDoc、CMineView和CAboutDlg类,将CMineWnd类代替CFra

5、meWnd,让程序启动的时候以此窗口为主窗口予以显示。结果如图1.2。图1.23.2 菜单的制作参考Windows自带的扫雷游戏,创建出“游戏”和“帮助”菜单,然后通过菜单资源编辑器设定菜单的功能选项,包括难度级别的选择、颜色和音效是否开启、扫雷英雄榜、使用手册、关于软件的信息等。具体的菜单选项分别如图1.3。3.2.1难度级别的选择不同的难度级别有不同的雷区大小和不同的布雷数目,所以需要设置游戏的难度级别。其宏定义如下所示,预定义了不同级别的横向方块数目、纵向方块数目和雷数。并将该宏定义放入新建的头文件“MineDefs.h”中。#define PRIMARY_XNUM9/初级x方向的方块区

6、域数目#define PRIMARY_YNUM9/初级y方向的方块区域数目#define PRIMARY_MINENUM10/初级雷的数目#define SECONDRY_XNUM16#define SECONDRY_YNUM16#define SECONDRY_MINENUM40#define ADVANCE_XNUM30#define ADVANCE_YNUM16#define ADVANCE_MINENUM99窗口除了雷区外至少还包括蓝色窗口边缘Frame_wide、白色的视觉效果区line_wide、3D的外壳边框3D_line_wide、雷区mine_area_wide等。于是还需要

7、定义关于位置的宏变量,如下所示。/窗口宽度相关定义#define DEFAULT_FRAME_X6/窗口X方向宽#define DEFAULT_FRAME_Y52/窗口Y方向宽#define LINE_WIDTH_03/线边0的宽度#define LINE_WIDTH_12/线边1的宽度#define SIDE_WIDTH_06/边0的宽度#define SIDE_WIDTH_1 5/边1的宽度#define SHELL_S_H37/小外壳的高度#define SHELL_S_START_X9/小外壳的X坐标始发点#define SHELL_S_START_Y9/小外壳的Y坐标始发点#defi

8、ne SHELL_L_START_X9/大外壳的X坐标始发点#define SHELL_L_START_Y52/大外壳的Y坐标始发点#define MINEAREA_FRAME_X12#define MINEAREA_FRAME_Y55/雷方块定义#define MINE_WIDTH16/雷方块的大小(宽度为16的位图)#define MINE_HEIGHT16#define MINE_AREA_LEFT12#define MINE_AREA_TOP55由于难度级别的不同,窗口大小也会随之改变,因此通过在CMineWnd类增加一个改变窗口大小的函数SizeWindow()去实现,其代码如下所示

9、。void CMineWnd:SizeWindow( void )/宽度UINT uWidth = DEFAULT_FRAME_X + m_uXNum * MINE_WIDTH +LINE_WIDTH_0 * 3 + SIDE_WIDTH_0 + SIDE_WIDTH_1; /高度UINT uHeight = DEFAULT_FRAME_Y + m_uYNum * MINE_HEIGHT + LINE_WIDTH_0 * 3 + SIDE_WIDTH_0 * 2 + SIDE_WIDTH_1 + SHELL_S_H;/ 改变窗口大小SetWindowPos(&wndTopMost, 0

10、, 0, uWidth, uHeight, SWP_NOZORDER | SWP_NOMOVE | SWP_NOCOPYBITS);GetClientRect(&m_rcClient);/ 笑脸按钮位置m_uBtnRect0 = m_rcClient.right / 2 - 12;m_uBtnRect1 = m_rcClient.right / 2 - 13;m_uBtnRect2 = m_rcClient.right / 2 + 12;/ 计数器位置m_uNumRect0 = m_rcClient.right - 55;m_uNumRect1 = m_rcClient.right -

11、 15;m_uNumRect2 = m_rcClient.right - 54;/ 3D效果外壳位置m_uShellRcX0 = m_rcClient.right;m_uShellRcX1 = m_rcClient.right - 14;m_uShellRcY0 = m_rcClient.bottom;m_uShellRcY1 = m_rcClient.bottom - SHELL_L_START_Y - 5;通过ClassWizard分别选择“初级”、“中级”和“高级”菜单资源ID,为它们添加处理函数OnMenuPrimary()、OnMenuSecond() 、OnMenuAdvance(

12、)。OnMenuAdvance()的实现如下,另外两个类似。void CMineWnd:OnMenuAdvance() m_uLevel = LEVEL_ADVANCE;m_uXNum = ADVANCE_XNUM;m_uYNum = ADVANCE_YNUM;m_uMineNum = ADVANCE_MINENUM;SetCheckedLevel();InitGame();Invalidate();SizeWindow();为了实现玩家对雷区大小的自定义,首先新建一个自定义雷区对话框资源(IDD_DLG_CUSTOM),然后添加高度、宽度、雷数三个静态文本控件和三个对应的(IDC_HEIGH

13、T)、(IDC_WIDTH) 、(IDC_NUMBER)编辑框控件,最后将OK和Cancel按钮分别改名为“确定”和“取消”。首先为该对话框创建CDlgCustom类,然后为三个编辑控件分别添加关联变量m_uHeight、m_uNumber、m_uWidth,最后为OK按钮创建命令消息处理函数OnOK(),代码如下所示。void CDlgCustom:OnOK() UpdateData();if (m_uWidth < 9) m_uWidth = 9;if (m_uWidth > 30) m_uWidth = 30;if (m_uHeight < 9) m_uHeight =

14、 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: Add extra validation hereCDialog:OnOK(

15、);3.2.2使用帮助的实现由于Windows 自带有扫雷游戏,所以直接调用它的使用手。为“使用帮助”菜单选项创建命令消息处理函数OnMemuHelpUse(),代码如下所示。void CMineWnd:OnMemuHelpUse() /在命令行调用HH.exe,并输入参数NTHelp.CHM,/令其打开该文件,即Windows 自带有扫雷游戏的是使用手册:WinExec("HHNTHelp.CHM", SW_SHOW);3.2.3关于信息的实现void CMineWnd:OnMemuAbout() ShellAbout(this->m_hWnd, "扫雷&

16、quot;, "",NULL);3.3布雷,扫雷核心算法的设计与实现把整个雷区看成一个二维数组,aij周围的雷个数是由如下8个雷区决定的(如果超出边界,应该再加以判断):ai-1j-1, ai-1j, ai-1j+1,ai, aij+1,ai+1 j-1, ai+1j, ai+1j+1,在被展开时,检查周围的雷数是否与周围标示出来的雷数相等,如果相等则展开周围未标示的雷区。这样新的雷区展开又触发这个事件,就这样递归下去,一直蔓延到不可展开的雷区。定义雷方块的数据结构,具体描述如下所示。typedef struct UINT uRow; /所在雷区二维数组的行UINT uCo

17、l; /所在雷区二位数组的列UINT uState; /当前状态UINT uAttrib; /方块属性UINT uOldState; /历史状态 MINEWND;/ 雷方块结构体定义雷方块的状态类别和属性类别,具体描述如下所示。#define STATE_NORMAL0/正常#define STATE_FLAG1/标志有雷#define STATE_DICEY2/未知状态0#define STATE_BLAST3/爆炸状态#define STATE_ERROR4/错误状态#define STATE_MINE5/雷状态#define STATE_DICEY_DOWN6/未知状态1#define

18、STATE_NUM87/周围有8雷 #define STATE_NUM78#define STATE_NUM69#define STATE_NUM510#define STATE_NUM411#define STATE_NUM312#define STATE_NUM213#define STATE_NUM114#define STATE_EMPTY15/无雷#define ATTRIB_EMPTY0/非雷#define ATTRIB_MINE1/雷整个游戏程序包含3个阶段:布雷、扫雷过程和结果(并不是操作结果展示,而是在扫雷过程中,玩家通过与游戏交互后的操作结果展示)。3.3.1 布雷随即获取

19、一个状态为非雷的点,将它的属性标志为雷,重复这样的工作,直到布下足够的雷为止,其流程如图1.4所示。开始生成随机的雷方块的坐标(x,y)判断(x,y)区域是否已经布下雷在(x,y)区域布雷,修改状态数据判断是否布下所有雷结束否是是否图1.4在CMineWnd类中添加游戏的布雷模块的处理函数,该函数的实现如下。void CMineWnd:LayMines(UINT row, UINT col)/埋下随机种子srand( (unsigned)time( NULL ) );UINT i, j;for(UINT index = 0; index < m_uMineNum;) /取随即数i = r

20、and() % m_uYNum;j = rand() % m_uXNum;if (i = row && j = col) continue;if(m_pMinesij.uAttrib != ATTRIB_MINE) m_pMinesij.uAttrib = ATTRIB_MINE;/修改属性为雷index+;3.3.2 扫雷鼠标左击事件鼠标左击事件流程如图1.5开始在雷区雷方块定位游戏结束胜利失败处理显示结束继续处理打开区域拓展最大的可能显示范围其他区域处理胜利处理否是是否否图1.5当鼠标左键点击雷区域,并且该区域不是雷方块,需要进行打开以及拓展工作。流程如图1.6开始获取该区

21、域周围雷的数目numNum = 0拓展该区域修改标志,打开显示该区域结束否是图1.6鼠标左键点击事件的关键代码如下所示。void CMineWnd:OnLButtonUp(UINT nFlags, CPoint point) /笑脸图按钮所在的区域CRect rcBtn(m_uBtnRect1, 15, m_uBtnRect2, 39);/雷区所在的区域CRect rcMineArea(MINE_AREA_LEFT, MINE_AREA_TOP, MINE_AREA_LEFT + m_uXNum * MINE_WIDTH, MINE_AREA_TOP + m_uYNum * MINE_HEIG

22、HT);if (rcBtn.PtInRect(point) / 点击笑脸图Invalidate();InitGame();else if (rcMineArea.PtInRect(point) /点击雷区域CString value;UINT around = 0;/根据不同的游戏状态作处理switch(m_uGameState) /游戏进行状态case GS_WAIT: case GS_RUN:/ first get the MINEWND which if pushing downm_pOldMine = GetMine(point.x, point.y);if (!m_pOldMine)

23、 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(

24、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;Invali

25、date();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);/ lay all the mines down /改变游戏状态为"运行/GS_RUN"m_uGameState = GS_RUN;if (

26、m_pOldMine->uOldState = STATE_NORMAL)/当该雷区域为正常未作标记才打开/如果该区域为雷,则死亡if (IsMine(m_pOldMine->uRow, m_pOldMine->uCol) Dead(m_pOldMine->uRow, m_pOldMine->uCol);ReleaseCapture();return;/ the special MINEWND is not a mine /不是雷的时候,获取其周围的雷数目around = GetAroundNum(m_pOldMine->uRow, m_pOldMine-&

27、gt;uCol);/ 如果为空白区域,拓展,否则打开该区域(显示周围有多少雷数)if (around = 0) ExpandMines(m_pOldMine->uRow, m_pOldMine->uCol);else DrawDownNum(m_pOldMine, around);else if (m_pOldMine->uOldState = STATE_DICEY)/标志为“?”问号的时候m_pOldMine->uState = STATE_DICEY;/判断是否为胜利if (Victory()Invalidate();ReleaseCapture();return

28、;break;case GS_VICTORY:case GS_DEAD:ReleaseCapture();/ release the cursorreturn;default :break;m_uBtnState = BUTTON_NORMAL;Invalidate();else /点击非雷区域if (m_uGameState = GS_WAIT | m_uGameState = GS_RUN)m_uBtnState = BUTTON_NORMAL;InvalidateRect(rcBtn);ReleaseCapture();/ release the cursorCWnd:OnLButton

29、Up(nFlags, point);在函数体的开始部分,先用rcBtn和rcMineArea两个矩形变量存储游戏的用户提示区域位置中的笑脸图区域以及雷区域的位置。利用接口函数PtInRect()判断当前鼠标的位置(由参数point携带鼠标当前位置信息)是否在这两个区域内,如果检测到鼠标左键点击并释放在笑脸图的按钮区域rcBtn上,则调用初始化函数重新开始游戏,如果检测到鼠标左键点击并释放在雷区域rcMineArea,假若当前游戏状态处于已初始化完成但尚未开始的状态GS_WAIT时,则打开计时器,并且调用LayMines()函数进行布雷,然后修改游戏状态为GS_RUN进入游戏。接着判断点击在小方

30、块的状态是否被用于通过右键标记(可以标记为雷或者未知,此时游戏规则规定左键点击不生效),如果未标记,该状态为普通状态STATE_NORMAL时,先通过IsMine()检测是否点中地雷而失败地结束游戏,如果是,则调用函数Dead()来进行失败后的工作处理,反之对它进行打开显示与拓展操作。先通过GetAroundNum()函数获取当前小方块相邻的8个位置的雷数。如果当前小方块相邻区域的雷数为0,则可以向8个方向进行拓展,并显示该方块区域,直到不可拓展为止;如果当前小方块相邻区域的雷数不为0,则显示该方块区域的相邻雷数,用作提供用户对其他位置的信息判断的提示。拓展操作的实现代码如下。void CMi

31、neWnd:ExpandMines(UINT row, UINT col)UINT i, j;UINT minRow = (row = 0) ? 0 : row - 1;UINT maxRow = row + 2;UINT minCol = (col = 0) ? 0 : col - 1;UINT maxCol = col + 2;UINT around = GetAroundNum(row, col);/显示该区域的方块状态m_pMinesrowcol.uState = 15 - around;m_pMinesrowcol.uOldState = 15 - around;/ “打开”该区域,

32、重绘DrawSpecialMine(row, col);/对周围一个雷都没有的空白区域if (around = 0)for (i = minRow; i < maxRow; i+) for (j = minCol; j < maxCol; j+) /对于周围可以拓展的区域进行的规拓展if (!(i = row && j = col) && m_pMinesij.uState = STATE_NORMAL&& m_pMinesij.uAttrib != ATTRIB_MINE) if (!IsInMineArea(i, j) continue;ExpandMines(i, j);/ 递归拓展操作经过打开或拓展后,最后通过Victory()判断游戏是否已经

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论