目录:
一、扫雷游戏实现的思路分析
1、对扫雷游戏的构思
1.1对整个游戏的构思
1.2棋盘的设想
1.3关于布雷的构思
1.4关于排雷
二、实现扫雷游戏
1.目录的创建
2.game():开始游戏
2.1宏定义
2.2初始化和打印
2.3开始布雷
2.4排雷
正文:
一、扫雷游戏实现的思路分析
1、对扫雷游戏的构思
相信扫雷这种小游戏我们大家都玩过,现在我们也可以在网上搜到扫雷的小游戏:
那么我们如何实现一个最简单的基础扫雷呢?
1.1对整个游戏的构思
首先,要想扫雷我们得有地方扫,因此我们需要先构建一个棋盘,因为本文实现的是最简单的基础扫雷,我们的目标就是实现一个9*9的棋盘(当然其他大小的棋盘的实现与9*9的无异)。
其次,扫雷游戏我们是不是需要布置雷?
布置雷后我们是不是要排查雷?如果排查的这个地方不是雷,我们是不是还要将它周围的8个坐标检查一下,并返回地雷的个数。
如果我们排查的地方是雷,就被炸死,游戏也就随之结束了,如果我们最后把所有雷都排查出来了,这个游戏也就随之结束了。
在搞清楚基本的设计思路后,我们继续对各个环节进行构思。
1.2棋盘的设想
扫雷首先我们需要在一定的范围内去排查雷,那么我们首先就要想好如何去构建一个棋盘。在本文中,我们使用一个二维数组(board[][]),来构建我们的棋盘。因为是一个9*9的棋盘,那么我们是不是就写board[9][9]就可以了呢?
认为可以的朋友,我们先思考一个问题:
这样一个9*9的棋盘,当我们检查其中一个时,我们是不是还要检查周围的八个坐标 ?
如果是检查中央的坐标还好,如果是检查棋盘边缘的坐标呢?我们的棋盘是由二维数组构成的,如果检查边缘坐标可能就会引起越界的问题。那怎样解决呢?
实际上,我们可以将棋盘扩大一圈(也就是将将存放数据的数组创建成11*11)然后在实际的应用中我们只用其中的9*9的棋盘即可。
1.3关于布雷的构思
扫雷游戏,每次开始游戏雷的位置都是不一样的,因从我们需要随机布雷。一个9*9的棋盘,我们可以随机的布上10个雷。那么这个雷和非雷区域如何辨别呢?假设雷的信息是(1),非雷的信息是(0),但是我们排雷肯定是要存储排雷后的棋盘状况的,那么雷的个数又如何存储呢?是否存储的信息会与原信息产生冲突呢?就算我们给雷和非雷的信息不用数字,改用字符,但是一个棋盘是能同时承载雷的信息、非雷的信息和雷的个数吗?太过于混乱。
所以我们可以实现两个棋盘,一个棋盘用于放置初始化的棋盘信息,这个棋盘是不能给玩家看的,另一个棋盘提供给玩家,里面放的是隐藏好后可玩的数据
1.char mine[11][11] = {0};//⽤来存放布置好的雷的信息
2.char show[11][11] = {0};//⽤来存放排查出的雷的个数信息
左图是提供给玩家的,右图是初始化后布好雷的棋盘。
1.4关于排雷
当我们输入坐标,如果这个坐标被布上雷了,那么我们就被炸死,游戏就结束了,如果这个坐标不是雷,那么我们就要去检查它周围的其他坐标有没有雷,然后反馈周围雷的个数。
当我们将所有雷都排完了,那么游戏就结束了。
具体的代码的实现我们在下文继续唠。
二、实现扫雷游戏
1.目录的创建
玩个游戏我们得有一个目录吧,这里我们使用do..while()语句来构建我们的代码,用switch语句,通过输入input来实现对不同情况的辨别。
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();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误!重新选择\n");
}
} while (input);
return 0;
}
2.game():开始游戏
当我们选择inpu=1,那么我们就开始了游戏。这里我们自定义了一个game函数,在game函数来实现初始化棋盘、布雷、扫雷等操作。
void game()
{
/*printf("扫雷\n");*/
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
DisplayBoard(show, ROW, COL);
//开始布雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//开始排雷
FindMine(mine,show,ROW,COL);
}
这里我们首先定义了两个数组mine 和 show(对应的是两个棋盘)。
2.1宏定义
因为现在我们要实现一个9*9的棋盘,所以我们定义二维数组board[11][11](为啥是11*11在上文我们是讲过的)无可厚非。但是,如果我们要改变棋盘的大小难道我们要对所有的数组都进行更改吗?这非常麻烦。因此,我们宏定义了ROW、COL这两个变量(ROWS和COLS是数组的大小,是为防止数组越界才扩大一圈定义,但是实际真正的棋盘大小是ROW和COL),有需求直接更改ROW和COL即可。
2.2初始化和打印
在game()函数中,我们定义了InitBoard()这个函数来初始化棋盘。在上文中我们提到要实现两个棋盘(mine,show),所以我们要初始化两次。但是我们只定义了一个初始化函数啊?所以这就是为什么InitBoard的定义中我们用了set这个变量。因为mine和show 数组分别初始化为字符‘0’和字符‘#’,通过改变set,我们就是实现了对两个数组的打印。
上面是对mine数组的初始化,下面是对show数组的初始化
但是我们要想到,InitBoard()函数是将棋盘给初始化了,但是并没有打印,所以我们还需要一个打印函数让我们能够看到棋盘的变化。
void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印中间的9*9 { int i = 0; int j = 0; printf("------扫雷--------\n"); for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("------扫雷--------\n"); }
有没有发现与之前打印的棋盘相比,打印函数打印的棋盘没有了行列编号,这就对我们确定行列坐标有了极大的困扰,所以我们怎么优化一下呢?
我们可以在加上啊!在打印棋盘的时候,我们打印的是9*9的棋盘,但是数组行列都有十一个数,因此我们可以在棋盘边缘打印行列。用for循环,只要我行的大小: i <=col(即横坐标)那么 我们就打印,列的话就是i<=row(即纵坐标)。
2.3开始布雷
我们在上文提到,我们每局的雷的数量应该是一样的,我们决定每次布10个雷。但是与棋盘大小一样当我们需要更改雷的数量时我们要一个个的更改变量吗?
看了上文的朋友可能疑惑,这个EASY_COUNT是定义什么的。谜底揭晓,我们用EASY_COUNT宏定义了雷的数量。
在上文我们还提到要随机布雷,即每次游戏雷的位置都是不一样的。所以我们就需要使用
rand()函数来产生随机值,但是如何将随机值固定在棋盘的范围内活动呢?
我们可以通过%棋盘大小来解决。
在初始化时,我们将mine数组的数据都改为字符'0',这时还没有布雷,所以如果产生的随机值的坐标=='0',说明该坐标无雷,我们就可以布雷。因为总共十个雷,要布十次雷,所以我们使用while循环布雷过程并定义count记录雷的数量,每循环一次count-1,直到十颗雷都布好,循环结束。
不要忘了rand()函数要调用库函数
2.4排雷
前面我们布好了雷,下面我们要开始排雷。
首先排雷我们要先确定我们要排哪一个坐标吧?这个坐标应该是在棋盘的范围内的吧?只要我们没排到雷我们都要继续排雷吧?(全部排完游戏结束这种情况之后再讨论)
所以排雷是一个循环持续的过程,我们要用while语句,我们还要判断是否目标点是否在棋盘内。
如果在棋盘内且坐标内容为‘1’,游戏结束。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入要排查的坐标:\n");
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
{
//不是雷,就要检查周围是否有雷,并反馈雷的个数
int c=GetMineCount(mine, x, y);//雷的个数
show[x][y] = c + '0';//字符数组放字符
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("坐标非法,请继续输入\n");
}
}
}
如果坐标内容不为'1',我们是不是就要判断该坐标周围有多少雷呢?
所以我们我们定义了一个GetMineCount函数,来判断坐标周围有多少雷。
我们知道字符'0'的ASCII码值为48,字符'1'的为49,字符'2'的为50....
这个地方附近没雷显示‘0’,有雷显示'1',那么我们是不是可以将周围八个坐标的内容的ASCII值加在一起,然后减掉8个'0'的ASCII值,是不是就是周围雷的数量?
因为show[ROWS][COLS]是char 类型的数组,所以我们再让求得的值+'0'的ASCII值,我们是不是就能打印雷的数量了?
事实证明我们是可以做到的。
那么这个扫雷游戏就完成了吗?
我们好像还忽略了游戏如何结束。一昧的扫雷是否会陷入死循环呢?我们是不是要判断什么时候游戏结束(把所有雷都排完了)。
一个9*9的棋盘有81个坐标点,我们有十个雷,那么,只要81-10=71,也就是将所有不是雷的地点都排完了,这个游戏也就结束了。
我们可以在while语句的执行条件上下手。定义一个win变量,每排一次雷就+1,如果它小于棋盘上的非雷点,那么游戏继续,当win的大小等于非雷点的总和时,所有雷都被排了出来,游戏就结束。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;
int x = 0;
int y = 0;
while (win<row*col-EASY_COUNT)
{
printf("请输入要排查的坐标:\n");
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
{
//不是雷,就要检查周围是否有雷,并反馈雷的个数
int c=GetMineCount(mine, x, y);//雷的个数
show[x][y] = c + '0';//字符数组放字符
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请继续输入\n");
}
}
if (win==row*col-EASY_COUNT)
{
printf("排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
这样,一个最最基础简单的扫雷游戏就做好了。但是它还有很多的不足,比如不能像游戏里那样一次探查大片区域,这需要对函数递归的知识进行应用。