思路
相信这款经典小游戏大家都曾在电脑上玩过,那么今天我们将以简易思路来实现一些它的基础游戏逻辑。
首先就是棋盘的设计,显然,简单9*9的扫雷棋盘需要通过二维数组来实现,这时我们不妨设置一个二维数组。这里我们可以发现问题所在,因为数组下标是从0开始,并且在后续实现对不是雷的坐标的周围雷数量统计这个功能时会造成访问超限,因此,我们不妨在周围扩充出来一圈,即将棋盘设置为11*11的规格。
如上图11*11棋盘所示,中间灰色的9*9区域为有效游戏区域,而外围一圈白色区域则是为正常实现功能而准备的空白区域,不对游戏过程及结果产生任何影响。因此我们在这个9*9的区域中可以通过随机数来设置雷(有雷的位置设为1,没有雷的位置设置为0)
但是我们要考虑到这里仍然存在一个问题:在正常的扫雷游戏中,如果排查到这个位置不是雷,则会显示出它一周存在雷的个数,但如果将这个位置再次赋值的话可能会影响雷的判断(比如(3,2)这个位置本来不是雷,但它的周围有一个雷,接着给它赋值 '1' 的话,这个‘1’是指“是雷”还是“周围有1个雷”,造成了歧义。
为了解决这个问题,我们可以设置两个棋盘,一个负责存储雷(mine),另一个负责测试并且展现到玩家面前(show)。为了美观,我们可以将两个二维数组都设置为char型,mine棋盘中都初始化为 ‘0’;show棋盘中未被测试的位置存为 ‘*’。
接下来我们就要考虑一下整体游戏的逻辑了。
代码实现
初始化棋盘
为了方便游戏设置,我们可以先对行数和列数(注意上文中解释的游戏区域和非游戏区域)、总雷数进行定义。
#define ROW 9
#define LINE 9
#define ROWS ROW+2
#define LINES LINE+2
#define TOTALCOUNT 10
接下来就是通过一个函数调用和循环的方式对二维数组进行初始化。
//先设置出两个棋盘
char mine[ROWS][LINES];
char show[ROWS][LINES];
//初始化棋盘
InitBoard(mine,ROWS,LINES,'0');
InitBoard(show, ROWS, LINES, '*');
void InitBoard(char a[ROWS][LINES], int n, int m, char ch)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
a[i][j] = ch;
}
}
}
设置雷
通过rand()函数来随机给出横坐标x和纵坐标y。但是要注意这里x、y的范围都是1~9(如前文所示,我们定义的ROW和LINE都是9),所以用rand()%ROW得到了范围为0~8的随机数,由rand()%ROW+1便能成功得到范围为1~9的随机数。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
SetMine(mine, ROW, LINE);
void SetMine(char a[ROWS][LINES], int row, int line)
{
int count = TOTALCOUNT;
while (count)
{
int x, y;
x = rand() % row + 1;
y = rand() % line + 1;
if (a[x][y] == '0')
{
a[x][y] = '1';
count--;
}
}
}
注意:不要忘记在主函数中srand的设置!
srand((unsigned int)time(NULL));
展示棋盘
将show棋盘展示给玩家供接下来的游戏使用。但要记得展示的是有效游戏区域哦~
ShowBoard(show, ROW, LINE);
//展示棋盘
void ShowBoard(char a[ROWS][LINES], int row, int line)
{
int i = 0;
for (i = 0; i <= line; i++)
{
printf("%d ",i);
}
printf("\n");
int j = 0;
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= line; j++)
{
printf("%c ", a[i][j]);
}
printf("\n");
}
}
统计周围雷的数量
当玩家输入需要测试的位置不为雷时,我们要统计目标位置周围一圈雷的数量。
int CountMine(char a[ROWS][LINES], int row, int line)
{
int count = 0;
for (int i = row - 1; i <= row + 1; i++)
{
for (int j = line - 1; j <= line + 1; j++)
{
if (a[i][j] == '1')
{
count++;
}
}
}
return count;
}
排查目标位置是否为雷
首先计算出所有有效区域内非雷位置的数量(num),在没有排查完所有雷之前一直套在循环中。第一步提示玩家输入想要排查的位置,然后在mine()()中判断这个位置是否为雷,如果是雷则提示用户“很遗憾,你被炸死了!”,并且展示出此局游戏中雷的分布,然后直接跳出循环结束此局游戏;反之则调用CountMine()函数统计此位置周围雷的总数,并将这个数转化成char型存储到这个位置上,再次展示整个有效游戏区域的棋盘。如果用户输入的位置不在有效游戏区域的话,则输出“输入无效请重新输入!”。
每一次的有效判断后不要忘记num--,当num减为0之前用户都没有跳出循环的话,则提示用户“恭喜你成功了!”
FindMine(mine, show, ROW, LINE);
void FindMine(char mine[ROWS][LINES], char show[ROWS][LINES], int row, int line)
{
int x, y;
int num = row * line - TOTALCOUNT;
{
while (num)
{
printf("请输入你要排查的位置-->");
scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row )&&( y >= 1 && y <= line))
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!");
ShowBoard(mine, ROW, LINE);
break;
}
else
{
show[x][y] = CountMine(mine, x, y) + '0';
ShowBoard(show, ROW, LINE);
num--;
}
}
else
{
printf("输入无效请重新输入!");
}
}
if (num == 0)
{
printf("恭喜你成功了!");
ShowBoard(mine, ROW, LINE);
}
}
}
优化游戏体验——游戏菜单
在每次游戏开始之前我们可以提供给用户一个选项,选择是否再次开始游戏还是直接退出游戏,这会在视觉和体验上增加我们游戏的可玩性。
这个时候我们可以使用一下do-while循环
do
{
printf("请输入您的选择-->\n");
printf("******************\n");
printf("**** 1. play ****\n");
printf("**** 0. exit ****\n");
printf("******************\n");
scanf("%d", &input);
if (input == 1)
{
printf("******** 扫雷游戏 ********\n");
game();
}
else if (input == 0)
{
printf("游戏结束,退出游戏!");
break;
}
else
{
printf("选择错误,请重新选择!");
}
} while(input);
以上我们就实现了简单的扫雷游戏,但对比正式的扫雷我们仍然差了许多功能,后续还会继续丰富~