扫雷
0
目录
-
前言
-
游戏三部曲
-
游戏设计
-
函数说明
-
程序打包
1
前言
终极目标:打造多关卡扫雷游戏
制作环境:
VS2015
支持:VC++2010 VS各个版本
easyx图形库(点我)
一直想发表扫雷这种锻炼思维的游戏,其实扫雷弄个标题栏可以随意选择挑战难度是效果最佳的,但是呢easyx图形库没有标题栏,所以就委屈各位看官一级一级打上去了。由于调试过程中玩的次数太多了,录视频就录这几关吧,后面的难度递增,需要你们去挑战了。
这篇文章会很长,但是,绝对干货,多知识点整合,也是数组的应用,外加一个递归调用。希望你们能够看到最后并制作这个有趣的游戏。然后~~~发给同学装×。
2
游戏设计
首先,我们来看一下游戏布局

游戏外圈我又加了一圈,这一圈是为了防止递归调用时出现无法访问内存的情况
当我们点击第一个格子的时候,如果是数字 0 也就是空白,那么需要递归自动翻开九宫格内的格子(否则玩家一个格子一个格子的点毫无游戏体验感),如果没有外圈,就会出现无法访问的情况,所有定义数组的时候要空出一片区域。
游戏设计:
| 设计 | 代表数字 |
|---|---|
| 未翻开的格子 | 0 |
| 雷所在格子 | -1 |
| 周围雷的数量(隐藏) | 1~8 |
| 踩到雷 | 9 |
| 空白~八个雷 | 10~18 |
| 标记 | >18 |
然后的数组应用和 拼图 类似
-
定义一个足够大的数组和控制游戏地图大小的变量
-
定义雷的数量和翻开的次数
int map[30][30] = { 0 }; //地图 用map_row map_col控制地图大小int map_row, map_col; //地图大小int times; //翻开的此时int mine; //雷的数量
-
初始化窗口大小
initgraph(map_col * 25, map_row * 25); //初始化地图
-
设置雷的位置
int mine_r, mine_c; //雷所在行和列for (int i = 0; i < mine;){mine_r = rand() % map_row + 1; //mine_r 1 ~ 8mine_c = rand() % map_col + 1;if (map[mine_r][mine_c] == 0){map[mine_r][mine_c] = -1; //在数组中放雷i++; //放入雷后雷的数量加一}}
-
计算空白处雷的数量
for (int i = 1; i <= map_row; i++){for (int j = 1; j <= map_col; j++){if (map[i][j] != -1){//检查周围雷的个数for (int m = i - 1; m <= i + 1; m++){for (int n = j - 1; n <= j + 1; n++){if (map[m][n] == -1) { //附近有雷,数字加一map[i][j]++;}}}}}}
这样,一个扫雷游戏基本设计就完成了,接下来设计鼠标操作
-
定义一个鼠标消息,然后获取这个鼠标消息
MOUSEMSG msg;//鼠标消息int row, col;//鼠标点击的行和列msg = GetMouseMsg(); //获取鼠标消息row = msg.y / 25 + 1; //鼠标点击行col = msg.x / 25 + 1; //鼠标点击列
-
对鼠标消息进行相应的数组操作
switch (msg.uMsg){case WM_LBUTTONDOWN://左键按下break;case WM_RBUTTONDOWN://右键按下 WM_RBUTTONDOWNbreak;}
-
递归在扫雷中的应用
void Empty(int row, int col){//递归实现以 0 为 九宫格的区域 自动翻开map[row][col] += 10; //翻开当前位置times++; //翻开次数+1for (int i = row - 1; i <= row + 1; i++){for (int j = col - 1; j <= col + 1; j++){//外圈空白区域不操作 仅对可以看到的区域进行递归调用if (i >= 1 && i <= map_row && j >= 1 && j <= map_col){if (map[i][j] < 9) //小于9 是未操作,未翻开的区域{if (map[i][j] == 0) //0的周围九宫格内无雷,翻开{Empty(i, j);}else{map[i][j] += 10; //有雷的不翻,让玩家思考是否需要翻开times++;}}}}}}
3
游戏三部曲
1、加载游戏数据(初始化 init();)
2、绘制图形(绘图 DrawMap();)
3、玩家操作(数据更新 Play();)
init(); //初始化
DrawMap(); //绘图
while (1)
{
Play(); //玩家操作
Judg(); //判断输赢
}
先把大纲列出来
01
主函数 main()
int main(){init(); //初始化DrawMap(); //绘制图形while (1){Play(); //玩家操作Judg(); //判断输赢}getchar();closegraph();return 0;}
02
初始化函数 GameInit()
需要初始化的数据:
-
地图的行和列map_row,map_col
-
素材图片 loadimage();
-
地图数据
这里我将地图数据单独拿出来初始化,是为了设计多关卡
srand((unsigned)time(NULL)); //设置随机种子map_row = 10; //地图行map_col = 10; //地图列isfail = 0;pass = 0;//加载图片loadimage(&imgs[0], L"素材/0.bmp", 25, 25);loadimage(&imgs[1], L"素材/1.bmp", 25, 25);......loadimage(&imgs[13], L"素材/雷2.bmp", 25, 25);initmap();
03
绘图函数 DrawMap()
地图的绘制就要考虑外圈空出来的区域不能绘制出来
void DrawMap(){BeginBatchDraw();for (int y = 1; y <= map_row; y++){for (int x = 1; x <= map_col; x++){if (map[y][x] > 18){//标记部分额外处理putimage((x - 1) * 25, (y - 1) * 25, &imgs[9]);continue;}switch (map[y][x]){//制作初期可以加上下面代码看看雷放到了哪里//case -1: //雷// putimage(x * 25, y * 25, &imgs[12]);// break;case 9: // -1 + 10 = 9 踩到雷了putimage((x - 1) *25, (y - 1) *25, &imgs[13]);isfail = 1;break;case 10: //没有雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[0]);break;case 11: //有一个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[1]);break;case 12: //有两个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[2]);break;case 13: //有三个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[3]);break;case 14: //有四个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[4]);break;case 15: //有五个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[5]);break;case 16: //有六个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[6]);break;case 17: //有七个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[7]);break;case 18: //有八个雷putimage((x - 1) * 25, (y - 1) * 25, &imgs[8]);break;default: //未点击翻开d额格子putimage((x - 1) * 25, (y - 1) * 25, &imgs[10]);break;}}}EndBatchDraw();}
04
数据更新函数 Play()
当玩家对鼠标进行点击操作(分左键和右键)时,进行相应的数据处理
switch (msg.uMsg){case WM_LBUTTONDOWN://左键按下if (map[row][col] >= 9)//翻开过的 和标记的 不进行操作{break;}if (map[row][col] == 0)//翻开 0项 和 周围的图片{Empty(row,col);}else//翻开点击的图片{map[row][col] += 10;//翻开times++; //翻开次数 +1}break;case WM_RBUTTONDOWN://右键按下 WM_RBUTTONDOWNif (map[row][col] >= 9 && map[row][col]<19)//翻开的不进行操作{break;}if (map[row][col] >= 19)//标记的取消标记{map[row][col] -= 20;}else//没有翻开的加20{map[row][col] += 20; //20是随意的数字,只要足够大就行}break;}
数据处理完成之后记得更新地图
DrawMap();
05
判断输赢
判断输赢为了避免重复的搜索,定义了一个isfail变量来判断输,根据翻牌次数和雷的数量及地图大小判断赢。
当绘制出现 9 即踩到雷时将isfail置一
case 9: // -1 + 10 = 9 踩到雷了putimage((x - 1) *25, (y - 1) *25, &imgs[13]);isfail = 1;
判断输赢是提供是否进入下一关,和是否重新开始本关卡。
void Judg(){//先查找是否点开了雷if (isfail){if (MessageBox(GetHWnd(), L"你踩到雷了,是否重新开始本关", L"失败", MB_YESNO) == IDYES) //GetHWnd()是获取一个顶置窗口{mine -= 5;isfail = 0;initmap();DrawMap(); //渲染地图}else{_exit(0);}}if (times == map_row * map_col - mine) //所有点 - 雷 = 非雷区{if (MessageBox(GetHWnd(), L"是否进入下一关", L"成功", MB_YESNO) == IDYES) {closegraph();//重新开始游戏initmap();DrawMap(); //渲染地图}else_exit(0);}}
4
函数说明
-
exit(0)
exit(0):退出程序 有缺陷,建议使用新版_exit(0);
-
GetHWnd()
GetHWnd():获取顶层窗口,让弹框至于最前面,不至于让人忽略
然后也没有其他特殊的函数需要说明,如果对easyx里面的基本函数不清楚建议参考easyx提供的文档,在文档里面有每个函数的使用例子,阅读文档是个好习惯。
5
程序打包
什么是程序打包?
你安装软件的时候有安装向导,就是把可执行文件(.exe文件)和依赖库(包括素材)保存在一个目录下,并创建桌面链接。






![数据结构05:树与二叉树[C++][线索二叉树:先序、后序]](https://img-blog.csdnimg.cn/a4945c438b254692aadc64999ac4f6a6.png)













