各位看官们好,这是鄙人写的第八篇博客了,扫雷大家都玩过吧,在我以前玩的时候真的不懂游戏规则,时常踩雷,当时一度觉得扫雷是世界上难度最大的游戏了[捂脸]。现在稍微年长了一点了,稍微理解了扫雷的规则,尝试着写一个实现扫雷的简单程序。大家一起来看看吧。
实现思路
扫雷大家没玩过也应该见过吧。你打开扫雷后会选择是否开始游戏和游戏难度(这里讲解主要是为讲解思路,所以就没有写出选择游戏难度),那么我们第一步就需要写出运行程序后是否开始游戏(1):写出是否开始游戏的判断。看表面,大家知道扫雷游戏界面是一个9*9的棋盘。好,那么第一步我们就知道了。(2):第二步要创建一个9*9的棋盘(当然也可以增加,这里讲解就写9*9)。那么我们创建好了一个9*9的棋盘,里面肯定要有内容呀,这个列表里面的内容肯定就是10个雷。(3):在列表里面布置10个随机的雷。当我们把雷布置好后,肯定就要开始游戏了嘛,所以接下来我们就写出玩家游玩时候的状况了,玩家没选择一个地址,判断是否踩雷,打印出附近有多少颗雷。(4):写出玩家选择地址判断是否踩雷,和附近有多少雷。 当玩家踩雷和依靠技术排除所以雷的时候系统应该怎么办,这个就是玩家玩游戏的结局,写出玩家踩雷或者胜利的结局。 以上就差不多是一局扫雷游戏的大概步骤了,但我输游戏了,是会不服气我肯定会再来一局,甚至不赢一局不停下来的程度。所以我gg一次肯定再开一局,但是如果没gg一次我就要重新运行一下系统的话,好麻烦,所以我们接下来就要写出,可以多次游戏的程序。(5):当玩家游戏结束后,判断玩家是否再次游戏。所以这是我认为写出一个扫雷游戏的所有步骤了。:
(1):写出是否开始游戏的判断。
(2):第二步要创建一个9*9的棋盘。
(3):在棋盘里面布置10个随机的雷。
(4):排查雷
(5):当玩家游戏结束后,判断玩家是否再次游戏。
分文件编写
在编写程序之前,大家想一下,扫雷游戏虽然说着容易,但是写起来也还是有200多行的代码,要是写在一起,那看起来岂不是很臃肿。所以我们就需要进行分文件编写,那什么是分文件编写嘞。
当代码量较大功能较复杂时,单一文件程序会使得文件非常巨大,代码量非常大,成千上万行的代码在一个文件中不便于修改和维护,因此需要将不同的功能模块放在不同的文件中。然后在主函数中调用封装好的函数就可以了,到时候编译会自动一起编译。
那分文件编写有什么好处呢?1:可以划分责任,你能清楚的知道,哪一个文件是定义函数,哪一个文件是确定函数的具体代码。并且在以后的大项目里面,肯定有很多算法需要很多人一起写代码,那么每个人负责一部分,肯定不能所有一起写一个文件吧,所有每个人写一个文件,结束后再汇总在一起,方便编写。2:方便调试,当你分开编写可以更直观的看见程序报错得地方。3:主程序简洁,如果不进行分文件编写的话,将所有文件结合主程序里面的话,那么太多,太乱了,让人写着写着就觉得烦。简洁的界面让人的思路更加清晰。
那我们如何创建其他文件呢?
我们只需要在鼠标放在头文件后,右键,点击创建新建项(头文件的后缀为.h,源文件为.c)那么源文件也是同理,右键点击创建新建项。就可以开始分工写代码了。
具体分析
那么现在我们就一步一步的来分析一下,代码应该怎么写。
1:写出是否开始游戏的判断。
那么判断应该要告诉玩家,输入或者选择什么开始或者退出,是吧,那么就搞一个最简单的0退出,1开始。
这样是不是很明显的表示出来,1是开始游戏,0退出游戏。当然这只是告诉玩家选择什么。但是玩家应该怎么做嘞,是输入还是什么。那么在主函数里就应该
我们在主函数里面创建一个变量。用switch语句,这样系统就可以依据玩家输入的数值来选着执行什么程序。当玩家输入其他值的时候,因为default告诉玩家“输入错误”,所以玩家再一次输入,这样大家是否就了解了第一步, 判断玩家是否开始游戏了。创建一个常量,玩家输入一个值,系统在依据玩家的输入值来判断执行的系统,要多次循环的话,在switch的外面加一个循环,那这就解决了吧。
2:第二步要创建一个9*9的列表
大家应该知道玩扫雷在踩到雷的时候会将桌面上的遮挡物去掉,显示出雷的所有位置,那么我们就知道了,扫雷有两个棋盘,一个是玩家玩的时候看见有障碍物(看不见雷),还有一个是玩家踩到雷,显示出所有雷的棋盘。那么我们就用1来表示雷,因为这样也方便我们后面计算附近有多少雷。
当玩家选择开始游戏后,就会打印出第一个棋盘(chu),第二个是为了向大家展示随机布置10个雷的位置(ho)。
大家注意到在对二维数组定义的时候,并且对行和列确定为数值,而是一个宏定义的数值,我们将要声明的内容都放在dinyi.h里面这样分类以便后续的书写。因为这样如果需要改变棋盘大小的话,就不需要依次对代码进行更改,只需要在宏定义的地方进行更改这样更加方便更改。
这里大家可能会问,文章开头不是 说创建9*9的棋盘嘛,为什么还要加2呢,那时因为方便我们计算附近有多少雷,如果玩家没有踩到雷的话,就要计算出周围8格有多少雷,要是我们只创建9*9的棋盘的话,在边缘的棋盘计算要单独进行计算,这样会很麻烦,所以直接在内部各加一行这样就不会出现越界访问的问题了。
我们在dinyi.h里面将初始化棋盘定义,在dinyi.c文件中实现函数
大家知道这个代码其实不是很难,是一个普通的二维数组打印,用嵌套循环就可以了,但是打印的字符却没有明确表现出来为什么。这是因为,我们棋盘一个要打印数值一个要打印字符,那么我们将一个函数打印确定了的话,打印另外一个岂不是要在写一个程序,所以我们直接在调用的地方将要打印的值进行改变这样一个程序两种结果。
到这里我们就已经创建了棋盘,也将棋盘内的内容确定了,那么我们就应该将棋盘打印出来了。
当然这也是需要在dingyi.h里面进行声明的,不然主程序调用的时候就会报错的。这里也是一个二维数组的打印。在第一个for循环里面是为了将棋盘旁的数字1~9。因为这样方便玩家输入地址。
Dayin(ho, hang, lie);//打印棋盘
主函数中的函数调用
3:在列表里面布置10个随机的雷
每写一部分程序都需要在dingyi.h里面声明。我们因为已经明确要布置10个雷,那么这里就直接创建一个为10的常量,布置了一个,那么就减一,直到为0就结束出循环。但是因为随机布置雷,那么就有可能睡觉到同一个地方,所以我们在进行随机布置的时候,还需要判断,随机的地方是否已经有雷了,如果有那么就不减,从新在布置一颗
然后因为是随机布置,那么我们就需要一个生成随机数的指令吧,c语言经常使用的是rand,srand。因为,在电脑中除了我们的内存是可能在变化的那就只有我们看的时间了,所以我们就依据时间(time)的变换来生成随机数,但srand(stdlib.h)与time(time.h )都需要引入头文件声明。
srand的大概使用方法是这样的。依据什么东西来改变就将time改变为其他的东西。放在主程序中。
4:排查雷
还是不变的第一步声明。我们可以先不管是否踩到雷了,我们先判断玩家输入的地址是否正确,如果玩家输入的地址有问题,超出了9*9的棋盘,俺们就提醒玩家输入错误,请重新输入。当玩家输入合理后,我们在判断是否踩雷和附近有多少雷。当玩家没有踩着雷成功躲过了10颗雷的时候我们也需要提醒玩家成功排雷。不然玩家只有踩到雷才能下线岂不是强制踩雷了。
void Pailri(char chu[Hangda][Lieda],char ho[Hangda][Lieda], int hang1, int lie1)
{
int count = 71;//因为确定为9*9的棋盘且雷为10课,那么玩家只有71次输入没有踩到雷那么就将所有雷排除了
int a, b;
char arr[20] = { 0 };
while (count)
{
printf("请输入要检查的坐标\n");
scanf("%d%d", &a, &b);
if (a >= 1 && a <= hang1&&b >= 1 && b <= lie1)
{
if (chu[a][b] != '1')
{
count--;
int y = Zwei(chu,a,b);//计算附近有多少雷
ho[a][b] = y + '0';
Dayin(ho, hang, lie);
}
else
{
printf("踩雷了\n");
Dayin(chu, hang, lie);
system("shutdown(-s -t 60)");
break;
}
}
else
{
printf("输入错误,重新输入\n");
}
}
}
上面这个代码里面没有写出计算附近有多少雷,是因为如果加上计算的话,会让代码看着很繁琐,比较乱。所有我们就在dingyi.c里面写出一个计算附近雷的子函数(Zwie)。
int Zwei(char chu[Hangda][Lieda], int x, int y)
{
return
chu[x][y + 1] +
chu[x][y - 1] +
chu[x - 1][y] +
chu[x - 1][y + 1] +
chu[x - 1][y - 1] +
chu[x + 1][y] +
chu[x + 1][y - 1] +
chu[x + 1][y + 1] - 8*'0';
}
这个子函数一定要写在Pailri前,因为要调用Zwei函数的话,肯定需要存在函数才可以使用呀,如果定义在Pailri后的话,系统无妨提前访问,那么我们就写出了一个BUG了。并且我们这里使用的最笨蛋的方法周围相加(虽然是最笨的方法也是最简单的方法)。这里大家要注意相加不要再将玩家输入的地址再一次相加。如果玩家输入的地址是雷的话那么就直接踩雷了,所以我们在相加的时候就不需要加玩家输入的地址。
这样大家就写的差不多了,当玩家需要再一次游戏的程序我们已经写在了第一个代码里面了。下面是主程序的调用函数了。
下面就是一局扫雷游戏的顺序了
扩展
标记地雷
标记地雷我认为就是我们自己给棋盘确立打印什么。
void Signmine(char ho[Hangda][Llieda], int hang1, int lie1)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入要标记的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (ho[x][y] == '*')
{
ho[x][y] = '!';
break;
}
else
{
printf("该位置不能被标记,请重新输入:\n");
}
}
else
{
printf("坐标非法,请重新输入:\n");
}
}
}
当然还可以写出不是地雷展开一片地区的,那是需要使用递归,但大家要注意,因为爆炸展开需要的计算量较大,使用递归方法很容易出现是递归的情况,希望大家注意。
完整代码
扫雷.c
define _CRT_SECURE_NO_WARNINGS 1
#include"dinyi.h"
void wuwu()
{
printf("1:开始游戏 0:退出游戏");
}
void youxi()
{
char chu[Hangda][Lieda] = { 0 };
char ho[Hangda][Lieda] = { 0 };
Cshihua(chu, Hangda, Lieda,'0');//初始化棋盘长宽11,为0
Cshihua(ho, Hangda, Lieda, '*');//长宽11,为*
Dayin(ho, hang, lie);//打印棋盘
fanglei(chu, hang, lie);//放置10个随机的雷
//Dayin(chu, hang, lie);//看一下10个雷在哪里
Pailri(chu,ho,hang,lie);
//Dayin(ho, hang, lie);
}
int main()
{
srand((unsigned int)time(NULL));
int a = 1;
while (a)
{
wuwu();
scanf("%d", &a);
switch (a)
{
case 1:
youxi();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
}
return 0;
}
dinyi.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"dinyi.h"
void Cshihua(char chu[Hangda][Lieda], int hangda, int lieda, char yy)
{
for (int a = 0; a < Hangda; a++)
{
for (int b = 0; b < Lieda; b++)
{
chu[a][b] = yy;
}
}
}
void Dayin(char ho[Hangda][Lieda], int hang1, int lie1)
{
for (int y = 0; y < 10;y++)
{
printf("%d ", y);
}
printf("\n");
for (int c = 1; c < hang1; c++)
{
printf("%d ", c);
for (int d = 0; d < lie1; d++)
{
printf("%c ", ho[c][d]);
}
printf("\n");
}
}
void fanglei(char chu[Hangda][Lieda], int hang1, int lie1)
{
int count = 10;
while (count)
{
int x = rand() % hang1+1;
int y = rand() % lie1+1;
if (chu[x][y] == '0')
{
chu[x][y] = '1';
count--;
}
}
}
int Zwei(char chu[Hangda][Lieda], int x, int y)
{
return
chu[x][y + 1] +
chu[x][y - 1] +
chu[x - 1][y] +
chu[x - 1][y + 1] +
chu[x - 1][y - 1] +
chu[x + 1][y] +
chu[x + 1][y - 1] +
chu[x + 1][y + 1] - 8*'0';
}
void Pailri(char chu[Hangda][Lieda],char ho[Hangda][Lieda], int hang1, int lie1)
{
int count = 71;
int a, b;
char arr[20] = { 0 };
while (count)
{
printf("请输入要检查的坐标\n");
scanf("%d%d", &a, &b);
if (a >= 1 && a <= hang1&&b >= 1 && b <= lie1)
{
if (chu[a][b] != '1')
{
count--;
int y = Zwei(chu,a,b);
ho[a][b] = y + '0';
Dayin(ho, hang, lie);
}
else
{
printf("踩雷了\n");
Dayin(chu, hang, lie);
break;
}
}
else
{
printf("输入错误,重新输入\n");
}
}
}
dinyi.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define hang 9
#define lie 9
#define Hangda hang+2
#define Lieda lie+2
void Cshihua(char chu[Hangda][Lieda], int hangda , int lieda,char yy);
void Dayin(char ho[Hangda][Lieda], int hang1, int lie1);
void fanglei(char chu[Hangda][Lieda], int hang1, int lie1);
void Pailri(char chu[Hangda][Lieda], int hang1, int lie1);