在本文章中,主要是针对之前写过的扫雷代码实现了功能上的优化,主要增加了包括不是雷,排查的时候可以点开一大片,使得用户可以标记雷,和显示排雷所需的时间三个功能。
目录
一 打开一大片功能
1 逻辑实现
在之前的关于扫雷游戏代码的实现那篇文章中,我们在game.c文件的排查雷函数中写到了具体排查雷逻辑的实现,在那里是通过判断用户输入坐标的mine数组里是否是字符1,如果是字符1,那么就退出游戏,如果不是1,就把该坐标周围的8个坐标中含有字符1的个数赋给show数组的相应坐标位置,这样用户至少需要排查71次才能把所有非雷找到,这样太繁琐了,如果增加了能够展开一大片的功能,可以大大简化排雷的过程,接下来就主要讲解该逻辑的实现。
那么我们可以想一下扫雷游戏是如何实现该功能的呢?
在扫雷游戏中我们可以看到,展开的边界要么是棋盘的边界,要么就是数字,数字也就是周围8个坐标中雷的个数嘛,所以该功能的实现就可以通过这么一个逻辑来实现:首先判断输入的坐标的周围8个坐标是否有雷,如果有雷,那就显示周围8个坐标有几个雷,如果周围8个坐标没有雷,那就把该坐标变为空格,然后就再以这个坐标周围8个坐标为核心向外扩8个坐标,再进行判断,该逻辑如图所示:
显然,这是一个递归问题,如果要判断的坐标周围的8个坐标没有雷,就把该位置置为空格,然后再分别以周围8个坐标为核心,再向外扩8个坐标,如果周围8个坐标还没有雷,那就再向外扩8个坐标,知道扩大周围8个坐标有雷的为止,所以该逻辑实现的递归逻辑如下图所示:
但要注意的一点是递归的停止条件除了坐标周围8个坐标有雷之外,还有一个结束条件就是坐标的行必须再1-9,列也必须在1-9之内,因为向外扩的过程中遇到边界也必须停止。
2 代码实现
有了以上逻辑,那么就可以写出实现该功能的代码了,代码如下:
void Expand_borad(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (Aroundmine(mine, x, y) == '0')
{
show[x][y] = ' ';
}
else
{
show[x][y] = Aroundmine(mine, x, y);
}
if (show[x][y] == ' ')
{
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if ((mine[i][j] != '1' && show[i][j] == '*') && (i >= 1 && i <= ROW) && (j >= 1 && j <= COL))
{
Expand_borad(mine, show, i, j);
}
}
}
}
}
但是这里还有一点需要注意,就是如果实现了该功能,那么下面排查雷函数(Pachamine函数)里循环的判断条件也要相应修改,因为之前是通过判断正确排查雷的次数来判断用户是否排查完71个雷了,判断该位置不是雷,count计数器就加1,如果最后count变为了71,那么就说明用户排查完了,但如果是继续使用该逻辑,当输入一个坐标,继而展开为一大片时,count才会加1,所以该逻辑并不再适合作为用户排查完的判断条件,这里呢,我们还是选择使用排查完的雷数(count)作为判断条件,当count变为71时,游戏结束,那么在展开一大片的逻辑下,我们选择创建一个全局变量count,当show数组中一个坐标变为空格或者变为该坐标周围8个坐标的雷数时,count就加1,这样count就是排查完雷的个数了,所以我们可以看到,当代码发生变化是,其函数或者循环的结束条件也要进行相应的修改,这十分重要,进行修改之后的Pachamine函数就变成了下面这些代码:
int count = 0;
void Expand_borad(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (Aroundmine(mine, x, y) == '0')
{
show[x][y] = ' ';
count++;
}
else
{
show[x][y] = Aroundmine(mine, x, y);
count++;
}
if (show[x][y] == ' ')
{
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if ((mine[i][j] != '1' && show[i][j] == '*') && (i >= 1 && i <= ROW) && (j >= 1 && j <= COL))
{
Expand_borad(mine, show, i, j);
}
}
}
}
}
void Pachamine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷函数
{
int x = 0;
int y = 0;
while (count < row * col - mine_number)
{
printf("输入你要排查的坐标:");
scanf("%d %d", &x, &y);
if (mine[x][y] == '1')
{
printf("不好意思,您被炸死了\n");
printf("\n");
printf("雷的位置为:\n");
Displayboard(mine, ROW, COL);
break;
}
else
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
Expand_borad(mine, show, x, y);
printf("还有%d个未被排查\n", row * col - mine_number - count);
Displayboard(show, ROW, COL);
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
if (count == row * col - mine_number)
{
printf("恭喜你,排雷成功!\n");
}
}
二 标记雷功能
1 逻辑实现
该逻辑实现只要让用户通过输入数字来决定是否要标记雷或者取消标记雷就可以了,可以在排查雷函数里创建一个整型变量,然后让用户通过输入数字,判断数字是否是1,2,3就可以了,如果用户输入的是1,代表进行标记雷,就把show数组相应坐标置为'#',如果输入的是2,那么就再把show数组相应目标再置回'*',如果输入的是3,那就正常进行上面的排查雷的逻辑。
2 代码实现
Pachamine函数代码变化如下:
void Pachamine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷函数
{
int x = 0;
int y = 0;
printf("如果想要标记雷,请在是否要标记雷后输入1,取消标记输入2,否则输入3,'#'代表该位置已被标记为雷\n");
while (count < row * col - mine_number)
{
int flag = 0;
printf("是否要标记或者取消标记雷:");
scanf("%d", &flag);
printf("输入你要排查或要标记的坐标:");
scanf("%d%d", &x, &y);
if (flag == 1 && show[x][y] == '*' && (x >= 1 && x <= ROW) && (y >= 1 && y <= COL))//标记雷
{
show[x][y] = '#';
Displayboard(show, ROW, COL);
}
else if (flag == 2 && show[x][y] == '#' && (x >= 1 && x <= ROW) && (y >= 1 && y <= COL))//取消标记雷
{
show[x][y] = '*';
Displayboard(show, ROW, COL);
}
else
{
if (mine[x][y] == '1')
{
printf("不好意思,您被炸死了\n");
printf("\n");
printf("雷的位置为:\n");
Displayboard(mine, ROW, COL);
break;
}
else
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//Displayboard(mine, ROW, COL);//测试一扩一大片逻辑
Expand_borad(mine, show, x, y);
printf("还有%d个未被排查\n", row * col - mine_number - count);
//int number = Aroundmine(mine, x, y);
//show[x][y] = number;//未添加一扩一大片功能之前的逻辑,只能一次排查一个坐标
Displayboard(show, ROW, COL);
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
}
if (count == row * col - mine_number)
{
printf("恭喜你,排雷成功!\n");
}
}
三 显示排雷游戏所需的时间
这里主要是在排雷游戏结束时,显示排查雷一共所需的秒数,并不是实现边运行,边显示时间的功能。
1 逻辑实现
在该功能中,主要是通过time.h头文件中的clock函数以及定义的常量CLOCKS_PER_SEC。
接下来我们看一下clock函数以及常量CLOCKS_PER_SEC:
该函数主要是用于返回程序从启动到调用该函数时所经过的“时钟嘀嗒数”,时钟嘀嗒数是计算机系统内部用于计量时间的一种计数方式,计算机系统通常有一个内部时钟,会以固定的频率发出脉冲或“嘀嗒”。每一次“嘀嗒”就会增加一个计数,这个计数就是时钟嘀嗒数,通过计算一段时间内的时钟嘀嗒数,可以衡量程序或操作所消耗的时间,所以我们还需要一个常量来把这个时间转换为秒数。
在CLOCKS_PER_SEC的定义中,我们可以看到它是一个常量,且将时钟嘀嗒数除以这个常量我们就可以得到秒数了。
那么clock函数结合CLOCKS_PEP_SEC常量就可以这样用:
#include<stdio.h>
#include<time.h>
#include<math.h>
int frequency_of_primes(int n) {
int i, j;
int freq = n - 1;
for (i = 2; i <= n; ++i)
{
for (j = sqrt(i); j > 1; --j)
{
if (i % j == 0)
{
--freq; break;
}
}
}
return freq;
}
int main()
{
clock_t t;
int f;
t = clock();
printf("Calculating...\n");
f = frequency_of_primes(99999);
printf("The number of primes lower than 100,000 is: %d\n", f);
t = clock() - t;
printf("It took me %d clicks (%f seconds).\n", t, ((float)t) / CLOCKS_PER_SEC);
return 0;
}
输出结果:
2 代码实现
通过以上逻辑分析,就很容易写出代码了,Pachamine函数修改后如下:
void Pachamine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷函数
{
int x = 0;
int y = 0;
printf("如果想要标记雷,请在是否要标记雷后输入1,取消标记输入2,否则输入3,'#'代表该位置已被标记为雷\n");
clock_t start, end;
double use_time;
start = clock();//下面代码开始运行时间
while (count < row * col - mine_number)
{
int flag = 0;
printf("是否要标记或者取消标记雷:");
scanf("%d", &flag);
printf("输入你要排查或要标记的坐标:");
scanf("%d%d", &x, &y);
if (flag == 1 && show[x][y] == '*' && (x >= 1 && x <= ROW) && (y >= 1 && y <= COL))//标记雷
{
show[x][y] = '#';
Displayboard(show, ROW, COL);
}
else if (flag == 2 && show[x][y] == '#' && (x >= 1 && x <= ROW) && (y >= 1 && y <= COL))//取消标记雷
{
show[x][y] = '*';
Displayboard(show, ROW, COL);
}
else
{
if (mine[x][y] == '1')
{
printf("不好意思,您被炸死了\n");
printf("\n");
printf("雷的位置为:\n");
Displayboard(mine, ROW, COL);
break;
}
else
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
Expand_borad(mine, show, x, y);
printf("还有%d个未被排查\n", row * col - mine_number - count);
Displayboard(show, ROW, COL);
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
}
end = clock();//上面代码结束运行时间
use_time = ((double)(end - start) / CLOCKS_PER_SEC);//将时间转变为以秒为单位的时间数
if (count == row * col - mine_number)
{
printf("恭喜你,排雷成功!\n");
}
printf("排雷所需的时间为:%d秒\n", (int)use_time);//输出上述循环代码所需运行时间
}
到这,扫雷游戏的扩展功能就结束了,如果以后还能本人想出扫雷函数的其他功能实现,会继续更新的,请大家尽请期待。