Justin向大家问好,那么今天所讲的是扫雷游戏简单版本。扫雷大家都玩过吧 , 如果是简单版本,就是对一片 9*9 的区域进行排雷,那么如果雷为10个,就需要排查71个,只要排查71个完,并且没有踩到雷,就为成功。
1.扫雷的初始化,需要创建二维数组
2.初始化棋盘
3.打印棋盘
4.布置雷
5.对雷进行排查
1.首先在test.c里面写,咱们把菜单打印出来 ,这里使用了函数的创建和调用以及do while循环和switch分支结构,使用函数方便了我们的操作,而使用do while循环是因为这是一个循环过程,我们如果选择1就进游戏,选择0就退出游戏,如果不是1也不是0就要重新的提供选择,而且我们一开始就要打印菜单,那么do while循环是好的选择。
2.switch分支结构提供了选项的选择里面的选项内容为case 1以及case 0,不是case 1和case 0就走default选项。
代码如下:
#include
void menu() // 不需要返回所以是void , 菜单的内容 1 为 玩游戏 , 0 为 退出游戏
{
printf("*********************\n");
printf("**** 1.PLAY GAME ****\n");
printf("**** 0.EXIT GAME ****\n");
printf("*********************\n");
}
void game()
{
}
void test() // 调用函数,我们返回值是void ,什么也不返回
{
int input = 0; //初始化
// 因为是一个循环过程所以要用do while
do
{
menu(); // 创建菜单的函数
printf("请选择是否开启Justin扫雷游戏:> "); // 提供选择
scanf_s("%d", &input); // 输入选择
switch (input) // 选择的内容
{
case 1:
game(); // game 里面写游戏的内容
break;
case 0:
printf("你退出游戏 ,Justin 觉得很可惜\n"); // 退出游戏
break;
default:
printf("警告,你输入错误,请重新选择\n"); // 不是选择1也不是0,那就是错误的,所以提示重新选择
break;
}
} while (input);
}
int main()
{
test(); // 创建函数
return 0;
}
运行情况如下:
1.因为我们还没有写game里面的游戏内容,所以选择1是没有游戏内容的,那么我们输入了2和3,也就是没有选择1和0,那么就会走default选项,输出了警告,最后我们输入了0,游戏退出,程序结束,因为这是一个循环的过程,所以使用do while是特别好的。
1.发现问题
1. 首先我们先来定义一下 ,0 不是雷 ,1是雷,因为扫雷游戏嘛。那么我们先来理解一下扫雷游戏的布局以及运行,我们今天要做的是9*9布局的棋盘,如果我们直接就设置9*9的布局。那么它会存在一种问题,因为大家都知道我们在扫雷的时候,选择一个坐标后,如果它不是雷,那么它就会统计,自身周围有多少个雷。以下面的图举例子,比如我输入坐标(2,5)那么它不是雷,它就会统计自身周围有多少个雷,那么(2,5)坐标周围有1个雷,所以最后统计为1个雷,并且会显示出来。
2.那么如果,我选择的是(8,6)坐标呢,那是不是下面的部分就统计不了了,就越界了。
2.解决问题
1.那么我们已经知道了问题所在,就来解决它,其实很简单,只要给布局扩展为11*11,就可以了,如下图。那么可能就会有好兄弟问了,Justin Justin 你前边不是说了我们要做9*9的布局吗,为什么变成11*11了呢??哈哈 ,我的好兄弟,我们的初心是不会忘的,我们扩展了布局就不会存在越界的情况了,已经解决问题,那么我们只要对11*11里面的9*9进行操作就可以了,对不对,而且,我们最后呈现的也是9*9的布局,我的好兄弟。
3.另一个问题所在
那么我们已经解决了布局的问题了,难过的是又出现了另一个问题。
我们前边说了0不是雷,1是雷,那么我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。
解决方法:
1.雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲 突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。 这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再 给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到 mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
2.保持神秘,show数组开始时初始化为字符 '*',为了保持两个数组的类型⼀致,可以使⽤同⼀ 套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。
3.这样一个mine数组来设置,show数组来展现
如下图:
咱们前边已经创立好了2个源文件,一个头文件
2.我们要在头文件game.h去定义ROWS,COLS
#include
#define ROW 9 //9*9的布局,所以ROW和COL为9
#define COL 9
#define ROWS ROW + 2 //11*11的布局,所以ROWS和COLS为11
#define COLS COL + 2
3.那么我们在test.c里面写,找到void game(),在里面写上2个数组,关于为什么创建我们上面已经说过啦,我们需要引用头文件到test.c ,因为我们在game.h里面定义了。代码如下
引用头文件为:
#include"game.h"
void game()
{
//创建二维数组
char mine[ROWS][COLS] = { 0 }; //用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; //用来存放排查出来雷的个数信息
}
有些好兄弟,可能会说 Justin Justin 你test.c的#include
在test.c里面写,代码如下:
//棋盘的创建,我们要对9*9布局进行操作所以ROW和COL要传
InitBoard(mine, ROWS, COLS, '0');//mine数组全部为'0'
InitBoard(show, ROWS, COLS, '*');//show数组全部为'*'
然后对函数进行声明,在game.h里面 ,代码如下:
//这里很巧妙我用了 int set 来接受'0'和'*' 这样我就可以对这2个进行设置而不用多去建立一个数组
void InitBoard(char arr[ROWS][COLS] ,int rows, int cols, char set);
最后在game.c里面写代码,进行棋盘的创建 ,一样的要包含game.h的头文件
#include"game.h"
void InitBoard(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;
}
}
}
好了,这一小步骤就完成了。
在test.c里面写代码,因为我们只想打印出9*9,所以传参为ROW ,COL,如图
//棋盘的打印
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
然后在game.h里面声明 ,如图
//棋盘的打印
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
最后,在game.c里面实现,代码如下
//打印棋盘
void DisplayBoard(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"); // 换行,不然捏在一起
}
}
打印结果:
但是!但是!但是!
这样子是不是不美观,所以我们改进一下 ,代码如下
//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
printf("——Justin的扫雷游戏——\n"); // 这样子更加好看
for (int j = 0; j <= col; j++)//打印列的数字,这里的j是从0开始,如果是1的话,会出现不对齐
{
printf("%d ", j);
}
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"); // 换行,不然捏在一起
}
}
效果如下
OK,这一小步骤完成了,兄弟们,快了快了!
我们要在game.h 里面定义雷,并且设置数量
#define Boom 10 //定义炸弹10个
兄弟们,不知道你们了解过srand , rand , time函数吗 。如果不了解可以看我之前的文章有大致讲解实现猜数字游戏——详细版-CSDN博客
里面有讲解这些函数,那么我们在这里就简单讲解一下,因为会用到 。
1.我们要生成随机数就需要用到rand函数,但是生成的是伪随机数,什么意思呢,你可以这样理解是计算机计算生成的随机数,不是真正意义上的随机数 。
2.那么怎么样可以让它生成真正的随机数呢,就需要用到srand函数,让rand函数做为srand函数的种子,理解起来可能有点抽象,但是这样生成的随机数就是真正的随机数,但是它每次生成的随机数都是一样的。
3.那么就需要用到time函数,time函数是一个时间戳,它的时间是不断的变化的,从1970年开始到现在,那么随机数就会随着时间的变化不断变化。
4.那么我们把3个函数一起使用,就可以生成出真正并且不断变化的随机数。
这3个函数都是需要包含头文件的,我们把它写在game.h里面,而且另外2个源文件都包含了game.h的头文件,所以可以直接使用。如下图:
#include
#include
然后在test.c里面的void test()里面写生成真正的随机数,因为不需要返回时间所以是NULL
srand((unsigned int)time(NULL));
最后在game.c里面写设置雷的代码,如图:
因为game.c也是包含game.h的头文件所以说test.c里面的生成随机数是可以直接用的
//在mine数组里面设置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = Boom; //初始化count为Boom
while (count)
{
//这里我们要用到rand函数,咱们前边定义了ROW为9 ,那么row来接收
int x = rand() % row + 1; //row%一个数是0-8 ,+1就为1-9;
int y = rand() % col + 1; //col%一个数是0-8 ,+1就为1-9;
if (mine[x][y] == '0') //mine数组里面的这个坐标,如果是'0'
{
mine[x][y] = '1';//那么就设置为雷'1'
count--; //每一次设置成功就减减
}
}
}
按道理,循环会为10次或者10次以上,因为如果不是'0' ,那么它就会跳过,再来循环一次,直到10个雷全部设置完
生成2次,2个图,效果如下:
好的兄弟们,那么这一步骤也完成了
好的,兄弟们。首先在test.c文件里的game写排查雷的函数,如下图
//排查雷
FindMine(mine, show, ROW, COL);
然后在game.h里面对该函数进行声明
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row , int col);
最后在game.c里面对函数进行实现。
那么我们先把整体的逻辑整出来,首先我们需要输入值对吧来扫雷,然后是一个需要不断的 输入来进行排雷,那么肯定是需要while循环的。明白这一逻辑,输入的值要有一个范围的,我们布局的是9*9的规模,超出这个范围要报出警告,所以,我们要限制输入值的范围。
1.输入值的范围
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断有效的范围
{
}
else // 不是该范围的提示信息
{
printf("Justin提示你输入错误,不在这范围\n");
}
整体框架代码
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0; // 初始化
int y = 0;
// 因为排雷是多次的,所以肯定是用while循环
while (1) //暂时先放1,没有什么原因
{
printf("Justin请你排雷,请输入:>");
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断有效的范围
{
}
else // 不是该范围的提示信息
{
printf("Justin提示你输入错误,不在这范围\n");
}
}
}
运行结果
2.对雷进行判断
如果你输入的值,正巧是雷的话,那很遗憾,就被炸死了。否则呢?不是雷,就要对该坐标自身的周围进行统计有多少个雷,代码实现如下
if (mine[x][y] == '1') // 如果输入的值,正巧是炸弹的话,就被炸死
{
printf("你被炸死啦,我的好兄弟啊\n");
DisplayBoard(mine, ROW, COL); //把布置雷的棋盘打印出来,死得明白点
break; //炸死了就退出结束了
}
else //输入的值,不是炸弹那就需要统计周围炸弹的个数
{
int count = GetMine(mine, x, y); //用函数来实现,把得到的值放入count
//因为是要在show数组显示,所以用show来接收,count得到的数+'0',可以转变为字符型
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL); //打印给玩家呈现的棋盘
}
让我们来解剖一下,想必关于雷的if语句执行的过程大家都已经明白了,那么我们来解析else所执行的语句。那么想要统计坐标周围雷的个数,首先先写个函数来实现雷个数的统计。然后需要一个count值来接收函数实现返回的值,那么为什么要+'0' ,因为可以把得到的数字转换成字符,我们之前创建的也是字符,然后把它放到show数组来显示。
3.如何实现雷个数的统计
那么实现这一个函数有2种方法
第一种方法:
我们先解释一下,如果输入的坐标不是雷也就是不是'1' , 那么就统计该坐标周围雷的个数,这是第一个思路。第二个思路,也就是我们前边设置的时候’0‘不是炸弹,’1‘是炸弹,这就是为什么要这样设计的原因,方便了我们后期统计雷的个数,下面我们用一个图来讲解,如何统计
那么在统计完后,我们还需要减掉8 * '0' , 因为我们周围是有8个数,如果全部是0也就是没有雷,那么显示的就是0个,如果有一个是雷,那么就会显示1个雷,我们要减去的是字符0,而不是数字0,字符0对应的是48,如果周围有一个雷,就是49-48=1,有2个就是50-48=2。
这就体现了我们前边使用字符0和1来做为坐标的好处,如果是其他的数,是不是就不好统计了
代码实现如下
//实现统计周围雷的个数,为什么加static ,因为我不想让别人看到和使用这个函数
//所以加上了static让它只能在这个内部使用,而且我把它写在这里也是不想暴露出去
static int GetMine(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';
}
方法二
其实了解了前边,那么这个方法也一样,只不过是用for循环
static int GetMine(char mine[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++)
{
count += (mine[i][j] - '0');
}
}
return count;
}
那么雷的统计就完成了,来看下整体的代码
4.进行优化
虽然说整体是做出来了,但是你得让它结束,除了被雷炸死的情况。
那么我们先初始化一个win ,然后在while循环的表达式里面,写入代码,如图
每次输入一次,并且不是雷,就在else里面win++
那么什么意思呢?因为排雷,除了10个雷还有剩余的71个不是雷。那么就是win<9*9-10 ,就等于win<71,排除了71个并且不被雷炸死,就胜利,结束循环。那么我们可以在下面写一个if语句,如
想要验证也可以,我们把雷的数量设置成80个,那么我们只用排查一个就可以了,运行结果
5.还有一个优化,就是解决可能会出现输入重复的坐标,那么解决方法如下图
如果是‘*’,那么没有被排查过,否则else 被排查过
当我输入重复的坐标后,会报出提示
test.c的代码
#include"game.h"
void menu() // 不需要返回所以是void , 菜单的内容 1 为 玩游戏 , 0 为 退出游戏
{
printf("*********************\n");
printf("**** 1.PLAY GAME ****\n");
printf("**** 0.EXIT GAME ****\n");
printf("*********************\n");
}
void game()
{
//创建二维数组
char mine[ROWS][COLS] = { 0 }; //用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; //用来存放排查出来雷的个数信息
//棋盘的创建,我们要对9*9布局进行操作所以ROW和COL要传
InitBoard(mine, ROWS, COLS, '0');//mine数组全部为'0'
InitBoard(show, ROWS, COLS, '*');//show数组全部为'*'
//棋盘的打印
DisplayBoard(show, ROW, COL);
//在mine数组设置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
void test() // 调用函数,我们返回值是void ,什么也不返回
{
srand((unsigned int)time(NULL));
int input = 0; //初始化
// 因为是一个循环过程所以要用do while
do
{
menu(); // 创建菜单的函数
printf("请选择是否开启Justin扫雷游戏:> "); // 提供选择
scanf_s("%d", &input); // 输入选择
switch (input) // 选择的内容
{
case 1:
game(); // game 里面写游戏的内容
break;
case 0:
printf("你退出游戏 ,Justin 觉得很可惜\n"); // 退出游戏
break;
default:
printf("警告,你输入错误,请重新选择\n"); // 不是选择1也不是0,那就是错误的,所以提示重新选择
break;
}
} while (input);
}
int main()
{
test(); // 创建函数
return 0;
}
game.c的代码
#include"game.h"
void InitBoard(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 DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
printf("——Justin的扫雷游戏——\n"); // 这样子更加好看
for (int j = 0; j <= col; j++)//打印列的数字,这里的j是从0开始,如果是1的话,会出现不对齐
{
printf("%d ", j);
}
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"); // 换行,不然捏在一起
}
}
//在mine数组里面设置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = Boom; //初始化count为Boom
while (count)
{
//这里我们要用到rand函数,咱们前边定义了ROW为9 ,那么row来接收
int x = rand() % row + 1; //row%一个数是0-8 ,+1就为1-9;
int y = rand() % col + 1; //col%一个数是0-8 ,+1就为1-9;
if (mine[x][y] == '0') //mine数组里面的这个坐标,如果是'0'
{
mine[x][y] = '1';//那么就设置为雷'1'
count--; //每一次设置成功就减减
}
}
}
//实现统计周围雷的个数,为什么加static ,因为我不想让别人看到和使用这个函数
//所以加上了static让它只能在这个内部使用,而且我把它写在这里也是不想暴露出去
static int GetMine(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';
}
//
//static int GetMine(char mine[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++)
// {
// count += (mine[i][j] - '0');
// }
// }
// return count;
//}
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0; // 初始化
int y = 0;
int win = 0;
// 因为排雷是多次的,所以肯定是用while循环
while (win < row * col - Boom) //win判断的是扫雷的结束,当满足这个条件就结束循环了
{
printf("Justin请你排雷,请输入:>");
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断有效的范围
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1') // 如果输入的值,正巧是炸弹的话,就被炸死
{
printf("你被炸死啦,我的好兄弟啊\n");
DisplayBoard(mine, ROW, COL); //把布置雷的棋盘打印出来,死得明白点
break; //炸死了就退出结束了
}
else //输入的值,不是炸弹那就需要统计周围炸弹的个数
{
int count = GetMine(mine, x, y); //用函数来实现,把得到的值放入count
//因为是要在show数组显示,所以用show来接收,count得到的数+'0',可以转变为字符型
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL); //打印给玩家呈现的棋盘
win++;
}
}
else//否则被排查过
{
printf("改坐标被排除过啦\n");
}
}
else // 不是该范围的提示信息
{
printf("Justin提示你输入错误,不在这范围\n");
}
}
if (win == row * col - Boom) // 满足这个条件胜利
{
printf("恭喜你,胜利啦,Justin 觉得你太厉害了\n");
DisplayBoard(mine, ROW, COL);// 打印雷的布置
}
}
game.h的代码
#include
#include
#include
#define ROW 9 //9*9的布局,所以ROW和COL为9
#define COL 9
#define ROWS ROW + 2 //11*11的布局,所以ROWS和COLS为11
#define COLS COL + 2
#define Boom 10 //定义炸弹10个
// 函数的声明
//这里很巧妙我用了 int set 来接受'0'和'*' 这样我就可以对这2个进行设置而不用多去建立一个数组
void InitBoard(char arr[ROWS][COLS] ,int rows, int cols, char set);
//棋盘的打印
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
//在mine数组里面设置雷
void SetMine(char mine[ROWS][COLS], int row , int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row , int col);
结尾
文章里的扫雷布局图片为了方便理解我使用了鹏哥C语言的课件,也经过了同意使用!
感谢大家的阅读,希望能帮助到大家!