1. 游戏规则
当点开一个格子的时候,如果点开一个格子不是雷,并且周围也没有雷,那么就将这一片全都展开。如果点开不是雷,但是周围有雷,里面会出现一个数字,而这个数字代表了该格子周围有多少颗雷,需要玩家有很强的逻辑推理能力,要推理出炸弹在哪里并避开它。当把所有没有炸弹的格子都点开之后,雷就被排了,玩家就赢了。反之,如果点开有炸弹的格子,玩家输。
扫雷游戏链接👉👉扫雷游戏网页版
2. 思路分析
本次编写的扫雷游戏是9x9的雷区
这里我将整个代码分为三个部分:test.c、game.c、game.h
test.c:扫雷游戏测试文件,用do…while循环构建游戏框架。设置一个用户交互菜单,根据玩家输入值,用switch语句判断是否进行游戏,以及当一轮扫雷游戏结束后是否继续进行游戏。
game.c:扫雷游戏实现的主体部分,实现设置棋盘、初始化棋盘、打印棋盘、设置雷、排雷、判断输赢等功能。
game.h:放置与扫雷游戏相关的函数声明、符号声明、包含的头文件。
3. 代码实现
3.1 test.c文件的编写
主函数内用do…while语句实现循环功能,首先利用menu()函数打印菜单,提醒玩家输入1或0以决定是否开始游戏,并用switch语句判断游戏是否进行:
若玩家输入1,则进行游戏;
若玩家输入0,则退出游戏;
若玩家输入的既非1也非0,则提示玩家重新输入。
当一轮游戏结束后,系统会再次提醒玩家输入1或0以决定是否再进行游戏。
3.1.1 构建主函数
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
3.1.2 构建menu()函数
void menu()
{
printf("**************\n");
printf("****1.play****\n");
printf("****0.exit****\n");
printf("**************\n");
}
3.2 game.c文件的编写
1.设置棋盘:
扫雷需要两个棋盘,一个是设置雷的“mine棋盘”,一个是向玩家展示的“show棋盘”,这里分别用mine[][],show[][]两个二维数组来设置。
对于mine数组,我们规定:字符 ‘0’ 代表示无雷,字符 ‘1’ 表示有雷。
对于show数组,我们规定:未翻格子前,用字符 ‘*’ 来对其进行遮盖。而格子翻转以后,如果周围有雷,则显示周围雷的个数;而如果无雷,将这一片展开。
讲到这儿,又出现了一个新的问题。二维数组设置成几行几列合适?
很多小伙伴会说,我们的棋盘是9x9,那应该数组也应该设置成9行9列。事实上,我们应该把数组设置成11行11列。那这是为什么呢?下面我将详细解答原因。
上面我们规定了,若翻转的格子不是地雷,并且周围存在雷时,则显示周围雷的个数。这一操作对雷区中间区域的格子不会产生任何影响,但对雷区边界的格子执行这一操作可能会导致数组的越界访问。
![](https://i-blog.csdnimg.cn/blog_migrate/893cbaba12957177a497a2375a044902.png)
由此我们可以得出结论,创建一个9行9列的数组并不合适!
那我们该创建几行几列的数组呢?
既然对9行9列的二维数组的边界元素进行操作时,会导致数组越界访问,那我们干脆就直接将二维数组扩大一圈,将那些会导致越界访问的范围包括在数组内,从源头上解决问题,这是一个非常巧妙的办法!
![](https://i-blog.csdnimg.cn/blog_migrate/9237f726489ca94372e71f7d13fda0cf.png)
2.初始化棋盘:
利用Initboard()函数将mine数组和show数组里的元素分别初始化为 ‘0’ 和 ‘*’ 。当我们把数组名、行和列都传给Initboard()的时候,我们会发现一个问题,如果先初始化mine数组为 ‘0’,就没办法将show数组初始化为 ‘*’;相反如果先把show数组初始化为 ‘*’,mine数组就不能初始化为 ‘0’。
怎样才能解决这个问题呢?
其实很简单,在传参的时候,我们将初始化的内容也一并传给Initboard()函数即可。
void Initboard(int board[ROWS][COLS], int rows, int cols, char set);
//ROWS、COLS值为11,ROW、COL值为9
//board[ROWS][COLS]来接收mine数组和show数组
//初始化棋盘传参为ROWS、COLS,用rows、cols分别来接收
//用set来接收初始化内容
注意:初始化棋盘是初始化整个11x11的棋盘,而下面安排雷的时候是在中间9x9棋盘上布置雷,传的参数是不一样的。
3.安排雷:
将mine数组和show数组初始化好以后,就可以开始埋雷了,埋雷是利用Setmine()函数。我们在传参的时候,数组一定传的是ROWS、COLS,而行和列一定是ROW、COL。
void Setmine(int board[ROWS][COLS], int row, int col);
//用row、col来接收ROW、COL
4.打印show数组(或者mine数组):
设置好雷以后,为了查看雷是否布置成功,可以利用Displayboard()函数打印mine数组进行查看。若雷已经成功设置,则打印show数组。Displayboard()函数传参同Setmine()函数一致。
void Displayboard(int board[ROWS][COLS], int row, col);
5.找雷并判断输赢:
完成以上功能后,最后就是找雷并判断输赢,这也是整个游戏最核心的功能。为了实现这个功能,采用Findmine()函数。Findmine()函数传参的时候需要同时传递mine数组、show数组两个数组。
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
3.2.1 构建game()函数
void game()
{
char mine[ROWS][COLS] = { 0 };//安排雷的棋盘11*11
char show[ROWS][COLS] = { 0 };//排查雷的棋盘11*11
//将mine初始化为'0',show棋盘初始化为'*'
Initboard(mine, ROWS, COLS, '0');
Initboard(show, ROWS, COLS, '*');
//安排雷
Setmine(mine, ROW, COL);//注意:埋雷的时候也是传ROW、COL
//打印mine和show
//Displayboard(mine, ROW, COL);设置雷的棋盘不用打印
Displayboard(show, ROW, COL);
//打印mine
//Displayboard(mine, ROW, COL);
//找雷
Findmine(mine, show, ROW, COL);
}
3.2.2 构建Initboard()函数
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;
}
}
}
3.2.3 构建Setmine()函数
本次游戏设置的雷的个数为10个。首先利用rand()函数生成1-9的随机值,根据生成的值得到相应的坐标。若坐标为空,则安排雷;反之,就跳过。调用rand()函数之前,需要在主函数中先调用srand()函数。
void Setmine(char board[ROWS][COLS], int row, int col)
{
int count = number;
while (count)
{
int x = rand() % row + 1;
int y = rand() % row + 1;
if (board[x][y]== '0')
{
board[x][y] = '1';
count--;
}
}
}
3.2.4 构建Display()函数
为了让棋盘看起来更加的方便,在棋盘的上边和左边分别打印了对应的行数和列数。
void Displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("-----扫雷游戏----\n");
for (j = 0; j <= col; j++)
{
printf("%d ", j);
}//打印列数0~9
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);//打印行数0~9
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-----扫雷游戏----\n");
}
效果展示:
![](https://i-blog.csdnimg.cn/blog_migrate/b6f5f1fcce7564dee11ec37f21cca9d6.png)
注意:真正的游戏过程中是不会打印mine数组的,这里我只是向大家展示效果😊。
3.2.5 构建Findmine()函数
实现以上功能后,到这里才算得上真正意义上的扫雷。
首先需要创建三个 int 类型的整形变量,x、y、win。x,y用于存放用户输入的坐标,win用于判断玩家是否获胜。
ROW * COL - number表示非雷的个数,本次扫雷游戏中number=10,则还有71坐标为非雷坐标。
每当玩家输入一个坐标,并且该坐标不是地雷时,win++。
如果win < ROW * COL -number,则表示玩家还没有排雷成功。
当win == ROW * COL -number时,表示玩家已经将所有的非雷坐标排查完毕,即排雷成功。
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 - number)
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row&&y >= 1 && y <= col)//判断玩家输入的坐标是否合法,合法的话进入
{
if (show[x][y] != '*')//判断坐标是否被排查过,若 show 数组在该坐标的元素为 ‘*’ 则表示该坐标未被排查
{
printf("该坐标已被排查,请重新输入!\n");
}
else
{
if (mine[x][y] == '1')// mine数组该坐标的元素为 ‘1’,则表示该坐标为地雷
{
printf("很遗憾,你被炸死了\n");
Displayboard(mine, ROW, COL);//让玩家死得明白
break;
}
else
{
win++;
Expand(mine, show, row, col, x, y);//展开一篇雷区函数,采用递归实现,后面着重讲解
Displayboard(show, ROW, COL);//统计该坐标周围8个格子雷的个数,并将在show数组中显示
}
}
}
else
{
printf("坐标非法,请重新输入!\n");//坐标不合法,提醒玩家重新输入
}
}
if (win == row*col - number)
{
printf("恭喜你,你赢了\n");
Displayboard(mine, ROW, COL);
}
}
3.2.6 构建Expand()函数
在扫雷游戏中,当我们点击的格子不是地雷,且周围也没有地雷时,会直接展开一片雷区,具体效果如下图。
![](https://i-blog.csdnimg.cn/blog_migrate/df85ffa73b8ca6232257e9730a14fde7.gif)
如何才能做到上图所示的效果呢?
其实很简单,只需要我们遍历玩家输入坐标周围的 8 个坐标,统计该坐标周围所存在的地雷个数。而这需要我们利用Expand()函数的递归,配合定义的get_mine_count()函数来实现。
![](https://i-blog.csdnimg.cn/blog_migrate/a8b751d61ee6cd9580f8faa7105cf481.png)
如果get_mine_count()函数的返回值为0,说明坐标周围没有雷,这时我们就可以利用递归函数Expand()进行展开。相反,如果get_mine_count()函数的返回值为非0,在show数组该坐标上显示雷的个数。
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
if (get_mine_count(mine, x, y) == 0)//get_mine_count()函数的返回值为0,满足if条件
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = -1; i < 1; i++)
{
for (j = -1; j < 1; j++)
{
if (show[x + i][y + j] == '*' && x + i >=1 && x + i <= row && y + j >=1 && y + j <= col)
//展开雷区需要满足的条件
{
Expand(mine, show, row, col, x + i, y + j);
}
}
}
}
else
{
show[x][y] = get_mine_count(mine, x, y) + '0';//get_mine_count()函数的返回值为非0,在show数组显示雷的个数
}
}
3.2.7 构建get_mine_count函数
计算周围雷的个数,这里我采用的是循环的方法,也可以直接返回。
//直接返回
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return 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] +
mine[x - 1][y + 1] - 8 * '0';
}
//循环
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
int i = 0;
int sum = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
int ret = board[x + i][y + j] - '0';
//注意:我这里其实算了9个坐标,但是由于中间坐标是'0','0'-'0'依然为0,对结果没有任何影响
sum = sum + ret;
}
}
return sum;
}
3.3 game.h文件的编写
game.h文件中放置的都是以上各种功能函数实现的声明、包含的头文件以及定义的宏。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//定义9*9棋盘(数组)
#define ROW 9
#define COL 9
//由于棋盘边界雷的个数不好统计,所以要在棋盘上加两行
#define ROWS ROW+2
#define COLS COL+2
#define number 10
//game()函数声明
void game();
//Initboard()函数声明
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);
//Displayboard()函数声明
void Displayboard(char board[ROWS][COLS], int row, int col);
//Setmine()函数声明
void Setmine(char board[ROWS][COLS], int row, int col);
//Findmine()函数声明
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//get_mine_count()函数声明
int get_mine_count(char board[ROWS][COLS], int x, int y);
//Expand()函数声明
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);
为什么要在头文件中定义行和列呢?
这是为了在以后如果想要增加游戏的难度(行列数),直接在头文件中更改就显得非常的方便,也有利于代码的维护。
4. 代码汇总
game.h
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//定义9*9棋盘(数组)
#define ROW 9
#define COL 9
//由于棋盘边界雷的个数不好统计,所以要在棋盘上加两行
#define ROWS ROW+2
#define COLS COL+2
#define number 10
void game();
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);
int get_mine_count(char board[ROWS][COLS], int x, int y);
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("**************\n");
printf("****1.play****\n");
printf("****0.exit****\n");
printf("**************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#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)
{
int i = 0;
int j = 0;
printf("-----扫雷游戏----\n");
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 = number;
while (count)
{
int x = rand() % row + 1;
int y = rand() % row + 1;
if (board[x][y]== '0')
{
board[x][y] = '1';
count--;
}
}
}
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
int i = 0;
int sum = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
int ret = board[x + i][y + j] - '0';
sum = sum + ret;
}
}
return sum;
}
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
if (get_mine_count(mine, x, y) == 0)
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = -1; i < 1; i++)
{
for (j = -1; j < 1; j++)
{
if (show[x + i][y + j] == '*' && x + i >=1 && x + i <= row && y + j >=1 && y + j <= col)
{
Expand(mine, show, row, col, x + i, y + j);
}
}
}
}
else
{
show[x][y] = get_mine_count(mine, x, y) + '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 - number)
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row&&y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该坐标已被排查,请重新输入!\n");
}
else
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
Displayboard(mine, ROW, COL);
break;
}
else
{
win++;
//没有被炸死,统计周围雷的个数
Expand(mine, show, row, col, x, y);
Displayboard(show, ROW, COL);
}
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (win == row*col - number)
{
printf("恭喜你,你赢了\n");
Displayboard(mine, ROW, COL);
}
}
void game()
{
char mine[ROWS][COLS] = { 0 };//安排雷的棋盘11*11
char show[ROWS][COLS] = { 0 };//排查雷的棋盘11*11
//将mine初始化为'0',show棋盘初始化为'*'
Initboard(mine, ROWS, COLS, '0');
Initboard(show, ROWS, COLS, '*');
//安排雷
Setmine(mine, ROW, COL);//注意:埋雷的时候也是传ROW、COL
//打印mine和show
//Displayboard(mine, ROW, COL);设置雷的棋盘不用打印
Displayboard(show, ROW, COL);
//打印mine
//Displayboard(mine, ROW, COL);
//找雷
Findmine(mine, show, ROW, COL);
}
5. 游戏展示
![](https://i-blog.csdnimg.cn/blog_migrate/afc6caa4c5d10ed1a33fc8988b6aef5d.gif)
好了,以上就是本期博客的全部内容了,希望看完本篇博客对你有所帮助😎。
若本篇博客存在错误,望指出,感谢!更多代码资料见GitHub:C-language