1.设计思路我们设计扫雷游戏简单版就是要利用好数组和函数。总体的核心我将其分为打印菜单数组1. 初始化棋盘 2. 打印棋盘 3. 布置雷 4. 排查雷(递归) 。整体的思路分为六步第一步运用两个字符数组一个放“雷”mine 一个查“雷”show第二步初始化棋盘第三步打印棋盘第四步布置雷第五步排查雷第六步递归。我们将使用三个文件完成任务其中含有的细节我会在具体过程中展现。2.具体过程首先游玩一个游戏我们需要给出一个菜单让玩家选择是否进行游玩因为菜单必定会展示在玩家面前所以我们可以使用一个do-while 循环来打印菜单对于是否进行游戏的游玩我们可以使用switch语句来实现多种选择写法如下//test.c void menu() { printf(****************\n); printf(**** 1.play ****\n); printf(**** 0.exit ****\n); printf(****************\n); printf(请输入:); } void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); //DisplayBoard(mine, ROW, COL); FindMine(mine, show, ROW, COL); } int main() { int input 0; srand((unsigned int)time(NULL));//先忽略此步后面会提到 do { menu(); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(退出游戏\n); break; default: printf(输入错误请重新输入); break; } } while (input); return 0; }我们先不关注game() 函数里面的具体内容在里我们将game写到一个函数当中再将具体的游戏实现分装到另一个文件中就可以让主函数更加美观简洁我们在此将运用到另外两个文件其中一个源文件(c.) 用来实现游戏另外一个头文件(.h) 用来声明函数包含库函数定义一些信息。因此我们创建了game.c 与 game.h 两个文件game.c文件用来放置函数具体的内容包括初始化棋盘展示棋盘设置雷查找雷的函数。在 game.h 中进行一些头文件的包含变量的定义以及上述 game.c 中函数的声明。这里我们先讲为什么要使用头文件在此扫雷函数的实现中原因有三个1. 我们在写代码的过程中需要包含头文件在使用多个文件的时候我们可以将这些头文件包含在一个头文件中(game.h)并在其他源文件(.c)中直接包含 game.h 这个头文件即可这样就省去了多次包含的功夫在game.h中体现如下//game.h #include stdio.h #include time.h//用于设置雷函数随机数生成 #includestdlib.h//也用于设置雷函数随机数生成2. 对于一些变量的共同变量的赋值如果我们在头文件中进行声明那么在需要修改值的时候只需要修改一次而不是每次修改都要一个个的去修改例如棋盘数组的行和列我们在每个函数中都会用到如果用#define 定义常量那么我们就不需要每次都修改。3. 函数的声明一般情况下我们如果需要使用其他源文件的函数需要使用多次extern声明外部符号但是如果在game.h 文件中声明就只需要同时在test.c 与 game.c 两个文件中都包含 game.h 文件//在game.c 和 test.c 中都写 #include game.h就可以直接完成声明。下面我们进入到具体的函数当中进行讲解2.1 初始化棋盘初始化棋盘前我先讲解棋盘的设计在此扫雷游戏中我们运用两个数组一个放置“雷”数组一个展示数组这样我们在排雷的时候就可以对mine(雷)数组进行修改然后再将变化传到show数组上进行打印此外两个数组的符号也不同mine数组中我们用字符 ‘1’ 来表示“雷”字符 ‘0’ 表示普通格子show数组中展示的是 “ * ”号与排查后周围雷的个数。这就是我们使用两个数组来存放信息的原因。然后我们来讲数组行列和雷的设计此处我们要设计的是9*9的扫雷游戏布置10个雷我们就可以在头文件中进行以下定义//game.h #define EASY_COUNT 10 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2这里出现了 ROWS 与 COLS 两个新变量原因是我们在排查雷的时候我们需要扫描当前位置周围的情况一个九宫格除中心外的另外八个格子那么我们在扫描棋盘边缘的位置时就会扫描到9*9以外的区域所以为了防止越界访问我们将棋盘扩大到11*11。所以我们在game( ) 函数中声明两个数组如下//test.c void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; }现在我们终于进入到初始化棋盘函数的讲解在此函数中我们的任务就是将mine数组的每行每列每个元素设置成 “ 0 ” 将show数组的每行每列每个元素设置成 “ * ”。写法如下//game.c void InitBoard(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; } } }//game.h void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//test.c void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); }2.2 展示棋盘在展示棋盘的函数中我们需要打印出行列号这里需要注意的两个点就是1. 列号我们单独打印行号可以放入打印棋盘行的循环当中 2. 我们只需要打印 9*9 的棋盘传递的参数行列直接为ROW 和 COL 即可写法如下//game.c void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf(-----扫雷游戏------\n); for (int i 0; i col; i) { printf(%d , i); } printf(\n); for (int i 1; i row; i) { printf(%d , i); for (int j 1; j col; j) { printf(%c , board[i][j]); } printf(\n); } }//game.h void DisplayBoard(char board[ROWS][COLS], int row, int col);//test.c void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //DisplayBoard(mine, ROW, COL);//这里我们可以打印雷的棋盘来查看雷棋盘的情况 DisplayBoard(show, ROW, COL); }2.3 布置雷布置雷函数我们需要用到随机数的生成设置 x 和 y 两个变量让 x 和 y 随机生成1-9 之间的数字这样就可以在棋盘的随机位置将mine 数组中初始化的 “ 0 ” 设置成 “ 1 ” 需要注意的是我们需要运用一个if语句判断当前位置是否是没有设置过雷的状况此外在此扫雷游戏中我们设置的雷的个数是10那么我们可以设置一个while 语句并将雷的个数设置为循环的次数设置一个参数 count EASY_COUNT 当成功进入if语句放置雷以后就可以利用count-- 来减少循环次数以此达成设置雷的目的具体代码如下//game.c void SetMine(char mine[ROWS][COLS], int row, int col) { int count EASY_COUNT; while (count) { int x rand() % row 1;//rand % row 生成0-8数字 int y rand() % col 1; if (mine[x][y] 0) { mine[x][y] 1; count--; } } }//game.h void SetMine(char mine[ROWS][COLS], int row, int col);//test.c void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); //DisplayBoard(mine, ROW, COL);//在调试时查看是否正确生成10个雷 }这里我们在SetMine 函数中使用了rand() 函数来生成随机数那么我们就要在test.c 文件中的main函数里使用srand 函数与利用时间戳达成生成伪随机数的目的写法如下srand((unsigned int)time(NULL));另外我们使用srand函数需要包括头文件 “stdlib.h”, 代码写法如下//game.h #include stdlib.h2.4 排查雷(含递归)在排查雷函数的思路是我们需要让玩家输入坐标排查当前的位置是否是雷再以当前坐标为圆心对该坐标周围的8个格子进行扫描然后再分别以8个格子为圆心对其周围进行扫描直到发现周围的雷数量 1 则停止。在此处我们将功能在分别分到三个函数中进行书写1. FindMine( ), (此处的“主函数”) 2. get_mine_count( ), (获取当前坐标雷周围的雷个数) 3. ExpandMine( ), (递归扫雷)2.4.1 FindMine函数第一步我们需要将两个数组mine 与 show与行列 (ROW 与 COL) 作为参数传给FindMine( )。在函数内部设定三个局部变量 xy与winx,y 代表横纵坐标win row*col - EASY_COUNT表示我们需要排查格子的次数棋盘总格数减去雷的个数就是我们总共需要排查的次数利用while循环以win作为判断条件使用scanf 让玩家输入横纵坐标再利用第一个if语句判断坐标是否合法是否在9*9的范围之内如果坐标不合法显示输入错误让玩家重新输入如果坐标合法进入第二个if语句中第二个if语句中有三种情况第一种情况就是在mine数组中判断该位置是否是雷“1”如果是雷显示“你被炸死了”然后打印出雷棋盘第二种情况是防止玩家输入重复的坐标判断show数组中该位置是否是星号“ * ”如果是显示已经排查过并让玩家重新输入第三种情况如果以上两个条件都不符合那么我们就进入到第二步进入到ExpandMine( ) 函数当中实现递归扫雷并在扫完本次雷之后打印一次展示棋盘如果在此我们把非雷的位置都找了出来那么win 将 变为0在最后我们用一个if语句判断win 是否为0如果为0就显示“排雷成功”并打印雷棋盘第一步的代码展示如下//game.c void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x 0; int y 0; int win row * col - EASY_COUNT; while (win) { printf(请输入需要排查的坐标:); scanf(%d %d, x, y); if (x 1 y 1 x row y col) { if (mine[x][y] 1) { printf(很遗憾你被炸死了。\n); DisplayBoard(mine, row, col); break; } else if (show[x][y] ! *) { printf(已排查过请重新输入!\n); } else { ExpandMine(mine, show, x, y, win);//第二步在这 DisplayBoard(show, ROW, COL); } } else { printf(输入错误请重新输入\n); } } if (win 0) { printf(恭喜你排雷成功\n); DisplayBoard(mine, ROW, COL); } }2.4.2 ExpandMine函数 与 get_mine_count 函数第二步我们将mine数组和show数组坐标x和y以及局部变量win的地址作为参数传递给ExpandMine函数在此函数里我们需要实现递归排雷的目的。在函数内部我们使用if语句与逻辑或操作符“ || ”判断xy 坐标是否合法在棋盘范围内该坐标下mine数组中是否是雷“1”show数组中是否是星号“ * ”如果满足以上任意一个条件直接终止当前函数的执行且不返回任何东西。如果if语句判断为假调用get_mine_count函数在此设置一个局部变量-count 来接收get_mine_count函数的返回值以得到当前坐标周围的雷的个数然后将雷的数量反映到show数组当中这里有一个很巧妙的地方就是我们在修改该坐标的数字的时候需要的是字符的形式而我们的局部变量count设置的是int 类型所以我们在修改雷的数量的时候要给count 加一个字符 ‘ 0 ’代码的写法如下show[x][y] count 0;因为数字在程序中是以ASCII码值的形式存储我们当初初始化棋盘的时候就将全部格子初始化成了字符 ‘0’ 字符 ‘ 0 ’的ASCII码值是48如果我们这里不加字符 ‘0’ 直接将int 类型的数字传给show数组当前坐标格就可能会出现异常显示成功展示了当前位置周围雷的个数的情况下我们将指针变量win进行减减操作表示进行了一次当前位置的排查。然后我们在判断周围雷的个数的时候如果周围格子都没有雷的话我们就希望向四周发散以周围格子坐标为基点继续向外排查雷的个数直到这个格子周围出现至少一个雷为止写法很简单就是使用if语句判断此时 count 是否为零如果为零就将ExpandMine函数调用8次将上下左右左上右上左下右下的8个位置都排查一遍进行递归代码写法如下if (count 0) { ExpandMine(mine, show, x - 1, y - 1, win); // 左上 ExpandMine(mine, show, x - 1, y, win); // 上 ExpandMine(mine, show, x - 1, y 1, win); // 右上 ExpandMine(mine, show, x, y - 1, win); // 左 ExpandMine(mine, show, x, y 1, win); // 右 ExpandMine(mine, show, x 1, y - 1, win); // 左下 ExpandMine(mine, show, x 1, y, win); // 下 ExpandMine(mine, show, x 1, y 1, win); // 右下 }最后查找雷和递归的总代码如下//game.c int get_mine_count(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 1][y - 1] mine[x 1][y] mine[x 1][y 1] mine[x][y 1] - 8 * 0; } void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win) { if (x1 || xROW || y1 || yCOL || show[x][y] ! * || mine[x][y] 1) { return; } int count get_mine_count(mine, x, y); show[x][y] count 0; (*win--); if (count 0) { ExpandMine(mine, show, x - 1, y - 1, win); // 左上 ExpandMine(mine, show, x - 1, y, win); // 上 ExpandMine(mine, show, x - 1, y 1, win); // 右上 ExpandMine(mine, show, x, y - 1, win); // 左 ExpandMine(mine, show, x, y 1, win); // 右 ExpandMine(mine, show, x 1, y - 1, win); // 左下 ExpandMine(mine, show, x 1, y, win); // 下 ExpandMine(mine, show, x 1, y 1, win); // 右下 } } void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x 0; int y 0; int win row * col - EASY_COUNT; while (win) { printf(请输入需要排查的坐标:); scanf(%d %d, x, y); if (x 1 y 1 x row y col) { if (mine[x][y] 1) { printf(很遗憾你被炸死了。\n); DisplayBoard(mine, row, col); break; } else if (show[x][y] ! *) { printf(已排查过请重新输入!\n); } else { ExpandMine(mine, show, x, y, win); DisplayBoard(show, ROW, COL); } } else { printf(输入错误请重新输入\n); } } if (win 0) { printf(恭喜你排雷成功\n); DisplayBoard(mine, ROW, COL); } }//game.h void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win);//test.c void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); //DisplayBoard(mine, ROW, COL);//在调试时查看是否正确生成10个雷 FindMine(mine, show, ROW, COL); }3.总结总体来说使用c语言实现扫雷游戏是对c语言多方面知识点的综合运用包括函数数组分支循环语句随机数生成递归少量指针知识等等需要我们有一个清晰的思路一步步达成目的而且要注重很多细节比如#define定义常量头文件函数声明棋盘字符的设计ASCII码值等等漏掉了某个细节都可能导致一些bug的产生。最重要的还是勤写代码把代码上手写一遍感触会越来越深。最后附上完整代码//game.h #define _CRT_SECURE_NO_WARNINGS #include stdio.h #include time.h #includestdlib.h #define EASY_COUNT 10 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); void DisplayBoard(char board[ROWS][COLS], int row, int col); void SetMine(char mine[ROWS][COLS], int row, int col); void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win);//game.c #include game.h void InitBoard(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; } } } void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf(-----扫雷游戏------\n); for (int i 0; i col; i) { printf(%d , i); } printf(\n); for (int i 1; i row; i) { printf(%d , i); for (int j 1; j col; j) { printf(%c , board[i][j]); } printf(\n); } } void SetMine(char mine[ROWS][COLS], int row, int col) { int count EASY_COUNT; while (count) { int x rand() % row 1; int y rand() % col 1; if (mine[x][y] 0) { mine[x][y] 1; count--; } } } int get_mine_count(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 1][y - 1] mine[x 1][y] mine[x 1][y 1] mine[x][y 1] - 8 * 0; } void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win) { if (x1 || xROW || y1 || yCOL || show[x][y] ! * || mine[x][y] 1) { return; } int count get_mine_count(mine, x, y); show[x][y] count 0; (*win--); if (count 0) { ExpandMine(mine, show, x - 1, y - 1, win); // 左上 ExpandMine(mine, show, x - 1, y, win); // 上 ExpandMine(mine, show, x - 1, y 1, win); // 右上 ExpandMine(mine, show, x, y - 1, win); // 左 ExpandMine(mine, show, x, y 1, win); // 右 ExpandMine(mine, show, x 1, y - 1, win); // 左下 ExpandMine(mine, show, x 1, y, win); // 下 ExpandMine(mine, show, x 1, y 1, win); // 右下 } } void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x 0; int y 0; int win row * col - EASY_COUNT; while (win) { printf(请输入需要排查的坐标:); scanf(%d %d, x, y); if (x 1 y 1 x row y col) { if (mine[x][y] 1) { printf(很遗憾你被炸死了。\n); DisplayBoard(mine, row, col); break; } else if (show[x][y] ! *) { printf(已排查过请重新输入!\n); } else { ExpandMine(mine, show, x, y, win); DisplayBoard(show, ROW, COL); } } else { printf(输入错误请重新输入\n); } } if (win 0) { printf(恭喜你排雷成功\n); DisplayBoard(mine, ROW, COL); } }//test.c #include game.h void menu() { printf(****************\n); printf(**** 1.play ****\n); printf(**** 0.exit ****\n); printf(****************\n); printf(请输入:); } void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); //DisplayBoard(mine, ROW, COL); FindMine(mine, show, ROW, COL); } int main() { int input 0; srand((unsigned int)time(NULL)); do { menu(); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(退出游戏\n); break; default: printf(输入错误请重新输入); break; } } while (input); return 0; }