C语言数组和函数实践:扫雷游戏
扫雷游戏的分析和设计
首先我们需要知道呈现给用户是什么样子的,这里给出我的模板:
首先在屏幕上输出一段菜单,在根据用户输入的值决定是否开始游戏。
游戏开始以后,让用户输入要查找的坐标,并给出其周围雷的个数,当用户排查完所有的雷或者被雷炸死,游戏结束,并再次询问用户是否继续游戏。
- 菜单的设计
如下所示
void menu() {
printf("---------1.play------\n");
printf("---------0.exit------\n");
return;
}
不再过多赘述这串代码
- 游戏界面的设计
我们呈现给玩家的是99大小的铺满号的未排雷区域,但是当用户输入坐标以后,怎么判断这个地方是不是雷呢?这个时候我们可以创造两个二维数组,一个存放雷,一个存放显示给用户的界面数组。我们将1和0变成字符‘1’和‘0’可以方便与‘*’进行操作。
代码如下:
void init1(char a[ROWS][COLS], int r, int c) {
///每个位置都设为'0'
for (int i = 0;i < r;i++) {
for (int j = 0;j < c;j++) {
a[i][j] = '0';
}
}
return;
}
void init2(char a[ROWS][COLS], int r, int c) {
for (int i = 0;i < r;i++) {
for (int j = 0;j < c;j++) {
a[i][j] = '*';
}
}
return;
}
这串代码实现了对两个数组的初始化,下一步我们要做的就是埋雷。
该如何埋雷呢?关键是要使雷随机分布并且数量确定。根据我前面文章介绍到的随机数生成函数以及随机种子生成可以实现这个功能。我们假设有10个雷。
void mailei(char a[ROWS][COLS],int r,int c) {
int count = 10;
while (count) {
int x = rand() % r + 1;
int y = rand() % c + 1;
if (a[x][y] == '1') {
continue;
}
else {
a[x][y] = '1';
count--;
}
}
return;
}
而**srand((unsigned int)time(NULL))**这个函数我们可以放在主函数当中。
**埋完雷后,我们就该把游戏界面输出到屏幕上了。**为了方便用户辨识坐标在第一行和第一列打印对应数字。
代码如下
void printlei(char a[ROWS][COLS], int r, int l) {
for (int i = 0;i <=l;i++) {
printf("%d ", i);
}
printf("\n");
for (int i = 1;i <= r;i++) {
printf("%d ", i);
for (int j = 1;j <= l;j++) {
printf("%c ", a[i][j]);
}
printf("\n");
}
}
但是这个时候我们要问了,当输入一个坐标后,程序该如何从埋雷数组对应坐标中计数并告诉用户呢?我们可以将这个坐标当作参数传递给埋雷数组并判断。
int leicnt(char mine[ROWS][COLS],int m,int q){
return mine[m - 1][q - 1] + mine[m - 1][q] + mine[m - 1][q + 1] + mine[m][q + 1] + mine[m + 1][q + 1] + mine[m + 1][q] + mine[m + 1][q - 1] + mine[m][q - 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;
while (win < row * col - 10)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
printlei(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int cnt = leicnt(mine, x, y);
if (cnt == 0) {
digui(show,mine,x, y);
}
else { show[x][y] = cnt + '0'; }
printlei(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,重新输入\n");
}
}
if (win == row * col - 10)
{
printf("恭喜你,排雷成功\n");
printlei(mine, ROW, COL);
}
}
这个函数就是游戏的主体。在用户输入坐标后,先判断是否合法,不合法就重新输入,合法就再根据此坐标是否是雷再选择是否调用leicnt函数,并改变展示数组的值,最终输出到屏幕上,而这里我们就会发现如果当周围雷的个数是0的时候,用户输入会繁琐,这里我们设计了一个递归函数,起名digui。
代码如下:
void digui(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y) {
if (x < 1 || x > ROW || y < 1 || y > COL || show[x][y] != '*') {
return;
}
int cnt = leicnt(mine, x, y);
if (cnt == 0) {
show[x][y] = ' ';
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
digui(show, mine, x + i, y + j);
}
}
}
else {
show[x][y] = cnt + '0';
}
}
首先是周围雷的个数是0才可以进入到这个函数,进入以后双层循环的功能是遍历坐标周围八个cnt的值,根据是否是0选择是否往深继续递归,如果不是0,就令展示数组的值改变,并最终在Findmine函数中打印展示数组
扫雷游戏的代码实现
之前我们学习了多文件的形式对函数的声明和定义,这里我们实践一下
game.c //⽂件中写游戏的测试逻辑
扫雷.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等
这里主要展示主函数中代码,放在game.h与game.c中的代码已在上面给出
int main() {
srand((unsigned int)time(NULL));
int input = 0;
do {
init1(mine, ROWS, COLS);
init2(show, ROWS, COLS);
menu();
scanf("%d", &input);
if (input == 1) {
mailei(mine,ROW,COL);
game();
}
else if (input == 0) {
break;
}
else {
printf("只能输入1或0,请重新输入:\n");
}
} while (input);
return 0;
}
ROWS,COLS,ROW,COL都是用#define 定义的
这样子我们就实现了一个简易版扫雷。下面我给出代码的运行实例
最后我们可以选择0退出游戏。
如有错误,恳请指正。