扫雷游戏相信各位小伙伴都玩过,但是有没有想过自己实现一个扫雷小游戏呢
![](https://img-blog.csdnimg.cn/img_convert/87f34b642549c995591c142707bff4e0.png)
游戏设计
定义并出初始化两个二维数组,分别是显示矩阵和雷区矩阵
用户手动输入一个坐标,程序判断该坐标是否是雷的坐标,如果是则游戏结束,否则判断是否胜利
3.如果即没有踩雷也没有胜利,则继续步骤2,直到踩雷或成功排雷
代码实现
由于这是一个简单版扫雷小游戏,主要的目的是为了让初学者熟悉c语言的函数以及数组的使用,所以并没有华丽的外观,我们就在visual studio或者clion运行时弹出的控制台中运行(注:clion想要在控制台运行需要修改运行配置,具体步骤看截图)
![](https://img-blog.csdnimg.cn/img_convert/90cfec00b1f854cdad4b3d1d47c8e7b5.png)
我们首先要做一个欢迎页面,让用户选择开始游戏还是退出游戏
int welcome() {
printf("****************************************\n");
printf("********** 1、开始游戏 *********\n");
printf("********** 2、退出游戏 *********\n");
printf("****************************************\n");
printf("请输入你的选择:");
int input;
scanf("%d", &input);
return input;
}
我们定义了返回值为int的函数,返回用户的选择,后期可以使用switch······case语句来根据用户的选择执行不同的程序
我们在main函数中实现选择分支语句
srand(time(NULL))是为了给后期生成随机数的,也就是生成雷的坐标,它们需要的头文件是sidlib.h和time.h。根据选择的不同会执行不同的代码,根据函数名称我们可以知道game函数就是我们的扫雷游戏函数,当然为了实现我们的程序模块化,这个函数内还嵌套调用了其他函数
int main() {
srand(time(NULL));
while (1) {
int input = welcome();
switch (input) {
case 1:
game();
break;
case 2:
return 0;//退出程序,这里不能是break因为这里的break只能跳出switch
default:
printf("输入错误,请输入正确选项!\n");
break;
}
}
return 0;
}
扫雷游戏可以定义两个二维数组,也就是矩阵,一个是show矩阵,用来向用户展示所选择的坐标周围一圈的总雷数,另一个是mine矩阵,用来存储哪些坐标有雷。
先来看看game()的完整面貌吧(这里面的ROW,ROWS,COL,COLS均为宏定义)
为了方便我们先看看这个程序都有哪些宏定义以及头文件
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MINE_COUNT 5 //雷的数量
#define ROW 3 //用户看到的矩阵有用部分为3行
#define COL 3 //用户看到的矩阵有用部分为3列
#define ROWS ROW+1 //显示行号需要多出一行
#define COLS COL+1 //显示列号需要多出一列
就像这张图片里的这样
![](https://img-blog.csdnimg.cn/img_convert/0c177649b21deeb76f6f8ff245a1a227.png)
因为我们要不停的刷新显示矩阵,所以在game()中用了while(1)来让循环一直进行,并且每次重新循环时都会使用system("cls");来清空控制台中之前的显示
void game() {
//定义两个矩阵
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化显示矩阵
init_show(show, ROWS, COLS);
int flag = 0;
while (1) {
system("cls");//用来清屏的,让画面看起来不繁杂
//刷新矩阵
display(show, ROWS, COLS);
//玩家排雷
int x, y;
mine_clear(&x, &y);//将x和y变量的空间地址传递进去,函数内将坐标放入到对应的空间中
//获得到排雷坐标,顺便如果是第一次排雷则初始化雷区
if (flag == 0) {
init_mine(mine, ROWS, COLS, x, y);
flag = 1; //这里设置为1 为了防止后面重新初始化雷区
}
//判断是否踩雷
int ret = judge_fail(mine, x, y);
if (ret == 0) {
printf("踩雷了~!\n");
return;
}
//判断是否胜利
ret = judge_win(show, ROWS, COLS);
if (ret == 1) {
printf("你真棒!\n");
return;
}
//统计排雷位置附近的雷数量
int count = count_mine(mine, ROWS, COLS, x, y);
//将雷数量设置到显示矩阵的对应位置
set_show(show, x, y, count);
//刷新显示
}
return;
}
我们可以看到这里面还有很多函数,这些函数的功能都用注释写在了傍边,我们就按照程序的执行顺序对里面的函数一一实现
首先是init_show(show, ROWS, COLS);
利用双重for循环初始化show矩阵让里面的每一个元素都为空格字符。
void init_show(char matrix[][COLS], int row, int col) {
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
matrix[i][j] = ' ';
}
}
return;
}
接下来就是display(show, ROWS, COLS);这个函数的功能是打印出show矩阵,运行的结果如图
![](https://img-blog.csdnimg.cn/img_convert/c750ce125b0ac1a1bc3d5bcfdcbb2906.png)
void display(char matrix[][COLS], int row, int col) {
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
if (i == 0) {//第0行中列号的表示
printf(" %d |", j);
continue;
}
if (j == 0) {//第0列中行号的表示
printf(" %d |", i);
}
else { //不是第0行也不是第0列的显示
printf(" %c |", matrix[i][j]);
}
}
printf("\n---|---|---|---|\n");
}
return;
}
接下来就是输入坐标函数了mine_clear(&x, &y);
我们首先定义了x,y两个变量,由于函数每次只能有一个返回值,所以我们用指针的方式对两个值进行初始化,我们传入x,y的地址,并且用指针去接收它们(注意:根据scanf()的规则,我们输入时要严格遵循scanf双引号内的格式)
void mine_clear(int* x, int* y) {
printf("请输入排雷位置:<x,y>:");
scanf("%d,%d", x, y);
}
接下来就是初始化雷区的函数了,init_mine(mine, ROWS, COLS, x, y);因为每次只需要初始化一次,所以我们在game()中定义了一个flag,防止雷区矩阵被重复初始化,这样游戏就没法玩了😵。
int mx = (rand() % ROW) + 1;和int my = (rand() % COL) + 1;这两句是为了产生随机坐标,rand()产生的随机数对行和列取余可以得到0到该数-1之间的任意整数,因为宏定义中ROW和COL都是3,所以取余之后的结果为0,1,2之中的任意一个,但是二维数组的下标都是从0开始的,我们在第0行和第0列分别显示列号和行号,所以雷区矩阵的真正有用部分在矩阵的第1列(行)至第三列(行),所以要在后面+1
雷区矩阵用0表示没有雷,用1表示有雷。
布雷时有两个要注意的点,1.用户第一次输入的位置不能有雷
2.已经放过雷的位置不能有雷
void init_mine(char matrix[][COLS], int row, int col, int x, int y) {
//在雷区矩阵中,以1表示雷,0表示空白
//注意:x,y位置不能有雷
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
matrix[i][j] = 0;
}
}
int count = MINE_COUNT;
while (count > 0) {
int mx = (rand() % ROW) + 1;
int my = (rand() % COL) + 1; //产生一个随机坐标往里面放雷
//x,y位置不能布雷,已经布雷的位置不能布雷
if (mx == x && my == y) {
continue;//与x,y位置重叠,但是排雷位置不能放雷
}
if (matrix[mx][my] == 1) {
continue; //如果mx,my位置已经有雷了,这个位置也不用放雷
}
matrix[mx][my] = 1;
--count;//每布雷成功一次,则剩余布雷数量减一
}
}
雷区矩阵初始化完成就该判断是否胜利或失败了,判断失败很简单,只需要判断是否踩雷,如果用户输入的坐标和雷区矩阵中存储1 的坐标一样则失败
判断失败的函数声明为int judge_fail(mine, x, y);返回一个数字,返回0表示踩雷了,返回1表示没有踩雷。
int judge_fail(char matrix[][COLS], int x, int y) {
//在矩阵的x,y位置判断有没有雷
if (matrix[x][y] == 1) {
return 0;//返回0表示踩雷了
}
return 1;//返回1,表示没有踩雷
}
判断成功的函数与判断失败的函数类似,声明为int judge_win(show, ROWS, COLS);
判断是否赢的条件就是还没有被翻开的位置个数-1是否与雷的数量相等,所以需要一个变量count来存储显示矩阵中还没被翻开的位置数,判断条件也很简单,没有被翻开则显示矩阵中该位置的字符为空格,
如果胜利了则提醒玩家胜利了并退出程序,没有胜利也没有踩雷的话则继续排雷,直到胜利或踩雷为止
int judge_win(char matrix[][COLS], int row, int col) {
int count = 0;
for (int i = 1; i < row; ++i) { //第0行第0列不计算在内
for (int j = 1; j < col; ++j) {
if (matrix[i][j] == ' ') {
++count;
}
}
}
if ((count - 1) == MINE_COUNT) { //没有被翻开的位置数量等于雷的数量,则胜利,返回1
return 1;
}
return 0;
}
如果翻开的位置没有雷的话则需要向玩家反馈该位置周围一圈的雷的数量,就像图片中展示的这样
![](https://img-blog.csdnimg.cn/img_convert/a8e38699fa87e72a565827e89ef6739d.png)
我先输入了2,2表示了我选择的排雷位置,然后显示矩阵中在该位置上显示了周围一圈雷的总数是5
函数的实现如下,需要注意的就是如果这个位置在边上或者角落处则它四周的某些位置是不计入在内的,所以我们使用条件语句让雷区矩阵中有效位置被记入在内,函数的返回值就是该位置周围有效位置的雷的总数
int count_mine(char matrix[][COLS], int row, int col, int x, int y) {
int count = 0;
//将x,y位置一圈边的数据加起来就是雷数,要注意这是因为上边初始化雷区的时候用数字0、1表示是否是雷
//但是在具体统计的时候要注意,x-1,x+1,y-1,y+1是不能超出矩阵范围的
if (x - 1 >= 1 && y - 1 >= 1) {
count += matrix[x - 1][y - 1];
}
if (x - 1 >= 1) {
count += matrix[x - 1][y];
}
if (x - 1 >= 1 && y + 1 < col) {
count += matrix[x - 1][y + 1];
}
if (y - 1 >= 1) {
count += matrix[x][y - 1];
}
count += matrix[x][y];
if (y + 1 < col) {
count += matrix[x][y + 1];
}
if (x + 1 < row && y - 1 >= 1) {
count += matrix[x + 1][y - 1];
}
if (x + 1 < row) {
count += matrix[x + 1][y];
}
if (x + 1 < row && y + 1 < col) {
count += matrix[x + 1][y + 1];
}
return count;
}
当这些函数都完成之后只剩下最后一个函数了,就是刷新显示矩阵,因为刚开始的显示矩阵初始化时,除了显示行和列的位置,其他位置都是空格,而我们每次输入坐标后需要向玩家反馈坐标周围的雷总数,所以我们需要将刚刚完成的统计四周雷数的函数的返回值填入该坐标内
函数实现如下,因为我们的雷区矩阵和显示矩阵都是字符类型,而count是int类型,所以需要在后面加上'0'。
void set_show(char matrix[][COLS], int x, int y, int count) {
matrix[x][y] = count + '0';//因为count是个数字,显示矩阵是char类型,
//因此要将对应位置设置为对应的数字字符
}
现在我们的简单版实现扫雷小游戏的所有函数都已经完成了,
完整代码显示
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MINE_COUNT 5
#define ROW 3
#define COL 3
#define ROWS ROW+1
#define COLS COL+1
int welcome() {
printf("*************************************\n");
printf("******** 1、开始游戏 *******\n");
printf("******** 2、退出游戏 *******\n");
printf("*************************************\n");
printf("请输入你的选择:");
int input;
scanf("%d", &input);
return input;
}
void init_show(char matrix[][COLS], int row, int col) {
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
matrix[i][j] = ' ';
}
}
return;
}
void display(char matrix[][COLS], int row, int col) {
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
if (i == 0) {//第0行中列号的表示
printf(" %d |", j);
continue;
}
if (j == 0) {//第0列中行号的表示
printf(" %d |", i);
}
else { //不是第0行也不是第0列的显示
printf(" %c |", matrix[i][j]);
}
}
printf("\n---|---|---|---|\n");
}
return;
}
void mine_clear(int* x, int* y) {
printf("请输入排雷位置:<x,y>:");
scanf("%d,%d", x, y);
}
void init_mine(char matrix[][COLS], int row, int col, int x, int y) {
//在雷区矩阵中,以1表示雷,0表示空白
//注意:x,y位置不能有雷
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
matrix[i][j] = 0;
}
}
int count = MINE_COUNT;
while (count > 0) {
int mx = (rand() % ROW) + 1;
int my = (rand() % COL) + 1; //产生一个随机坐标往里面放雷
//x,y位置不能布雷,已经布雷的位置不能布雷
if (mx == x && my == y) {
continue;//与x,y位置重叠,但是排雷位置不能放雷
}
if (matrix[mx][my] == 1) {
continue; //如果mx,my位置已经有雷了,这个位置也不用放雷
}
matrix[mx][my] = 1;
--count;//每布雷成功一次,则剩余布雷数量减一
}
}
int judge_fail(char matrix[][COLS], int x, int y) {
//在矩阵的x,y位置判断有没有雷
if (matrix[x][y] == 1) {
return 0;//返回0表示踩雷了
}
return 1;//返回1,表示没有踩雷
}
int judge_win(char matrix[][COLS], int row, int col) {
int count = 0;
for (int i = 1; i < row; ++i) { //第0行第0列不计算在内
for (int j = 1; j < col; ++j) {
if (matrix[i][j] == ' ') {
++count;
}
}
}
if ((count - 1) == MINE_COUNT) { //没有被翻开的位置数量等于雷的数量,则胜利,返回1
return 1;
}
return 0;
}
int count_mine(char matrix[][COLS], int row, int col, int x, int y) {
int count = 0;
//将x,y位置一圈边的数据加起来就是雷数,要注意这是因为上边初始化雷区的时候用数字0、1表示是否是雷
//但是在具体统计的时候要注意,x-1,x+1,y-1,y+1是不能超出矩阵范围的
if (x - 1 >= 1 && y - 1 >= 1) {
count += matrix[x - 1][y - 1];
}
if (x - 1 >= 1) {
count += matrix[x - 1][y];
}
if (x - 1 >= 1 && y + 1 < col) {
count += matrix[x - 1][y + 1];
}
if (y - 1 >= 1) {
count += matrix[x][y - 1];
}
count += matrix[x][y];
if (y + 1 < col) {
count += matrix[x][y + 1];
}
if (x + 1 < row && y - 1 >= 1) {
count += matrix[x + 1][y - 1];
}
if (x + 1 < row) {
count += matrix[x + 1][y];
}
if (x + 1 < row && y + 1 < col) {
count += matrix[x + 1][y + 1];
}
return count;
}
void set_show(char matrix[][COLS], int x, int y, int count) {
matrix[x][y] = count + '0';//因为count是个数字,显示矩阵是char类型,
//因此要将对应位置设置为对应的数字字符
}
void game() {
//定义两个矩阵
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化显示矩阵
init_show(show, ROWS, COLS);
int flag = 0;
while (1) {
system("cls");
//刷新矩阵
display(show, ROWS, COLS);
//玩家排雷
int x, y;
mine_clear(&x, &y);//将x和y变量的空间地址传递进去,函数内将坐标放入到对应的空间中
//获得到排雷坐标,顺便如果是第一次排雷则初始化雷区
if (flag == 0) {
init_mine(mine, ROWS, COLS, x, y);
flag = 1; //这里设置为1 为了防止后面重新初始化雷区
}
//判断是否踩雷
int ret = judge_fail(mine, x, y);
if (ret == 0) {
printf("踩雷了~!\n");
return;
}
//判断是否胜利
ret = judge_win(show, ROWS, COLS);
if (ret == 1) {
printf("你真棒!\n");
return;
}
//统计排雷位置附近的雷数量
int count = count_mine(mine, ROWS, COLS, x, y);
//将雷数量设置到显示矩阵的对应位置
set_show(show, x, y, count);
//刷新显示
}
return;
}
int main() {
srand(time(NULL));
while (1) {
int input = welcome();
switch (input) {
case 1:
game();
break;
case 2:
return 0;//退出程序,这里不能是break因为这里的break只能跳出switch
default:
printf("输入错误,请输入正确选项!\n");
break;
}
}
return 0;
}