项目总结-扫雷程序

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

项目总结---扫雷程序
一、项目简述
该项目为windows应用程序项目,为纯C语言程序,其大量用windows API函数,倘若能自主完成该扫雷程序,对于初学者来说是一次大的飞跃。

理由很明显,扫雷程序所涵盖的知识点有:窗口和消息、位图和BitBlt、图形基础、鼠标按键消息处理、键盘快捷键处理、子窗口控件、对话框、菜单及其他资源、定时器、播放声音。

对于如此多的知识点涵盖,扫雷程序无非是初学者的首选之一。

二、实现过程
类结构设计:
类数据成员,包含windows类应用名,窗口句柄,消息类型,指向自身类的静态指针。

类成员函数,包含两大类,一是公共成员函数,主要是窗口的设计和注册函数,窗口的创建函数,窗口消息循环函数,以及窗口处理的静态回调函数;二是私有成员函数,主要提供给窗口处理函数的各个私有成员函数。

全局变量设计:
每个小方格出现的状态枚举类,游戏的输赢进行状态,笑脸的状态,高分用户成绩信息结构体,已经整个扫雷程序所用到的变量集合到一个结构体中。

1.切入点:实现扫雷区域的贴图
其中,面临的最大问题是:
如何将位图(BitMap)贴到windows应用窗口中?
为此,将要解决如下三个问题:
1)如何用数据结构表示扫雷区域中的每一个小方格?
将扫雷区域的模拟为一个二维数组,扫雷区域中的每一个小方格为二维数组中的一项。

然后用资源位图贴在二维数组中的每一个位置,这便涉及到如下的问题。

2)Windows应用程序如何获取小方格的资源位图?
在项目解决方案中的资源文件里添加资源,选择资源类型为Bitmap位图资源,将扫雷程序所需要的资源位图导入到资源文件里,并同时形成*.rc文件。

然后回到windows应用程序中,用LoadBitmap(hInstance,TEXT(“BitmapName”)函数将所添加的资源加载到函数返回的BITMAP类型中。

3)如何将资源位图(BitMap)贴到小方格中?
在上面的解决问题中,可利用返回的资源BITMAP类型,来获取资源位图的宽和高,便于将资源依次的贴到二维数组模拟的小方格中。

有了位图资源的宽(cxSource)和高(cySource),再依次的将位图贴到扫雷区域中。

实现代码如下:
for(int i = 0; i < var.Row; i++)
for(int j = 0; j < var.Col; j++)
BitBlt(hdc, i*cxSource, j*cySource, cxSource, cySource, hdcMem, 0, 0, SRCCOPY);
2.布雷
找到了程序的入口点,那么接下来的工作便顺着藤慢慢的找到最终的果实。

但过程还是很艰难的,可是最终的果实是更美味的。

知道了如何将位图(BitMap)贴到windows应用窗口中,紧随之而来的就是布雷。

普通的布雷方法:
扫雷程序的核心就是能更为快速的翻开不是雷所在的方格,并且每次开局具有雷的方格是随机布置在扫雷区域中的。

鉴于贴图时的构想,将扫雷区域再次模拟为一个二维数组,那么这个二维数组每一个值的类型为布尔型(BOOL),其中某个位置区域为雷表示这个位置的值true,反之,则为false。

更为优秀的布雷方法:
为了能不出现雷在扫雷区域某个地方过于集中,比如总雷数有8个,这8个雷全部出现在扫雷区域上/下半部分或左/右半部分,对于这种情况,就将每次布雷过于集中地情况必须重新布雷,直到不出现过于集中地情况。

相比普通的布雷方法,虽然时间复杂度提高了,但对于这样的小程序,时间上是可以被可用性而弥补的,相比应用程序的可用性大大提升了,可算小巫见大巫。

3.鼠标点击翻开小方格
这个部分是整个扫雷程序的核心重点,要完成每点击一个小方格,能将这个小方格成功的翻开。

在点击小方格之前,对于小方格的状态是未知的。

不明确这个小方格是一个雷,还是一个数字,还是一个空白。

当遇到雷时,也就意味着游戏者失败;当点击为一个数字是,能成功地翻开这个小方格;当点击为空白时,那么就依次翻开周围连续为空白的小方格,直到遇着数字或旗子或雷时,就结束这次翻转小方格(这次点击的效果)。

在这之前要做很多的技术处理(相关准备),比如怎样获取鼠标的位置,并且怎样准确的算出鼠标点击的位置与当前被点小方格的位置是一致的。

不至于出现点击鼠标位置下的小方格,而响应的鼠标位置下以外的小方格的情况。

1)获取鼠标点击的位置
在消息处理函数中的WM_MOUSEMOVE随时都记录了鼠标移动的信息,鼠标的x轴坐标记录在lParam低字节中,y轴坐标记录在lParam高字节中。

可通过如下的函数获取鼠标点击的位置:xPos = LOWORD(lParam);
yPos = HIWORD(lParam);
值得注意的地方是:获取鼠标位置后,要知道某一个位置区域是哪一个小方格,还得经过一下处理。

参见以上实现扫雷区域的贴图的第三个问题,可以知道小方格(资源位图)的宽和高,利用这个宽和高,可将二维数组的小标位图贴的具体位置,如第1排第3个小方格,贴的位置也就是窗口区域x方向上的长度为2* cxSource,y方向上的0* cySource;如第2排第4个小方格,贴的位置也就是窗口区域x方向上的长度为3* cxSource,y方向上的1* cySource。

2)点击为雷的处理情况
解决方案是设置一个全局的状态变量,类型为枚举,在以上程序设计结构中也有提到,这个变量是当前扫雷程序的运行状态,共有四种状态,表示扫雷处理等待、进行、输、赢的状态。

当点击小方格翻开为雷时,也就意味着游戏者失败。

这时将这个全局状态变量设置为输的状态,并停止一系列有关活动。

同时遍历整个二维数组,将所有为雷的小方格全部翻开贴上‘雷’的位图,注意点击为雷的小方格贴为加红色的‘雷’位图,以及标错旗子的小方格贴为加X的‘雷’图,还有旗子标正确的小方格状态不变。

3)点击为数字的处理情况
对于这个情况,有个小阻碍,同时也是一个大问题。

那就是如何知道这个小方格为哪个数字,与布雷不同,之前并没有对每个小方格像布雷一样进行数字的随机分布,并且要达到每个数字与这个数字周围的雷数相符。

这是一个陷阱,一个很不容易想到的问题。

针对这个为题,解决之道是,计算鼠标点击的小方格周围紧挨着小方格的雷数目,该数
目为多少那么点击的小方格的数字就为多少。

多简单的一个解决方案,能否知道这个思想,就直接决定着一个人对扫雷程序的了解有多深。

4)点击为空格的处理情况
该情况堪为最难处理,何以见得?凭借达到的效果可以证明。

当点击为空格时,同时要将所有与点击小方格连续的空格全部翻开。

直到遇着数字或旗子或雷时,就结束这次翻转小方格(这次点击的效果),必定遇着最外层的数字也将被翻开。

解决办法是用递归实现,递归结束条件为:遍历到当前小方格为数字或旗子或雷时,就结束。

具体实现的代码如下:
Bool DrawReversal(HWND hwnd,int row,int col)//反转方格
{
if (/*结束条件*/)
return TRUE;
UINT cMines = CountMines(row, col); //计算方格周围的雷数目
SetAndDrawState(hwnd,row, col, (BOX_STATE)(BS_DOWN - cMines));
if (cMines == 0) //表示该点击的小方格为空格
{
for (int r = row-1; r <= row+1; r++)
for (int c = col-1; c <= col+1; c++)
if (RangeRowCol(r, c) && (r != row || c != col))
DrawReversal(hwnd,r,c);
}
return TRUE;
}
值得注意的地方:在每次遍历某个小方格周围的雷数目时,不能超出扫雷区域,那么就要对递归小方格的边界外进行排除,如上的RangeRowCol(r, c)函数就是对边界进行处理。

4.加载雷计数器和时间计数器及笑脸
完成上面的三个过程,现在的扫雷程序基本上是有了初步形状了。

为了更好的让程序有基本的功能,在此,还需添加雷计数器和时间计数器,以及笑脸。

1)雷计数器贴图
很明显着也是贴图,有了时间的资源位图,可根据实现扫雷区域的贴图过程,去实现计数器的贴图,在这里就不必多说。

同样这里有个需要解决的问题,那就是每点击一次右键将小方格标为棋子,那么计数器就相应的减一,当减到为零时,会继续减下去直到为负99为止。

那么是根据什么得到计数器的值呢?是根据扫雷区域中旗子的数目得到的,计数器的显示数目为该等级的总雷数目减去当前扫雷区域的旗子数目,当然就可能为负值。

计数器区域的贴图为三个位图的贴图,也就是个十百位(从右到左)依次三个贴图。

