基于VC的扫雷游戏开发

1.  引言:

1.1          背景:

“扫雷”是Windows操作系统自带的一款小游戏,以其简约的界面而不失逻辑推理的玩法深得广大玩家的喜爱。该游戏通过左键打开安全的格子,格子上的数字表明了在此格周围的八个格子中地雷的总数;用右键标记地雷、待定地雷;双击已开的格子自动开出周围八个未开格子。将所有非雷格子全部打开则游戏成功,开到有雷格子则游戏失败。

1.2          开发环境与工具:

VisualC++6.0开发环境下,用C语言以及从easyX图形库下载的扩充库<graphics.h>、标准输入输出库<stdio.h>、屏幕处理库<conio.h>、日期和时间头文件<time.h>、多媒体相关应用程序接口"winmm.lib"Windows7扫雷音效文件等完成此次开发。

1.3          意义:

虽然并没有涉及到优秀的游戏编程语言C++或JAVA,也没有利用可观的界面框架MFC,没有载入任何漂亮的图片。用的只是最基础的C语言,去操控着整个游戏画面的形成,都是用最简单的函数一点点地勾勒出来的界面,而算法方面也是经过了精心的策划最终敲定的。这个过程中锻炼了我们的编程能力。

2.  总体构架与分析:

2.1          设计流程:

游戏整体设计思路即从三大方面展开:界面与鼠标、算法、优化。下图简单表明了大概的设计流程。

2.2          ※基本思路与算法:

首先需要建立这个9X9表格,利用二维数组map[9][9]存储每个格子的情况(1就是有雷,0就是无雷,当然这里用宏定义可以提高可读性,作为初学者我们务必要遵循这些良好的习惯,因此用MINE和NOMINE表示有无雷),另外还建立了一个_map[9][9]存储格子状态,由右键控制其变化(2就是未翻,3就是已翻,4就是标记,5就是待定)。当玩家左击下去,利用鼠标捕获函数获得用户点击的坐标(x,y)然后将它转换成数组下标(这样就实现了界面到代码化的过度)会出现两种情况,有雷与无雷,有雷则直接失败,无雷又分9种情况(编一个判断函数judge()(后面会详细介绍):以点击的这个格子为中心搜索周围8个的情况累计总数)。周围有0—8个雷,当是1-8个雷的时候则只会在你点下去的格子上显示数字并不会延展(这是最简单的情况也是递归的出口!),但当是0个的时候,则要进入递归去搜索以周围8个格子“各自”为中心的雷数情况。而这8个格子每个格子又都套用刚才“点击那个格子”的方法去计算他们各自周围的雷数。依次类推,而递归必须要出口,这个出口就是直到递归到表格的边界或递归到一个有数字的格子(可以从Windows上的扫雷的边缘观察,看是不是这样的)。另外还有开格函数open()(后面会详细介绍),它也嵌套调用了judge()函数,只不过添加了一些更为复杂的判断。至此,扫雷的基本思路就已经理清了。

2.3          头文件、宏定义、所有函数声明、全局变量:

2.4          主函数框架:main()

主函数中用initgraph(640,480)创建一个640*480大小的图形窗口,包含对界面的初始化函数menu() stop(),以及定义类MOUSEMSG(其中用到了对象的坐标、以及鼠标信息(左键、右键、双击)),再在whlie(true)永真循环内对鼠标的时时捕获函数GetMouseMsg(),随时锁定了用户鼠标的所在位置。如果鼠标落在事先设定好的“难度方块”范围内则用move()函数伸展出9X9 16X16的选择方块,在extend()函数中进一步对用户的鼠标进行捕获。而如果鼠标落在事先设定好的“退出方块”范围内则closegraph()退出操作系统。如此以来,会形成多重永真循环,在未到达出口时(即没有扫到雷或者用户没有主动点击退出),那么是在游戏的进行阶段,当到达出口时(即用户扫到雷或者扫雷成功或主动点击退出),那么将调用MessageBox()函数询问用户是否要重来,是的话则嵌套调用extend()函数重新开始,否则退出操作系统。

2.5          ※游戏正式开始函数框架:extend())

3.  开场界面:此界面被封装在int first()函数中,当用户按下任意键时返回1,主函数响应其值,进入游戏主界面。

