这是游戏的运行过程,截了部分图
扫雷差不多是我们写的第一个比较大的项目,因此我需要用多文件写函数,来模拟我们在工作的时候多人合作来写不同函数不同模块。
由图我们可以看到头文件里面是我们要用到的函数
这里面的函数可以用include来引用,但是因为是我们自己造的函数库因此,不能用<>
通过这种方式,就可以将不同的函数库加入进来,而这样就可以实现一个项目多个人同时开工,大大提升了效率。
那么进入正题,我将每个函数的作用和内容放在下面:
注意:我们在写函数的时候,要遵循高内聚,低耦合。所谓高内聚就是函数独立存在,低耦合就是保证尽可能互不影响。否则该函数的可用性大大降低,可能这能在这个地方可以用,其他地方无法放入。
我们首先要做的就是,了解扫雷的运行是怎样的,是将玩家点到的一个格子的四周都扫一遍,看看在周围有几个就显示几,如果在这个地方周围没有雷,就会向周围扩散,将所有周围没有雷的格子显现出来。
但是有边界问题,如果在边上,就不能扫完周围,有一边是没有的,因此我们要额外加一行,总共就是加一圈的格子。这样还有另一个好处,刚好可以将数组里面的1和坐标里面的1对应,数组里面的0成了边界
1
char** arr_establish_char(int a) {
char** arr=NULL;
arr = (char**)calloc(a ,sizeof(char*));
if (arr == NULL)
return NULL;
for (int x = 0; x < a; x++) {
arr[x] = (char*)calloc(a, sizeof(char));
}
for (int x = 0; x < a; x++) {
for (int y = 0; y < a; y++) {
*(*(arr + x) + y) = '0';
}
}
return arr;
}
这个函数是创建动态数组的,当然这也有缺陷,我觉得把他放在main函数里面更好,因为最后free这个二维数组脱离了这个函数,当然我们可以再写一个free函数来释放这个二维数组也是可以的。
要用到的库函数有stdlib.h
这个函数的最终目的是创建一个长和宽相同的二维数组,在后面我们可以打印出来。
2
void lei_establish(int** arr,int p,int q) {
srand((unsigned int)time(NULL));
int sum = 0;
while (p) {
int x = rand() % p + 1;
int y = rand() % p + 1;
if (x == p || y == p)
continue;
if (*(*(arr + x) + y) != 1) {
*(*(arr + x) + y) = 1;
sum++;
}
if (sum == q)
break;
}
}
这个函数是来产生随机坐标固定数目的雷的函数,里面有一个知识:
srand是获取一个数字,基于这个数字rand函数再产生对应的伪随机数,如果没有srand,rand每次产生的伪随机数是一样的。同时值得注意的是srand的接收值要是没有符号的整型,如果要一个时刻变化的数,就可以接收时间,时间数是一个一直在变的数字。
要用到的库函数是time.h
因为我们产生的随机数可能大小超过了自己创建的边长大小,我们可以用取余来缩小范围
比如我们只要20以内的,我们就可以%20;但是还是有问题,因为我们只要1到20的,%20会产生0,我们要把这个除掉,因此要限制范围,排除重复出现的随机雷和不符合要求的随机雷
3
void out_window(char** arr_out, int x) {
for (int j = 0; j <= x; j++) {
for (int i = 0; i <= x; i++) {
*(*(arr_out + j) + i) = '*';
}
}
}
为了让大家理解这个函数,我们想一下有个数组是实现了存储随机产生的雷的坐标,我们可以用x字符来表示这个雷,但是我们最终打印肯定不能用这个二维数组,因此我们要创建多个二维数组来完成我们的扫雷游戏,那么上面的这个函数就是我们让玩家看到的扫雷界面
4
void print(char** arr,int p) {
for (int x = -1; x < p+1; x++) {
for (int y = -1; y < p+1; y++) {
if (x == -1 || x == p) {
if (y == -1 || y == 0||y==p-1||y==p)
printf("%2c ", '#');
else
printf("%2d ", y);
}
else if (y == -1 || y == p) {
if(x == -1 || x == 0||x==p-1||x==p)
printf("%2c ",'#');
else
printf("%2d ", x);
}
else if (x == 0 || x == p - 1||y==0||y==p-1)
printf("%2c ", '#');
else if (*(*(arr + x) + y) == '0')
printf("%2c ",'0');
else
printf("%2c ", *(*(arr + x) + y));
}
printf("\n");
}
}
这个是扫雷呈现出来的图形界面,包括了装饰,坐标的显示以及在中间的核心扫雷板块
这是我们输入15长度大小的扫雷界面。*****这种的就是我们要扫的区域。
5
static char lei_sort(int** arr_back, char** arr_face, int x, int y) {
char sum ='0';
for (int a = x-1; a < x+2; a++) {
for (int b = y-1; b < y+2; b++) {
if (*(*(arr_back + a) + b) == 1)
sum++;
}
}
return sum;
}
这是我们数雷的函数,以这个坐标为中点的3x3区域进行雷数的勘定,最后返回,这个函数是为下个函数进行铺垫的
6
void face_establish(int** arr_back, char** arr_face, int p) {
for (int x = 1; x <=p; x++) {
for (int y = 1; y <= p; y++) {
if (*(*(arr_back + x) + y) == 1) {
*(*(arr_face + x) + y) = 'x';
}
else
*(*(arr_face + x) + y)=lei_sort(arr_back,arr_face,x,y);
}
}
}
这里的1是内部二维数组表示的雷的意思,如果是1,那么在外部数组里面体现为x,如果不是1,就会把数出的雷赋给外部数组
7
static void room_break(char** arr_face, char** arr_out, int x, int y,int p,int q) {
*(*(arr_out + x) + y) = ' ';
for (int m = x - 1; m < x + 2; m++)
{
for (int n = y - 1; n < y + 2; n++)
{
if (*(*(arr_face + m) + n) == '0'&& *(*(arr_out + m) + n) != ' '&&m<=p&&m>=1&&n<=p&&n>=1)
{
*(*(arr_out + m) + n) = ' ';
room_break(arr_face, arr_out, m, n,p,q);
}
if(*(*(arr_face + m) + n) != '0'&& *(*(arr_face + m) + n)!=' ')
*(*(arr_out + m) + n) = *(*(arr_face + m) + n);
}
}
}
这个函数的实现要运用到递归,如果大家不熟悉递归的话,就自行学习一下,不然很难看懂,
这里要提示一下,我们要注意边界问题,防止递归死循环。
这里递归主要思想是用两个二维数组的结合来判断是否进行函数下一次的调用,我们看外部函数,最后没有置换的就是*,那么通过四周扫描,如果还有*就继续扫描这个*周围的*,但是会出现问题,会扫到重复的*,因此我们可以在另一二维数组里面标记一下是否已经扫了这个*,防止一个*被多次扫到。这样递归就可以停下来了。
8
int game_do(char** arr_face, char** arr_out, int x, int y,int p,int q) {
if (*(*(arr_face + x) + y) == '0') {
room_break(arr_face, arr_out, x, y,p,q);
return 1;
}
else if (*(*(arr_face + x) + y) == 'x') {
printf("你被炸死了\n");
return 0;
}
else {
*(*(arr_out + x) + y) = *(*(arr_face + x) + y);
return 1;
}
}
这个函数就数玩家玩耍的函数,我们在进行0的扫除的时候就可以顺便在此之前将这个坐标判断一下,如果是雷就直接游戏结束
9
int sign(char** arr_out, char** arr_face, int x, int y,int sum) {
*(*(arr_out + x) + y) = '^';
if (*(*(arr_face + x) + y) == 'x') {
sum--;
}
return sum;
这个函数就是标记函数,通过查看标记的数量和正确性来确定游戏是否胜利,如果玩家输入了一个坐标是雷,那么这个标记在两个二维数组里面都改变,如果不是雷,那么只会在外部函数里面改变并通过打印体现给玩家。总之,最后决定游戏宋胜利的是内部二维数组所有雷都被正确标记到才可。
重要的函数我都放出来了,下面是我简单写的游玩界面,但是有bug,有时候输入错的字符可能会有问题,导致整个游戏卡住,因此需要大家自己总结,不要都抄我的。static void menu() {
printf("******************\n");
printf("** begain 1 **\n");
printf("** end 0 **\n");
printf("******************\n");
printf("请输入>:");
}
static void saolei() {
int x = 0, a =0,b=0,y=0;
int enter_judge_number=-1;
char class;
char** arr_face = NULL;
int** arr_back = NULL;
char** arr_out = NULL;
printf("请输入雷区边长大小(不小于9)以及难度(A/B/C)\n");
while (1) {
enter_judge_number = scanf("%d%*c%c", &x, &class);
if (enter_judge_number == 2 && x >= 9 && (class == 'A' || class == 'B'||class=='C'))
break;
}
if (class == 'C') {
y = x*x / 8;
}
else if (class == 'B') {
y = x*x / 7;
}
else {
y = x*x / 6;
}
int xs = x + 2;
arr_face=arr_establish_char(xs);
arr_back = arr_establish_int(xs);
arr_out = arr_establish_char(xs);
out_window(arr_out, x);
lei_establish(arr_back, x,y);
face_establish(arr_back, arr_face, x);
int k = 1,o=0;
print(arr_out, xs);
do
{
int num = 0;
printf("如果要标记请输入1,如果扫雷就输入0\n");
int l = 0;
char ch = 0;
while (1) {
scanf("%d", &num);
if (num == 0) {
while (1)
{
printf("扫雷\n");
l = scanf("%d%*c%d", &a, &b);
if (a <= x && b <= y && l == 2) {
break;
}
}
k = game_do(arr_face, arr_out, a, b, x, y);
if (k)
print(arr_out, xs);
else
{
for (int g = 1; g <= x; g++) {
for (int f = 1; f <= x; f++) {
if (*(*(arr_face + g) + f) == '0')
*(*(arr_face + g) + f) = ' ';
}
}
print(arr_face, xs);
printf("\n");
printf("是否考虑重新玩游戏\n");
}
break;
}
else if(num==1)
{
int j = 0, i = 0;
printf("请输入要标记的坐标\n");
scanf("%d%*c%d", &j, &i);
y = sign(arr_out, arr_face, j, i, y);
printf("标记成功\n");
print(arr_out, xs);
if (y == 0)
{
printf("游戏成功\n");
printf("是否考虑重新玩游戏\n");
k = 0;
}
break;
}
else {
printf("输入错误重新输入");
continue;
}
}
}
while (k);
}
static void text() {
int num = 3;
do {
menu();
scanf("%d", &num);
switch (num) {
case 1:
printf("游戏开始\n");
saolei();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误请重新输入\n");
break;
}
} while (num);
}
int main() {
text();
return 0;
}
最后
我希望大家只看一下我放的几个函数即可,其他代码函数的衔接由你们自己独立完成,否则是无法培养出代码素养的。