文章目录
前言
这是我写的第二个小游戏,前一个:
https://blog.csdn.net/m0_71914032/article/details/131730775?spm=1001.2014.3001.5501
二者都是棋盘格式的小游戏,有很多地方相同,诸如二维数组的运用以及棋盘表格的打印,这里就不多赘述了。
#1 结构体变量
对于棋盘的每一个格子,都需要储存独属于自身的数据,包括:
1.该格子是否有雷
2.该格子周围八个格子有多少雷
3.该格子是否被探查过
自带的数据类型不足以满足我们的需求,于是选用结构体类型。
typedef struct Grid
{
int mine_or_not;
int num_of_mine_around;
int search_or_not;
}Grid;
实际使用中几乎可以把结构体类型当作一个普通的类型,便于理解与使用。
#2 自定义常量
为了提升代码的灵活性和可调性,使用自定义常量。
#define ROW 9
#define COL 9
#define Mine 10
这样做的好处是可以方便的任意修改棋盘的大小,以及雷的数量。
只需在此处修改数字,方便快捷。
#3 所需函数
#3.1 棋盘初始化 InitBoard;
代码如下:
void InitBoard(Grid Board[ROW][COL])
{
int i = 0;
for (i = 0; i < ROW; i++)
{
int j = 0;
for (j = 0; j < COL; j++)
{
Board[i][j].mine_or_not = 0;
Board[i][j].num_of_mine_around = 0;
Board[i][j].search_or_not = 0;
}
}
}
将结构体内三个成员初始化为 0 ;
意味着一开始任何一个格子,都没有安放雷,周围八个格子都没有雷,以及都没有被探查过。
#3.2 随机生成雷 PlantMine;
代码如下:
void PlantMine(Grid Board[ROW][COL])
{
int i = 0;
int j = 0;
int num = Mine;
while (num)
{
i = rand() % 9;
j = rand() % 9;
if (Board[i][j].mine_or_not == 0)
{
Board[i][j].mine_or_not = 1;
// 上边
if (i - 1 >= 0)
{
Board[i - 1][j].num_of_mine_around += 1;
// 左上角
if (j - 1 >= 0)
{
Board[i - 1][j - 1].num_of_mine_around += 1;
}
// 右上角
if (j + 1 <= 8)
{
Board[i - 1][j + 1].num_of_mine_around += 1;
}
}
// 下边
if (i + 1 <= 8)
{
Board[i + 1][j].num_of_mine_around += 1;
// 左下角
if (j - 1 >= 0)
{
Board[i + 1][j - 1].num_of_mine_around += 1;
}
// 右下角
if (j + 1 <= 8)
{
Board[i + 1][j + 1].num_of_mine_around += 1;
}
}
//左边
if (j - 1 >= 0)
{
Board[i][j - 1].num_of_mine_around += 1;
}
//右边
if (j + 1 <= 8)
{
Board[i][j + 1].num_of_mine_around += 1;
}
num--;
}
}
}
此处不单单只是修改格子的 mine_or_not 至 1,表示该格子有雷;
还有顺便修改周围八个格子的 num_of_mine_around;
虽然麻烦,但是这一步是为了后续的方便。
#3.3 打印棋盘 ShowBoard;
代码如下:
void ShowBoard(Grid Board[ROW][COL])
{
// 第一行数字和分隔符
int i = 0;
for (i = 0; i <= ROW; i++)
{
printf(" %d ", i);
if (i < COL)
{
printf("|");
}
}
printf("\n");
for (i = 0; i <= ROW; i++)
{
printf("---");
if (i < COL)
{
printf("|");
}
}
printf("\n");
// 打印棋盘主体
for (i = 0; i < ROW; i++)
{
// 打印第一列数字
printf(" %d |", i + 1);
int j = 0;
for (j = 0; j < COL; j++)
{
//如果该格子未被探查过,则打印空格
if (Board[i][j].search_or_not == 0)
{
printf(" ");
}
else
{
//若该格子被探查过,而且不是雷,就打印出该格子周围八格的雷的数量
if (Board[i][j].mine_or_not == 0)
{
printf(" %d ", Board[i][j].num_of_mine_around);
}
else//否则说明踩到雷了,打印 ! 示意一下
{
printf(" ! ");
}
}
//分隔符
if (j < COL - 1)
{
printf("|");
}
}
printf("\n");
if (i < ROW - 1)
{
int j = 0;
for (j = 0; j <= COL; j++)
{
printf("---");
if (j < COL)
{
printf("|");
}
}
printf("\n");
}
}
}
该函数仅仅只是打印棋盘。
即根据每个格子目前的情况(是否被探查,探查后是否有雷)来打印棋盘。
并不对格子内的数据做修改。
修改部分放在下一个函数。
#3.4 探查坐标 SearchMine;
void SearchMine(Grid Board[ROW][COL], int row, int col, int* pcount)
{
//这个if语句用于判断所想要探查的坐标符不符合棋盘要求
//若不符合,相当于不执行该函数
if (row >= 1 && row <= 9 && col >= 1 && col <= 9)
{
//探查到已经探查过的格子时,直接返回
//如果没有这一步,将会导致死循环
if (Board[row - 1][col - 1].search_or_not == 1)
{
return;
}
else
{
//对于没有探查过的格子,先标记为探查过
Board[row - 1][col - 1].search_or_not = 1;
//如果没有雷,说明成功的探查到一个安全的格子,让count--
if (Board[row - 1][col - 1].mine_or_not == 0)
{
(*pcount)--;
}
else
//否则说明踩到雷了,修改count为 0,并且结束函数
{
*pcount = 0;
return;
}
}
//我们知道扫雷游戏种。如果你点到了周围没有雷的格子,会自动把相邻所有没有雷的格子全部揭示。
//接下来实现这一步
//这部分用到递归的思想,对于没有雷的格子,如果该格子周围一个雷都没有,则将周围八格全部探查
if (Board[row - 1][col - 1].num_of_mine_around == 0)
{
//这里不再需要考虑周围八格会不会越界,因为函数开头会检测
SearchMine(Board, row - 1, col - 1, pcount);
SearchMine(Board, row - 1, col, pcount);
SearchMine(Board, row - 1,col + 1, pcount);
SearchMine(Board, row,col - 1, pcount);
SearchMine(Board, row,col + 1, pcount);
SearchMine(Board, row + 1,col - 1, pcount);
SearchMine(Board, row + 1,col, pcount);
SearchMine(Board, row + 1,col + 1, pcount);
}
}
}
在该函数中,如果踩到雷了,count的值就会为 0;
反之,count的值将会-1,直到与雷的数量相等;
这两种count的最终值,用于区分胜负。
#3.5 胜负判断 Win_or_Lose;
游戏结束有以下两种情况:
#3.5.1 踩到雷了,游戏失败
当count = 0 的时候,说明此时游戏结束是因为踩到雷了;
#3.5.2 探查完所有安全的格子,扫雷成功
当count = Mine 的时候,说明此时游戏结束是因为所有雷的格子都已经被确定了
代码如下:
void Win_or_Lose(int* pcount)
{
if (*pcount == 0)
{
printf("你踩到雷啦,游戏结束\n");
exit(0);
}
if (*pcount == Mine)
{
printf("恭喜你,扫雷成功!\n");
exit(0);
}
}
这个函数实际上可以作为不一个函数存在,能够直接加在打印棋盘的函数最后;
但是为了每个函数功能分明单一,所以单独把这部分代码摘出来,另写一个函数。
结尾
总的来说,我认为扫雷游戏的难度是在N子棋之上的,前提是N子棋不涉及电脑下棋时的智能提升。
扫雷游戏的编写,用到了结构体二维数组,深度优先的递归探查等等没那么初级的知识点,
这些方面的要求自然是比N子棋高。
不过实际上我在编写扫雷代码上花的时间却比N子棋更少,这点还是让我觉得满欣慰的。
再接再厉。