一个简单扫雷游戏的实现
1. 概念
要实现一个扫雷首先要知道扫雷是怎样玩的,我们可以将游戏过程做一个描述:
游戏开始时能看一个被划为很多方块的面板,当用户点击或输入面板上的某一个方块的坐标时,该方块会被翻开。(第一次输入时不会踩到雷)
如果翻开的位置是雷,则游戏结束,如果不是雷,则 该方块会变为其周围八个方块中地雷个数的总和。当地雷数的和为0时,则直接翻开周围的8个方块并将其变为 其周围方块中雷数目的总和,如果被翻开的地方仍为0,则重复此过程直到被翻开的位置周围地雷总数不为0。
当剩下的未翻开的位置全为雷时,用户胜利,游戏结束。
2. 开始前的准备
了解了扫雷的概念后我们就可以来思考如何实现了。
- 设置面板
首先我们需要一个可以使用户能够看见游戏面板。这可以用一个二维数组来表示,并在里面赋上符号“*”。
然而只有游戏面还不够,我们还需要一个隐藏起来的面板用来存放雷的坐标,这样当用户在显示面板中输入一个坐标时,只需要看该坐标在雷面板中是否有雷就可以知道是否踩到了雷。如下:
char show[ROWS][COLS]; //显示面板
char mine[ROWS][COLS]; //雷面板
memset(show, '*', sizeof(show)); //设置两个面板
memset(mine, '0', sizeof(mine));
2. 埋雷
接下来我们需要在雷面板里存入雷的信息。此时我们存入的信息必须是随机的,每次打开游戏时雷所在的位置都不一样,这里需要用到随机数。我们可以用
srand((unsigned)time(NULL));
产生一个随机数种子,再用
rand() % (end-start + 1) + start;
在范围end~start间(包括二者)产生一个随机的数字。
为了不让用户在第一次输入时候就踩到雷,我们可以在用户输入第一个坐标后再进行埋雷,此时只要在判断条件中去除第一次坐标就可以了user_x&&y!=user_y
完整代码如下:
/*埋雷*/
void setmine(char Mine[][COLS],int _rows,int _cols,int user_x,int user_y)
{
int x, y;
srand((unsigned)time(NULL)); //随机数种子
for (int i = 0; i < MINE_COUNT;){
x = GetInex(1, _rows - 2); //获得随机坐标
y = GetInex(1, _cols - 2);
if (Mine[x][y] == '0'&&x!=user_x&&y!=user_y){
Mine[x][y] = '1'; //埋雷
i++;
}}}
为什么rows和cols要-2呢?我们会在下面说到。
/*获得随机坐标*/
int GetInex(int start,int end){
int index;
index=rand() % (end-start + 1) + start;
return index;
}
3.显示面板
有了雷和用户面板的信息,接下来我么就需要把它打印到屏幕上来。同时也将横纵坐标的轴给打印出来,方便用户输入坐标。
打印的方式多种多样,笔者在这用的最简单的面板,各位可自行设计。
打印出来的结果:
代码:
/*显示面板*/
void showbroad(char show[][COLS], int _rows, int _cols)
{
printf(" ");
for (int i = 1; i < _cols-1; i++){
printf("%d ", i);
}
printf("\n ");
for (int i = 1; i<_cols-1; i++){
printf("__");
}
for (int i = 1; i<_rows - 1; i++){
printf("\n%2d|",i);
for (int j = 1; j < _cols - 1; j++){
printf("%c ",show[i][j]);
}
}
printf("\n");
}
3.游戏开始
埋好了雷,有了面板,接下来便要开始游戏的逻辑编写了。
- 游戏开始时,我们首先看到的是一个有坐标的面板,这时直接调用上面的显示面板函数,传入show数组,行和列:
showbroad(show, ROWS, COLS);
接下来提示用户输入坐标并埋雷:
printf("Please Enter <rows,cols>:\n");
scanf("%d%d", &x, &y);
if (i){
setmine(mine, ROWS, COLS,x,y); //埋雷
i--;
}
- 判断用户输入是否合法,不合法则提示重新输入
if ((x >= 1 && x <= _row - 2) && (y >= 1 && y <= _cols)){...}
else printf("Enter Erro Try Again:");
- 当输入合法时候,就要开始判断用户是否踩到了雷,没有则继续,否则显示Game over,退出。
if (mine[x][y] == '0'){...}
else {printf("Game Over!\n");
break; }
当用户没踩到雷时候,将该位置翻开,显示为周围(8个)位置雷数的总和,并将排雷数+1.
int num = GetNearMine(mine,x,y); //统计周围雷个数
show[x][y] = num+'0'; //填充数字
(*p)++;
/*统计周围雷数*/
int GetNearMine(char mine[][COLS], int _x, int _y)
{
return mine[_x - 1][_y - 1] + mine[_x - 1][_y] +\
mine[_x - 1][_y + 1] +mine[_x][_y - 1] +\
mine[_x][_y + 1] +mine[_x + 1][_y - 1] +\
mine[_x + 1][_y] + mine[_x + 1][_y + 1] - 8 * '0';
}
此时我们发现当地雷所在位置是面板的边上时,统计雷数时候有那么几个位置是超出了面板范围不能统计的,为了解决这个问题,我们在设置面板时候将面板的行和列都+2。(ROWS+2)*(COLS+2),但显示时候只显示行列-2的面板(ROWS×COLS)。这样在统计雷数时候就不会出错了,因为那些超出的部分都已经被我们设置为了‘0’。
所以上面为什么是rows-2和cols-2的问题也清楚了。
- 逻辑进行到这一步,接下来我们就需要对那些统计雷数为0的坐标进行展开了。
if (num == 0){
Expand(mine, show, x, y,p); //展开
}
要展开我们需要将该为0的坐标点周围的8个坐标依次进行与上面同样的个数统计流程,这里我们使用一个switch语句来获得周围8个点的坐标,并用一条if语句设置坐标检查,统计每个坐标周围的总雷数,并将排雷点数+1。当那些坐标也符合总雷数为0的条件时,再次调用函数递归。
代码:
/*展开*/
void Expand(char mine[][COLS],char show[][COLS],int user_x,int user_y,int *p)
{
int x;
int y;
for (int i = 0; i < 8; i++){ //以输入坐标为中心逐个判断
x = user_x;
y = user_y;
switch (i){
case(0) : x--; y--; break;
case(1) : x--; break;
case(2) : x--; y++; break;
case(3) : y--; break;
case(4) : y++; break;
case(5) : x++; y--; break;
case(6) : x++; break;
case(7) : x++; y++; break;
}
if (x<1 || x>10 || y<1 || y>10||show[x][y]!='*'){ continue; } //坐标越界或被排查过则跳过
int _num = GetNearMine(mine, x, y);
show[x][y] = _num + '0';
(*p)++;
if (_num == 0){
Expand(mine, show, x, y,p); //递归
}
}
}
最后我们将坐标刷新,并用已排雷数判断是否已排完
showbroad(show, ROWS, COLS);
//showbroad(mine, ROWS, COLS);
if ((ROWS - 2)*(COLS - 2) - *p == MINE_COUNT){ //排完所有雷
printf("YOU WIN! :)\n");
接下来,我们将这些代码编入循环,让游戏可以一直进行下去,直到胜利或失败。
代码:
/*游戏逻辑*/
void Play(char mine[][COLS], char show[][COLS], int _row, int _cols){
int x, y, i = 1;
showbroad(show, ROWS, COLS);
int count = 0;
int *p = &count;
while (1)
{
printf("Please Enter <rows,cols>:\n");
scanf("%d%d", &x, &y);
if (i){
setmine(mine, ROWS, COLS,x,y); //埋雷
i--;
//showbroad(mine, ROWS, COLS);
}
if ((x >= 1 && x <= _row - 2) && (y >= 1 && y <= _cols-2))
//检查输入是否合法
{
if (mine[x][y] == '0') //没有踩雷
{
int num = GetNearMine(mine,x,y); //统计周围雷个数
show[x][y] = num+'0';
(*p)++;
if (num == 0){
Expand(mine, show, x, y,p); //展开
}
system("cls"); //刷新
showbroad(show, ROWS, COLS);
//showbroad(mine, ROWS, COLS);
if ((ROWS - 2)*(COLS - 2) - *p == MINE_COUNT){
//排完所有雷
printf("YOU WIN! :)\n");
break;
}
}
else {printf("Game Over!\n");
break;
}
}
else printf("Enter Erro Try Again:");
} }
至此,我们便用c语言实现了一个简单的扫雷。
如下是游戏界面: