1、雷区和提示区。提示区包括两个计数器和一个按键操作结果图像提示。游戏过程中,当玩家用鼠标点击相应的方块,程序就会作出相应的鼠标响应事件,并伴随着GDI绘图,而众多鼠标事件的处理,都是围绕着实现扫雷程序的算法而衍生的。3 总体设计3.1 游戏框架的搭建3.1.1 工程项目的创建利用应用程序向导创建一个名称为Mine的工程项目。由于不需要诸如工具栏、状态栏等功能,并且扫雷游戏的框架是不允许改变窗口大小的,所以在向导的第四步里面把所有的选项置空,然后点击“Advanced”按钮,在弹出的对话框中选中“Windows Styles”选项卡,将“Maximize box”项置空,其他均使用默认设置。3.1
2、.2 框架的改造通过类向导添加一个继承于CFrameWnd的类,命名为CMineWnd,删除CMineDoc、CMineView和CAboutDlg类,将CMineWnd类代替CFrameWnd,让程序启动的时候以此窗口为主窗口予以显示。结果如图1.2。图1.23.2 菜单的制作参考Windows自带的扫雷游戏,创建出“游戏”和“帮助”菜单,然后通过菜单资源编辑器设定菜单的功能选项,包括难度级别的选择、颜色和音效是否开启、扫雷英雄榜、使用手册、关于软件的信息等。具体的菜单选项分别如图1.3。3.2.1难度级别的选择不同的难度级别有不同的雷区大小和不同的布雷数目,所以需要设置游戏的难度级别。其宏
3、定义如下所示,预定义了不同级别的横向方块数目、纵向方块数目和雷数。并将该宏定义放入新建的头文件“MineDefs.h”中。#define PRIMARY_XNUM 9 /初级x方向的方块区域数目#define PRIMARY_YNUM 9 /初级y方向的方块区域数目 #define PRIMARY_MINENUM 10 /初级雷的数目#define SECONDRY_XNUM 16#define SECONDRY_YNUM 16#define SECONDRY_MINENUM 40#define ADVANCE_XNUM 30#define ADVANCE_YNUM 16#define ADV
4、ANCE_MINENUM 99窗口除了雷区外至少还包括蓝色窗口边缘Frame_wide、白色的视觉效果区line_wide、3D的外壳边框3D_line_wide、雷区mine_area_wide等。于是还需要定义关于位置的宏变量,如下所示。/窗口宽度相关定义#define DEFAULT_FRAME_X 6 /窗口X方向宽#define DEFAULT_FRAME_Y 52 /窗口Y方向宽#define LINE_WIDTH_0 3 /线边0的宽度#define LINE_WIDTH_1 2 /线边1的宽度#define SIDE_WIDTH_0 6 /边0的宽度#define SIDE_W
5、IDTH_1 5 /边1的宽度#define SHELL_S_H 37 /小外壳的高度#define SHELL_S_START_X 9 /小外壳的X坐标始发点#define SHELL_S_START_Y 9 /小外壳的Y坐标始发点#define SHELL_L_START_X 9 /大外壳的X坐标始发点#define SHELL_L_START_Y 52 /大外壳的Y坐标始发点#define MINEAREA_FRAME_X 12#define MINEAREA_FRAME_Y 55/雷方块定义#define MINE_WIDTH 16 /雷方块的大小(宽度为16的位图)#define M
6、INE_HEIGHT 16#define MINE_AREA_LEFT 12#define MINE_AREA_TOP 55由于难度级别的不同,窗口大小也会随之改变,因此通过在CMineWnd类增加一个改变窗口大小的函数SizeWindow()去实现,其代码如下所示。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
7、_FRAME_Y + m_uYNum * MINE_HEIGHT + LINE_WIDTH_0 * 3 + SIDE_WIDTH_0 * 2 + SIDE_WIDTH_1 + SHELL_S_H; / 改变窗口大小 SetWindowPos(&wndTopMost, 0, 0, uWidth, uHeight, SWP_NOZORDER | SWP_NOMOVE | SWP_NOCOPYBITS); GetClientRect(&m_rcClient); / 笑脸按钮位置 m_uBtnRect0 = m_rcClient.right / 2 - 12; m_uBtnRect1 = m_rcCl
8、ient.right / 2 - 13; m_uBtnRect2 = m_rcClient.right / 2 + 12; / 计数器位置 m_uNumRect0 = m_rcClient.right - 55; m_uNumRect1 = m_rcClient.right - 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_u
9、ShellRcY1 = m_rcClient.bottom - SHELL_L_START_Y - 5;通过ClassWizard分别选择“初级”、“中级”和“高级”菜单资源ID,为它们添加处理函数OnMenuPrimary()、OnMenuSecond() 、OnMenuAdvance()。OnMenuAdvance()的实现如下,另外两个类似。OnMenuAdvance() m_uLevel = LEVEL_ADVANCE; m_uXNum = ADVANCE_XNUM; m_uYNum = ADVANCE_YNUM; m_uMineNum = ADVANCE_MINENUM; SetCh
10、eckedLevel(); InitGame(); Invalidate(); SizeWindow();为了实现玩家对雷区大小的自定义,首先新建一个自定义雷区对话框资源(IDD_DLG_CUSTOM),然后添加高度、宽度、雷数三个静态文本控件和三个对应的(IDC_HEIGHT)、(IDC_WIDTH) 、(IDC_NUMBER)编辑框控件,最后将OK和Cancel按钮分别改名为“确定”和“取消”。首先为该对话框创建CDlgCustom类,然后为三个编辑控件分别添加关联变量m_uHeight、m_uNumber、m_uWidth,最后为OK按钮创建命令消息处理函数OnOK(),代码如下所示。v
11、oid CDlgCustom:OnOK() UpdateData(); if (m_uWidth 30) m_uWidth = 30; if (m_uHeight 24) m_uHeight = 24; 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 h
12、ere CDialog:OnOK();3.2.2使用帮助的实现由于Windows 自带有扫雷游戏,所以直接调用它的使用手。为“使用帮助”菜单选项创建命令消息处理函数OnMemuHelpUse(),代码如下所示。OnMemuHelpUse() /在命令行调用HH.exe,并输入参数NTHelp.CHM,/令其打开该文件,即Windows 自带有扫雷游戏的是使用手册 :WinExec(HH NTHelp.CHM, SW_SHOW);3.2.3关于信息的实现OnMemuAbout() ShellAbout(this-m_hWnd, 扫雷, yixiaoqianjin,NULL);3.3布雷,扫雷核心
13、算法的设计与实现把整个雷区看成一个二维数组,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 uCol; /所在雷区二位数组的列 UINT uState; /当前状态 U
14、INT uAttrib; /方块属性 UINT uOldState; /历史状态 MINEWND; / 雷方块结构体定义雷方块的状态类别和属性类别,具体描述如下所示。#define STATE_NORMAL 0 /正常#define STATE_FLAG 1 /标志有雷#define STATE_DICEY 2 /未知状态0#define STATE_BLAST 3 /爆炸状态#define STATE_ERROR 4 /错误状态#define STATE_MINE 5 /雷状态#define STATE_DICEY_DOWN 6 /未知状态1#define STATE_NUM8 7 /周围有
15、8雷 #define STATE_NUM7 8#define STATE_NUM6 9#define STATE_NUM5 10#define STATE_NUM4 11#define STATE_NUM3 12#define STATE_NUM2 13#define STATE_NUM1 14#define STATE_EMPTY 15 /无雷#define ATTRIB_EMPTY 0 /非雷#define ATTRIB_MINE 1 /雷整个游戏程序包含3个阶段:布雷、扫雷过程和结果(并不是操作结果展示,而是在扫雷过程中,玩家通过与游戏交互后的操作结果展示)。3.3.1 布雷随即获取一个
16、状态为非雷的点,将它的属性标志为雷,重复这样的工作,直到布下足够的雷为止,其流程如图1.4所示。图1.4在CMineWnd类中添加游戏的布雷模块的处理函数,该函数的实现如下。LayMines(UINT row, UINT col)/埋下随机种子 srand( (unsigned)time( NULL ) ); UINT i, j; for(UINT index = 0; index uRow, m_pOldMine-uCol); if (m_uGameState = GS_WAIT) m_uBtnState = BUTTON_NORMAL; Invalidate(); ReleaseCaptu
17、re(); return; /假若周围已经标识的雷周围真正的雷数,拓展 if (m_pOldMine-uState != STATE_FLAG) OpenAround(m_pOldMine- if (ErrorAroundFlag(m_pOldMine-uCol) Dead(m_pOldMine- else /如果游戏尚未开始,点击左键启动游戏 if (m_uGameState = GS_WAIT) if (m_uTimer) KillTimer(ID_TIMER_EVENT); m_uTimer = 0; m_uSpendTime = 1; if (m_bSoundful) sndPlayS
18、ound(LPCTSTR)LockResource(m_pSndClock), SND_MEMORY | SND_ASYNC | SND_NODEFAULT); /启动定时器 m_uTimer = SetTimer(ID_TIMER_EVENT, 1000, NULL); /布雷 LayMines(m_pOldMine- / lay all the mines down /改变游戏状态为运行/GS_RUN m_uGameState = GS_RUN;uOldState = STATE_NORMAL) /当该雷区域为正常未作标记才打开 /如果该区域为雷,则死亡 if (IsMine(m_pOld
19、Mine-uCol) Dead(m_pOldMine- ReleaseCapture(); return; / the special MINEWND is not a mine /不是雷的时候,获取其周围的雷数目 around = GetAroundNum(m_pOldMine- / 如果为空白区域,拓展,否则打开该区域(显示周围有多少雷数) if (around = 0) ExpandMines(m_pOldMine- else DrawDownNum(m_pOldMine, around); else if (m_pOldMine-uOldState = STATE_DICEY)/标志为
20、“?”问号的时候 m_pOldMine-uState = STATE_DICEY; /判断是否为胜利 if (Victory() break; case GS_VICTORY: case GS_DEAD: ReleaseCapture(); / release the cursor return; default : 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
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1