这篇文章我会简要分析扫雷的实现办法
扫雷需要对数组和函数有灵活的运用,会利用循环和分支实现扫雷判定,同时要学会用分支语句、断点调试去细化规则,防止bug的出现,这对我们的代码理解有很大的帮助,同时也能进一步规范我们的代码习惯,学会合理使用.c和.h文件,训练我们用有限的知识来实现目标的能力。
1.创建3个文件
在进行这种较大项目时,我们要用到很多函数将大问题细化,如果写到一个文件里,会导致代码可读性下降,因此创建三个文件test.c、game.c和game.h。其中test.c负责游戏主干,game.c负责游戏主要功能的实现,game.h负责函数的声明
小技巧:函数声明,引入库函数可以集中在自己创建的.h文件中,这样test.c和game.c只需要引用game.h就可以同时引入多个库函数头文件,很方便
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 9
#define COL 9
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <windows.h>
void GameMain();
int choose();
2.写出游戏主干
实现游戏的第一步,是将一些必要的但简单的框架敲出来,让游戏有一个基本逻辑
首先打印菜单,其次选择是否游戏,如果是则开始游戏,否就退出游戏
写代码的过程多用函数简化,使得代码更易读
写函数应先确定函数名和参数,在写完框架后在具体考虑如何实现
#include "game.h"
int main()
{
srand((unsigned)time(NULL));//时间种子
do
{
int choice = 0;
menu();//打印菜单(void)
choice = choose();//选择是否开始游戏(int),函数返回值决定去向
system("cls");
if (choice == 1)
GameMain();//开始游戏,进入游戏主函数(void)
else if (choice == 0)
{
printf("你已退出游戏\n");
break;//跳出循环,结束游戏
}
else
printf("请重新选择\n");
} while (1);//一轮游戏完成后重复该过程或break跳出循环
return 0;
}
其中,时间种子是我后加上去的,开始的时候可以不管它。最重要的是do while循环,下面是我menu和choice函数的实现,比较简单
void menu()//打印菜单
{
printf("*****************************\n");
printf("**** 1.开始游戏 ****\n");
printf("**** 0.退出游戏 ****\n");
printf("*****************************\n");
}
int choose()//选择是否开始游戏
{
int i = 0;
scanf("%d", &i);
return i;
}
3.游戏主函数的实现
在进入游戏主函数之后,我们需要知道
(1)扫雷需要两个棋盘,一个用于展示,另一个需要储存信息。
(2)扫雷需要将数组进行初始化,并使它们各司其职
(3)扫雷在选定一个坐标后需要对该格子做出判定,如果踩雷则退出,没踩雷需要对周围8个格子雷的个数进行计算并把信息展示出来,最后要对扫雷成功做出判定。
4.创建棋盘
兼顾(1)(3)要防止判定越界问题,即在边角对周围8个格子统计雷的个数时,不能超出数组的范围,所以在9*9的棋盘上游戏时,我们需要11*11的数组,隐藏最外层
布置雷要使用rand函数,在mian函数下布下时间种子,产生随机数用于产生雷
游戏主函数逻辑如下
void GameMain()
{
int count = 81;//共81个格子,根据格子剩余数判定输赢
char arr_show[ROW + 2][COL + 2] = { 0 };//该数组用于展示,不储存雷的信息
int arr_me[ROW + 2][COL + 2] = { 0 };//该数组用于储存雷的信息,不展示
mine(arr_me);//布置十颗雷
init(arr_show);//将用于展示的数组初始化
while (1)//进入游戏循环
{
int ret = 0;//接收输赢信息
Boardprint(arr_show);//打印棋盘
ret = Gather(arr_me, arr_show,count);//收集判定信息
if (ret == 82)//游戏输了
{
break;
}
if (ret == 10)//剩下10颗都是雷
{
printf("你赢了\n");
break;
}
}
}
布置雷函数实现如下
void mine(int arr_me[ROW + 2][COL + 2])//布置十颗雷
{
int a = 0;
int b = 0;
int i = 0;
for (i = 10; i >= 1;)
{
a = (rand() % ROW) + 1;
b = (rand() % COL) + 1;
if (arr_me[a][b] != 1)
{
arr_me[a][b] = 1;
i--;
}
}
}
初始化展示的棋盘对应的函数表示如下
void init(char arr_show[ROW + 2][COL + 2])//将展示出来的数组标上'*'
{
int i = 0;
for (i = 1; i <= ROW; i++)
{
int j = 0;
for (j = 1; j <= COL; j++)
{
arr_show[i][j] = '*';
}
}
}
5.打印展示出来的数组
从数组的第二行第二列作为展示的第一行第一列打印出来,同时要标出坐标方便选择
void Boardprint(char arr_show[ROW + 2][COL + 2])//打印每步操作后的数组
{
int i = 0;
int j = 0;
system("cls");
for (i = 0; i <= ROW; i++)
{
printf("%d ", i);
}
printf("\n");
for (j = 1; j <= COL; j++)
{
int k = 0;
printf("%d ", j);
for (k = 1; k <= COL; k++)
{
printf("%c ",arr_show[j][k]);
}
printf("\n");
}
}
最终效果如下
6.实现判定
int Gather(int arr_me[ROW + 2][COL + 2], char arr_show[ROW + 2][COL + 2], int count)//用于收集坐标、判定是否被炸死、是否能赢
{
int a = 0;
int b = 0;
int i = 0;
printf("请输入坐标,中间用空格分开\n");
scanf("%d %d", &a, &b);
if (arr_me[a][b] == 1)
{
printf("你被炸死了\n");//被炸死
return 82;
}
else
{
//点开一个格子显示周围有几颗雷
i = arr_me[a + 1][b + 1] + arr_me[a + 1][b] + arr_me[a + 1][b - 1] + arr_me[a][b + 1] + arr_me[a][b] +
arr_me[a][b - 1] + arr_me[a - 1][b + 1] + arr_me[a - 1][b] + arr_me[a - 1][b - 1];
arr_show[a][b] = i + '0';//将这个整型值转为字符赋给char数组
return --count;//每点开一个格子剩余格子-1,用于最后判断赢
}
}
其中,因为只有81个格子,所以在被雷炸死时,我返回82,在没被雷炸死时,我以剩余格子数为信息返回,再由游戏主函数的分支语句判定是break还是继续循环,在arr_me和arr_show进行转换时,注意字符和数字要用'0'来转化,如'9' == 9 + '0',同样地,4 == '4' - '0'。
于是,游戏已经实现了,但其中有的易错点需注意,在初始化数组时,千万不要图方便将arr_show[ROW + 2][COL + 2]传过去,因为我们需要传的是数组首元素地址,而这种表示形式是函数中间的一个元素,会报错,我就是在这里被卡了4天
mine(arr_me[ROW + 2][COL + 2]);//err
init(arr_show[ROW + 2][COL + 2]);//err
完整代码我储存在了Gitee上:链接为
仅练习/test_2023_10_30(扫雷)/test_2023_10_30 · SGlow/C语言学习记录 - 码云 - 开源中国 (gitee.com)