扫雷游戏的扩展
1.是否可以选择游戏难度
◦ 简单 9*9 棋盘,10个雷
◦ 中等 16*16棋盘,40个雷
◦ 困难 30*16棋盘,99个雷
2.如果排查位置不是雷,周围也没有雷,可以展开周围的一片
3.是否可以标记雷
4.是否可以加上排雷的时间显示
上面四个扩展的操作篇来啦嘿嘿!!!
初级篇在这里!!!:
如何以一个正常人的思维渐进过程实现编码扫雷游戏(初级版)-CSDN博客
扩展1:是否可以选择游戏难度
◦ 简单 9*9 棋盘,10个雷
◦ 中等 16*16棋盘,40个雷
◦ 困难 30*16棋盘,99个雷
很简单,只要改变ROW和COL和EASY_COUNT的值即可,
具体是:在涉及到雷个数的函数内设置雷的个数的形参,后面在test.c中写入不同的参数值,然后在开始游戏之前写个switch选择难度,不同分支下的雷数参数值不同。
扩展2:展开周围的一片
实现这一功能就不得不变化一下之前写的FindMine函数了(一点点变化),更重要的是编写ExplandDisplay函数的代码,使其嵌入到FindMine函数中。
大致思想:只要count=0就把周围8个坐标全部展开,如果还有count=0的继续展开,这就要用到
递归
在写主体代码的时候我遇到了
三个问题(第三个在下面):
第一个问题是:如果坐标(a,b)的count值等于0(即坐标(a,b)周围没有雷),展开周围8个坐标后发现坐标(c,d)也是count值等于0,那么调用自身(即递归一次),展开周围8个坐标,这样的话会把原先的坐标(a,b)又展开一遍,这样就陷入了
死循环。
解决方法:将已经排查过的且count=0的地方改为空格符,这样防止了下次碰到时的循环。判断条件有两个:一个是count=0,第二个是不为空格符。满足后将此地址改为空格符,然后进入下一次递归。
第二个问题是 :因为棋盘有边界,不断地递归扩展可能会触碰到边界,导致越界,不规则展开,
结果如下:
![88667fb057bb46f3a10a35a0b3f6edd3.png](https://i-blog.csdnimg.cn/blog_migrate/8b47f23efda2bea7b660c759416b18bb.png)
解决方法:
因此要设置好边界if (x >= 1 && x <= ROW & y >= 1 && y <= COL)
//防止递归时越界导致的不规则展开(这可不是在规定输入的坐标范围!!!!)
下面是ExplandDisplay函数的代码(方法1)(具体代码看下下面的)
void ExplandDisplay(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
extern int win;//二次声明
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//防止递归时越界导致的不规则展开
{
int count = GetMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
win++;
}
else
{
show[x][y] = ' ';
win++;
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] != ' ')
ExplandDisplay(mine, show, i, j);
}
}
}
else
{
return;
}
}
}
第三个问题在于:当FindMine函数调用ExplandDisplay函数时,如何使变量win地址固定(即使变量win在两个函数中通用)?其次还要求ExplandDisplay函数在每次递归时不重复初始化变量win?
刚开始我在想这个问题的时候,一直想用static关键词(static int win=0)把它放在ExplandDisplay函数中(这样能使win的值得到保留而不重复初始化),后来发现这样做只满足了第二个条件,变量win还是无法于FindMine函数中变化(因为作用域不同)
解决方法:
那么我就想到了统一作用域,也就是说将变量win设置为全局变量,然后在game.c的FindMine函数和ExplandDisplay函数中调用它,这样static的亲兄弟extern关键字完美解决了这个问题。
方法1:(记得在test.c文件中定义全局变量win=0)
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
extern int win;//一次声明
int x = 0;
int y = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
Display(mine, row, col);
break;
}
else
{
ExplandDisplay(mine, show, x, y );
Display(show, row, col);
}
}
else
printf("该坐标已经被排查了,重新输入坐标\n");
}
else
{
printf("输入语法错误,请重新输入\n");
}
}
if(win== row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
Display(mine, ROW, COL);
}
}
void ExplandDisplay(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
extern int win;//二次声明
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//防止递归时越界导致的不规则展开
{
int count = GetMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
win++;
}
else
{
show[x][y] = ' ';
win++;
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] != ' ')
ExplandDisplay(mine, show, i, j);
}
}
}
}
}
方法2:
在预习指针内容后,我又发现也可以通过指针将win的地址交给另一个变量pw,这样win在两个函数中都可以统一使用啦,ExplandDisplay函数要多定义一个*pw形参
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
int* pw = &win;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
Display(mine, row, col);
break;
}
else
{
ExplandDisplay(mine, show, x, y, pw);
Display(show, row, col);
}
}
else
printf("该坐标已经被排查了,重新输入坐标\n");
}
else
{
printf("输入语法错误,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
Display(mine, ROW, COL);
}
}
void ExplandDisplay(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pw)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//防止递归时越界导致的不规则展开
{
int count = GetMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
(*pw)++;
}
else
{
show[x][y] = ' ';
(*pw)++;
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
ExplandDisplay(mine, show, i, j, pw);
}
}
}
}
}
这两个方法都可以很好地实现扩展效果
扩展3:是否可以标记雷
标记雷需要创建一个函数并且将其和排查雷放在一起
代码如下:
void Flag(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
Display(show, row, col);
printf("请输入要标记的坐标:");
scanf("%d %d", &x, &y);
system("cls");
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y]=='!')
{
printf("该坐标已被标记,请重新输入\n");
}
else if (show[x][y] == '*')
{
show[x][y] = '!';
system("cls");
printf("标记成功\n");
Display(show, row, col);
}
}
else
printf("输入语法错误,请重新输入\n");
}
扩展4:计算游戏时间
start = clock();
game();
stop = clock();
printf("用时:%f秒\n", (double)(stop - start) / (CLOCKS_PER_SEC));
在game()开始的前后计算时间即可
其它扩展:
1.取消标记函数:
和标记函数相似:
void CancelFlag(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
Display(show, row, col);
printf("请输入要取消标记的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '!')
{
show[x][y] = '*';
system("cls");
printf("取消成功\n");
Display(show, row, col);
}
else
printf("该坐标未被标记,取消失败\n");
}
else
printf("输入语法错误,请重新输入\n");
}
2.爆炸函数:
效果还行哈哈哈
void Boom(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
system("cls");
printf("3");
Sleep(888);
system("cls");
printf("2");
Sleep(888);
system("cls");
printf("1");
Sleep(888);
system("cls");
int i, j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == '!')
{
int count = GetMineCount(mine, i, j);
show[i][j] = count + '0';
}
}
}
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
{
show[i][j] = '#';
}
}
}
Display(show, row, col);
printf("\n");
printf("炸弹为:#\n");
printf("你被炸死了\n");
}
完整源码:
1.game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 1
#include<time.h>
#include<stdlib.h>
#include<windows.h>
void InitBoard(char board[ROWS][COLS], int rows,int cols,char set);//初始化函数
void Display(char board[ROWS][COLS], int row, int col);//打印函数
void SetMine(char board[ROWS][COLS], int row, int col);//埋雷函数
int GetMineCount(char board[ROWS][COLS], int x, int y);//算雷函数
void FindMine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col);//找雷函数
void ExplandDisplay(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);//扩展函数
void Flag(char show[ROWS][COLS], int row, int col);//标记函数
void CancelFlag(char show[ROWS][COLS], int row, int col);//取消标记函数
void Boom(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//爆炸函数
//计时器内置
2.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void Display(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("---------------扫雷游戏---------------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char arr[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] =='0')
{
arr[x][y] = '1';
count--;
}
}
}
int GetMineCount(char board[ROWS][COLS], int x, int y)//显示周围雷的个数
{
return (board[x - 1][y] + board[x - 1][y - 1] + board[x][y - 1] + board[x + 1][y - 1] + board[x + 1][y]+
board[x + 1][y + 1] + board[x][y + 1] + board[x - 1][y + 1] - 8 * '0');
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
int operate = 0;
int* pw = &win;
while (win < row * col - EASY_COUNT)
{
printf("|------------------|\n");
printf("|--- 1.排查地雷 ---|\n");
printf("|--- 2.标记地雷 ---|\n");
printf("|----3.取消标记----|\n");
printf("|------------------|\n");
printf("\n");
printf("请选择你的操作:");
scanf("%d", &operate);
system("cls");
if (operate == 1)
{
Display(show, row, col);
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
Boom(mine, show, ROW, COL);
break;
}
else
{
system("cls");
ExplandDisplay(mine, show, x, y, pw);
Display(show, row, col);
}
}
else
printf("该坐标已经被排查了,重新输入坐标\n");
}
else
{
printf("输入语法错误,请重新输入\n");
}
}
if(operate==2)
Flag(show, ROW, COL);
if(operate == 3)
CancelFlag(show, ROW, COL);
}
if (win == row * col - EASY_COUNT)
{
system("cls");
printf("恭喜你,排雷成功\n");
Display(mine, ROW, COL);
}
}
void ExplandDisplay(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pw)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//防止递归时越界导致的不规则展开
{
int count = GetMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
(*pw)++;
}
else
{
show[x][y] = ' ';
(*pw)++;
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')//不能写成if (show[i][j] != ' ')否则会把标记也展开而消失
ExplandDisplay(mine, show, i, j, pw);
}
}
}
}
}
void Flag(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
Display(show, row, col);
printf("请输入要标记的坐标:");
scanf("%d %d", &x, &y);
system("cls");
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y]=='!')
{
printf("该坐标已被标记,请重新输入\n");
}
else if (show[x][y] == '*')
{
show[x][y] = '!';
system("cls");
printf("标记成功\n");
Display(show, row, col);
}
}
else
printf("输入语法错误,请重新输入\n");
}
void CancelFlag(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
Display(show, row, col);
printf("请输入要取消标记的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '!')
{
show[x][y] = '*';
system("cls");
printf("取消成功\n");
Display(show, row, col);
}
else
printf("该坐标未被标记,取消失败\n");
}
else
printf("输入语法错误,请重新输入\n");
}
void Boom(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
system("cls");
printf("3");
Sleep(888);
system("cls");
printf("2");
Sleep(888);
system("cls");
printf("1");
Sleep(888);
system("cls");
int i, j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == '!')
{
int count = GetMineCount(mine, i, j);
show[i][j] = count + '0';
}
}
}
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
{
show[i][j] = '#';
}
}
}
Display(show, row, col);
printf("\n");
printf("炸弹为:#\n");
printf("你被炸死了\n");
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
int win = 0;
void menu()
{
printf("---------------扫雷游戏---------------\n");
printf("******************************\n");
printf("************1.开始游戏********\n");
printf("************0.结束游戏********\n");
printf("******************************\n");
}
void game()
{
//创建11*11数组
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化11*11棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//setMine埋雷(9*9范围内)
SetMine(mine, ROW, COL);
//打印9*9棋盘
//Display(mine, ROW, COL);
Display(show, ROW, COL);
//(核心)排雷
FindMine(mine, show, ROW, COL);
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
printf("扫雷开始\n");
clock_t start,stop;
start = clock();
game();
stop = clock();
printf("用时:%f秒\n", (double)(stop - start) / (CLOCKS_PER_SEC));
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}