《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。使用C语言来实现扫雷是对基础代码能力的测试。本篇来分享一下如何写扫雷(9*9方格)。(含源代码)
一.游戏的规则
我们要写扫雷,就必须了解扫雷的游戏规则。扫雷是由许多方格组成,方格中随机分布着雷(1个格子最多只有1个雷)。赢下扫雷游戏的规则是把所有的安全方格找出,失败的游戏规则是踩到雷。
二、扫雷所需文件
一个扫雷游戏代码繁杂,所以我们建议使用分文件编写。
什么是分文件编写?
就是我们将代码放在不同文件,这样就不会把所有代码放在主文件中。这样做的好处就是:方便调试、主程序看着简洁。
我将程序分为三个文件,即下图:
2-1扫雷所需文件
那我们该怎么建立文件?
2-2建立文件
如上图所示,头文件中创建game.h>来声明函数。
源文件我们建立两个:game.c>用来函数具体实现、test.c>主体。
三、菜单的创建
我们首先创建一个操作菜单menu来实现进入退出游戏的功能。
3-1菜单的创建
然后将它封装在一个循环里面,在这里我们使用do....while循环来实现游戏的循环,用switch来进行选择。
3-2菜单的创建及使用
四、游戏数据分析
1.棋盘分析
因为我们要实现一个9*9的方格,所以我们很容易想到创建一个9*9的方格来充当棋盘
4-1创建空棋盘
我们将1当作雷,将0当成空格子(没雷)。例如下图:
4-2创建棋盘
假如我们以(2,4)当成研究对象:
4-3数据分析
在(2,4)周围的八个方格中我们可以看到有一个雷。 而当我们以(7,0)当成研究对象的时候我们可以发现,数组越界了。数组越界会带来意想不到的错误。那我们该怎么创建棋盘呢?经过思考后,我们将棋盘创建成11*11比较好。
4-4创建11*11棋盘
我们在浅绿色的格子里放入0(没雷),我们在设计的时候,给数组扩大一圈,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就行,这样就解决了越界的问题。所以我们将存放数据的数组创建成11*11是比较合适。
我们继续分析,我们在棋盘上布置了雷,棋盘上有雷和(1)和非雷(0)的信息。假设我们排查了一个方格,该方格没有雷,但是这个方格的周围有一个雷(例如图4-3),那我们需要将排查出雷的信息储存并显示出来,这个信息就是我们排雷的重要信息。那这个方格周围雷的信息我们要怎么显示?我们是直接显示在这个方格内?还是怎么显示?
如果我们直接显示在此方格内,该方格显示为1(如下图)
4-5数据分析
为了容易分辨我们用一来表示此方格周围有一个雷
而我们将1当成雷来使用,这样雷的信息会和周围雷的个数混淆。
我们有很多方法来解决这个问题。例如:雷和非雷的信息不要使用数字,使用某些字符就行,这样就避免冲突了,但是这样做棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。
在这里,我们采用另外一种方法我们有很我们专门给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不干扰了,把雷布置到 mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期 排查参考。 同时为了保持神秘,show数组开始时初始化为字符 '*',为了保持两个数组的类型⼀致,可以使⽤同一套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。
4-6 mine棋盘
4-7 show棋盘
这里两个棋盘对应的数组是:
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查好的雷的信息,用于打印。
//此处的ROWS和COLS在game.h文件中均定义过。
五、游戏具体实现
5.1定义行和列
我们在写扫雷程序时,由于行和列的频繁使用,所以我们在game.h文件中宏定义:
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
上面的ROWS和COLS是为了防止数组越界访问。
5.2定义难度
我们在game.h中创建难度:
//棋盘难度
#define EASY_COUNT 10 //初期的是9 * 9的方格,一共10颗雷
#define MIDDLE_COUNT 40 //中级是16 * 16的方格,40颗雷。
#define HARD_COUNT 99 // 高级则是16 * 30的方格99颗雷
我们以9*9为例
5.3创建棋盘
我们在game.h中创建棋盘:
//棋盘的初始化
void InitBorad(char board[ROWS][COLS], int rows, int cols ,char set);
我们在game.c文件中实现创建棋盘:
//棋盘的创建
void InitBorad(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;
}
}
}
我们在test.c文件中实现调用:
//初始化棋盘
InitBorad(mine, ROWS, COLS, '0');
InitBorad(show, ROWS, COLS, '*');
5.4打印棋盘
同样道理,我们在game.h文件中声明打印棋盘函数:
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
我们在game.c对打印棋盘函数进行实现:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//打印列号
for (i = 0; i <= row; 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");
}
}
我们在test.c函数调用:
//玩家看到的棋盘
DisplayBoard(show,ROW,COL);
//布置雷
SetMine(mine, ROW, COL);
//必须先布置雷,原棋盘才能显示雷
//展示出原棋盘,类似开挂
//DisplayBoard(mine, ROW, COL);
5.5布置雷
在game.h文件声明布置雷函数:
//布置雷
void SetMine(char borad[ROWS][COLS], int row, int col);
在game.c实现布置雷的函数 :
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;//0~8+1-->1~9
int y = rand() % col + 1;//同上
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
在test.c对函数进行调用:
//布置雷
SetMine(mine, ROW, COL);
5.6排查雷
在game.h文件中声明排查雷的函数:
//排查雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
在game.c文件中实现排查雷函数功能:
//排查雷
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[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;
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
{
if (show[x][y] != '*')
{
printf("该坐标已被排查过,无需再次排查\n");
}
else
{
int count = GetMineCount(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);
}
}
在test.c文件中调用排查雷函数:
//排查雷
FindMine(mine, show, ROW, COL);
六、game()函数实现
我们将以上代码放在game()函数里面。
6-1game函数实现
七、游戏运行
运行程序出现菜单:
7-1菜单
我们输入1开始游戏 :
7-2运行游戏
PS:由于我写了个清屏函数,它之前会删除掉。
我们输入想排查坐标:(1,1)
7-3运行游戏
他的结果如下图:
7-4运行游戏
我们直接打开原棋盘,测试踩到雷是否会停止游戏:
7-5测试踩雷
我们打开原棋盘:
7-6测试踩雷
我们可以看到 (3,1)有雷,那我们输入坐标(3,1)来看程序是否停止。
7-7程序停止
看到这里,本次分享扫雷程序已经结束,以上结果都是在本人电脑环境下展示出来的。如有错误,请给予指正。
另外,很期待和大家的私信交流。