前言
在上一篇博客中我们实现了简单的三子棋小游戏,初步运用了二维数组,今天还是用二维数组来实现另一个小游戏——扫雷的基础版本,写这两个小游戏的目的主要是理解并运用数组,同时巩固前面学习的知识比如循环和函数等等,将其结合具体情况解决我们的需要。
代码实现
在这一部分我们会用代码实现扫雷,并理清游戏与代码的逻辑。扫雷的游戏界面如图
这是在网上找到的扫雷过程,从图中我们可以知道,扫雷的游戏界面可以用数组来打印、每选择一个点要查询周围八个点是否有雷,当附近有雷时我们要打印数字来提示玩家周围的雷的数量,当所有非雷的元素都被找出来游戏就结束。
游戏菜单
游戏菜单的打印和主函数的逻辑我们可以做成与三子棋一样,我们的主要精力还是要放在游戏的实现上,所以我们直接按照上次的代码写就行。
#include"game.h"
void Menu()
{
printf("*****************************\n");
printf("******** 1.play *******\n");
printf("******** 0.exit *******\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
do
{
Menu();
printf("请选择->\n");
scanf("%d", &input);
switch (input)
{
case 1:
//game();
printf("开始游戏\n");
break;
case 0:
printf("退出\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
数组创建与初始化
在这个程序里,我们如果要实现一个9*9的扫雷棋盘,我们需要创建两个数组,一个用来打印提示信息显示给玩家看,另一个用来存放雷的位置。而考虑到后面我们需要判断一个点周围是否有雷的情况,如果时9*9的数组,我们就要用很多个if语句来分情况遍历周围的点,为了后续排雷功能更好实现,我们直接在每一条边都再加上一行元素,这一行元素我们不显示,同时他们也不会是雷,这样我们所需要创建的数组就需要比游戏棋盘的行和列都加二。游戏界面的行和列和数组的行和列后序我们经常会用到的变量我们都在game。h中用define来定义。
然后我们在game函数里要创建两个数组。关于数组的类型,由于我们给玩家展示的棋盘我们要将没有被排到过的点显示为 ‘ * ’ ,所以我们这个showboard数组的类型必须是char类型,而存储雷的位置的数组mineboard我们也可以用char类型,这样我们只用写一个数组初始化函数就行了。
关于数组初始化,由于两个数组的类型都是char类型,在mineboard中我们用 ‘1 ’来表示雷,用
‘ 0 ’来表示没有雷。在showboard中,我们用‘ * ’来表示未被排查,用数字字符来表示周围雷的数量,如果周围没有雷,我们用空格来表示。于是我们就需要将mineboard数组初始化为 ‘ 0 ’,将showboard数组初始化成 ‘ * ’。这时候我们除了数组名,行和列以外,还要多加一个参数来指定初始化的内容。
void Initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i=0;i<rows;i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
在初始化这个函数中,我们传的参数是ROWS和COLS,这是因为我们要将整个数组全部初始化,如果不初始化后续会影响我们排雷。
打印游戏棋盘
在打印游戏棋盘的函数中,我们要传的是ROW 和 COL,而不是ROWS和COLS,因为数组里面有用的部分就是中间的9*9,外围的四排我们不用打印,这时候要注意下标的范围,行标和列标都是1~9。在我们打印的时候,由于行数和列数有点多,我们可以在上面一行和左边一列把行和列打印出来,更方便玩家选点。列的信息可以在循环中打印,行的信息可以在进去循环之前打印,我们还可以在棋盘的上下打印分隔线来划分每一次排雷的范围。
void Displayboard(char board[ROWS][COLS], int row, int col)
{
printf("-----------------------------\n");
int i = 0;
int j = 0;
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-----------------------------\n");
}
数组放雷
放雷这个功能我们还是用到随机数来实现,生成的随机数对9取模再加一,我们就能得到1~9的随机数,得到坐标后,我们要判断这个坐标是否已经放过雷。在这之前,我们要在game。h中用define定义雷的数量。同时,放雷的函数我们传的参数也是ROW和COL这个游戏界面的范围,也就是行列坐标都是0~9.
void GenerateMine(char mineboard[ROWS][COLS], int rows, int cols,int mine_count)//往棋盘中放雷
{
int x = 0;
int y = 0;
while (mine_count)
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mineboard[x][y] == '0')
{
mineboard[x][y] = '1';
mine_count--;
}
玩家排雷
我们可以在game函数中用一个while循环来接受玩家的输入信息,在接收到输入信息时,首先要判断坐标的合法性,坐标合法时再进入下一步,然后判断这个点是否已经被排查过了(是否为空格),如果没有被排查过再次判断这个点是否为雷,如果是雷,游戏结束,如果不是雷,则进入函数checkmine用于这个点的周围八个点的排查,checkmine函数要传刚刚输入的两个坐标和两个数组,ROW和COL,我们还要传一个用来记录已经排查的坐标的个数的参数,find_count,由于我们要在函数里对其进行修改,所以要传它的指针。
当我们要进到checkmine,首先,这个点已经确定是空格了,所以我们直接让count_find(当count_find==ROW*COL-MINE_COUNT时,游戏结束,所有非雷的坐标都被找了出来,玩家赢了)的值加1;先将其赋值为空格(如果周围有雷后面会改成雷的数量),然后我们要定义一个minecount来记录周围八个点的雷的数量,然后用两层循环遍历周围八个点。当minecount不为0时,也就是周围八个点存在雷,那么这次函数就只用将周围八个点中不是雷的点变成空格,每次变成空格是count_find+1。当minecount为0时,意味着周围八个点都不是雷,此时需要展开这个点,用两层循环将八个点遍历一遍,如果坐标合法,且不为空格和数字字符(是空格则说明已经被排查过了,不要再check),就将该点坐标传进check函数,这是地推的过程。每一个点当他周围有雷时就不再继续往周围递推了,这时候就会回归上一级函数,递归也就返回了。
递推或者说checkmine函数最需要注意的一点是mine_find的值,当showboard中每有一个值被改为空格(被排查),他就要加一,而且被改为空格后就不要再改会 ‘ * ’。我们只有在两个地方会将数组元素改为空格,一是该点进入check函数时,先将其改成空格,如果周围有雷再改成数字字符,二是当周围有雷时,我们要将周围八个点中不是雷的数组元素改为空格,因为他们要将被排查了,但是他们不用显示雷的数量。
void check(int x, int y, int* count_find, char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col)
{
*count_find=*count_find+1;
showboard[x][y] = ' ';
int minecount = 0;
int i = 0;
int j = 0;
//if (*count_find == row * col - MINE_COUNT)//这一行不用写,有没有不影响结果
// return;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (x + i >= 1 && x + i <= 9 && y + j >= 1 && y + j <= 9)
{
if (mineboard[x + i][y + j] == '1')
{
minecount++;
}
else
{
if (i != 0 || j != 0)
{
showboard[x + i][y + j] = ' ';
*count_find = *count_find + 1;
}
}
}
}
}
if (minecount == 0)
{
for (i = -1; i <= -1; i++)
{
for (j = -1; j <= 1; j++)
{
if(x+i>=1&&x+i<=row&&y+j>=1&&y+j<=col)
check(x + i, y + j, count_find, mineboard, showboard, row, col);
}
}
}
else
{
showboard[x][y] = minecount+'0';
}
}
运行截图
在测试时我们可以打印count_find来测试我们的count_find是否记录正确
被炸死
成功通关
测试的是75个雷,也就是只要排除7个点,我们先把雷的数组打印出来来测试能否检测赢得游戏
展开测试
将雷的数量设少一点来测试递归。
这时候图上还有十一个' * ',说明count_find的统计也没有问题。
结语 求点赞!
扫雷这个游戏的checkmine函数真的卡了很久,制作不易,感谢各位的观看,如果感觉对你有帮助,可以点上一个免费的赞,这对作者的创作来说是一个很大的鼓励,感谢各位的阅读。