hello,大家好,今天我们继续为大家带来一个小游戏,扫雷。相信这个游戏又是很多人的童年,那么我们今天就来实现一下这个扫雷游戏。
目录
虽然说是一个基本上大家都听过,并且大多数人都玩过的一个小游戏,但是其实很多人当时都是瞎玩的。所以呢,我们先来介绍一下游戏规则。这个游戏,我们可以直接在电脑上搜索到,打开浏览器直接搜索扫雷。我们使用如下图所示的游戏

点击9*9棋盘,如下图所示,每一行是9个元素,每一列也是9

我们随机点开其中的一个方格,他会进行展开,其中白色方格代表这块没有雷。有数字的方格代表其周围一圈的八个格子里面有雷的个数 。我们就是根据这些提示进行排雷

而一旦踩中了雷以后,就会失败

1.布置雷:创建一个二维数组,随机生成十个坐标放到位置上
2.排查雷:选择一个坐标,判断是否是雷,如果是,则爆炸,如果不是则遍历以他为中心的9个元素,并显示一个周围雷的个数。
在我们前面讲解了三子棋还有猜数字小游戏后,相信大家已经对这个思路很熟悉了,我们还是采用分文件的形式写代码,创建test.c,game.h,game.c三个文件。
这里就不在赘述了,直接给出代码,以下都是test.c文件中的代码
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
- void meau()
- {
- printf("*************************\n");
- printf("******* 1. 开始扫雷 ******\n");
- printf("******* 0. 退出游戏 ******\n");
- printf("*************************\n");
-
- }
- void game()
- {
- printf("开始扫雷\n");
- }
- void test()
- {
- meau();
- int input = 0;
- do
- {
- printf("请输入选项>:\n");
- scanf("%d", &input);
- switch (input)
- {
- case 1:
- game();
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default:
- printf("输入错误,请重新输入\n");
- break;
- }
- } while (input);
- }
- int main()
- {
- test();
- return 0;
- }
当然还是需要测试的,我们进行一次测试,结果符合我们的预期

我们的目标是布置雷与排查雷,那么这个时候有一个问题了,我们如果使用一个数组来记录布置雷的话,假如说我们记作,有雷是1,无雷是0。那么未来我开始排查雷的时候,想要扫雷,扫的这个位置是需要显示出周围有几个雷的。那这样的话,加入说我周围只有一个雷,那么打印出来的就只有1。可是这样子的话,我们这个位置就相当于被修改了,莫名奇妙的多出了一个雷,这就导致了一些混乱,产生了大量的bug。所以说这样的方法不太合理,我们需要进行优化。
那么如何进行优化呢?我们想到,既然只使用一个数组会乱,那么我们使用两个数组呢?一个数组仅仅用来放雷,一个数组用来打印出来让人来看。并且要记录这个周围有多少个雷。所以说,这样做的话就是可行的。程序就不会乱套了。
对于我们那个需要让人看的数组而言,我们一开始人是看不见雷的,所以说我们需要一开始打印的全是*号这个字符,为了方便起见,我们显示出周围有几个雷的时候也是使用字符数字来显示出周围有多少个雷。那么这样的话,不妨统一一下,两个数组都是字符数组。

有很多人不经思考的直接回答说9*9即可,可是当我们在排查雷的时候,遇到边界的坐标如何排查呢,我们如果想要排查他的一圈的话,那么毫无疑问越界了。所以这不是明智之举。我们应当进行在这个数组上加上一圈,所以行和列都应该加上2。这样就不会越界访问了,当然为了统一一下,我们两个数组都是这样加上一圈。

有了前面那么多代码的思考,那么我们能不能实现一下呢。我们先定义一下数组吧。如下图所示,我们为了方便起见我们直接在game.h中define一下,然后我们想要在test.c文件中使用这个define定义常量。就得引入这个game.h这个头文件。然后就可以定义字符数组了。当然我们是要使用行和列都+2的那个define定义的常量。

很显然,我们要初始化数组两次,所以说,我们除了传数组名,行和列以外,需要传一个初始化成什么样子的一个字符。需要注意的是,这里传的是字符0,字符0和数字0是不一样的,字符0的ASCII值是48。'\0'的ASCII值是0,而如果传一个0的话,那么他最终打印出来的就是一堆'\0'也就是什么都没有。
- //初始化数组
- void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);



这块还是比较简单的,我们用两层循环即可