三个数的值是由计数器显示的数目而定,那么如何将一个整数分解为三个个位数依次贴到计数器上?这就不是什么难题了,只要将计数器显示数目依次对10求余再对10整除就可以将一个三位数分解为三个个位数。

2)计时器的贴图
扫雷开始时,点击鼠标左或右键触发计时器计数,计时器显示贴图就每隔一秒贴一次图,就形成了计时器。

贴图同雷计数器贴图一样,不必多说。

其中触发计时器计数需用如下函数
实现:
SetTimer(hwnd,ID_TIMER,1000,NULL);
但需要注意的一点就是计时器到达上限、输或赢时,必须将计时器‘杀死’。

需要用如下函数实现:
KillTimer (hwnd,ID_TIMER);
3)笑脸的贴图
同样需要加载笑脸资源的位图文件,这个要注意的是每次鼠标点击笑脸时,就将整个扫雷区域进行重新刷新并重新布雷,和初始化小方格状态等等的初始化。

这无疑是对鼠标点击的位置进行处理,这里已在以上鼠标点击翻开小方格里有所讲述,同样可以略过。

5.加载菜单
菜单所需要实现的扫雷程序功能有如下:在开始弹出式菜单中有重新开局、等级水平选择、标记、声音、颜色、高分榜以及退出的功能,在帮助弹出式菜单中有帮助文档以及关于文档的功能。

首先就面临着一个如何加载菜单的问题,接着就是菜单的选项功能,最重要的难题就是怎样弹出对话框。

1)如何加载菜单
加载菜单是扫雷程序的重要一项,众所周知,扫雷程序也和众多娱乐游戏一样有等级的选择,同时又一些其他的设置。

当然等级水平是一重要功能,当选择不同等级时,难度也相应的改变,初级水平为10*10的小方格扫雷区域,雷数为8个;中级为16*16的小方格扫雷区域,雷数为40个;高级为16*30的小方格扫雷区域,雷数为99个;还有根据用户意愿的自定义水平。

在设计主窗口时只需将设计好的菜单资源名加到设计窗口的lpszMenuName属性中。

另外加载的方法是用函数来实现:
LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MINE_MENU));
每次选择不一样的等级时,都会改变扫雷窗口的大小,这时就会用到相应的函数去完成,如下两个都可以:
SetWindowPos(hwnd, NULL, 0, 0, WndW, WndH,SWP_NOZORDER | SWP_NOMOVE);
MoveWindow(hwnd, NULL, 0, 0, WndW, WndH,SWP_NOZORDER | SWP_NOMOVE);
其中WndW和WndH表示窗口的宽和高,在设置这两个变量时一定得要小心,注意考虑扫雷程序的可移植性,当扫雷程序在A电脑中设置的宽和高大小适合时,不一定会在B电脑上大小适合。

可以如下解决:
窗口的宽有扫雷的区域的宽以及加上窗口左右两边的边框宽度,可用如下函数获取边框宽度:GetSystemMetrics(SM_CXDLGFRAME);//左右边框宽度
窗口的高有扫雷的区域的高加上窗口上下两边的边框高度,还有加上标题的高度,以及菜单和笑脸的高度,可用以上函数获取,但参数分别为:SM_CYDLGFRAME,SM_CYCAPTION,SM_CYMENU。

以上的扫雷区域的宽高和笑脸的高度都可用资源位图的宽和高。

2)怎样弹出对话框
在扫雷程序中对话框是一种提示信息和输入信息,比如在用户自定义等级水平时,需要弹出对话框给用户输入扫雷的行、列和雷数目;在用户查询高分榜时,也会弹出对话框显示扫雷程序的英雄豪杰们;同时在帮助文档和关于文档中也会涉及对话框的应用。

首先得知道如何弹出菜单,在作者的理解下,可把对话框理解成另外一个窗口,当然实际上对话框也是一个窗口。

调用窗口的函数如下:DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG),hwnd,DlgProc);
对话框是一个窗口,那么同主窗口也一样有自己本身的消息处理函数,也是回掉函数,函数指针是DlgProc,这个函数指针就是指向窗口的处理函数。

这个函数和主窗口函数几乎是一样对消息的处理。

那么当然也有所不同,值得注意的是对话框的返回值,对话框的返回是由一个函数决定的:
EndDialog(hDlg, bool);
其第二个参数是决定着对话框的返回值。

3)菜单的其他功能
扫雷程序有重新开局、颜色、声音、标记的功能。

