扫雷是经典游戏之一,现在我们用C语言实现一个简易版的扫雷游戏,这需要用到循环、数组、函数等多方面的知识。
首先,我们需要一个棋盘
在代码中,就用*来代替棋盘上的格子。如下图就是一个9*9的棋盘,这用可以用字符类型的二维数组储存,用两个for循环进行输出。
第二步,就是布置地雷,由于地雷随机生成,此处必须要用到随机数生成srand和rand。另外,我们可以将地雷的位置设置为‘1’,安全的位置设置为‘0’,但是它们的分布不能让玩家直接看到,因此还需要另一个二维数组 来存放地雷。
第三步,要对某一个位置进行排查地雷,可以通过输入坐标的方式,不过需要显示出雷的情况,即如果是雷,玩家就被“炸死”,不是雷,就显示周围九宫格内雷的数量。
若所有的地雷都被排查出来,游戏胜利。
以上是基本的思路,但实际的实现还有很多问题存在,我们同时注意用函数简化过程。
游戏的开始设置一个游戏菜单,在这个菜单上可以选择开始游戏,也可以退出,由于程序实现的操作过程与普通游戏过程不同,我们还可以设置一个规则界面用以查看。以下为menu函数结构
void menu() //游戏菜单
{
printf("*************************\n");
printf("*******1->开始游戏*******\n");
printf("*******2->规则***********\n");
printf("*******0->退出***********\n");
printf("*************************\n");
printf("请输入:");
}
主函数部分,就要实现输入1开始游戏,输入2显示菜单,输入0退出。为了使游戏可以多次游玩,所以加上循环结构。
int input ;
do
{
menu();
scanf("%d", &input);
if (input == 1)
game();
else if(input==2)
{
//说明部分
}
else if (input == 0)
{
printf("已退出\n");
return 0;
}
else
printf("输入错误,重新选择\n");
} while (input);
接下来是程序的核心部分,实现game函数。首先是设置棋盘,我们同时希望自定义棋盘大小和地雷的数量,这里只需设置一个较大的数组,我们采用20*20的上线,输入一个num代表行、列数(这里也可以分别设置行、列数,我们直接统一为正方形棋盘,并没有多大的区别),地雷可以在这个范围内任意,但输入错误就改为默认值(这里的上线和错误输入时的默认值都可以自行设定)。由此设置两个数组:bomb用以存放地雷,show用以显示给玩家。
int num,count;
printf("请输入难度数值n(建议小于18),将出现n*n的棋盘\n");
scanf("%d", &num);
if(num>=18)
{
printf("过大了,默认为9\n");
num = 9;
}
printf("请输入想要设置的地雷数量(>0):\n");
scanf("%d", &count);
if (count <= 0||count>num*num)
{
printf("错误!默认为n+1\n");
count = num+1;
}
char bomb[20][20];
char show[20][20];
下一步则是数组的初始化,将bomb先全部放‘0’,show全部放‘ * ’。这需要编写initboard函数
initboard(bomb, num, '0');
initboard(show, num, '*');
void initboard(char bomb[][20],int num,char set)//将棋盘初始化
{
for (int i = 0; i < num + 2; i++)
for (int j = 0; j < num + 2; j++)
bomb[i][j] = set;
}
紧接着将show展示出来,编写一个showboard函数,这个函数将反复用到,同时我们将行列序号一并打出,方便之后位置的输入。
void showboard(char a[][20], int num)//打印棋盘
{
int i, j;
printf("-------------------------------\n");
printf(" ");
for(i=0;i<=num;i++) //先进行列数的打印
printf("%d ", i);
printf("\n");
for (i = 1; i <= num; i++)
{
if (i < 10)
printf(" ");
printf("%d ", i); //打印行数
for (j = 1; j <= num; j++)
{
printf("%c ", a[i][j]);
if (j >= 10) //当数量超过10时,需要多加空格保持排列整齐
printf(" ");
}
printf("\n");
}
printf("-------------------------------\n");
}
下一步设置炸弹,通过随机数将不同位置的‘0’赋为‘1’
void setbomb(char bomb[][20], int num, int count)//设置雷
{
srand((unsigned int)time(NULL));
while (count>0)
{
int x = rand() % num + 1, y = rand() % num + 1;
if (bomb[x][y] == '0') //只有为'0'的位置可以设置
{
bomb[x][y] = '1'; //每设置成功一个,雷的数量减一
count--;
}
}
}
最后就是排查雷的部分,这一部分最复杂也最困难。我们首先需要实现两个操作,排查雷和标记雷,排中雷,则失败结束游戏,否则显示周围雷数,这就需要计算雷数的函数(注意字符与数的转换:‘1’-‘0’=1,‘2’-‘0’=2,以及1+‘0’=‘1’)
int amount(char bomb[][20], int x, int y)//计算周围雷的数量
{
return bomb[x - 1][y - 1] + bomb[x][y - 1] + bomb[x + 1][y - 1] + bomb[x - 1][y] +
bomb[x + 1][y] + bomb[x - 1][y + 1] + bomb[x][y + 1] + bomb[x + 1][y + 1]-8*'0';
}
然后将show数组中的相应位置替换
show[x][y] = amount(bomb, x, y) + '0';
接下来就有一个十分复杂的程序用以实现展开一片(这是在排查的位置为0时将周围的数一并显示,包括0以及边缘的数字,代码则在最后完整程序展示)
另一个标记雷的操作,是判断胜负的关键,我们同样输入坐标,将坐标为值变为‘#’(所有的改变都应该是在show数组上进行的),如果所有雷都标记成功,胜利,但若有错误的标记,判负,游戏中可以撤销标记,这里的实现只能是在对这一坐标执行排查的操作 。排查和标记,我们都希望能多次操作,可以使用while(scanf...)的形式,那么这里需要设置循环停止的条件(也就是切换操作),我这里设置第一项为0时停止,只需要输入0 0,就可以切换操作(条件也可以另设,只需在“规则”界面说明)。每进行一次操作,就将show数组打印一次。
以下为整个程序的完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <stdlib.h>
#include<time.h>
void menu() //游戏菜单
{
printf("*************************\n");
printf("*******1->开始游戏*******\n");
printf("*******2->规则***********\n");
printf("*******0->退出***********\n");
printf("*************************\n");
printf("请输入:");
}
void initboard(char bomb[][20],int num,char set)//将棋盘初始化
{
for (int i = 0; i < num + 2; i++)
for (int j = 0; j < num + 2; j++)
bomb[i][j] = set;
}
void showboard(char a[][20], int num)//打印棋盘
{
int i, j;
printf("-------------------------------\n");
printf(" ");
for(i=0;i<=num;i++) //先进行列数的打印
printf("%d ", i);
printf("\n");
for (i = 1; i <= num; i++)
{
if (i < 10)
printf(" ");
printf("%d ", i); //打印行数
for (j = 1; j <= num; j++)
{
printf("%c ", a[i][j]);
if (j >= 10) //当数量超过10时,需要多加空格保持排列整齐
printf(" ");
}
printf("\n");
}
printf("-------------------------------\n");
}
void setbomb(char bomb[][20], int num, int count)//设置雷
{
srand((unsigned int)time(NULL));
while (count>0)
{
int x = rand() % num + 1, y = rand() % num + 1;
if (bomb[x][y] == '0') //只有为'0'的位置可以设置
{
bomb[x][y] = '1'; //每设置成功一个,雷的数量减一
count--;
}
}
}
int amount(char bomb[][20], int x, int y)//计算周围雷的数量
{
return bomb[x - 1][y - 1] + bomb[x][y - 1] + bomb[x + 1][y - 1] + bomb[x - 1][y] +
bomb[x + 1][y] + bomb[x - 1][y + 1] + bomb[x][y + 1] + bomb[x + 1][y + 1]-8*'0';
}
void checkbomb(char bomb[][20], char show[][20],int num,int count)//排查雷
{
int x , y , i = 0, j = 0, mark = 0;
while (mark < count)
{
printf("请输入要排查的坐标行,列(输入0 0暂停):\n");
while (scanf("%d %d", &x, &y))
{
if (x == 0)
break;
if (show[x][y] != '*' && show[x][y] != '#')
{
printf("此位置已被排查\n");
continue;
}
if (x >= 1 && x <= num && y >= 1 && y <= num)
{
if (bomb[x][y] == '1')
{
printf("很遗憾,这是雷\n");
printf("下面是雷的位置\n");
showboard(bomb, num);
printf("重新开始游戏\n");
main(); //失败后,返回main函数
}
else
{
show[x][y] = amount(bomb, x, y) + '0';
if (amount(bomb, x, y) == 0) //成片出现0
{
int flag = 0, record = 0;//设置两个变量来记录
while (1)
{
//这一循环历遍show数组,当某一’*‘与’0‘紧挨(即上下左右)
//同时本身为应为’0‘,就应该显示出来
for (i = 1; i <= num; i++)
for (j = 1; j <= num; j++)
{
if (show[i][j] != '0' && amount(bomb, i, j) == 0)
{
if (show[i + 1][j] == '0' || show[i - 1][j] == '0'
|| show[i][j + 1] == '0' || show[i][j - 1] == '0')
{
show[i][j] = '0';
flag++;//为了全部排出,这一循环要一直进行
}
}
}
if (record < flag)
record = flag; //用record来跟踪flag,当flag没有再增加
else //说明没有新的’0‘出现,while循环停止
break;
}
for (i = 0; i <= num; i++) //0边缘的数
for (j = 0; j <= num; j++)
{
if (show[i - 1][j - 1] == '0' || show[i][j - 1] == '0' ||
show[i + 1][j - 1] == '0' || show[i - 1][j] == '0' ||
show[i + 1][j] == '0' || show[i - 1][j + 1] == '0' ||
show[i][j + 1] == '0' || show[i + 1][j + 1] == '0')
show[i][j] = amount(bomb, i, j) + '0';
} //九宫格内出现0的位置都应该将数显示出来
}
showboard(show, num);
}
}
else
printf("没有这样的坐标,重新输入\n");
}
printf("请输入要标记为雷的坐标行,列(输入0 0暂停):\n");
while (scanf("%d %d", &i, &j))
{
if (i == 0)
break;
if (show[i][j] != '*')
{
printf("此处不可进行该操作\n");
continue;
}
if (i >= 1 && i <= num && j >= 1 && j <= num)
{
show[i][j] = '#';
showboard(show, num);
if (bomb[i][j] == '1')
mark++; //mark记录标记成功的数量
if (mark == count)
{
int flag = 0;
for (i = 1; i <= num; i++)
for (j = 1; j <= num; j++)//对于错误的标记,在结束时赋为’!'
if (show[i][j] == '#' && bomb[i][j] != '1')
{
bomb[i][j] = '!';
flag = 1;
}
if (flag == 0)
{
printf("恭喜,你已排出所有雷!\n");
showboard(bomb, num);
}
else
{
printf("存在错误的标记!\n");
showboard(bomb, num);
}
break;
}
}
else
printf("坐标输入有误,重新输入\n");
}
}
}
void game() //游戏实现
{
int num,count; //num为行和列,count是雷的数量
printf("请输入难度数值n(建议小于18),将出现n*n的棋盘\n");
scanf("%d", &num);
if(num>=18)
{
printf("过大了,默认为9\n");
num = 9;
}
printf("请输入想要设置的地雷数量(>0):\n");
scanf("%d", &count);
if (count <= 0||count>num*num)
{
printf("错误!默认为n+1\n");
count = num+1;
}
char bomb[20][20];
char show[20][20];
printf(" *********开始扫雷********\n");
initboard(bomb, num, '0');
initboard(show, num, '*');
showboard(show,num);
setbomb(bomb, num,count);
checkbomb(bomb, show, num, count);
}
int main()
{
int input ;
do
{
menu();
scanf("%d", &input);
if (input == 1)
game();
else if(input==2)
{
printf("自定义棋盘大小后,通过输入坐标进行排查,若为雷游戏结束,展示雷的位置(棋盘中为1的位置),否则显示九宫格内的雷数\n");
printf("之后输入坐标标记你认为是雷的位置,将暂时变为“#”\n");
printf("注:以上两种操作均可多组输入,格式为\n1 1(操作第一行第一列)或2 3 4 5(分别操作第二行第三列和第四行第五列)\n");
printf("暂停某一操作,需要输入0 0\n");
printf("当标记完所有的雷,获胜,但若此时有标记错误的位置,判负,想要撤销标记,在排查步骤输入坐标即可\n");
}
else if (input == 0)
{
printf("已退出\n");
return 0;
}
else
printf("输入错误,重新选择\n");
} while (input);
}