3.1          自弹音乐:随着用Beep()函数编写的的背景音乐“星之所在”的响起,

屏幕逐渐打印出如下图示。(此部分代码单独编写在“音符.cpp”文件中)

 

(自弹音乐及封面图示)

※算法详解:利用<windows.h>头文件中的Beep(频率值,时间间隔)可以蜂鸣出不同的音效。通过参考网站上提供的do,ri,mi,fa,so,la,xi不同音阶对应的频率值,可用switch()筛选出传递来的音符和节拍,并将单个的音符声响编写成一个Note(intnote,int time)函数。而对于乐曲“星之所在”,又通过参考网站上的乐谱将所有音符“频率化”,将所有节拍“时间化”(定义宏E代表八分音符,F代表四分音符),就构成了所需参数。而频率和时间分别储存于两个含有142个元素的数组note[]和time[]中。再利用指针推移可提取对应音符应该声响的秒数。并将调用Note()函数的这个主调函数取名为Music(),再在first()函数中调用Music()。这样就模拟了弹奏。而对于画面的逐渐显示,则通过建立IMAGE类型对象img,调用loadimage()(加载指定路径位图到img中缓存)和putimage()(在指定坐标处显示在位图对象里以指定坐标为起始点的指定宽度、高度的位图)。最终构成函数Picture(),只要在Note()函数之前调用次函数,那么每次声响都会伴随着画面的一点点推移。下面是“音符.cpp”文件中关于弹奏的代码:

(开场界面图示)

3.2          雷图样的绘制:

(1)   先画出一个正立的正三角形,再画出一个倒立的正三角形。三角形属于多边形所以可以用填充多边形函数fillpoly(顶点数,存有各点横纵坐标的数组地址)进行显示。