当然这些都是对程序设计的细节问题,但是同样也不可小觑。

开局是对扫雷重新设置所有变量,重新刷新窗口,但要注意哪些是开局要重新设置的,哪些是不能设置的,决定办法当然用一个函数是最好的。

颜色、声音和标记都是用一个bool 类型的去设置变量的,更具变量的值可达到扫雷程序的功能实现。

6.扫雷程序的优化处理
1)鼠标按键的优化
扫雷程序鼠标点击右几个特点现象,当鼠标点下一个小方格会出现下凹的现象,并点下鼠标不放,然后拖动鼠标,下凹的现象会随着鼠标的移动而移动,但之前的小方格会恢复之前的状态。

当鼠标点击小方格一直没有松开,而移到扫雷区域以外的地方,最后一个小方格也会自动的弹起。

2)扫雷边框的美观
这部分主要集中在扫雷程序主窗口外观的设计问题,扫雷区域、笑脸、时间计数器和雷数目计数器的边框设计。

用到系统自带的object去处理,当然也可以用用户自定义的object,比如颜色设置。

边框的凹和凸其实是一种视觉感官问题,当把某一部分颜色设置暗淡一点,某个部分设置高亮一点,那么这样的凹凸感觉就可形成了。

其中某个区域部分的颜色设置主要用了一下的函数去处理:
FillRect(hdc, &rect,(HBRUSH)GetStockObject(BrushObject));
rect表示某个窗口区域。

三、扫雷程序扩展
扫雷程序可以说是一个很大的工程,但有可以说成是一个小工程,为何如此,当初学者面对扫雷程序时,可以想象这个工程有多大,当一位深资windows应用程序编程者来说,那就小菜一碟了。

也就是说初学者通过扫雷程序可以知道windows应用程序编程的基本知识,最为关键的是初学者对windows应用程序编程入门有了较稳关键的一步,有了初步的了解。

通过对扫雷程序的编程,可对扫雷有了更深的理解。

在项目简介时,有说过扫雷程序是一个涵盖知识点众多的项目,包括:窗口和消息、位图和BitBlt、图形基础、鼠标按键消息处理、键盘快捷键处理、子窗口控件、对话框、菜单及其他资源、定时器、播放声音等等的知识。

对于这么多的知识涵盖,那么对贪吃蛇,连连看,五指棋,俄罗斯方块等的项目那就是小菜一碟。

也就无非是对位图的处理,鼠标按键、键盘按键的消息处理,控件的消息处理。

比如连连看的小游戏,主要就是怎样用资源位图进行贴图;怎样判断两个位图是否为同一个位图;怎样求出相同位图之间最短的路径,这也就是弯路最少,经过的小方格数目最少,并不能超过多少个弯道,当然这就是该程序的核心算法部分了,还有就是怎样处理没有解的状
态(不能连接相同的位图)。

如此看来,连连看也无法逃脱对位图的处理,其难度和扫雷程序旗鼓相当,项目甚至可能更简单。

四、心得及体会
经过对扫雷程序的编程与精心研究,从中获得了不少了启发与知识,特别是对windows API的运用;最重要的莫过于是对windows编程思想的理解,以及对windows的编程框架有所了解,明白程序的运行步骤,也略有了解设计模式。

其中最让我记忆犹新的部分是贴图、菜单设计和对话框的应用,这部分可以应用到很多应用程序中,真可算是受益匪浅。

虽然学到了不少的东西,我也为此感到很满足,毕竟爱好软件设计这方面的,那怕有一点可学习的东西,都是取之不舍的;但是有一个很让人感到遗憾,那就是如何将整个产品打包成一个真正的应用程序,也就是说有传统的安装步骤,比如用户点击下一步什么的,用户自定义安装等等。

用Release调试生成的EXE执行文件在某些他电脑上运行可能会出现各种错误,尤其是纯windows系统;其归根结底是由一个问题所引发的,原因是windows系统中没有程序所链接的DLL文件,也就是应用程序无法获得为程序提供的函数,这些函数定义体在DLL中,执行文件只有这些函数的函数指针,而没有具体的函数定义体,所以应用程序运行时系统会提示无法启动*.DLL文件。

这只需用相应的depends.exe工具查处执行文件所包含的DLL文件,然后把这些文件一起拷贝到执行文件目录下,系统目录下,或环境变量的PATH路径中就可以运行这个执行文件了。

总之,热爱软件设计的作者在老师的带领下,完成这次扫雷程序使作者收获匪浅。

相关文档
最新文档