首先我们来看一下扫雷游戏是什么样子的
可以看到,它首先生成的是一个有9*9的格子的图,而且是看不到雷的
随便点一个地方后,就会变成这样
格子上的数字是提示以此格子为中心向外扩散一圈后查找到的雷的个数,当目标坐标附近一圈内有n给雷时,它就会打印n
当你点到雷后
它会直接结束游戏,并显示全部雷的位置
一个扫雷游戏的具体流程是这样了,接下来是内部的分析
分析:
棋盘
通过游戏截图可以知道,棋盘是一个9*9的棋盘,所以需要创建一个二维数组用来存储棋盘。
初始棋盘上是什么都没有显示的,所以我们可以用一些字符来当初始界面,我是用的 '*',当然也可以使用其他字符,不影响。
然后就是布置雷,我们假设这里的雷为 '1',若要在这里将 '1' 和 '*' 全部丢进一个棋盘,会直接将雷显示出来,所以我们就再创建一个用来布置雷的棋盘,但它不会打印出来,而是与初始棋盘来对比,这样就不会开局即结束。
布置两个棋盘还有一个好处:
可以看到下面这个界面
如果只有一个棋盘,将很难判断上面的 '1' 到底是雷还是雷的个数。
另外,我们根据上图发现,如果你要查找边缘的格子附近的雷时,会造成越界访问,因为我们要查找时会查找目标坐标周围一圈的格子
会变成这样
我这里就用3*3的表格举例子,9*9是一样的
那么我们要怎么解决这个问题呢,很简单,只要给它外面再加一圈就可以了,让它从9*9的数组变成11*11的数组,但我们只用里面的9*9即可
就像这样
9*9方法一样
黄色的一圈是加上去的,用来防止越界访问的。
---------------------------------------------------------------------------------------------------------------------------------
开始:敲代码
首先,我们要先打印菜单,玩家根据需求决定是否游玩游戏
菜单代码如下:
#include <stdio.h>
void menu()
{
printf("********************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("********************\n");
}
void game()
{
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
break;
}
case 0:
{
printf("退出游戏\n");
break;
}
default:
{
printf("输入错误\n");
}
}
} while (input);
return 0;
}
效果如下:
简易的界面就这样完成了,后面会逐步为game()函数添加内容,用来实现游戏功能
打印棋盘
首先我们创建2个数组,用来存储棋盘
void game()
{
char show[11][11] = { 0 };
char mine[11][11] = { 0 };
}
然后初始化棋盘:
雷的棋盘全部初始化为 '0' 玩家看到的棋盘初始化为 '*'
void game()
{
char show[11][11] = { 0 };
char mine[11][11] = { 0 };
//初始化棋盘
board(mine, 11,11,'0');
board(show, 11,11,'*');
}
board()函数的实现:(数组,行,列)
void board(char board[11][11], int r, int c,char p)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
board[i][j] = p;
}
}
}
接下来就是打印棋盘:
void game()
{
char show[11][11] = { 0 };
char mine[11][11] = { 0 };
//初始化函数
board(mine, 11,11,'0');
board(show, 11,11,'*');
//打印个棋盘
display_board(mine, 9, 9);
display_board(show, 9, 9);
}
display_board()函数的实现:(数组,行,列) -->这里的行和列是指我们要用到的,所以不是11
void display_board(char board[11][11], int r, int c)
{
printf("-------扫雷--------\n");
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
效果如下:
但这个效果,如果要去找对应的目标坐标有点麻烦,所以给打印上序列号
优化后的display_board()函数和效果图:
效果满意后,就将打印雷的函数隐藏起来,因为在游戏中不需要看见雷的棋盘,注释掉即可。
布置雷
我们打印好棋盘后,就要开始布置雷了。
在简单模式内,我们是需要布置10个雷的
void game()
{
char show[11][11] = { 0 };
char mine[11][11] = { 0 };
//初始化函数
board(mine, 11,11);
board(show, 11,11);
//打印棋盘
//display_board(mine, 9, 9, '0');
display_board(show, 9, 9, '*');
//布置雷
Put_mine(mine, 9, 9);
}
Put_mine()函数的实现:(雷的数组,行,列)
void Put_mine(char mine[11][11], int r, int c)
{
int Mine = 10;
while (Mine)
{
//生成1~9的随机数
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
Mine--;
}
}
}
srand((unsigned int)time(NULL)); //还要这段代码加在 main() 函数内的代码的最前面
在Put_mine()函数内用到了 rand() 、 srand() 、time() 这三个函数,下面分别来解释一下在此处的作用
1.rand()函数:用来生成随机数,但想要使用,得先设置好种子,不然生成的随机数是有规律的
2.srand()函数:用来设定种子,但想使用,得要传会变化的值,它所需要的值的类型
是 unsigned int 类型,所以用 (unsigned int) 进行强制转换
3.time()函数:用来生成时间戳,时间戳会随时间的变化而变化,NULL表示传一个空值,在不确定要传什么值进去时用,返回的值是 time_t 类型的。
上面3个函数的使用得导入头文件:
#include <stdlib.h> //rand()和srand()的头文件
#include <time.h> //time()的头文件
来查看一下效果:
可以看到,效果没有问题,有好好的将10个雷布置上去
排查雷
把雷布置完后,我们就是开始排查雷了
void game()
{
char show[11][11] = { 0 };
char mine[11][11] = { 0 };
//初始化函数
board(mine, 11,11,'0');
board(show, 11,11,'*');
//打印棋盘
//display_board(mine, 9, 9);
display_board(show, 9, 9);
//布置雷
Put_mine(mine, 9, 9);
//display_board(mine, 9, 9);
//查找雷
Find_mine(show, mine, 9, 9);
}
Find_mine()函数的实现:(玩家展示的数组,雷的数组,行,列)
void Find_mine(char show[11][11], char mine[11][11], int r, int c)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入要排查的坐标:>");
scanf("%d,%d", &x, &y);
if ((x > 0 && x < 10) && (y > 0 && y < 10)) //判断输入的坐标是否合法
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
overboard(mine, show, r, c);//被炸死后显示全部雷的位置('X'表示)
break;
}
if (show[x][y] == '*')
{
int count = Count_mine(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';
}
else
show[x][y] = count + '0';//转换成数字字符
display_board(show, 9, 9);
}
else
{
printf("输入坐标已排查过,请重新输入\n");
continue;
}
}
else
{
printf("输入坐标不存在,请重新输入\n");
}
}
}
效果如下:
overboard()函数:(只是额外的一个打印函数,用来将雷的位置打印到玩家的棋盘上)
void overboard(char mine[11][11], char show[11][11], int r, int c)
{
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
if (mine[i][j] == '1')
{
show[i][j] = 'X';
}
}
}
display_board(show, 9, 9);
}
Count_mine()函数是用来统计个数的:
int Count_mine(char mine[11][11], int x, int y)
{
int count = 0;
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
count += mine[x + i][y + j];
}
}
return (count - (9 * '0'));
}
这个函数的原理是,将一个坐标附近一圈(包括自身)全部加起来,然后减去 9*'0'
用循环将全部的位置是的值加起来,因为是字符型的值:'1' - '0' = 1[数字] 所以将全部的值加起来后减去 9*'0' 就可以得到数字型的个数。在将值返回出去后,再加上 '0' 就能再次得到字符型的数值。
判断是否扫雷成功
int ret = Iswin(show, 9, 9);
if (ret == 10)
{
printf("排雷成功\n");
overboard(mine, show, r, c);
break;
}
这段代码是加入在 Find_mine() 函数内的 while 循环的尾部
Iswin()函数:
int Iswin(char show[11][11],int r,int c)
{
int count = 0;
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
原理是统计玩家棋盘是还留有多少个 '*' ,当留有的 '*' 的个数与 雷的个数 相同时,即为扫雷成功
下面来实验一下 [将雷调成了80个来直接试验]
实验结果没问题,到这里,一个简易的扫雷游戏也就做完了。
一点优化
当然,这个简易版本还有很多可以优化的地方,比如:
如果 9*9 玩腻了,想玩更大的棋盘怎么办,要一个一个改吗?那样会很麻烦。我们可以直接用 #define 来定义几个字符
#define col 9
#define row 9
#define COLS col+2
#define ROWS row+2
像这样后,再将上面用到的数字全部修改为对应的符号即可
修改完后,再需要修改棋盘大小时,直接修改定义的字符后面的数字即可,不用到处去找数字,然后修改。
如果还嫌雷的个数太少,也可以用上面的方法来进行方便的修改
已上就是一个最简易的扫雷的实现,感谢你的观看。