(2)   再在经过计算的坐标处画出填充圆fillcircle(圆心横坐标,圆心纵坐标,圆半径)(在此之前要用setfillstyle(颜色)声明接下来的填充颜色,默认的填充颜色是白色,那么画黑色圆则要用setfillstyle(BLACK)。

3.3          “125代表队”字样显示:outtextxy(字样横坐标,纵坐标,存字数组)

3.4          “按任意键进入”字样往返移动效果:

※算法详解:设想这一排字在移动,利用outtextxy()可以在指定位置打印指定的文字,而字样只需在固定的纵坐标处以不同的横坐标打印出来即可,相当于x是每次自增1,而y不变,那么用for循环就可以很容易实现。但是字幕要有移动的效果,必须兼具两点:第一,字幕要在原地停留小段时间;第二,上一时刻的字幕要被覆盖成原来的样子。至于停留,我们可以用Sleep(毫秒数)暂停。这样就解决了单向移动问题,至于字幕到达右端又返回的过程可以以此类推。但是我们在此其间可以在任意时刻按任意键,我们先来解决“任意时刻”,显然如果为原for语句套上一个while(true){……}永真循环那么就能让字幕一直往返运动了,但是这样的话就没有了出口,到底何时才是个尽头呢?巧妙的是我们要按任意键啊~于是在while语句里面加上一个if(kbhit())如果有按键就返回主函数开始游戏。下面是代码截图:

下面是对代码的详细解释:

(1)   外部定义一个IMAGE类的对象tmp,此对象用于存储指定坐标区域的位图。

(2)    在while(true)循环外利用getimage()函数截取当前字样连同其背景的矩形存储于tmp对象中,getimage(&tmp,i,450,textwidth("按任意键进入"),textheight("按任意键进入"))其中&tmp表明了要存入的地址,i是上述所说的在推移的横坐标,450是不变的纵坐标,textwidth()\textheight()的作用是获取按引号内字串的宽度和高度。这样以来,就tmp中就存储了一个同“按任意键进入”宽度高度相同的长条黑色矩形。

(3)    在for语句里用outtextxy(i,450,“按任意键进入”)打印字样,又用Sleep(20)让字样暂留一小段时间后,再用putimage(i,450,&tmp)放出刚才保存的黑色矩形覆盖字样。

4       界面与鼠标:

(游戏界面图示)

4.1              矩形与网格的绘制:

bar(左上x,左上y,右下x,右下y)绘制一个灰色矩形,并在利用循环推移k变量的增加,在内用line(起点x,起点y,终点x,终点y)为矩形绘制表格。

4.2              鼠标与数组下标,数组下标与中心的关系:

之前定义了MouseMsg类型的对象m,又时时利用GetMouseMsg()获取了鼠标的信息,其中的m.uMsg中存有鼠标的横纵坐标。但是对于用户的点击,我们并不希望就在他点击的位置打印数字,标记红旗等,我们希望的是在表格中的小方块中心打印图样,这样会非常美观。不仅如此,还要对表格内的情况进行记录,如哪些位置有雷,哪些位置无雷,哪些已经翻过,哪些被标记过。下面就是对这两种转换(其中(130,210)是9x9表格的左上坐标,20是每个小方格的边长):

点击位置坐标->数组下标:i=(m.y-130)/20;j=(m.x-210)/20;

 

数组下标->表格中心:x=210+20*j;y=130+20*i;

4.3              各种图样绘制:

(1)暂停与继续图样:

两个矩形构成一个暂停按钮,一个三角形构成一个启动按钮。而三角形同上文中的雷图样的画法类似,均是用填充多边形函数fillpoly()绘制。

(2)选中小方块与底色小方块的绘制:

利用fillrectangle()函数绘制带有边框的填充矩形,而两者的不同则是选中小方块是浅灰色的,底色小方块是和底色相同的深灰色。另外还需注意,此函数之所以带参,说明它并不是一个在指定位置打印的图片的函数。与用户点下的位置密切相关,其打印的位置是按“点击位置坐标->数组下标(实参)”转换,而这样以来,再在函数中经过“数组下标(形参)->表格中心处”的转换即可达到打印的目的(之所以不直接传入点击处的坐标值,是缘于在编写过程中需要用数组下标去标记表格内的有雷、无雷、点击过、未点击过等情况,所以重用避免混淆)。这样可以在之后绘制雷、小红旗、问号(前三项如法炮制,不再敷述)、彩色数字、光标选中效果中嵌套调用,提供方便。

(3)  彩色数字的绘制:

此函数colornumber(m,x,y)中第一个参数是指周围的雷数目,其余就是上文提到的数组下标。具体过程则是当用户按下一个非雷格子的时候出现以浅灰色(表示已经翻过)的格子为背景的1-8不同颜色的数字。显然可以先用blank(x,y)打印一个已翻的格子图样,再用switch..case..语句筛选这8个数字,不同的case对应不同的颜色。而需要注意一点的是outtextxy()函数的形参是数组地址,那么不能直接把整型变量写入,必须用sprintf(数组地址,原始数据的格式,原始变量)进行转换。

4.4              鼠标移动时的选中效果:

※基本算法:定义整型变量lastx,lasty存储上一时刻鼠标所在的位置。“此时”鼠标坐标经转换成数组下标,再用全局数组 _map1[x][y]判断鼠标“此时”所在位置是否“未翻”,如果未翻,则用blank()函数将鼠标所在位置指向的小方块变成浅灰色,这样就实现了“选中状态”。但是lastx,lasty还没有起到作用,我们可以将“此时”的坐标赋给“lastx,lasty”,这样当下一次while(true)循环时,lastx,lasty就拥有的是上一时刻鼠标的位置。于是趁循环才开始,就把上一时刻所在的位置用remove()覆盖还原成底色。

5       算法:

5.2          初始化与随机布雷:

 

(1)初始化:

   利用两个循环将_map1[9][9]与map1[9][9]进行了初始化。分别将这81个格子的状态用UNHIT和NOMINE来表征。前者表征了所有格子的附加状态(右键),后者表征了格子有无雷这一重要状态,两者分开使用使得代码更加清晰,有条理。

(2)随机布雷:

   利用随机数发生器的初始化函数srand()实现随机种子的产生,其参数是unsigned型的变量,并且每一个不同的值都对应着一组不同的随机数。要想每次都是不同组的随机数,那么可以调用time(NULL)函数获取当前系统的时间并将其强制转换成unsigned型,就能达到此目的。此后,它同随机数产生函数rand()联合使用才可以达到产生不同组随机数的目的。下面分析代码:代码中用永真循环实现了随机布雷,其中rand()%9表明产生0-8的随机数(套用公式a-b的随机数a+rand()%(b-a+1)),同时用s记录已经随机布雷的个数,满10个后则退出循环。

5.3          标记:

   当点击右键的时候,转换其鼠标坐标为数组下标,利用switch..case..语句对_map1[x][y]进行筛选。其中minenum表示显示在左下位置记录剩余雷数个数的变量,minenumber()函数负责打印左下角剩余雷数的个数。下面的流程很清晰的表明了这段代码的用意:

5.3      ※蔓延:此程序段是本游戏的核心算法。

(浅灰色蔓延图示)

※算法详解:首先定义一个整型变量num用于统计以形参i,j为数组下标格子周围8个格子的总雷数。并且这种搜索的前提必须是此格子“未翻”或“待定”。满足了此前提后用switch..case..语句进行一重筛选,筛选出此格子是“有雷”还是“无雷”。

如果是“有雷”,那么表明游戏失败,于是用for语句摊牌所有布下的雷,将他们全部都打印出来。并且建立一个HWND类的对象wnd,利用GetHWnd()获取窗口的句柄作为MessageBox()的第一个参数。如果玩家选择的是Yes按钮则:1.将9X9选项的红色字体用outtextxy()还原成白色。2.用cover()将上局的表格覆盖成黑色。3.嵌套调用extend()函数,重新开始。如果玩家选择的是No则退出。

如果是“无雷”,那么就要对以此格子为中心周围8个格子进行搜索(除了检查是否有雷,还需注意数组下标必须大于等于0,这样就排除了边界情况)。map1[i-1][j-1],map1[i-1][j],map1[i-1][j+1],map1[i][j-1],map1[i][j+1],map1[i+1][j-1],map1[i+1][j],map1[i+1][j+1]。这八个数组变量分别表示这8个格子的有无雷情况。如果有雷,则num++。最后如果0<num<=8,那么就用number()这个函数进行数字的打印、已翻的格子的统计、胜负的判定(如果胜利,其中包含文件读取,读取历史最快记录,同玩家此次游戏所用时间进行比较,进而弹出不同的对话框);如果num=0,则说明无雷,则先用number()函数在此格上打印一个空白浅灰色格子。再在不越界的情况下以周围8个格子为中心搜索他们各自周围的总雷数情况(递归调用judge()函数)。这样,当递归到“边界”或“有雷的格子”时,则此次judge()就已经找到出口。

5.4      ※开格:这也是本游戏非常重要的算法。

※算法详解:所谓开格,即指当玩家在以某个格子为中心,在其周围标记了已经确定的雷之后,对着中心格子左键双击,电脑自动帮你把其余的格子打开的过程。但是如果标记错误,那么就直接失败,避免了玩家在游戏中投机取巧的bug。那么这个过程是具体应该怎么实现呢?下面依次详细解释它的含义:

(1)同judge()函数中一样搜索周围总雷数num。

(2)变量all用于检测“雷的位置是否和红旗位置匹配”,如果匹配则all++。

(3)如果all==num,证明匹配数和总数相同,说明此次开格是正确的。那么就用judge()去递归,去搜索,去蔓延,但是注意前提是“不搜索有雷(标记)的格子且不能越界”。

(4)如果all!=num,证明匹配数和总数不同,说明此次开格是错误的(玩家想要投机取巧试探雷的位置或者纯属标记错误)。那么则进行游戏失败的处理。

6.优化:

6.1      ※计时与暂停:此算法有效巧妙地避免了“多线程”,是亮点算法。

(1)计时功能:

※算法详解:clock()函数是用来获取时间差并在左下角打印时间的函数,其传入的参数是在游戏开始时获取的系统时间(time_t类型的变量)。在clock()函数内部也有一个时间变量t2用于获取函数被调用时的系统时间(如果未暂停,每while(true)循环一次就要获取一次时间)这样(t2-t1)得到的就是从游戏开始进行到目前的时间。

(2)暂停功能:

※算法详解:内部变量onoff用来标记玩家是否按过暂停(按过暂停STOP,未按暂停KEEPON)。clock()函数可以打印时间差,那么按下暂停后clock()函数内的t2仍然继续获取时间,唯一能变的就是传入的实参“初始时间t1”。显然当暂停以后,我们如果能把“暂停到继续”的时间计算出来,然后同游戏开始时获取的系统时间“做加法”,那么就弥补了损失的这段时间即t1=t1+(t4-t3)。那么,t4和t3应该怎么处理呢?显然我们可以想下,游戏原先是正常进行,当你按下暂停的时候就打破了这个规律,那么着手点显然就是按下暂停的那个if()语句:首先要将onoff标记成STOP状态,其次获取此时的系统时间(暂停时)t3,然后进入while(true)循环,实现暂停状态,而此循环的出口就是玩家点击“继续键”。一旦点击继续键,即时获取继续时的时间t4,再马上break,跳出这个暂停状态。(其中t1,t2,t3,t4均设置成了全局变量,就是想到他们将在不同函数中使用)

6.2      背景音效:

(1)对于一般按键响铃printf(“\a”)解决。而游戏开始、游戏成功、游戏失败的音效则利用“restorator2007”将C:\\Program Files\\Microsoft Games\\

Minesweeper\\MineSweeper.dll的音效文件导出。并用“千千静听”转换成.wav格式的音频文件。

(2)包含多媒体接口:#pragma comment(lib,"winmm.lib")

(3)调用PlaySound()函数进行.wav格式文件的播放:PlaySound(音乐文件路径,NULL,以何种方式查找);

参数一:指定.wav文件的存放路径

参数二:停止所有与调用任务有关的声音

参数三:SND_FILENAME:指定以WAV文件名来调用

SND_ASYNC:用异步方式播放声音,PlaySound函数在开始播放后立即返回(相当于多线程)

6.3              最快记录:

这段代码写在获胜的if语句下。定义了两个文件指针*fp1(用于读取最高纪录),*fp2(用于覆盖写入新最高纪录)。首先,以读取方式打开文件。1.如果不存在“record.txt”文件则以写入方式打开并用InputBox(数组地址,输入长度,“正文”,“标题栏”)弹出一个对话框接受玩家姓名的输入。再用fprintf()语句写入刚才的姓名和所用时间。2.如果存在“record.txt”文件,则先要用fscanf()语句读入旧时间记录,并和新的时间进行比较,如果新时间比旧时间快,则重复上述写入操作。反之弹出鼓励玩家的对话框。最后,关闭文件。

6.4              安装包:

安装包的制作,利用了“小兵安装包制作工具”软件。安装界面由我们自己用PhotoShop进行剪辑,拼凑。并同时配上了悦耳的背景音乐“星之所在”。

7.参考内容:

(1)函数使用方法:MSDN、EasyX

(2)各种音符对应Beep()函数中的形参值:

http://qqhack8.blog.163.com/blog/static/114147985201122624025880/

(3)“星之所在”乐谱:http://www.sooopu.com/html/94/94629.html

(4)安装包制作:小兵安装包制作工具

(5)dll格式文件导出:Restorator2007

基于VC的扫雷游戏开发相关推荐

  1. c语言五子棋开题报告,基于VC的五子棋游戏的设计与实现(附答辩记录)

    基于VC的五子棋游戏的设计与实现(附答辩记录)(包含选题审批表,任务书,开题报告,中期检查报告,毕业论文12300字,程序) 摘 要:以计算机技术和网络技术为核心的现代网络技术已在现实生活和生产中得以 ...

  2. 基于java的扫雷论文_毕业论文基于JAVA的扫雷游戏设计

    毕业论文基于JAVA的扫雷游戏设计 课 程 设 计 报 告 课程名称: 计算机技术综合课程设计 题 目: 基于JAVA语言的扫雷游戏设计 学 院: 信息工程 系: 计算机 专 业: 计算机科学与技术 ...

  3. VC系统扫雷游戏外挂源代码程序下载(转帖

    VC系统扫雷游戏外挂源代码程序下载(转帖) 2008-03-04 10:25 经过了多次测试写出了历史上第一个有点意义的MFC程序.效果差强人意.^_^ CODE: // CrackWinmineDl ...

  4. MMO游戏War Riders宣布将于基于区块链的游戏开发公司Immutable集成

    可赚取加密货币的MMO游戏War Riders宣布,将于基于区块链的游戏开发公司Immutable集成,合作将使玩家可免Gas的铸造和交易,并扩大War Riders的用户. 文章链接:https:/ ...

  5. 实验项目三:基于A*搜索算法迷宫游戏开发

    基于A*搜索算法迷宫游戏开发 由于这一个不太完美,重新写了一个基于python的程序. 一.前言 二.基本流程 三.界面设计 四.迷宫随机生成 五.移动迷宫与尾迹生成 六.A*迷宫自动寻路 七.附加 ...

  6. 基于java的扫雷论文_毕业论文基于java的扫雷游戏的设计与实现.doc

    毕业论文基于java的扫雷游戏的设计与实现 JAVA程序设计A课程设计 题 目 基于JAVA的扫雷游戏的设计与实现 院 (系) 信息工程学院 专 业 班 级 计算机科学与技术(2)班 学 生 姓 名 ...

  7. JAVA基于J2ME的手机游戏开发和实现——贪吃蛇

    随着通信技术的发展和手机的普及,手机游戏的开发技术越来越为人们所关注.以J2ME为开发平台,利用Java提供强大工具,不但可以在手机上实现静态HTML技术所无法实现的计算处理.数据存储.与服务器的通信 ...

  8. 基于jQuery经典扫雷游戏源码

    分享一款基于jQuery经典扫雷游戏源码.这是一款网页版扫雷小游戏特效代码下载.效果图如下: 在线预览   源码下载 实现的代码. html代码: <center><h1>jQ ...

  9. 扫雷程序设计Android答辩,基于QT的扫雷游戏设计与实现扫雷游戏答辩.ppt

    基于QT的扫雷游戏设计与实现扫雷游戏答辩.ppt 程序设计 基于QT语言的扫雷游戏,答辩学生,一.分析扫雷游戏的基本功能,1.从外观上分析 方块 笑脸 计时器 有雷标识 无雷标识 胜利画面 失败画面, ...

  10. 基于EasyX的扫雷游戏

    基于EasyX的扫雷游戏 一.预备知识 二.游戏逻辑 1.扫雷元素 2.扫雷规则 三.游戏设计 1.地图设计 2.点击设计 3.结束设计 4.整体设计 一.预备知识 1.使用EasyX必须要知道的一些 ...

最新文章

  1. 分析轮子(二)- ,, (左移、右移、无符号右移)
  2. boost::hana::empty用法的测试程序
  3. POJ 1562深搜判断连体油田个数
  4. 如何找到Eclipse左侧项目栏
  5. 垃圾收集六大算法全面理解
  6. java 获取bean的注解_如何获取spring 注解的bean
  7. 拓端tecdat|R语言中的神经网络预测时间序列:多层感知器(MLP)和极限学习机(ELM)数据分析报告
  8. 《运算放大器权威指南(Op Amps for Everyone)》读书笔记(一)
  9. 希尔伯特变换及其性质
  10. u盘linux 修复grub,Ubuntu 18.04与Win10双系统U盘安装后的GRUB2修复
  11. c语言学习指南app,c语言学习手册app
  12. C# WPF仿360安全卫士11
  13. 牛客网小白月赛22计算A+B(题解)
  14. macOS免费串口工具coolTerm/Minicom/Comtool/Volt+(伏特加)/友善串口调试助手/screen/picocom
  15. 操作符 算数操作符
  16. 【信号与系统】系统线性时不变、因果稳定性的判定
  17. [杂谈] 14. Catalan卡特兰数
  18. 大专计算机网络毕业论文简单,计算机网络大专毕业论文范文
  19. 【DTOJ Begin】1019. 过桥(bridge)
  20. DNS服务器它到底是干啥的呢?

热门文章

  1. Visio绘制电路图
  2. C#窗体excel与dbf的导入导出
  3. 在Rammap(内存分析工具)的基础上实现自动优化
  4. IDEA 常用快捷键
  5. WIN7清理C盘空间垃圾的BAT脚本
  6. stm32固件库手册使用方法
  7. 运筹说 第36期 | 算法介绍之运输问题
  8. ENVI完整安装步骤
  9. Camshift算法
  10. linux docker运行exe,在Windows上的Bash上运行Docker容器