扫雷游戏相信大部分人都玩过,那怎么用代码来实现它呢?
要实现这个游戏我们首先要清楚这个游戏的玩法,以及它的输赢是怎么判断的。
让我们先来看一眼这个游戏,首先得有一个棋盘,然后玩家选中位置进行扫雷,如果这个位置不是雷的话,就会显示出一个数字,这个数字告诉你这个位置周围八个位置中有多少个位置是雷。然后我们进行进一步的扫雷。如果某次扫雷的位置是雷的话就游戏失败,直到把所有不是雷的位置找出来,才获得游戏胜利。
根据游戏的规则,我们大致知道了我们要实现哪些功能:
第一:有一个可以扫雷的方格,并且自动生成一些雷。
第二:扫雷,被扫非雷位置打印出周围有几个雷。
第三:判断胜负的机制。
有了这三个功能扫雷游戏就基本实现了,但是我们应该考虑到游戏的体验感,如果位置周围没有雷,我们就要一个一个点,多费劲啊!我们玩网页版扫雷的时候会发现,有时点一个位置好多位置都出来了。有了这个扫一片的功能体验感不就上来了吗!!
有了要实现的功能这下我们就只要根据功能写出代码了
首先我们先将游戏的大致框架代码写出来(这个框架用过多次,不知道的可以看之前的文章),这里采用do while 循环,实现玩了一局可以接着玩。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("欢迎来到扫雷游戏!\n");
printf("*****************************\n");
printf("******** play(1) *******\n");
printf("******** exit(0) *******\n");
printf("*****************************\n");
}
int main()
{
int a;
do
{
menu();
printf("请输入>:\n");
scanf("%d",&a);
switch(a)
{
case 1:
Sleep(1000);
system("cls");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入>:\n");
}
Sleep(2500);
system("cls");
} while (a);
return 0;
}
然后我们来进行游戏代码内容的编写。
第一 我们要实现一个可以扫雷的方格,方格的形式多样(这里沿用三子棋的棋盘),但是本质确实一样的。我们来想一想这个方格怎么编,我们要实现
1.开始的时候是不知道雷的位置的(意味着每个格子内的东西要相同,雷的位置是不可知的)。
2.在扫雷后就可以显示周围雷的数目。
3.雷的位置必须要不变(也就是雷要存起来)。
实现可以变的格子二维数组是必须要用到的,但是这时候就会出现一个问题,就是如果我只用一个二维数组的话,那么要开始时每个位置看起来一样(可以有很多种,我这里采用#来表示),又要存放雷和显示该位置周围雷的数目,显然这是不可能的。所以我们用两个二维数组,一个用于存放雷,一个用于显示数字和初始的位置,而这个要显示的二维数组是要显示出周围有几个雷的,所以这两个数组必须要一一对应,但是存放雷的数组要比显示的数组要大一圈,这个我们等会再讲。
所以打印方格的代码就可以得到了,其中方格修饰可以看本人三子棋那一篇文章,这里便不再赘述。其中值得注意的是扫雷游戏不同于三子棋的是它的方格多容易眼花,所以我们可以加上每一格对应的格数。
所得到的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void board(char dis[ROW][COL]) //打印出棋盘,用|和-来区分。
{
int i = 0;
int a, b;
printf(" ");
for (i = 1; i <= ROW; i++)
printf(" %d ",i);
printf("\n");
for (a = 0; a < ROW; a++)
{
printf("—");
for (i = 0; i < COL; i++)//每一行的分隔符打印总体为|---|---|---|.....
{
if (i < COL - 1)//加上if对最后那个进行特殊处理,即最后面加上|,使其更加美观。
printf("|---");
if(i == COL - 1)
printf("|---|");
}
printf("\n");
printf("%d ",a + 1);
for (b = 0; b < COL; b++)//打印出存放于数组的字符。
{
if (b < COL - 1)
printf("| %c ", dis[a][b]);//
if (b == COL - 1)
printf("| %c |",dis[a][b]);
}
printf("\n");
if (a == COL - 1)//最后面一行单独打印
{
printf("—");
for (i = 0; i < COL; i++)
{
if (i < COL - 1)
printf("|---");
if (i == COL - 1)
printf("|---|");
}
}
}
printf("\n");
}
方格得到了,我们就要设置雷了,雷的设置同猜数字游戏中的被猜数字一样,必须要自动生成,否则就失去了意义。我们可以利用随机数来实现这个功能,即随机获得一个 位置让其为雷(雷可以用很多符号表示,但要注意不要与前面设置的初始位置的符号一样),这里就要用到随机数的生成了,不熟悉的同学可以看本人”随机数的生成“这篇文章。
得到的代码如下(其中srand放在了game函数里面):
void my_board(char arr[ROW + 2][COL + 2])//另一个记录雷位置的棋盘,通过两个棋盘来统计出每个位置雷的个数并显示出来到dis数组上。
{
int i;
int a;
int b;
for (i = 0; i < ROW; )
{
a = rand() % ROW + 1;
b = rand() % COL + 1;
if (arr[a][b] != '*')//防止出现两个重复的雷使得雷的数目变少而出现bug
{
arr[a][b] = '*';
i++;
}
}
}
第二 扫雷实现
这时候我们来扫雷了,扫雷位置简单,输入两组数字即可我们可以放到game主体中。实现game游戏的主体,首先定义两个数组,放入上面的board函数中,打印出来,然后再由玩游戏的人来输入,知道游戏胜利或者失败,这里我们可以do while来实现,其中判断条件就可以用判断函数返回值来充当。所以得到的主体函数代码为:
void game()//游戏的整体采用do while循环。
{
srand((int)time(NULL));
char arr[ROW + 2][COL + 2];
char dis[ROW][COL];
int a,b;
for (a = 0; a < ROW + 2; a++)//防止其为随机值导致bug所以将其初始化
for (b = 0; b < COL + 2; b++)
arr[a][b] = '#';
for (a = 0; a < ROW; a++)//先存放进去一个字符,防止其为*导致出现Bug。
{
for (b = 0; b < COL; b++)
{
dis[a][b] = '#';
}
}
my_board(arr);//布置雷
board(dis);//打印棋盘
do //扫雷部分,
{
scanf("%d %d", &a, &b);
if (dis[a - 1][b - 1] != '#' || a > ROW || b > COL || a < 1 || b < 1)//防止输入错误
{
printf("输入错误,请重新输入>:\n");
continue;
}
judge(a, b, dis, arr);
system("cls");
board(dis);
} while (judge(a,b,dis,arr));//利用函数返回值进行判断是否继续循环。
if (dis[a - 1][b - 1] == '*')
printf("游戏失败!\n");
else
printf("游戏胜利!\n");
}
第三 判断部分的实现
被扫位置的判断是否为雷,和是否胜利,如果不是雷就自动判断周围有几个雷。
顺序为:首先要判断所选位置是否为雷,如果是雷则直接退出并返回0退出game中的循环,如果不是雷就判断其是否胜利,(这里我们可以通过看剩下的位置与雷的位置是否相同),如果胜利一样返回0退出循环。(这个时候就要与之前失败退出区分开来,这里我们可以这样,再失败退出前将要显示的数组所选位置变成‘ * ’,然后在出game中的循环后再用if来区分,这样既可以达到游戏视觉效果,又可以实现区分失败和胜利。)如果没有胜利,则计算周围的雷的数目。
雷的数目怎么计算呢?我们可以这样,用两个for循环从所选位置的左上格来逐一计数,然后再统计到所选位置打印出来。这里值得注意的是两个数组都是char类型的要打印出数字就要用到ascii码值了,0对应的ascii码值为48,所以只要再统计的数上面加上48就可以了。(这里值得注意的有两个,第一,由于如果创建的两个数组大小相同的话,那么就要靠考虑存放雷的数组的界限问题了,这样就略显复杂了,但是如果将存放雷的数组扩大一圈的话,就不要考虑这个问题了,第二,由于两个数组的大小不同,就要考虑数组[]内的数字变化,当然为了防止出现这个情况,你也可以将显示数组也扩大一圈。)
判断部分总的代码实现为:
int judge(int a, int b, char dis[ROW][COL], char arr[ROW + 2][COL + 2])//判断输赢还是既没有输也没有赢,通过整形返回从而达到如果没有赢就继续下的效果。
{
int count1 = 0,count2 = 0;
int col, row;
int i, j;
for (i = 0; i < ROW; i++)//判断是否胜利,即#是否等于雷的个数,如果等于则返回0结束循环。
for (j = 0; j < COL; j++)
{
if (dis[i][j] == '#')
count2++;
}
if (count2 == ROW)
{
return 0;
}
if (arr[a][b] != '*')
{
for (row = a - 1; row <= a + 1; row++)//对周围的雷进行计数。
{
for (col = b - 1; col <= b + 1; col++)
{
if (arr[row][col] == '*')
count1++;
}
}
dis[a - 1][b - 1] = 48 + count1;//将所选的非雷位置边上八个位置的雷的数目统计出来,由于为字符所以就加上48(ascii码值)
arr[a][b] = '&';//标记出已经判断过的位置。
if (dis[a - 1][b - 1] == 48 )//判断是否该位置旁边位置都没有雷,如果是则将这些位置清除。
clear(arr,dis,a,b);
return 1;
}
else //判断是否为雷如果是雷就返回0结束循环。
{
dis[a - 1][b - 1] = '*';//标记dis用于判断输赢和打印出雷
return 0;
}
}
到这里游戏就基本完成了,但是为了加强游戏体验感,我们可以加上一个功能:清除0周围的位置
这个功能的实现首先要判断所选位置是否为0,如果是的话就进入这个功能,如果不是就跳过(上面代码已经报括了这一步)。至于这个功能怎么实现呢?这里我们就要先了解一下扫一片雷是按照什么标准扫的。其实它扫一片的标准为:如果所选位置对应的数字为0,则将周围的位置都进行judge,如果周围位置还有0的话继续进行同样的操作,等等,听到了一个敏感词吗?重复同样的操作,这不是标准的递归吗?所以我们用递归来实现,但是这里有一个隐藏的易出bug的点:如果你碰到0位置周围还有0,这个时候,你所选中的位置也在那个0的周围,如果两两都对周围进行judge那不是会一直重复下去吗,这不是死递归了吗?这样游戏可运行不下去,所以我们应该标记一下让已经judge过的位置不会再次进入judge,至于怎么对周围的位置进行操作,我们同样可以用两个for循环然后把对应位置的坐标放进judge中。(这里值得注意的是如果所选位置为周围的一圈的话,那么放到for循环中会超出数组的范围所以要对周围位置加上限制。)
总代码如下:
void clear(char arr[ROW + 2][COL + 2], char dis[ROW][COL], int a, int b)
{
int i, j;
for (i = a - 1; i <= a + 1; i++)
for (j = b - 1; j <= b + 1; j++)
if (arr[i][j] != '&' && i >= 1 && j >= 1 && i <= 9 && j <= 9)
judge(i, j, dis, arr);
}
虽然这个函数代码量只有这么点但是易错点却不少,这就要靠多动脑子和多自己去调试了。
好了这个游戏到这就结束了。