扫雷实现
文章从思路开始,再到代码的具体实现。个人认为,思路远比代码实现重要。有思路,就可以想方设法用代码实现。碰到的问题也可以通过思考解决。开始的时候不必照顾到所有细节,等实现的时候如果碰到问题,再去调整也是可以的。
大体框架
扫雷游戏比较简单,就是在一个格子上点击,这个格子如果不是雷,那么它会显示它周围的八个格子里有多少雷,如果是雷,那么就结束了。
游戏开始,可以用之前实现猜数字的方式进行,打印menu然后依据用户选择进行游戏或者退出。那么依据这个,就可以先写一个大的框架出来了:
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");
break;
}
} while (input);
return 0;
大框架中使用do...while
循环,打印menu
,根据用户选择使用switch
语句跳转。这里由于0
是退出游戏,正好可以作为do...while
循环的循环条件(非0继续,为0退出。)game();
作为游戏本体,目前是没有实现的,可以先放在这里。下一个小节会详细解析。
补充一下menu
:
void menu()
{
printf("******************\n");
printf("******1.play******\n");
printf("******0.exit******\n");
printf("******************\n");
}
game的实现
这里game可能会比较复杂,为了方便管理,给game本体创建一个新的源文件和头文件,并且在main函数所在的文件包含就可以了。
棋盘创建
扫雷的棋盘是一个m*n的方块,那么就可以想到使用二维数组来作为扫雷的棋盘。扫雷的游戏过程就是在对这个二维数组进行操作。需要创建这个二维数组,那么就得知道其行数和列数。这里就有新的问题了,扫雷的难度是根据行数、列数、雷的数量来决定的,那么为了方便后期修改,在创建二维数组的时候最好不要写死。另外,游戏在进行的过程中会统计某个位置周围8个位置的雷的数量。为了方便统计,肯定是想将这个数组初始化为全0的,然后在里面随机的加入1,最后只要统计某个格子周围1的数量就可以将统计值写到这个格子里。但这样就带来了一个问题。如果在棋盘上使用数字,统计的时候也是数字,那么其它格子在统计的时候就会再次统计已经计算过的数字,这样就非常的麻烦。为了解决这个问题,可以创建字符型的二维数组作为棋盘。同时,区分存储雷的二维数组和显示出来的二维数组。另外,如果按照棋盘大小创建数组的话,对于边缘位置的统计会比较麻烦,因此可以让棋盘大一点。总结下来:
- 使用字符型二维数组作为棋盘;
- 区分存储雷的二维数组和显示雷的二维数组;
- 使用变量来控制二维数组的大小和雷的数量,用于不同的难度;
- 数组的大小比棋盘大小大一圈;
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void game()
{
//数组的创建
char mine[ROWS][COLS];
char show[ROWS][COLS];
}
初始化棋盘
走到这里,下一步就是初始化棋盘。mine这个棋盘作为埋雷的棋盘,没有雷的位置规定为字符0
,有雷的位置规定为字符1
。show这个棋盘作为打印棋盘,均初始化为*
即可。为了使用1个函数来初始化所有的棋盘,可以这样设计初始化函数:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
这个函数有4个参数,分别是需要初始化的棋盘char board[ROWS][COLS]
,棋盘的大小(int rows, int cols
),初始化使用的字符set
。初始化之后,就需要打印一下棋盘,这样就会在屏幕上显示出来棋盘的界面了。那么game函数就可以写成:
void game()
{
//数组的创建
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine,ROWS,COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//棋盘的打印
Displayboard(mine, ROW, COL);
Displayboard(show, ROW, COL);
}
这里其实不需要打印mine,因为这个就是地雷的棋盘,只有游戏过关或者被炸死的时候才去打印。
这个初始化和打印应该如何实现呢?其实就是遍历数组。废话不说直接上代码:
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 Displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------扫雷游戏-----\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j=1;j<=col;j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("------扫雷游戏-----\n");
}
棋盘初始化之后,就可以开始布置雷了。
布置雷
布置雷就是在mine数组里随机的填上 EASY_COUNT
数量的地雷。函数设计为:
void Setmine(char mine[ROWS][COLS], int row, int col);
这个函数是需要随机的在mine
数组里写0。这里就需要用到rand
函数。所以同样涉及到srand
、time
。具体可以参考: 随机数生成这篇文章。这个函数在实现的时候还需要注意,已经被埋过雷的地方就不要再埋雷了,同时注意埋雷的范围。
具体实现如下:
void Setmine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count != 0)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
同时,修改game
函数:
void game()
{
//数组的创建
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine,ROWS,COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//棋盘的打印
//Displayboard(mine, ROW, COL);
Displayboard(show, ROW, COL);
//布置雷
Setmine(mine,ROW,COL);
}
布置完成之后,就可以考虑开始找雷了。
排查雷
找雷的时候,根据输入的坐标,分为以下情况:
- 坐标不是雷,则需要显示坐标周围8个方格内拥有雷的数量,同时打印
show
数组; - 所有的雷都找到了,游戏结束,同时打印
mine
数组; - 坐标是雷,炸死,游戏结束,同时打印
mine
数组;
这里是需要使用到mine、show
这两个数组的。因此,函数设计为:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
数雷
对于第一种情况,需要统计,这里也封装一个函数:
int GetMineCount(char mine[ROWS][COLS], int x, int y)
用于统计坐标为(x,y)处,周围8个方格内的雷的数量。具体实现如下:
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x - 1][y] +
mine[x + 1][y] +
mine[x - 1][y + 1] +
mine[x][y + 1] +
mine[x + 1][y + 1] - 8 * '0';
}
注意:
- mine数组时字符型的,需要转成数字;
- 坐标(x,y)周围的8个方块的坐标分别是什么(想不明白画个图就好了)。
获胜条件
扫雷获胜的条件是除过被埋雷的地方,其余方块都被打开了。这里我们可以设计一个变量win
,用于统计已经被打开方块的数量。当win == 棋盘大小-已经埋雷数量时,游戏获胜。
另外,在输入坐标的时候,也需要判断坐标是否合法。
代码实现
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')//mine数组里,字符1是地雷
{
printf("很遗憾,你被炸死了\n");
Displayboard(mine, ROW, COL);
break;
}
else//else说明不是雷且坐标合法
{
int count = GetMineCount(mine,x,y);
show[x][y] = count + '0';//这里是将统计出来的雷数量转换成字符,放在show数组里
Displayboard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,重新输入:>");
}
}
if (win == row * col - EASY_COUNT)//跳出循环的时候,要么被炸死,要么获胜。按照获胜条件写的判断一下即可。
{
printf("恭喜你,排雷成功\n");
Displayboard(mine, ROW, COL);
}
}
完成这个代码之后,扫雷游戏基本就实现了。当然,也需要更新一下game函数
:
void game()
{
//数组的创建
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine,ROWS,COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//棋盘的打印
Displayboard(show, ROW, COL);
//布置雷
Setmine(mine,ROW,COL);
//排查雷
FindMine(mine,show,ROW,COL);
}
运行效果
进阶
这个只是初始的游戏。有以下功能待实现:
- 标记雷;
- 如果点到的位置周围8个位置都没有雷,应该可以自动扩展出去;
这些功能会在学习完递归之后实现。