注意:此图中出现了一个经典的错误,标准的零分
在第12行中,这个set不应该带有单引号,因为我们set他本身是一个变量,对变量带一个单引号,那么就势必导致一些不可预料的错误。
这也是很多人都喜欢犯的一个错误!!!
- //初始化数组
- void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
- {
- int i = 0;
- for (i = 0; i < rows; i++)
- {
- int j = 0;
- for (j = 0; j < cols; j++)
- {
- board[i][j] = set;
- }
- }
- }
我们初始化好数组以后,我们肯定希望我们现在就能看到这个数组。这也是方便我们后续进行测试的一个重要步骤,不过需要注意的是,我们打印肯定不是打印这个11*11的数组,而是只打印9*9的数组即可,因为这个人只需要看到9*9的就可以了
这里唯一需要注意的是,我们定义和声明函数的时候,数组的大小可千万不要写错了,写出了ROW,注意,我们这里数组的大小仍然是11*11的,只不过因为我只需要9*9就足够了。
- //打印数组
- void DisplayBoard(char board[ROWS][COLS], int row, int col);



对于这个代码,其实也不是很难,同样两个for循环解决

同样的,写一点,测一点,好在结果也确实符合我们的预期,需要注意的是,有的人可能注意到了printf为什么有绿色警告,其实这是因为我们的game.c文件中还没有


当然,我们此时打印出来的数组还不够完美,因为打印出来的数组密密麻麻,我们无法识别坐标,所以我们最好能把行标和列表打印出来就更加完美了。

这样一来就完美了


这样可以使得测试更好看

- //打印数组
- void DisplayBoard(char board[ROWS][COLS], int row, int col)
- {
- printf("-------扫雷--------\n");
- int i = 0;
- int j = 0;
- for (j = 0; j <= col; j++)
- {
- printf("%d ", j);
- }
- printf("\n");
- for (i = 1; i <= row; i++)
- {
- printf("%d ", i);
- for (j = 1; j <= col; j++)
- {
- printf("%c ", board[i][j]);
- }
- printf("\n");
- }
- printf("-------扫雷--------\n");
-
- }
我们布置雷都是往mine数组里面放的,这个数组的大小是11*11,需要操作的行是9,列是9。所以定义和声明为
void SetMine(char board[ROWS][COLS], int row, int col);


(2)雷的个数以及雷坐标的生成
我们开始生成雷的个数和坐标。雷的个数我们可以使用一个define来进行定义。雷的坐标则需要使用一个随机数的使用。代码实现如下




如下图所示,我们在布置好雷以后在后面打印一下数组

结果为下图所示,刚好符合我们的预期,就是10个雷

- //布置雷
- void SetMine(char board[ROWS][COLS], int row, int col)
- {
- //雷的个数
- int count = EASY_COUNT;
- while (count)
- {
- //生成随机下标
- int x = rand() % row + 1;
- int y = rand() % col + 1;
- //布雷
- if (board[x][y] == '0')
- {
- board[x][y] = '1';
- count--;
- }
- }
- }
我们在排查的时候,是在mine数组中进行查找,然后将结果反馈给show数组,所以需要两个数组都传过去。我们进行排查的时候,只需要使用行9列9即可



我们在排查雷的过程中的逻辑是这样,让用户输入一个坐标,通过这个坐标先判断是否合法,如下图所示

现在我们可以确保我们的坐标是合法的了,在合法的基础上,如果这个坐标中的位置已经是字符1了,那玩家就被炸死了,游戏结束并打印出雷区的棋盘。如果不是字符1,那就开始统计他的一圈有多少个雷,为了方便统起见,我们使用一个函数来进行统计。这个函数我们不妨就定义为
int get_mine_count(char mine[ROWS][COLS], int x, int y);
这个函数返回一个整型数字,传一个数组和需要查询的坐标即可。我们最后只需要将返回的这个整型数字+字符0即可得到对应的字符,然后将这个字符赋值给show数组对应的位置,并打印出来

那么我们现在该实现get_mine_count这个函数了,这个难度也不大,我们把周围一圈的都加起来然后减去8个字符0就可以解决了

此时我们的test.c文件是这样的。前面打印出来的那些部分我们后期可以将其给删掉。但是现在我们是正在测试中,没有必要删除。


被炸死案例
坐标不合法案例
当我们一直排雷下去,我们会发现我们的代码有一点问题,那就是排雷无法结束。所以说我们要在循环加上限制条件,我们可以定义一个变量,每一次没有被炸死则这个变量++。对于我们9*9的棋盘而言只要有81-10=71次没有被炸死,就赢了。所以说我们的代码实现如下

为了方便测试,我们将雷的数量暂时改为80个,符合我们的预期。

在这里相信很多人也都发现了一个bug,那就是这个坐标已经被排查过了,还重复排查的话,那win依然会++,这就是一个漏洞了。所以我们要优化这个bug
我们在这个位置加上这样一段代码即可,只要不是'*',就代表这个位置已经被查过了,利用一个continue就可以解决问题了。

下面是运行测试

- //排雷时候需要统计的雷个数
- 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 + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
- mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
- }
-
- //排查雷
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
- {
- int x = 0;
- int y = 0;
- int win = 0;
- while (win< row*col-EASY_COUNT)
- {
- printf("请输入坐标>:\n");
- scanf("%d %d", &x, &y);
- if (show[x][y] != '*')
- {
- printf("这个位置已经被排查过了,请重新输入坐标\n");
- continue;
- }
- if (x >= 1 && x <= row && y >= 1 && y <= col)
- {
- if (mine[x][y] == '1')
- {
- printf("很遗憾,你被炸死了\n");
- DisplayBoard(mine, ROW, COL);
- break;
- }
- else
- {
- int n = get_mine_count(mine, x, y);
- show[x][y] = n + '0';
- DisplayBoard(show, ROW, COL);
- win++;
- }
- }
- else
- {
- printf("坐标不合法,请重新输入\n");
- }
- }
- if (win == row * col - EASY_COUNT)
- {
- printf("恭喜你,你赢了\n");
- DisplayBoard(mine, ROW, COL);
- }
- }
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include"game.h"
- void meau()
- {
- printf("*************************\n");
- printf("****** 1. 开始扫雷 ******\n");
- printf("****** 0. 退出游戏 ******\n");
- printf("*************************\n");
-
- }
- void game()
- {
- printf("开始扫雷\n");
- char mine[ROWS][COLS] = { 0 };
- char show[ROWS][COLS] = { 0 };
- //初始化数组
- 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);
-
-
- }
- void test()
- {
- srand((unsigned int)time(NULL));
- int input = 0;
- do
- {
- meau();
- printf("请输入选项>:\n");
- scanf("%d", &input);
- switch (input)
- {
- case 1:
- game();
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default:
- printf("输入错误,请重新输入\n");
- break;
- }
- } while (input);
- }
- int main()
- {
- test();
- return 0;
- }
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include
- #include
- #include
-
- #define ROW 9
- #define COL 9
-
- #define ROWS ROW+2
- #define COLS COL+2
-
- #define EASY_COUNT 10
-
- //初始化数组
- 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 board[ROWS][COLS], int row, int col);
- //排查雷
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"game.h"
- //初始化数组
- void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
- {
- int i = 0;
- for (i = 0; i < rows; i++)
- {
- int j = 0;
- for (j = 0; j < cols; j++)
- {
- board[i][j] = set;
- }
- }
- }
- //打印数组
- void DisplayBoard(char board[ROWS][COLS], int row, int col)
- {
- printf("-------扫雷--------\n");
- int i = 0;
- int j = 0;
- for (j = 0; j <= col; j++)
- {
- printf("%d ", j);
- }
- printf("\n");
- for (i = 1; i <= row; i++)
- {
- printf("%d ", i);
- for (j = 1; j <= col; j++)
- {
- printf("%c ", board[i][j]);
- }
- printf("\n");
- }
- printf("-------扫雷--------\n");
-
- }
- //布置雷
- void SetMine(char board[ROWS][COLS], int row, int col)
- {
- //雷的个数
- int count = EASY_COUNT;
- while (count)
- {
- //生成随机下标
- int x = rand() % row + 1;
- int y = rand() % col + 1;
- //布雷
- if (board[x][y] == '0')
- {
- board[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 + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
- mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
- }
-
- //排查雷
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
- {
- int x = 0;
- int y = 0;
- int win = 0;
- while (win< row*col-EASY_COUNT)
- {
- printf("请输入坐标>:\n");
- scanf("%d %d", &x, &y);
- if (show[x][y] != '*')
- {
- printf("这个位置已经被排查过了,请重新输入坐标\n");
- continue;
- }
- if (x >= 1 && x <= row && y >= 1 && y <= col)
- {
- if (mine[x][y] == '1')
- {
- printf("很遗憾,你被炸死了\n");
- DisplayBoard(mine, ROW, COL);
- break;
- }
- else
- {
- int n = get_mine_count(mine, x, y);
- show[x][y] = n + '0';
- DisplayBoard(show, ROW, COL);
- win++;
- }
- }
- else
- {
- printf("坐标不合法,请重新输入\n");
- }
- }
- if (win == row * col - EASY_COUNT)
- {
- printf("恭喜你,你赢了\n");
- DisplayBoard(mine, ROW, COL);
- }
- }
-
- #define _CRT_SECURE_NO_WARNINGS 1
-
-
- #include<stdio.h>
- #include<time.h>
- #include<stdlib.h>
-
- #define ROW 9
- #define COL 9
-
- #define ROWS ROW+2
- #define COLS COL+2
-
- #define EASY_COUNT 10
-
- //初始化数组
- 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 board[ROWS][COLS], int row, int col);
- //排查雷
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
- void meau()
- {
- printf("*************************\n");
- printf("****** 1. 开始扫雷 ******\n");
- printf("****** 0. 退出游戏 ******\n");
- printf("*************************\n");
-
- }
- void game()
- {
- printf("开始扫雷\n");
- char mine[ROWS][COLS] = { 0 };
- char show[ROWS][COLS] = { 0 };
- //初始化数组
- 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);
-
-
- }
- void test()
- {
- srand((unsigned int)time(NULL));
- int input = 0;
- do
- {
- meau();
- printf("请输入选项>:\n");
- scanf("%d", &input);
- switch (input)
- {
- case 1:
- game();
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default:
- printf("输入错误,请重新输入\n");
- break;
- }
- } while (input);
- }
- int main()
- {
- test();
- return 0;
- }
-
-
- //初始化数组
- void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
- {
- int i = 0;
- for (i = 0; i < rows; i++)
- {
- int j = 0;
- for (j = 0; j < cols; j++)
- {
- board[i][j] = set;
- }
- }
- }
- //打印数组
- void DisplayBoard(char board[ROWS][COLS], int row, int col)
- {
- printf("-------扫雷--------\n");
- int i = 0;
- int j = 0;
- for (j = 0; j <= col; j++)
- {
- printf("%d ", j);
- }
- printf("\n");
- for (i = 1; i <= row; i++)
- {
- printf("%d ", i);
- for (j = 1; j <= col; j++)
- {
- printf("%c ", board[i][j]);
- }
- printf("\n");
- }
- printf("-------扫雷--------\n");
-
- }
- //布置雷
- void SetMine(char board[ROWS][COLS], int row, int col)
- {
- //雷的个数
- int count = EASY_COUNT;
- while (count)
- {
- //生成随机下标
- int x = rand() % row + 1;
- int y = rand() % col + 1;
- //布雷
- if (board[x][y] == '0')
- {
- board[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 + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
- mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
- }
-
- //排查雷
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
- {
- int x = 0;
- int y = 0;
- int win = 0;
- while (win < row * col - EASY_COUNT)
- {
- printf("请输入坐标>:\n");
- scanf("%d %d", &x, &y);
- if (show[x][y] != '*')
- {
- printf("这个位置已经被排查过了,请重新输入坐标\n");
- continue;
- }
- if (x >= 1 && x <= row && y >= 1 && y <= col)
- {
- if (mine[x][y] == '1')
- {
- printf("很遗憾,你被炸死了\n");
- DisplayBoard(mine, ROW, COL);
- break;
- }
- else
- {
- int n = get_mine_count(mine, x, y);
- show[x][y] = n + '0';
- DisplayBoard(show, ROW, COL);
- win++;
- }
- }
- else
- {
- printf("坐标不合法,请重新输入\n");
- }
- }
- if (win == row * col - EASY_COUNT)
- {
- printf("恭喜你,你赢了\n");
- DisplayBoard(mine, ROW, COL);
- }
- }
本节主要讲解了扫雷游戏的实现。这是一个经典的游戏。其中的很多细节都在里面讲到了。当然还有一些不足之处,我们在网页上的扫雷他是可以进行展开的,可以展开一片的。这一点我们本节咱不讨论。我们会放在后面的文章中将扫雷进行升级,同样的三子棋也在后面的文章中进行升级。
写文章不易,如果对你有帮助的话,不要忘记点赞加收藏哦!!!