《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏规则是在最短的时间内根据点击格子出现的数字找出所有非雷格子,这个数字代表这个位置周围一圈有几个雷。同时避免踩雷,踩到一个雷即全盘皆输。对于是雷的可疑格子,我们可以对其进行标记或者取消标记。
为了代码的可读性更强,将代码拆分成不同的模块。
基础版功能:
1.踩到雷,提示游戏结束
2.未踩到雷,提示周围一圈雷的个数
游戏玩完一把不过瘾,还想再玩一把,玩很多次,用循环来实现,选择do…while循环,即使一次都不玩也可以进入到菜单页面。
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 2:
printf("在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。\n");
break;
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新输入!\n");
break;
}
} while(input);
return 0;
}
打印菜单,调用menu函数。
void menu()
{
printf("********** 扫雷游戏 ***********\n");
printf("********** 2.游戏规则 ***********\n");
printf("********** 1.开始游戏 ***********\n");
printf("********** 0.退出游戏 ***********\n");
printf("********************************\n");
}
运行结果如下:
根据菜单选择相应的数字。2,游戏规则。1,开始游戏。0,退出游戏。选择其他数,提示选择错误,重新选择。
选择2,运行结果如下:
选择1,运行结果如下:
选择0,运行结果如下:
选择3,运行结果如下:
游戏封装成一个函数game。
点击棋盘上的任意一个格子,如果是雷,就被炸死了,游戏结束。如果不是雷,格子上会有数字,提示你这个格子周围一圈有几个雷。只要把棋盘上所有不是雷的位置找到,剩下的都是雷,这时候游戏结束,扫雷成功。
在棋盘上随机布置10个雷,应该记录下来这些雷的信息,我们用一个二维数组。但二维数组的行列如何确定呢?
当我们排查坐标为(5,5)的位置。如果坐标(5,5)不是雷,我们要查看它周围一圈有几个雷,即蓝色框出区域的八个格子,然后记录下雷的个数,存放到第二个棋盘中坐标为(5,5)的位置。但如果排查坐标(8,9)的周围八个格子是否有雷,当我们时创建的是9x9的棋盘,会发现在排查坐标为(7,10)(8,10)(9,10)的位置时,数组就越界了。这时候我们考虑扩大数组,让之前的数组上下各多一行,左右各多一列,这样在排查刚才那个位置周围雷的信息时,数组就不会越界了。
我们要创建一个11x11的数组,这样才可以保证我们在排查中间9x9的棋盘上任意一个位置周围8个坐标都不会越界。我们布置雷,还是布置在中间9x9的棋盘上,外面多出来的一圈放置的全是字符’0’。我们在打印棋盘的时候,也是打印9x9的棋盘。
定义数组的时候不要直接写它的大小,而是在game_sweep.h中进行定义,想修改的时候会方便很多,定义布置雷的个数时也是如此。
#define ROW 9 //布置雷数组的行,打印棋盘的行
#define COL 9 //布置雷数组的列,打印棋盘的列
#define ROWS ROW+2//真实创建的数组的行
#define COLS COL+2//真实创建的数组的列
#define EASY_COUNT 10//雷的个数
void game()
{
char mine[ROWS][COLS] = {
0 };//存储布置好的雷的信息
char show[ROWS][COLS] = {
0 };//存储排查出的雷的信息
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
下面详细介绍game函数的实现。
初始化棋盘,调用InitBoard函数。
初始化棋盘也就是初始化数组。有雷的位置设置成1,非雷的位置设置成0。布置雷的数组mine初始化为全字符’0’,存放排查出来的雷的信息的数组show初始化为全字符’*’。为了mine和show数组初始化为不同的内容,多传一个参数,这个参数决定数组初始化的内容。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols;j++)
{
board[i][j] = set;
}
}
}
我们为什么要设置两个数组呢?
在我们排查雷的时候,如果此时排查的某个坐标周围只有一个雷,这时就要在这个位置放一个1。那么问题就出现了,1产生了歧义。这个1到底是雷还是排查出的雷的信息(周围只有一个雷)。我们想到可以再创建一个数组,用于存放排查出来的雷的信息。要创建两个数组,一个数组存放布置好的雷的信息,另外一个数组存放排查出的雷的信息。为了保持神秘感,我们将存放排查出的雷的信息的数组初始化为字符’*’,排查出坐标周围一圈有一个雷就放一个字符’1’进去,所以要创建一个char类型的数组,为了让两个数组保持一致,用于布置雷的数组初始化为字符’0’,雷的位置设置成字符’1’。
打印棋盘,调用DispiayBoard函数。
传的是整个11x11的数组,打印棋盘的时候只需要打印中间9x9的棋盘。在排查雷的时候,要输入排查的坐标,行列过多,不方便一眼看出要排查位置的坐标,所以我们给棋盘加上行号和列号。
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);
//printf("|");
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
运行结果如下:
布置雷,调用SetMine函数。
需要布置的雷的个数在game_sweep.h文件中已定义过,用rand函数生成一个随机的坐标。rand函数随机生成一个0-32767之间的数字,我们在中间9x9的棋盘布置雷,所以坐标范围为1-9,rand()%9+1即可以产生一个1-9的随机数。使用rand函数之前要调用srand,srand只需调用一次。记得引入相关的头文件
和 。生成完随机坐标后判断一下该坐标是否已经布置过雷,如果该坐标位置是字符’0’,证明没有雷,可在此布雷,将该位置设置成字符’1’,接着雷个数count-1。如果该坐标位置是字符’1’,证明有雷,再重新生成坐标,直到count=0,说明10个雷已经布置完,循环停止。
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
运行结果如下:
排查雷,调用FindMine函数。
在mine数组中排查雷的信息,最后将排查后的信息放到show数组中。排查雷的过程:首先,输入排查的坐标。判断坐标的合法性,坐标范围为1-9。坐标不合法,提示非法坐标,重新输入。然后,检查坐标是不是雷。如果是雷,提示很遗憾,你被炸死了,游戏结束。显示一下布置雷的棋盘。如果不是雷,统计该坐标周围8个坐标一共有几个雷,最后将雷的信息存放到show数组中。9x9的棋盘,10个雷,71个位置没有雷,排查一个坐标,win++,只要win的个数小于71,循环继续,当win=71时,提示恭喜你,排雷成功。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1.输入需要排查的坐标
//2.检查坐标处是不是雷
//(1)如果是雷,很遗憾,你被炸死了,结束游戏
//(2)如果不是雷,统计此坐标周围八个坐标有几个雷,个数存储到show数组中,游戏继续
int x = 0;
int y = 0;
int count = 0;
int win = 0;
while (win<row*col-EASY_COUNT)
{
printf("请输入需要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
count = GetMine_count(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
运行结果如下:
统计非雷坐标周围一圈雷的个数,调用GetMine_count函数。
如果此时GetMine_count得到的是数字3,也就是说count中存放的是数字3,给show数组中要放的是字符3。我们观察ASCII码表可知,字符’0’的ASCII码值是48,数字0加上48就可以变成字符’0’。字符’1’的ASCII码值是49,数字1加上48就可以变成字符’1’。而字符’0’的ASCII码值就是48,所以数字1加上字符’0’就可以变成字符’1’。我们只需要将得到的count值加上字符0后再存放到show数组中。
GetMine_count函数仅仅是为了支持FindMine函数而实现的,可以加上static,static修饰函数,让该函数只能在自己所在的源文件内部使用,也就是只能在game_sweep.c中使用。我们写这个函数也只是为了让FindMine函数使用,加上static,这个函数就不会跟其他源文件内部的函数冲突了。
static int GetMine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';
}
mine数组中存放了字符’0’和字符’1’,字符’0’代表该位置没有雷,字符’1’代表该位置有雷。我们统计(x,y)坐标周围一圈有多少个字符’1’,字符’1’减去字符’0’可得到数字1,八个字符相加最后减去八个字符’0’,可得到相应int类型的数字。如果觉得上面的代码不好理解,我们还可以用下面这种方法。
static int GetMine_count(char mine[ROWS][COLS], int x, int y)
{
int count_mine = 0;
if (mine[x - 1][y - 1] == '1')
count_mine++;
if (mine[x - 1][y] == '1')
count_mine++;
if (mine[x - 1][y + 1] == '1')
count_mine++;
if (mine[x][y - 1] == '1')
count_mine++;
if (mine[x][y + 1] == '1')
count_mine++;
if (mine[x + 1][y - 1] == '1')
count_mine++;
if (mine[x + 1][y] == '1')
count_mine++;
if (mine[x + 1][y + 1] == '1')
count_mine++;
return count_mine;
}
创建一个变量count_mine用于统计非雷坐标周围八个坐标是雷的个数,如果是雷,count_mine+1,八个坐标判断完后,将统计的个数返回。当然,我们还可以写成下面这种循环的形式。
static int GetMine_count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] =='1' )
{
count++;
}
}
}
return count;
}
中间(x,y)坐标位置一定是字符’0’,所以不用再对这个点特殊处理。遍历周围八个坐标,是雷的位置,count+1,将统计的个数返回。
运行结果如下:
我们可以看到坐标(6,5)的位置周围一圈有3颗雷,调用GetMine_count函数统计完后,会将字符’3’放到show数组中坐标(6,5)的位置。
上面的扫雷游戏只是实现了最基本的功能,跟我们平时玩的扫雷游戏还是有些不同。真实的扫雷游戏中,当我们点击下方红色框起来的位置时,棋盘会展开一片,出现空格和排查出来的雷的信息。
左上角的位置一定是雷,我们可以用红色旗子进行标记,也可以取消标记。棋盘页面会显示当前雷的个数以及标记雷后剩余的雷的个数,原本是10颗雷,当我们标记左上角的位置后,只剩下9颗雷了。
我们根据真实的扫雷游戏的功能,优化一下我们基础版的代码。
优化功能如下:
1.防止第一次踩雷。
2.展开棋盘。
3.标记雷。
4.取消已标记的雷。
棋盘上会显示系统布置雷的总数,当前标记雷的个数以及成功标记的雷的个数。如果我们判断错误,把不是雷的位置标记成雷,观察提示就可以知道了,便可及时修改,取消标记。
下面我们来具体分析下优化的功能。
优化功能增加的函数全部都是在排查函数FindMine中使用。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1.输入需要排查的坐标
//2.检查坐标处是不是雷
//(1)如果是雷,很遗憾,你被炸死了,结束游戏
//(2)如果不是雷,统计此坐标周围八个坐标有几个雷,个数存储到show数组中,游戏继续
int x = 0;
int y = 0;
int win = 0;
int mine_num = 0;
int input = 0;
int cor_mine_num = 0;
int first = 1;
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
do
{
menu_mark();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
//printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
printf("请输入需要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
if (first == 1)
{
FirstMine(mine, x, y);
DisplayBoard(mine, ROW, COL);
OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
first--;
break;
}
if (first == 0)
{
printf("很遗憾,你被炸死了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
return;
}
}
else
{
first = 0;
OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
break;
case 2:
//printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
printf("请输入需要标记雷的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
show[x][y] = '@';
DisplayBoard(show, ROW, COL);
mine_num++;
if (mine[x][y] == '1')
{
cor_mine_num++;
}
if (cor_mine_num == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
return;
}
}
else if (show[x][y] == '@')
{
printf("输入坐标已被标记过,请重新输入!\n");
}
else
{
printf("输入坐标已被排查过,不是雷,无法标记,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
break;
case 3:
//printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
printf("请输入需要取消标记雷的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '@')
{
show[x][y] = '*';
DisplayBoard(show, ROW, COL);
mine_num--;
if (mine[x][y] == '1')
{
cor_mine_num--;
}
if (cor_mine_num == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
return;
}
}
else if (show[x][y] == '*')
{
printf("输入坐标未被标记过,无法取消标记,请重新输入!\n");
}
else
{
printf("输入坐标已被排查过,不是雷,无法取消标记,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
win = JudgeWin(show, ROW, COL);
} while (win > EASY_COUNT);
if (win == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
二次选择菜单,调用menu_mark函数。
我们在开始游戏后要进行选择,是扫雷,还是标记雷或者是取消已标记的雷。
void menu_mark()
{
printf("***1.扫雷 2.标记雷 3.撤销已标记的雷***\n");
}
运行结果如下:
防止第一次踩雷,调用FirstMine函数。
如果玩家上来输入的第一个坐标是雷,也就是说这个位置是‘1’,我们会把这个‘1’变成‘0’,这时就少了一个雷,然后我们在棋盘上随机产生一个坐标,如果这个坐标不是雷,就在这个位置放置一个雷,相当于把刚才位置的雷移到了这个位置。避免玩家一上来就被炸死,游戏体验好点。
void FirstMine(char mine[ROWS][COLS], int x, int y)
{
int count_first = 1;
int m = 0;
int n = 0;
while (count_first)
{
mine[x][y] = '0';
m = rand() % (ROWS - 2) + 1;
n = rand() % (COLS - 2) + 1;
if (mine[m][n] == '0')
{
mine[m][n] = '1';
count_first--;
}
}
}
运行结果如下:
可以看出坐标(1,6)位置是雷,第一次去排查雷的时候,并没有被炸死,而是把(1,6)位置上的雷放到了(7,3)位置上。
展开棋盘,调用OpenMine函数。
当我们排查棋盘上的某个坐标时,如果发现这个坐标不是雷,但这个坐标周围八个坐标有雷,则将这八个坐标中有雷的个数统计一下,统计得到的值放到show数组这个排查的坐标位置。此时棋盘不会展开一片。如果发现这个坐标不是雷,这个坐标周围八个坐标也不是雷,将该坐标设置成空格,然后再依次排查周围的八个坐标,排查它们每个坐标周围的八个坐标是不是雷,如果不是雷,再将该坐标设置成空格,如果是雷,统计雷的个数。这里用到了递归的方法,最后棋盘会展开一片,展开到某个坐标外面一圈不是雷,但外面这一圈的周围一定有雷则停止。
//展开函数
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//如果该坐标位置处不是雷,并且它周围一圈也没有雷,该位置设置为空格,并展开
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int n = 0;
n = GetMine_count(mine, x, y);
if (n == 0)
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*'&& mine[i][j] == '0')
{
OpenMine(mine, show, i, j);
}
}
}
}
else
{
show[x][y] = n + '0';
}
}
}
运行结果如下:
为了知道雷的具体位置,我们把布置完雷的棋盘打印出来。观察坐标(5,4)可知,该位置不是雷,该位置周围共有三颗雷,所以将3存放在排查出雷的信息的棋盘对应的位置,此时不会展开一片。
图中坐标(7,3)的位置不是雷,它周围的八个坐标也都没有放置雷,这个时候就可以展开一片。
我们在扫雷的过程中,可以根据棋盘上排查出来的雷的信息来判断某个坐标是不是雷,这时我们可以将雷标记出来,标记成功,则该位置显示为‘@’。
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
if (first == 1)
{
FirstMine(mine, x, y);
DisplayBoard(mine, ROW, COL);
OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
first--;
break;
}
if (first == 0)
{
printf("很遗憾,你被炸死了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
return;
}
}
else
{
first = 0;
win = OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
}
}
运行结果如下:
图中坐标(1,3)的位置是雷,此时对它进行标记,标记成功后,对应的位置变成’@’,同时我们可以看到提示共有雷个数为:10,当前标记雷个数为:1,成功标记雷的个数为:1。
如果此时我们再对(1,3)进行标记,会提示输入坐标已被标记过,请重新输入。
我们知道坐标(1,1)的位置不是雷,如果此时我们对该坐标进行标记,会看到提示:共有雷个数为:10,当前标记雷个数为:2,成功标记雷的个数为:1。这时候我们就知道刚才标记出错了,把不是雷的位置错认为有雷。
坐标(7,7)的位置已被排查过,这时再去标记该位置,会提示输入坐标已被排查过,不是雷,无法标记,请重新输入。
我们在标记雷的时候,发现标记错了,这里不是雷我们却把它标记成了雷,这时候我们可以取消标记,该位置又变回了‘*’。
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '@')
{
show[x][y] = '*';
DisplayBoard(show, ROW, COL);
mine_num--;
if (mine[x][y] == '1')
{
cor_mine_num--;
}
if (cor_mine_num == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
return;
}
}
else if (show[x][y] == '*')
{
printf("输入坐标未被标记过,无法取消标记,请重新输入!\n");
}
else
{
printf("输入坐标已被排查过,不是雷,无法取消标记,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
运行结果如下:
我们知道标记错了,我们可以取消已标记的雷,坐标(1,1)又变成了’*’,显示未被排查过。
当我们取消一个未被标记过的坐标时,会提示输入坐标未被标记过,无法取消标记,请重新输入。
坐标(7,7)的位置已被排查过,这时再去取消标记该位置,会提示输入坐标已被排查过,不是雷,无法取消标记,请重新输入。
判断游戏输赢,调用JudgeWin函数。
9x9的棋盘,我们布置了10颗雷,当棋盘上剩下10个坐标未被排查,说明玩家扫雷成功。这10个坐标放置的可能是’ * ‘,也可能是’ @ ‘。遍历棋盘上的所有坐标,遇到’ * ‘或者’ @ ',win+1,当win=10的时候,提示排雷成功。
int JudgeWin(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int win = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == '@')
{
win++;
}
}
}
return win;
}
运行结果如下:
上面判断输赢的方法是通过统计未排查的坐标个数,若个数等于布置雷的个数,说明排雷成功。我们还可以有另外一种判断输赢的方法,通过统计排查过的坐标个数,若个数等于棋盘总坐标个数减去布置雷的个数,排雷成功。
int count = 0;
int OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//如果该坐标位置处不是雷,并且它周围一圈也没有雷,该位置设置为空格,并展开
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int n = 0;
n = GetMine_count(mine, x, y);
if (n == 0)
{
show[x][y] = ' ';
count++;
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*' && mine[i][j] == '0')
{
OpenMine(mine, show, i, j);
}
}
}
}
else
{
show[x][y] = n + '0';
count++;
}
}
return count;
}
统计排查过的坐标个数在展开函数OpenMine中进行,定义一个变量count,当我们排查一个坐标不是雷,如果它周围一圈也不是雷,count+1,然后递归展开它周围的坐标。如果它周围一圈有雷,count+1。最后将count值返回。
运行结果如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game_sweep.h"
void menu()
{
printf("********** 扫雷游戏 ***********\n");
printf("********** 2.游戏规则 ***********\n");
printf("********** 1.开始游戏 ***********\n");
printf("********** 0.退出游戏 ***********\n");
printf("*********************************\n");
}
void game()
{
char mine[ROWS][COLS] = {
0 };//存储布置好的雷的信息
char show[ROWS][COLS] = {
0 };//存储排查出的雷的信息
//初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 2:
printf("在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。\n");
break;
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新输入!\n");
break;
}
} while(input);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"game_sweep.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
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);
//printf("|");
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//int GetMine_count(char mine[ROWS][COLS], int x, int y)
//{
// return mine[x - 1][y - 1] +
// mine[x - 1][y] +
// mine[x - 1][y + 1] +
// mine[x][y - 1] +
// mine[x][y + 1] +
// mine[x + 1][y - 1] +
// mine[x + 1][y] +
// mine[x + 1][y + 1] - 8 * '0';
//}
int GetMine_count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] =='1' )
{
count++;
}
}
}
return count;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1.输入需要排查的坐标
//2.检查坐标处是不是雷
//(1)如果是雷,很遗憾,你被炸死了,结束游戏
//(2)如果不是雷,统计此坐标周围八个坐标有几个雷,个数存储到show数组中,游戏继续
int x = 0;
int y = 0;
int count = 0;
int win = 0;
while (win<row*col-EASY_COUNT)
{
printf("请输入需要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
count = GetMine_count(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
#include
#include
#include
#define ROW 9 //布置雷数组的行,打印棋盘的行
#define COL 9 //布置雷数组的列,打印棋盘的列
#define ROWS ROW+2//真实创建的数组的行
#define COLS COL+2//真实创建的数组的列
#define EASY_COUNT 10//雷的个数
//初始化棋盘
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 mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
#include"game_sweep.h"
void menu()
{
printf("********** 扫雷游戏 ***********\n");
printf("********** 2.游戏规则 ***********\n");
printf("********** 1.开始游戏 ***********\n");
printf("********** 0.退出游戏 ***********\n");
printf("*********************************\n");
}
void game()
{
char mine[ROWS][COLS] = {
0 };//存储布置好的雷的信息
char show[ROWS][COLS] = {
0 };//存储排查出的雷的信息
//初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 2:
printf("1.在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。\n");
printf("2.在扫雷过程中可标记雷或者取消已标记的雷。\n");
printf("3.为了提升体验感,玩家在输入的第一个坐标如果是雷,不会被炸死。\n");
break;
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新输入!\n");
break;
}
} while (input);
return 0;
}
#include"game_sweep.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
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);
//printf("|");
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//int GetMine_count(char mine[ROWS][COLS], int x, int y)
//{
// return mine[x - 1][y - 1] +
// mine[x - 1][y] +
// mine[x - 1][y + 1] +
// mine[x][y - 1] +
// mine[x][y + 1] +
// mine[x + 1][y - 1] +
// mine[x + 1][y] +
// mine[x + 1][y + 1] - 8 * '0';
//}
int GetMine_count(char mine[ROWS][COLS], int x, int y)
{
int count_mine = 0;
if (mine[x - 1][y - 1] == '1')
count_mine++;
if (mine[x - 1][y] == '1')
count_mine++;
if (mine[x - 1][y + 1] == '1')
count_mine++;
if (mine[x][y - 1] == '1')
count_mine++;
if (mine[x][y + 1] == '1')
count_mine++;
if (mine[x + 1][y - 1] == '1')
count_mine++;
if (mine[x + 1][y] == '1')
count_mine++;
if (mine[x + 1][y + 1] == '1')
count_mine++;
return count_mine;
}
//void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
//{
// //如果该坐标位置处不是雷,并且它周围一圈也没有雷,该位置设置为空格,并展开
// if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
// {
// int n = 0;
// n = GetMine_count(mine, x, y);
// if (n == 0)
// {
// show[x][y] = ' ';
// if (show[x - 1][y - 1] == '*')
// {
// OpenMine(mine, show, x - 1, y - 1);
// }
// if (show[x - 1][y] == '*')
// {
// OpenMine(mine, show, x - 1, y);
// }
// if (show[x - 1][y + 1]=='*')
// {
// OpenMine(mine, show, x - 1, y + 1);
// }
// if (show[x][y + 1]=='*')
// {
// OpenMine(mine, show, x, y + 1);
// }
// if (show[x + 1][y + 1] == '*')
// {
// OpenMine(mine, show, x + 1, y + 1);
// }
// if (show[x + 1][y] == '*')
// {
// OpenMine(mine, show, x + 1, y);
// }
// if (show[x + 1][y - 1] == '*')
// {
// OpenMine(mine, show, x + 1, y - 1);
// }
// if (show[x][y - 1] == '*')
// {
// OpenMine(mine, show, x, y - 1);
// }
// }
// else
// {
// show[x][y] = n + '0';
// }
// }
//
//}
//展开函数
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//如果该坐标位置处不是雷,并且它周围一圈也没有雷,该位置设置为空格,并展开
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int n = 0;
n = GetMine_count(mine, x, y);
if (n == 0)
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*'&& mine[i][j] == '0')
{
OpenMine(mine, show, i, j);
}
}
}
}
else
{
show[x][y] = n + '0';
}
}
}
//int count = 0;
//int OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
//{
// //如果该坐标位置处不是雷,并且它周围一圈也没有雷,该位置设置为空格,并展开
// if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
// {
// int n = 0;
// n = GetMine_count(mine, x, y);
// if (n == 0)
// {
// show[x][y] = ' ';
// count++;
// int i = 0;
// int j = 0;
// for (i = x - 1; i <= x + 1; i++)
// {
// for (j = y - 1; j <= y + 1; j++)
// {
// if (show[i][j] == '*' && mine[i][j] == '0')
// {
// OpenMine(mine, show, i, j);
// }
//
// }
// }
//
// }
// else
// {
// show[x][y] = n + '0';
// count++;
// }
// }
// return count;
//
//}
void menu_mark()
{
printf("***1.扫雷 2.标记雷 3.撤销已标记的雷***\n");
}
//避免第一次就被炸死
void FirstMine(char mine[ROWS][COLS], int x, int y)
{
int count_first = 1;
int m = 0;
int n = 0;
while (count_first)
{
mine[x][y] = '0';
m = rand() % (ROWS - 2) + 1;
n = rand() % (COLS - 2) + 1;
if (mine[m][n] == '0')
{
mine[m][n] = '1';
count_first--;
}
}
}
判断输赢
int JudgeWin(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int win = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == '@')
{
win++;
}
}
}
return win;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1.输入需要排查的坐标
//2.检查坐标处是不是雷
//(1)如果是雷,很遗憾,你被炸死了,结束游戏
//(2)如果不是雷,统计此坐标周围八个坐标有几个雷,个数存储到show数组中,游戏继续
int x = 0;
int y = 0;
int win = 0;
int mine_num = 0;
int input = 0;
int cor_mine_num = 0;
int first = 1;
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
do
{
menu_mark();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入需要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
if (first == 1)
{
FirstMine(mine, x, y);
DisplayBoard(mine, ROW, COL);
OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
first--;
break;
}
if (first == 0)
{
printf("很遗憾,你被炸死了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
return;
}
}
else
{
first = 0;
OpenMine(mine, show, x, y);
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
break;
case 2:
printf("请输入需要标记雷的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
show[x][y] = '@';
DisplayBoard(show, ROW, COL);
mine_num++;
if (mine[x][y] == '1')
{
cor_mine_num++;
}
if (cor_mine_num == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
return;
}
}
else if (show[x][y] == '@')
{
printf("输入坐标已被标记过,请重新输入!\n");
}
else
{
printf("输入坐标已被排查过,不是雷,无法标记,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
break;
case 3:
//printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
printf("请输入需要取消标记雷的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '@')
{
show[x][y] = '*';
DisplayBoard(show, ROW, COL);
mine_num--;
if (mine[x][y] == '1')
{
cor_mine_num--;
}
if (cor_mine_num == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
return;
}
}
else if (show[x][y] == '*')
{
printf("输入坐标未被标记过,无法取消标记,请重新输入!\n");
}
else
{
printf("输入坐标已被排查过,不是雷,无法取消标记,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
win = JudgeWin(show, ROW, COL);
} while (win > EASY_COUNT);
if (win == EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
//void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
//{
// //1.输入需要排查的坐标
// //2.检查坐标处是不是雷
// //(1)如果是雷,很遗憾,你被炸死了,结束游戏
// //(2)如果不是雷,统计此坐标周围八个坐标有几个雷,个数存储到show数组中,游戏继续
// int x = 0;
// int y = 0;
// int win = 0;
// int mine_num = 0;
// int input = 0;
// int cor_mine_num = 0;
// int first = 1;
// printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
//
//
// while (win < row * col - EASY_COUNT)
// {
// menu_mark();
// printf("请选择:>");
// scanf("%d", &input);
// switch (input)
// {
// case 1:
// //printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
// printf("请输入需要排查的坐标:>");
// scanf("%d%d", &x, &y);
// if (x >= 1 && x <= row && y >= 1 && y <= col)
// {
// if (mine[x][y] == '1')
// {
// if (first == 1)
// {
// FirstMine(mine, x, y);
// DisplayBoard(mine, ROW, COL);
// OpenMine(mine, show, x, y);
// DisplayBoard(show, ROW, COL);
// first--;
// break;
// }
// if (first == 0)
// {
// printf("很遗憾,你被炸死了,游戏结束!\n");
// DisplayBoard(mine, ROW, COL);
// return;
// }
// }
// else
// {
// first = 0;
// win = OpenMine(mine, show, x, y);
// DisplayBoard(show, ROW, COL);
//
// }
// }
// else
// {
// printf("输入非法坐标,请重新输入!\n");
// }
// break;
// case 2:
// //printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
// printf("请输入需要标记雷的坐标:>");
// scanf("%d%d", &x, &y);
// if (x >= 1 && x <= row && y >= 1 && y <= col)
// {
// if (show[x][y] == '*')
// {
// show[x][y] = '@';
// DisplayBoard(show, ROW, COL);
// mine_num++;
// if (mine[x][y] == '1')
// {
// cor_mine_num++;
// }
// if (cor_mine_num == EASY_COUNT)
// {
// printf("恭喜你,排雷成功!\n");
// DisplayBoard(mine, row, col);
// return;
// }
// }
// else if (show[x][y] == '@')
// {
// printf("输入坐标已被标记过,请重新输入!\n");
// }
// else
// {
// printf("输入坐标已被排查过,不是雷,无法标记,请重新输入!\n");
// }
// }
// else
// {
// printf("输入非法坐标,请重新输入!\n");
// }
// printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
// break;
// case 3:
// //printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
// printf("请输入需要取消标记雷的坐标:>");
// scanf("%d%d", &x, &y);
// if (x >= 1 && x <= row && y >= 1 && y <= col)
// {
// if (show[x][y] == '@')
// {
// show[x][y] = '*';
// DisplayBoard(show, ROW, COL);
// mine_num--;
// if (mine[x][y] == '1')
// {
// cor_mine_num--;
// }
// if (cor_mine_num == EASY_COUNT)
// {
// printf("恭喜你,排雷成功!\n");
// DisplayBoard(mine, row, col);
// return;
// }
// }
// else if (show[x][y] == '*')
// {
// printf("输入坐标未被标记过,无法取消标记,请重新输入!\n");
// }
// else
// {
// printf("输入坐标已被排查过,不是雷,无法取消标记,请重新输入!\n");
// }
// }
// else
// {
// printf("输入非法坐标,请重新输入!\n");
// }
// printf("共有雷个数为:%d,当前标记雷个数为:%d,成功标记雷的个数为:%d\n", EASY_COUNT, mine_num, cor_mine_num);
// break;
// default:
// printf("选择错误,请重新选择!\n");
// break;
// }
// }
// if (win == row*col-EASY_COUNT)
// {
// printf("恭喜你,排雷成功!\n");
// DisplayBoard(mine, row, col);
//
// }
//}
#include
#include
#include
#define ROW 9 //布置雷数组的行,打印棋盘的行
#define COL 9 //布置雷数组的列,打印棋盘的列
#define ROWS ROW+2//真实创建的数组的行
#define COLS COL+2//真实创建的数组的列
#define EASY_COUNT 10//雷的个数
//初始化棋盘
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 mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
目前,优化后的扫雷游戏在功能上跟网上的扫雷游戏基本一样。优化版新增三个功能:防止第一次踩雷、展开棋盘和标记雷、取消已标记雷。部分功能给出了多种实现方法。本篇文章内容有点多,若有错误,恳请各位读者及时指出,感谢。