目录
一、前言
大家好,我是一名C语言爱好者,今天我想和大家分享一下我在学习C语言过程中编写的一个小项目——扫雷游戏。
二、游戏规则与介绍
首先,让我们来了解一下扫雷游戏的规则。扫雷是一款经典的单人游戏,它由一个方形的网格和一些隐藏的雷组成。玩家需要根据已知的雷的位置和周围格子的数字提示来推断出其他格子是否是雷,并将所有非雷的格子全部揭开。如果玩家不小心揭开了一个雷,游戏就会结束。
接下来,我将介绍一下我是如何使用C语言来实现这个小游戏的。
三、游戏实现
1、建立游戏大体选择框架
第一步,和之前所写《小游戏实现——三字棋(纯随机落子)》所做的一样,使用“do while”循环语句建立起游戏的大体框架:打印游戏界面->选择开始或者退出->选错则给出反馈重新选择(实现条件为接受的数据不为2则继续循环)。
void men()//界面
{
printf("*******************************\n");
printf("********** 1.play *************\n");
printf("********** 2.out *************\n");
printf("*******************************\n");
}
void test()//界面选择结构
{
srand((unsigned)time(NULL));
int input = 0;
do
{
men();
printf("请输入>:");
scanf("%d", &input );
switch (input)
{
case 1:
printf("play game.\n");
break;
case 2:
printf("out game.\n");
break;
default:
printf("请重新输入!\n");
break;
}
} while (2!=input);
}
int main()
{
test();
return 0;
}
2、游戏主体
(1)、建立雷盘与显示棋盘的初始化和创建思路
我创建了一个二维数组来表示游戏的网格。数组的每个元素可以取三种状态:非雷的格子、雷的格子。我使用了字符常量来表示这些状态,例如‘0’表示非雷的格子,‘1’表示标记为雷的格子,‘ * ’作为未打开的格子。
其中我们需要注意几个问题:
- 在打开这个位置时进行周围雷数扫描时是否存在越界? (选择边缘位置时则会越界访问)
- 如果仅有一个盘,在打印雷数为 1 时如何分辨其意义为该处是雷还是周围仅有一个雷? (可使用两个盘一个用于储存雷盘,一个用于给玩家游玩)
- 我们创建什么类型元素的数组作为圆盘合适,两者是否需要大小一致? (使用整型数组会扩大空间后续操作方便,也可使用字符数组(用ascall完成计数)。大小一致更容易后续操作)
解决这些问题后我选择使用字符数据进行后续操作,在以上步骤创建完后则使用循环结构进行遍历将两个盘进行初始化(可以将要初始化的内容传递到函数中则写一套函数即可初始化两个棋盘)
例:
geshi(leipan,'0');//初始化雷区棋盘
geshi(dypan,'*');//初始化打印棋盘
void geshi(char leipan[hens][shus], char set)//初始化棋盘
{
int i = 0;
int j = 0;
for (i = 0; i < hens; i++)
{
for (j = 0; j < shus; j++)
{
leipan[i][j] = set;
}
}
}
(2)、布置雷与打印显示盘
然后,我编写了一个函数来随机生成雷的位置。我使用了rand函数来生成随机的行和列(rand函数需与srand、time函数搭配使用),并将对应的格子标记为雷。需要注意的是,我还要检查生成的位置是否合法,即是否已经有雷存在。
void bulei(char leipan[][shus])//布置雷
{
int count = leishu;
for (;1<=count&&count<=10;)
{
int x = rand() % hen + 1;
int y = rand() % shu + 1;
if ('1' != leipan[x][y])
{
leipan[x][y] = '1';
count--;
}
}
}
打印显示盘仅需要单独写一个函数进行遍历打印即可,这里就不赘述了。
(3)、判断输赢
我使用了一个循环来让玩家不断输入操作,直到达成游戏结束条件退出后继续打印界面选择。每次玩家输入操作后,我会判断游戏是否结束,并根据游戏的状态打印相应的信息。
接下来,我编写了一个函数来计算每个格子周围的雷的数量。我使用了嵌套的for循环来遍历数组,并根据每个格子的状态来计算周围的雷的数量。如果某个格子是隐藏的雷,我就不计算周围的雷的数量返回游戏失败,如果只剩下雷则返回游戏成功。
然后,我编写了一个函数来实现揭开作为没有雷的格子。直到揭开的格子周围有雷则停止递归并返回雷数。在揭开格子的过程中,我需要检查格子的状态是否合法,即是否已经揭开(避免相邻俩个格子相互递归造成死循环)或标记为雷。
void saomiao(char leipan[hens][shus], char dypan[hens][shus],int x, int y)
{
if ((x > 0) && (x < 10) && (y > 0) && (y < 10))
{
char i = leipan[x][y - 1] +
leipan[x][y + 1] +
leipan[x - 1][y - 1] +
leipan[x - 1][y] +
leipan[x - 1][y + 1] +
leipan[x + 1][y - 1] +
leipan[x + 1][y] +
leipan[x + 1][y + 1] - 7 * '0';
if ('0' == leipan[x][y] && '0' == i)
{
if (dypan[x][y] != '#')
{
dypan[x][y] = '#';
saomiao(leipan, dypan, x, y - 1);
saomiao(leipan, dypan, x, y + 1);
saomiao(leipan, dypan, x - 1, y - 1);
saomiao(leipan, dypan, x - 1, y);
saomiao(leipan, dypan, x - 1, y + 1);
saomiao(leipan, dypan, x + 1, y - 1);
saomiao(leipan, dypan, x + 1, y);
saomiao(leipan, dypan, x + 1, y + 1);
}
}
else
{
dypan[x][y] = i;
}
}
else
{
dypan[x][y]=leipan[x][y];
}
}
panduan(char leipan[hens][shus], char dypan[hens][shus])//判断输赢
{
int x = 0;
int y = 0;
int count = 0;
while (1)
{
printf("请输入排雷坐标>:");
scanf("%d,%d", &x, &y);
if (1 <= x && x <= hen && y >= 1 && y <= shu)
{
if ('1' == leipan[x][y])
{
printf("你被炸死了!\n");
daying(leipan);
break;
}
else
{
saomiao(leipan,dypan, x, y);
daying(dypan);
count++;
}
}
if (71 == count)
{
printf("你赢了!");
break;
}
}
}
注:1.连续打开四周所有没用雷的格子需要写一个自动将周围坐标输入并打印周围雷数的函数;递归一定满足这个条件。
2.阻止死递归的方法则是将打开的格子进行标记,进行递归前进行一个判断则避免了这种情况的发生。