扫雷游戏的功能说明
使用控制台实现经典的扫雷游戏
游戏可以通过菜单实现继续玩或者退出游戏
扫雷的棋盘是9*9的格子
默认随机布置10个雷
可以排查雷
· 如果位置不是雷,就显示周围有几个雷。
· 如果位置是雷,就炸死游戏结束
· 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
你可以在c语言扫雷中获得源代码,喜欢的顺手点个star吧!!
1. 游戏的分析与设计
数据存储
我们以 9*9 的棋盘举例进行分析:
首先,我们需要一个二维数组来存储哪些地方有雷,但这个二位数组应该是多大的?不妨先初始化一个9行9列的二维数组进行尝试。
int arr[9][9] = { 0 };
我们先跳过埋雷这一步骤,先思考 Print 这个函数该如何实现。
在埋雷时,我们将有雷的地方在数组中赋值为1,这样在打印棋盘的时候就只需要统计被点开位置周围 9*9 位置中1的个数就可以了。
那么这其中有两个问题:
1. 棋盘边缘位置的数字该如何统计?
2. 打印棋盘时不能将哪里有雷告诉玩家,应该如何实现?
我们逐一来解决:
- 我们确实可以通过一些复杂的判断来使这些边缘部位的数字统计时不会越界访问,但我想提供一种更简单的办法:无论棋盘大小为多少,都在棋盘的上下左右加上一行(列)0,这样,在数字计算时就不会发生越界访问,且不会影响数组的准确性。
- 这里我们肯定有办法解决,比如雷的息不要使用数字,使用某些字符就行,这样就避免冲突了,但是这样做棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。
这里我们采用另外一种方案,我们专门给一个棋盘(对应一个数组mine)存放布置好的雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排查出的雷的信息。这样就互不干扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘,show数组开始时初始化为字符*,为了保持两个数组的类型一致,可以使用同一套函数处理,mine数组最开始也初始化为字符 0 ,布置雷改成 1 。如:
char mine[11][11] = { 0 };
char show[11][11];//先省略其初始化为'*'的步骤
多文件格式
扫雷是一个项目,我们可以使用多文件存储代码来使代码逻辑性更强
test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等
2. 菜单打印与基本程序框架
菜单
由于本程序采用控制台终端进行输出,因此在游戏最开始打印一个菜单对玩家进行提示是非常有必要的!
我们可以将其封装在 *menu()*函数中以方便调用。
注:menu函数声明再 game.h 中,实现在 game.c 中,调用在 test.c 中。
可以简单地做一个这样的菜单对玩家进行提醒。
基本框架
我们可以通过一个 input 变量存储玩家的输入,以此进行游戏是否进行的判断。
另外,游戏显然应该可以进行多轮,且第一次输入input一定会进行,因此,我们可以采用 do…while循环来控制整个程序。
在循环内部,我们还可以用switch对input进行分类讨论,样板如下:
//test.c,main函数中
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();//此处进行游戏(此时尚未实现game函数)
break;
case 0:
printf("游戏退出\n");
// input 为 0,循环退出,即游戏结束
break;
default:
printf("输入错误,请重新输入\n");
// input != 0,本次循环结束,但循环继续进行
break;
}
} while (input);
3. game函数实现
布雷
接下来我们实现 game 函数:
//test.c(game函数可以放在test.c中,但其他函数应该定义再其他文件中)
char mine[11][11] = { 0 };
char show[11][11] = { 0 };//先省略其初始化为'*'的步骤
inititshow(show);//初始化 show
inititmine(mine);//埋雷
接下来实现 initishow和initimine函数
//game.h
void initishow(char* show[COLS],int row,int col);
void initimine(char* mine[COLS],int row,int col,int num_mine);
注:这里char* show[COLS]
是对二维数组传参的一种写法,如果你对此并不了解,可以使用
char show[ROWS][COLS]
,我会在将来的指针博客中详细解释这种写法!
那么在实现这两个函数之前,我们不妨先定义两个宏。
为了在之后更方便对游戏进行修改,不妨将一些数值设置为宏,如:
//game.h(以9*9棋盘为例)
#define EASY_COUNT 10//雷的数量
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
initishow的实现较为简单,先实现:
//game.c
void initishow(char* show[COLS], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
show[i][j] = '*';
}
}
}
接下来是initimine,这个要复杂一些,为了保证每次生成的雷的位置不相同,我们需要使用随机数,如果你不了解随机数,你可以在猜数字详解的开篇部分了解,这里不再赘述。
//game.c
void initimine(char(*mine)[COLS], int row, int col, int num_mine)
{
for (int i = 0; i < row + 2; i++)
{
for (int j = 0; j < col + 2; j++)
{
mine[i][j] = '0';
}
}
srand(time(NULL));
int count = 0;
while(count<num_mine)
{
int r = rand() % row + 1;
int l = rand() % col + 1;
if (mine[r][l] != '1')
{
mine[r][l] = '1';
count++;
}
}
}
为例便于观察上面的代码是否正确,同时也为接下来的棋盘打印做准备,我们可以实现一个 Print 函数。
//game.h
void Print(char(*tmp)[COLS], int row, int col);
//game.c
void Print(char(*tmp)[COLS], int row, int col)
{
//注意:由于采用控制台进行游戏,因此要打印行/列方便玩家选择
//同时,由于玩家不一定懂编程知识,因此行/列都要从1开始以符合直觉
printf(" -----扫雷-----\n");
for (int i = 0; i < col - 1; i++)
{
printf("%d ",i);
}
printf("\n");
for (int i = 1; i < row - 1; i++)
{
printf("%d ", i);
for (int j = 1; j < col - 1; j++)
{
printf("%c ", tmp[i][j]);
}
printf("\n");
}
printf(" -----扫雷-----\n");
}
//test.c
Print(show, ROWS, COLS);
Print(mine, ROWS, COLS);//当然,对mine的打印只是为了观察,后续应该删除或注释掉
可以观察:
雷的数量是否正确
雷是否会在边缘生成(即随机生成的代码是否正确)
排雷
至此,前部分任务已完成,接下来是进行排雷:
//test.c game函数中
while (1)
{
int c = 0;
int r = 0;
scanf("%d %d", &r, &c);
if (r <= 9 && r >= 1 && c <= 9 && c >= 1)
{
if (mine[r][c] == '1')
{
printf("游戏失败!!!\n");
break;
}
else
{
cult(mine, show, r, c);//cult计算点击位置的数字
Print(show, ROWS, COLS);
}
}
else
printf("输入非法,重新输入。\n");
}
接下来实现cult函数。
这个函数的原理就是统计mine数组中,点击坐标的周围九宫格中‘1’的个数,并放入show数组的相应部位。
//game.c
void cult(char(*mine)[COLS], char(*show)[COLS], int r, int c)
{
char num = '0';
for (int i = r - 1; i <= r + 1; i++)
{
for (int j = c - 1; j <= c + 1; j++)
{
if (mine[i][j] == '1')
num++;
}
}
show[r][c] = num;
}
胜利判定
接下来,我们要实现游戏胜利的判定:
如何实现?我们可以计算玩家合法输入的次数,如果玩家把所有的非雷区域全部点开,游戏胜利。
改造后的game函数:
void game()
{
char mine[11][11] = { '0' };
char show[11][11] = { '0' };//先省略其初始化为'*'的步骤
initishow(show,ROWS,COLS);//初始化 show
initimine(mine,ROW,COL,EASY_COUNT);//埋雷
Print(show, ROWS, COLS);
Print(mine, ROWS, COLS);//当然,对mine的打印只是为了观察,后续应该删除或注释掉
int win = 0;//胜利判定变量
while (1)
{
int c = 0;
int r = 0;
scanf("%d %d", &r, &c);
if (r <= 9 && r >= 1 && c <= 9 && c >= 1)
{
if (mine[r][c] == '1')
{
printf("游戏失败!!!\n");
Print(mine, ROWS, COLS);//本次游戏结束,不妨将mine打印出来给玩家看
break;
}
else
{
cult(mine, show, r, c);
Print(show, ROWS, COLS);
win++;//合法点击,win++
}
}
else
printf("输入非法,重新输入。\n");
if (win == COL * ROW - EASY_COUNT)//判定游戏是否胜利
printf("游戏胜利!!!'\n");
}
}
那么至此,我们已经完成了扫雷游戏的基本框架。
4. 拓展
1.是否可以选择游戏难度
简单 9*9 棋盘,10个雷
中等 16*16棋盘,40个雷
困难 30*16棋盘,99个雷
2.如果排查位置不是雷,周围也没有雷,可以展开周围的一片
3.是否可以标记雷
4.是否可以加上排雷的时间显示
当然,使用c语言在控制台中进行游戏,其中的一些功能并不好实现,你可以尝试实现2和3。
当然,在上面的这个框架上,如果实现了3,就可以更换胜利判定为将所有的雷都被标记。
谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
你可以在c语言扫雷中获得源代码,喜欢的顺手点个star吧!!