目录
问题描述
游戏说明
思路分析
数据结构
mine数组
show数组
文件结构
分步实现过程
main函数的实现框架
game函数——游戏过程函数
数组的创建和初始化
initarr函数——数组的初始化
printarr函数——数组的打印
关于参数的说明——
setmine()函数——随机布置10个雷
findmine()函数——扫雷过程实现的函数
getmine()函数——判断位置周围3*3范围内雷的数量
findmine()函数代码的完善
完整代码
效果展示
通过两个数组来实现,
一个数组用来存储棋盘本身的数据,这个数组暂且称为mine
一个用来对外展示,这个数组暂且称为show
我们需要在9*9的棋盘上布置雷的信息和排查雷,所以需要创建⼀个9*9的数组来存放 信息。如果这个位置布置雷,我们就存放1,没有布置雷就存放0.
但是这个时候也存在一个问题,当我们要排查的坐标处于数组的边缘时,计算周围雷的数量就可能产生越界和出错,为了防止这种行为发生,我们创建一个11*11的数组并初始化,但是实际使用中依然使用9*9的范围
在游戏排雷的过程中对外展示,初始界面暂且全部设置为'*',每次排查一个坐标时,就将该位置改为周围一圈雷的数量
至于为什么要创建两个数组来分别存储数据和对外展示,
假设我们排查了某 ⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录 存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布 置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难,这个时候就产生了歧义——到底1表示该位置是雷呢,还是说该位置的周围有一个雷的?
所以为了防止这种情况发生,我们将两组数据分开来单独存放
通过三个文件来实现(为了方便后期的修改和调试)
这里
我们使用game.h来存放函数的声明
使用game.c来实现函数完整的定义
使用test.c包含main函数和函数的调用
游戏的框架——主函数的实现
进入程序首先要出现一个菜单以及对游戏的说明,这里我们用一个函数封装来实现,暂且称为
游戏菜单说明开始游戏和结束游戏的方式,进入程序的第一步就要展现出来,这里我们把它放在game.c文件,并在game.h文件声明,test.c的main函数中调用,(以下皆同,不再重复)
使用printf函数不要忘记包含stdio.h头文件
为了代码更加简便,我们将所有头文件都放在game.h头文件中,再在主程序test.c中包含game.h头文件
包含自己创建的头文件的方式是 #include"game.h" 放在双引号内部
#include"game.h"
void menu()
{
printf("*********扫雷游戏***********\n");
printf("*****输入数字1 开始游戏*****\n");
printf("*****输入数字0 结束游戏*****\n");
printf("*********游戏说明***********\n");
printf("*开始游戏后,输入两位数坐标*\n");
printf("*并按回车确认您要排雷的位置*\n");
}
接下里是
刚刚我们完成了菜单的打印,接下来就要创建一个整型变量来存储用户输入的值,并对此做出反应,这里用一个switch语句来实现——分别在输入1的时候开始游戏,输入0的时候结束游戏,在输入其他值的时候做出 出错提醒
#include"game.h"
int main()
{
menu();
int a = 0;
scanf("%d", &a);
switch (a)
{
case 1:
//游戏过程
break;
case 0:
printf("结束游戏\n");
break;
default:
printf("输入的值错误,请重新输入\n");
}
return 0;
}
关于switch语句这篇文章有更详细的介绍C语言结构语句介绍-CSDN博客
但是如果要想要多次游戏的话,就需要将switch分支语句和menu函数放在一个循环里实现,但是第一次进入程序的时候,我们必须保证能至少进行一次判断,这里使用do while循环就比较合适
而且将输入的值作为循环是否执行的条件恰到好处
#include"game.h"
int main()
{
int a = 0;
do
{
menu();
scanf("%d", &a);
switch (a)
{
case 1:
//游戏过程
break;
case 0:
printf("结束游戏\n");
break;
default:
printf("输入的值错误,请重新输入\n");
}
} while (a);
return 0;
}
来看一下效果
现在,游戏的整体框架在main函数的部分就已经完成了,接下来我们将游戏过程封装在一个函数game()函数中来实现,来逐步完成细节
首先,进入游戏内部,我们需要先完成数据结构的构建,即上面我们提到了两个数组
分别是对内存储棋盘数据的mine数组和对外展示的show数组
出于对安全的考虑,我们创建11*11的数组,实际使用的范围是9*9的数组
并且,为了方便以后修改代码不需要修改太多的地方,我们在创建数组的时候不直接使用常量,而是在game.h中使用define定义常量,这样以后修改棋盘大小的时候只需要改动此处即可
#define ROW 9 //实际使用的变量大小
#define COL 9
#define ROWS 11 //创建数组的变量大小
#define COLS 11
接下来 ,创建数组
需要注意的是,因为我们初始的时候想要用'*'来对外展示棋盘,所以数组类型是char类型,为了分别操作,最好使两个数组的类型相同
void game() //游戏过程实现函数
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
}
并且字符数组没办法使用一个值进行初始化,所以我们还需要一个函数对数组进行初始化
要实现数组的初始化,最简单的办法就是使用两个for循环嵌套,依次对每一行每一列进行赋值
下面我们来实现这个函数
void initarr(char arr[ROWS][COLS],int rows, int cols, char set)//棋盘初始化函数
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
关于参数的说明——
记得别忘记在头文件中声明这个函数
在game函数中的调用
void game() //游戏过程实现函数
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
initarr(mine, ROWS, COLS, '0');
initarr(show, ROWS, COLS, '*');
}
实现了数组的初始化函数之后,接下来就是数组的打印函数了
这个函数的时候和上面初始化函数的实现类似,同样也是用一个函数来实现两个数组的打印
代码如下
void printarr(char arr[ROWS][COLS], int row, int col)//棋盘打印函数
{
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
这里第一个参数数组参数和上面初始化函数的功能和规则都是相同的,第二个和第三个参数与初始化函数中使用的参数是不同的,这是因为初始化的时候,为了方便,我们是直接将申请的所有数组空间都初始化了的。而在打印的时候,则完全不同,因为我们想对外展示的只是9*9范围的数组,所以打印的时候也是9*9范围的数组,这一点是需要注意的 这样在for循环中控制变量的循环范围就是1-9这个中间范围
(别忘记在打印完每一行的时候换行哦)
完成了初始化函数和打印函数之后,就可以先把打印函数放在game函数里测试一下看看效果了
这里呢,可以看到已经达到我们想要的效果了,但是多考虑一点的话就会发现,游戏实现之后我们需要输入棋盘的坐标来排雷,如果这样每个都要数的话岂不是太麻烦了,不妨打印的时候我们在每一行每一列都加上对应的行号和列号
代码修改如下
void printarr(char arr[ROWS][COLS], int row, int col)//棋盘打印函数
{
for (int i = 0; i <= row; 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 ", arr[i][j]);
}
printf("\n");
}
}
修改的思路——
效果如下——
这一步完成之后呢,接下来回想一下主题,下一步来完成布置10个雷,并且是随机的10个雷,这里呢我们将雷表示为字符'1',非雷表示为字符'0',这个设置保存在存放后台数据的数组mine中
关于随机数生成的实现在这篇文章有详细的介绍,这里介于篇幅所限,就不再展开长篇大论了
猜数字游戏C语言代码实现-CSDN博客(文章中关于rand函数 srand函数 和time函数都有详细介绍)
先上代码再解释每一步细节
void setmine(char arr[ROWS][COLS], int row, int col)//布置雷
{
int count = SET_COUNT;
while (count)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (arr[x][y] =='0')
{
arr[x][y] = '1';
count--;
}
}
}
代码解释说明——
完成setmine函数的定义之后,在头文件加上函数声明
#include
#include
#include
#define ROW 9 //实际使用的变量大小
#define COL 9
#define ROWS 11 //创建数组的变量大小
#define COLS 11
#define SET_COUNT 10
void menu();//游戏菜单
void game();//游戏控制函数
void initarr(char arr[ROWS][COLS], int rows, int cols, char set);//初始化函数
void printarr(char arr[ROWS][COLS], int row, int col);//棋盘打印函数
void setmine(char arr[ROWS][COLS], int row, int col);//随机布置雷
然后在game函数中加上setmine函数的调用
这里我们先在setmine函数后面加上一条打印mine数组的数据
void game() //游戏过程实现函数
{
char mine[ROWS][COLS]; //存放棋盘雷的数据
char show[ROWS][COLS]; //对外展示的扫雷界面
initarr(mine, ROWS, COLS, '0');//初始化mine数组
initarr(show, ROWS, COLS, '*');//初始化show数组
printarr(mine, ROW, COL); //打印mine数组
//printarr(show, ROW, COL); //打印show数组
setmine(mine, ROW, COL); //布置雷
printarr(mine, ROW, COL); //打印mine数组
}
来测试几次看看
可以看到每次布置雷的结果都是不同的,这样我们想要达到的随机效果就实现了
接下来,最后一步,也是最关键的一步,就是实现扫雷过程中输入下标排查雷并获得反馈的过程
回到最初,在这个阶段我们想要实现的效果是
现在,我们来一步一步完善findmine函数的框架
第一,这个函数的参数——这次需要把两个数组都作为参数传进去,因为这里需要对mine内的数据进行判断是不是雷,并在show数组改变数据进行反馈打印出来,然后我们再需要两个参数,分别是我们需要操作的9*9的数组的行数和列数
第二,屏幕打印一句提示,提醒输入两个坐标,并读取两个输入的值
第三,得到输入的两个值之后作为数组下标去进行判断——首先再最外层判断输入的值是不是合法的值,有没有越界,如果不是合法的值,提醒重新输入;否则,进行下一步判断。
第四,如果输入的是合法的值——判断该位置是否是雷,如果是雷的话,屏幕打印游戏失败(此时最好能把答案打印出来了结玩家的疑惑),并退出;如果该位置不是雷,根据游戏的要求,需要打印出来该位置周围3*3范围内存在的雷数量
第五,判断位置周围存在的雷数量——为了防止该函数内部变得过于繁琐,最好是把判断和计算数量的部分单独拿出来作为一个函数实现。
到这一步的代码实现如下
void findmine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
printf("请输入要排查的位置,按回车键确认\n");
scanf("%d %d", &x, &y);
if ((x >= 1) && (x <= ROW) && (y >= 1) && (y <= COL))
{
if (arr1[x][y] == '1')
{
printf("很遗憾,你被炸死,游戏失败\n");
printf("正确位置如下\n");
printarr(arr1, ROW, COL);
break;
}
else
{
//计算周围雷的数量,并修改show数组输出
}
}
else
{
printf("输入的值不正确,请重新输入\n");
}
}
为了实现该函数,最简单的办法就是用一个for循环来实现——因为该位置是一个3*3的范围,行号是从x-1到x+1,列号是从y-1到y+1,只要创建一个变量来记录,每次判断该位置是不是雷,如果是雷的话,该值+1,最终就可以得到雷的数量
该代码实现如下
int getmine(char arr[ROWS][COLS], int x, int y)//计算周围雷的数量
{
int count = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (arr[i][j] == '1')
{
count++;
}
}
}
return count;
}
现在,我们再回过头来补充findmine函数内的代码
上面我们用getmine函数得到了排查位置周围雷的数量,但是,不要忘记了,该值是一个整型值,而我们mine数组和show数组都是char类型的数组。
初始的时候,我们使用的是字符'0',所以在此处得到数量的一个整型值之后,也应该变为一个字符类型表示。比如getmine函数计算后得到一个数字3,如何才能转换为字符'3'呢
参照ASCII表中,字符0的ASCII值是48,而想要获得后面的数字的ASCII值,比如3,只要在字符0的基础上+3就可以了
再将得到的这个值赋值给show数组对应位置下标的值,并打印在屏幕上
findmine函数完善如下
void findmine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
printf("请输入要排查的位置,按回车键确认\n");
scanf("%d %d", &x, &y);
if ((x >= 1) && (x <= ROW) && (y >= 1) && (y <= COL))
{
if (arr1[x][y] == '1')
{
printf("很遗憾,你被炸死,游戏失败\n");
printf("正确位置如下\n");
printarr(arr1, ROW, COL);
break;
}
else
{
int count = getmine(arr1, x, y);
arr2[x][y] = count + '0';
printarr(arr2, ROW, COL);
}
}
else
{
printf("输入的值不正确,请重新输入\n");
}
}
然后在game()内加上findmine函数的调用
但是还有一个问题,上面的代码实现只是一次,但在实际过程中,不可能一次就能猜中结果,所以排雷的过程中应该放在一个循环中来实现
那循环结束的条件呢,到此为止,还有一个非常重要的事情没有完成,那就是排雷的结束条件
仔细想想,在9*9的棋盘中,布置10个雷,那么剩余的71个数量就是安全的,如此,只要把所有安全的位置都排查出来,那就算游戏胜利
这里,我们再用一个变量win来记录并作为循环结束的条件
并且当循环结束退出的时候,再使用一个if语句判断循环是否是因为游戏胜利而结束的
findmine函数完整代码如下
void findmine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)
{
int win = row * col - SET_COUNT;
while (win)
{
int x = 0;
int y = 0;
printf("请输入要排查的位置,按回车键确认\n");
scanf("%d %d", &x, &y);
if ((x >= 1) && (x <= ROW) && (y >= 1) && (y <= COL))
{
if (arr1[x][y] == '1')
{
printf("很遗憾,你被炸死,游戏失败\n");
printf("正确位置如下\n");
printarr(arr1, ROW, COL);
break;
}
else
{
int count = getmine(arr1, x, y);
arr2[x][y] = count + '0';
printarr(arr2, ROW, COL);
win--;
}
}
else
{
printf("输入的值不正确,请重新输入\n");
}
}
if (win == 0)
{
printf("恭喜您,排雷成功,游戏胜利\n");
printarr(arr1, ROW, COL);
}
}
至此,整个游戏的代码已经全部完成,快去运行来尝试一下吧
#include
#include
#include
#define ROW 9 //实际使用的变量大小
#define COL 9
#define ROWS 11 //创建数组的变量大小
#define COLS 11
#define SET_COUNT 10
void menu();//游戏菜单
void game();//游戏控制函数
void initarr(char arr[ROWS][COLS], int rows, int cols, char set);//初始化函数
void printarr(char arr[ROWS][COLS], int row, int col);//棋盘打印函数
void setmine(char arr[ROWS][COLS], int row, int col);//随机布置雷
void findmine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col);//排雷函数
int getmine(char arr[ROWS][COLS], int x, int y);//计算周围的雷的数量
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("*********扫雷游戏***********\n");
printf("*****输入数字1 开始游戏*****\n");
printf("*****输入数字0 结束游戏*****\n");
printf("*********游戏说明***********\n");
printf("*开始游戏后,输入两位数坐标*\n");
printf("*并按回车确认您要排雷的位置*\n");
}
void game() //游戏过程实现函数
{
char mine[ROWS][COLS]; //存放棋盘雷的数据
char show[ROWS][COLS]; //对外展示的扫雷界面
initarr(mine, ROWS, COLS, '0');//初始化mine数组
initarr(show, ROWS, COLS, '*');//初始化show数组
//printarr(mine, ROW, COL); //打印mine数组
printarr(show, ROW, COL); //打印show数组
setmine(mine, ROW, COL); //布置雷
findmine(mine, show, ROW, COL);//排查雷
}
void initarr(char arr[ROWS][COLS],int rows, int cols, char set)//棋盘初始化函数
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
void printarr(char arr[ROWS][COLS], int row, int col)//棋盘打印函数
{
printf("-————扫雷游戏————-\n");
for (int i = 0; i <= row; 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 ", arr[i][j]);
}
printf("\n");
}
}
void setmine(char arr[ROWS][COLS], int row, int col)//布置雷
{
int count = SET_COUNT;
while (count)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
void findmine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)
{
int win = row * col - SET_COUNT;
while (win)
{
int x = 0;
int y = 0;
printf("请输入要排查的位置,按回车键确认\n");
scanf("%d %d", &x, &y);
if ((x >= 1) && (x <= ROW) && (y >= 1) && (y <= COL))
{
if (arr1[x][y] == '1')
{
printf("很遗憾,你被炸死,游戏失败\n");
printf("正确位置如下\n");
printarr(arr1, ROW, COL);
break;
}
else
{
int count = getmine(arr1, x, y);
arr2[x][y] = count + '0';
printarr(arr2, ROW, COL);
win--;
}
}
else
{
printf("输入的值不正确,请重新输入\n");
}
}
if (win == 0)
{
printf("恭喜您,排雷成功,游戏胜利\n");
printarr(arr1, ROW, COL);
}
}
int getmine(char arr[ROWS][COLS], int x, int y)//计算周围雷的数量
{
int count = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (arr[i][j] == '1')
{
count++;
}
}
}
return count;
}
#include"game.h"
int main()
{
srand((unsigned int)time(NULL));
int a = 0;
do
{
menu();
scanf("%d", &a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("结束游戏\n");
break;
default:
printf("输入的值错误,请重新输入\n");
break;
}
} while (a);
return 0;
}