扫雷【含递归和标记的完整代码及详细讲解】
目录
一、游戏思路
二、游戏部分设计
1、棋盘的初始化
2、棋盘的打印
3、雷场的布置
4、用户排雷
5、递归函数部分
三、完整代码
四、结语
扫雷,扫雷是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
今天我们就来完成扫雷的代码实现,当然比较简易,凭借代码只能基本的玩法和雏形,下面我们进入代码环节。
一、游戏思路
对于扫雷,我们需要设计两个棋盘,一个mine,一个show,后者用于用户自身的体验,前者用于开发者来修改,在用户进行游戏时,他修改了show的部分,我们的棋盘mine根据玩家对show的更改来完成判定游戏的输赢。在这点确定之后我们要完成以下几点:
1、对棋盘的初始化
2、对棋盘的打印
3、对mine进行布置雷场
4、玩家对雷的查找和雷信息的掌握
5、提高玩家查找效率
二、游戏部分设计
1、棋盘的初始化
void Init_board(char board[ROWS][COLS], int rows, int cols,char set)
{for(int i=0;i<rows;i++){for(int j=0;j<cols;j++){board[i][j] = set; //我们看的用‘0’来表示,用户看的用‘*’来表示}}
}
因为有两个棋盘,所以展示给用户和开发者的是不一样的符号,用户的用于体验,开发者的便于实现代码,所以这里我们用char set来代表棋盘的符号,对于用户我们用*,对于开发者,我们用0来表示,这会为接下里找雷的信息埋下伏笔。
这里还有一个重点:我们的初始化要再原本棋盘的基础上增加两行两列,这样会更加方便处理位于棋盘边线上的位置
2、棋盘的打印
void Display_board(char board[ROWS][COLS], int row, int col)
{for(int i=0;i<=col;i++){printf("%d ", i);}printf("\n");for(int i=1;i<=row;i++) //此时的row,col,用ROW,COL来表示,多出的两个长宽不表示出来{printf("%d ", i);for(int j=1;j<=col;j++){printf("%c ", board[i][j]);}printf("\n");}printf("---------扫雷--------\n"); //分割开会更好区分
}
棋盘的打印只需要打印我们需要的棋盘大小位置就ok,但是这里要非常小心,我们函数的形参必须要用棋盘的整体,不然带入进去的只是棋盘的一个位置,包括之后的其他部分要用棋盘,都必需要带上棋盘整体也就是包括隐藏的部分
且我们一般只展示show的部分,但是在我们自己检验时,可以打印开发者mine的部分,方便修改
3、雷场的布置
void Set_board(char mine[ROWS][COLS], int row, int col)
{int x, y;//用x,y来替代雷的坐标int count = COUNT; //还需要定义雷的数量countwhile (count){x = rand() % row + 1; //余数为0~8,加1,则为1~9y = rand() % col + 1;if (mine[x][y] == '0') //防止雷重复占位{mine[x][y] = '1';//定义字符为1的位置为雷;为什么定义为1呢?方便后面提示的计算使用count--;}}}
布置雷场,我们用rand函数随机布置,此时我们看到的棋盘部分在二维数组中是从1开始的,因为有隐藏的棋盘部分,我们布置雷场也就在展示的部分布置,且在mine棋盘上完成。且我们可以通过改变count的值来对扫雷的难度进行改变。在Mine中有雷的位置,我们设置为字符1,方便待会儿用户找雷时,进行信息传递。
4、用户排雷
void Find_borad(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x, y;int count = 0; //非*的数量int mark = 0; //标记的数量int input = 0;while (col * row - count - COUNT) //当非*的数量(count)加上雷的数量(COUNT)为总数时,说明已找到所有的雷{printf("----请选择----\n");printf("---1、标记或取消标记---\n");printf("---2、检查---\n");scanf("%d", &input);switch (input){case 1:if (mark <= COUNT){mark = 0;printf("请输入标记坐标:>");scanf("%d %d", &x, &y);if (show[x][y] != '!'){show[x][y] = '!';for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}else if(show[x][y] == '!'){show[x][y] = '*';for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}}else{printf("标记已超过雷的数量,若需再次标记,需要取消之前的标记\n");printf("请输入取消标记的坐标:>");scanf("%d %d", &x, &y);show[x][y] = '*'; for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}break;case 2:printf("请输入:>");scanf("%d %d", &x, &y);if (x < 0 || x>9 || y < 0 || y>9){printf("输入错误,请输入1~9之间的数字\n");}else //输入正确范围后进入{if (mine[x][y] == '1'){printf("非常可惜,踩到地雷,游戏失败!\n");Display_board(mine, row, col); //打印给用户看看雷的位置break;//跳出循环,游戏结束}else{count = 0; //这部很重要,每次重新进入循环,需要count为0,不然count的值会累加findmax_board(mine, show, x, y);//show[x][y] = sum(mine, x, y)+'0'; //这个sum作为辅助,来找到我们输入位置周围的地雷//数量,加上‘0’为了得到一个字符Display_board(show, row, col); //每次输入后都应打印这个雷盘for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] != '*'&&show[i][j]!='!')count++; //每次检查后查看非*的数量}}printf("%d", count);//count++;}}if (count == col * row - COUNT) printf("你太厉害了呀,找到了所有的地雷!");//当查看了所有的非雷位置还没爆炸时,获胜break;default: printf("非法输入,请重新输入"); break;}}
}
在这个部分我们要完成的内容很多:
1、首先我们要站在用户的角度上,为了方便用户记住雷的位置,我们要进行选择,是进行标记还是继续查找雷的位置,标记错误还要进行取消标记
2、这里显然要对mine和show的部分都要进行操作,所以两个数组都要带入
3、进入标记页面后,我们要限定标记的数量,用Mark来记录标志的数量,然后用“!”来代表标记,每次标记完,都得打印棋盘show,让用户看标记地点,且遍历记录标记数量,防止超过标记限制,并且判断是否已经标记,标记了就撤销标记
4、进入检查页面,判断这个地址及周围是否有雷,如果这个位置雷则游戏失败,如果周围有雷则显示有几颗雷,如果周围没有雷,则递归显示周围的位置直到有雷停止
5、获胜条件,当我们检查完了所有安全区域后,未检查到雷,则获胜
5、递归函数部分
void findmax_board(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) //x,y为选取的坐标
{ int count2 = sum(mine, x, y);if (count2 == 0) //该坐标周围无雷{show[x][y] = ' ';if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x - 1, y - 1);}if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 <= ROW && y > 0 && y <= COLS){findmax_board(mine, show, x - 1, y );}if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 <= ROWS && y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x - 1, y + 1);}if (show[x][y - 1] == '*' && x > 0 && x <= ROWS && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x, y - 1);}if (show[x][y + 1] == '*' && x > 0 && x <= ROWS && y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x, y + 1);}if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 <= ROWS && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x + 1, y - 1);}if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 <= ROWS && y > 0 && y <= COLS){findmax_board(mine, show, x + 1, y);}if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 <= ROWS&& y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x + 1, y + 1);}}else{show[x][y] = count2 + '0';}
}
值得注意的是,字符0,加上数字x,显示的就是字符下。该递归函数中,要进行范围的限制,不然程序会出错,必须在这个显示的棋盘上进行递归。且每次该递归函数完成之后都要打印show棋盘,给玩家看。
三、完整代码
game.h部分
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2 //比正常的棋盘宽两个会更加容易实现对边界的扫雷排查
#define COLS COL+2
#define COUNT 10 //雷的数量
void Init_board(char board[ROWS][COLS], int rows, int cols,char set);
void Display_board(char board[ROWS][COLS], int row, int col);
void Set_board(char mine[ROWS][COLS], int row,int col);
void Find_borad(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void findmax_board(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int sum(char mine[ROWS][COLS], int x, int y);
game.c部分
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void Init_board(char board[ROWS][COLS], int rows, int cols,char set)
{for(int i=0;i<rows;i++){for(int j=0;j<cols;j++){board[i][j] = set; //我们看的用‘0’来表示,用户看的用‘*’来表示}}
}
void Display_board(char board[ROWS][COLS], int row, int col)
{for(int i=0;i<=col;i++){printf("%d ", i);}printf("\n");for(int i=1;i<=row;i++) //此时的row,col,用ROW,COL来表示,多出的两个长宽不表示出来{printf("%d ", i);for(int j=1;j<=col;j++){printf("%c ", board[i][j]);}printf("\n");}printf("---------扫雷--------\n"); //分割开会更好区分
}
void Set_board(char mine[ROWS][COLS], int row, int col)
{int x, y;//用x,y来替代雷的坐标int count = COUNT; //还需要定义雷的数量countwhile (count){x = rand() % row + 1; //余数为0~8,加1,则为1~9y = rand() % col + 1;if (mine[x][y] == '0') //防止雷重复占位{mine[x][y] = '1';//定义字符为1的位置为雷;为什么定义为1呢?方便后面提示的计算使用count--;}}}
int sum(char mine[ROWS][COLS],int x ,int y){return mine[x-1][y-1] + mine[x-1][y] + mine[x-1][y+1] + mine[x][y-1]+ mine[x][y+1] + mine[x+1][y-1] + mine[x+1][y] + mine[x+1][y+1] - 8 * '0';
}
void Find_borad(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x, y;int count = 0; //非*的数量int mark = 0; //标记的数量int input = 0;while (col * row - count - COUNT) //当非*的数量(count)加上雷的数量(COUNT)为总数时,说明已找到所有的雷{printf("----请选择----\n");printf("---1、标记或取消标记---\n");printf("---2、检查---\n");scanf("%d", &input);switch (input){case 1:if (mark <= COUNT){mark = 0;printf("请输入标记坐标:>");scanf("%d %d", &x, &y);if (show[x][y] != '!'){show[x][y] = '!';for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}else if(show[x][y] == '!'){show[x][y] = '*';for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}}else{printf("标记已超过雷的数量,若需再次标记,需要取消之前的标记\n");printf("请输入取消标记的坐标:>");scanf("%d %d", &x, &y);show[x][y] = '*'; for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] == '!')mark++; //每次之后检查标记的数量}}Display_board(show, row, col);}break;case 2:printf("请输入:>");scanf("%d %d", &x, &y);if (x < 0 || x>9 || y < 0 || y>9){printf("输入错误,请输入1~9之间的数字\n");}else //输入正确范围后进入{if (mine[x][y] == '1'){printf("非常可惜,踩到地雷,游戏失败!\n");Display_board(mine, row, col); //打印给用户看看雷的位置break;//跳出循环,游戏结束}else{count = 0; //这部很重要,每次重新进入循环,需要count为0,不然count的值会累加findmax_board(mine, show, x, y);//show[x][y] = sum(mine, x, y)+'0'; //这个sum作为辅助,来找到我们输入位置周围的地雷//数量,加上‘0’为了得到一个字符Display_board(show, row, col); //每次输入后都应打印这个雷盘for (int i = 1; i <= 9; i++){for (int j = 1; j <= 9; j++){if (show[i][j] != '*'&&show[i][j]!='!')count++; //每次检查后查看非*的数量}}printf("%d", count);//count++;}}if (count == col * row - COUNT) printf("你太厉害了呀,找到了所有的地雷!");//当查看了所有的非雷位置还没爆炸时,获胜break;default: printf("非法输入,请重新输入"); break;}}
}
//以上的函数效率太低了,我们在玩扫雷游戏时,往往会点了一个地方,引发连锁反应,周围的没有雷的地方都自动扫描出来
//下面我们用递归的方法来解决
// 1、选取的位置本身不是雷时,赋值为周围的雷的数量
// 2、判断周围位置是否是雷,不是雷接着赋值
void findmax_board(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) //x,y为选取的坐标
{ int count2 = sum(mine, x, y);if (count2 == 0) //该坐标周围无雷{show[x][y] = ' ';if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x - 1, y - 1);}if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 <= ROW && y > 0 && y <= COLS){findmax_board(mine, show, x - 1, y );}if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 <= ROWS && y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x - 1, y + 1);}if (show[x][y - 1] == '*' && x > 0 && x <= ROWS && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x, y - 1);}if (show[x][y + 1] == '*' && x > 0 && x <= ROWS && y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x, y + 1);}if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 <= ROWS && y - 1 > 0 && y - 1 <= COLS){findmax_board(mine, show, x + 1, y - 1);}if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 <= ROWS && y > 0 && y <= COLS){findmax_board(mine, show, x + 1, y);}if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 <= ROWS&& y + 1 > 0 && y + 1 <= COLS){findmax_board(mine, show, x + 1, y + 1);}}else{show[x][y] = count2 + '0';}
}
test.c部分
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{printf("*******************\n");printf("****1、开始游戏****\n");printf("****0、退出游戏****\n");printf("*******************\n");
}
void game()
{printf("-----------------游戏规则----------------------\n");printf("1、将所有非雷位置检查完毕则获胜\n");printf("2、输入格式为(x空格x)\n");printf("3、标记只是作为提醒,且标记个数不能超过雷的数量\n");printf("------------最后祝您游戏愉快!-------------------\n");printf("\n");char mine[ROWS][COLS] = { 0 };//这个棋盘是给我们定义用的char show[ROWS][COLS] = { 0 };//这个棋盘是用来展示给用户的Init_board(mine, ROWS, COLS, '0');//首先初始化棋盘Init_board(show, ROWS, COLS, '*');//给用户看的用*来代替//Display_board(mine, ROW, COL);//不打印,这是给开发者调整用的Display_board(show, ROW, COL); //此时开始设置雷的数量和位置Set_board(mine, ROW, COL); //只给开发者的棋盘进行改变//接下来开始查找雷//Display_board(mine, ROW, COL);//不打印,这是给开发者调整用的,取消注释则可看雷的位置Find_borad(mine,show, ROW, COL);
}
void test() {srand((unsigned int)time(NULL));int input = 0;do{menu();//首先打印选择页面scanf("%d", &input);switch (input){case 1:printf("开始游戏:>\n");game();break;case 0:printf("已退出游戏");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);}
int main() {test();return 0;
}
四、结语
扫雷的难度在三子棋之上,有兴趣的小伙伴可以自己做做看,我的仅作为参考,有不足之处,欢迎大家指出。最后已经看到这里了,创作不易,点点赞吧!
扫雷【含递归和标记的完整代码及详细讲解】相关推荐
- CV项目肢体动作识别(三)内附完整代码和详细讲解
CV项目肢体动作识别(三)内附完整代码和详细讲解 首先我还是给出完整的代码,然后再进行详细的讲解.这一次我们用模块化的思想,把一个功能模块化(moudle),这种思想在工程中非常常见,在分工中你需要做 ...
- C语言实现扫雷(含递归和标记)
小白初学C语言做个简单的游戏.首先用二维数组打印两个棋盘,一个棋盘布雷,另一个棋盘显示界面.如果要9*9的棋盘,那么数组元素应该为11*11,否则在二维数组边缘排查雷的数量时会发生越界,但是多出来的元 ...
- 人脸表情识别系统的设计与实现(含UI界面,有完整代码)
人脸表情识别系统的设计与实现(含UI界面,有完整代码) 这是之前本科做的毕设,当时使用的是keras搭建了一个简单的神经网络作为入门实现了在fer2013人脸表情数据集上的表情分类,并移植到了树莓派上 ...
- 汇编语言实验十完整代码和详细解析
汇编语言实验十完整代码和详细解析 建议先自己思考问题的答案,不懂则返回看书 扩展建议: [非必要内容,个人经验感悟] 从这儿开始,我们正式接触子程序,主要是通过 call和 ret 来实现的.这样就涉 ...
- bat脚本常用命令及亲测示例代码超详细讲解
这篇文章主要介绍了bat脚本常用命令及亲测示例代码超详细讲解,在这里需要注意编辑bat文件请使用ANSI编码,不然容易出现中文乱码,需要的朋友可以参考下 目录一 1.语句注释 2.暂停 3.输出和换行 ...
- c++ 实现贪吃蛇(含技术难点解析和完整代码)
文章目录 0.参考资料 1 技术难点 1.1 关于光标的移动 1.2 关于蛇的移动 1.2.1 从键盘上读取输入 1.2.2 蛇的移动 1.3 食物的生成 2.完整代码 0.参考资料 借鉴了这位大佬的 ...
- 汇编语言实验12完整代码及详细解析
汇编语言实验12完整代码及考察点 建议先自己思考问题的答案,不懂则返回看书 很简单的一个程序,不做额外的分析,看代码就懂了.本章主要是理解中断的过程.原理. assume cs:code code s ...
- blog微服务架构代码_Spring Cloud微服务架构代码结构详细讲解
上一篇我们介绍了spring cloud云服务架构 - particle云架构代码结构,简单的按照几个大的部分去构建代码模块,让我们来回顾一下: 第一部分: 针对于普通服务的基础框架封装(entity ...
- 数据结构《顺序栈》知识点详解+C语言完整代码-超详细
顺序栈 栈 1. 定义 2. 逻辑结构 3. 存储结构 4. 运算规则 5. 实现方式 C语言代码实现 1. 顺序栈的表示 2. 结构体 3.初始化 4.入栈 5.出栈 6. 取栈顶元素 7.求长 8 ...
最新文章
- 独家 | 一文读懂神经网络(附解读案例)
- 让机器听懂世界,触及人类梦想还有多远?
- PMP-【第5章 项目范围管理】-2021-1-27(116页-135页)
- 国内数据中心制冷系统设计与发展
- 在SAP CRM呼叫中心里创建Service Request的实现技术
- 送给“苦逼”的IT人系列1:IT人的“钱”景以及收入的两道坎
- 有两个链表a,b,设结点包括学号,姓名。从a链表中删去与b链表中有相同学号的那些结点。
- python count函数用法 comm_python3:MySQL 8.0学习笔记(第五部分:单表查询操作)
- C++工作笔记-对结构体的进一步认识
- 百度SEO站群wordpress设置网站TDK源码插件
- httpclient 不支持国密ssl_Hyperledger Fabric成都见面会:TWGC国密改造介绍
- 详解:hiveserver2的使用与介绍
- C#中的方法(函数),委托和事件
- x264源码下载信息
- 打开统计年鉴html,南京统计年鉴2018(HTML)
- 服务器防止ce修改器,原神CE修改器防封版
- Linux的文件的权限管理
- Squoosh - 谷歌出品的免费开源图片压缩工具,图片大小减少90%!支持 API 开发调用
- steam linux 安装目录,「Linux」- 安装 Steam 客户端 @20210219
- 能消除眼部疲劳的电脑桌面设置方法
热门文章
- linux 下 安装 nfs 服务
- 六元均匀直线阵的各元间距为_微波技术与天线复习题
- 资料分析思维导图模板
- 计算机技能大赛主持人串词,小学部生活技能大赛串词
- VMware WorkStation 8序列号
- 【HDU 2619】Love you Ten thousand years
- 20130822-STM8L101F3P6的PD0使用异常,尚未解决~!
- cisco S3750交换机配置VLAN
- android listview排序分组,Android:如何对ListView的数据进行排序?
- 【githubgirl】开源的画板与笔记工具,可用于日常文字记录和头脑风暴等场景,也可绘制草图或图标