目标
创建一个9行9列的扫雷范围,一共有10个地雷。开始游戏后,用户选择一个位置,如果有雷,则游戏失败,如果没有雷,则该位置显示以此为中心一圈范围的雷的数量,并继续扫雷。将10个雷之外所有非雷的位置找出来则成功。游戏的扫雷范围和扫雷数量可以更改。
扫雷游戏的编写思路
用c语言编写一段扫雷游戏的代码首先需要将扫雷游戏的功能拆分多个模块,每个模块的功能写对应的函数去实现,最后按需求依次在主函数中使用这些具有不同功能的函数从而完成扫雷游戏的简单实现。
因此我们先创建三个文件,一个是mian.c源文件,在该源文件中写主体结构的代码,一个是game.c源文件,在这个源文件中定义各个模块所需要的具备各种功能的各个函数,一个是game.h头文件,该文件中主要声明各个函数。
1 整体框架的构建
1.1 构建主函数
int main()
{
int input = 0;
do
{
menu();//目录
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏。\\n");
break;
default:
printf("选择错误,重新选择。\\n");
break;
}
} while(input);
return 0;
}
先在main.c文件中创建一个do-while循环,循环中写扫雷游戏的框架。do-while的目的是先执行,再根据选择决定要不要继续执行。
所以接下来用scanf接收用户的选择。我们规定,输入1开始游戏,输入0退出游戏。因此,我们将输入值“input”放在while的条件中,1为真继续执行,0为假,退出循环,结束游戏。
我们用switch语句来实现以上要求,输入1,选择开始游戏,那么在里面放入game函数(后续编写),开始游戏;输入0,退出游戏;输入其他数字,提醒用户输入错误,重新选择。由于非0表示真,所以会继续循环重新选择。
1.2 制作目录
制作一个目录美化我们的代码,让用户知道输入1开始游戏,输入0退出游戏。
void menu()
{
printf("———————\\n");
printf("———————\\n");
printf("——-1. play——-\\n");
printf("——-0. exit——-\\n");
printf("———————\\n");
printf("———————\\n");
}
写一个函数,内容是打印目录,这样在主函数中直接使用这个函数就可以打印目录。
2 扫雷游戏棋盘的构建
2.1 用二维数组表示扫雷棋盘
扫雷的棋盘可以看成9行9列的表格,格子中是空的或者有地雷。我们可以创建一个9行9列的二维数组简单将其表示出来。其中,字符‘0’表示没有雷,字符‘1’表示有雷。
在我们选择某个边界的位置时,周围一圈的位置就会溢出我们创建的二维数组,查找雷的时候并不方便。所以,我们可以创建一个比扫雷范围大一圈的二维数组。例如,9行9列的棋盘我们就创建一个11行11列的二维数组,这样判断边界位置上周围雷的个数的时候就不会超出数组的范围,便于计算。


2.2 头文件的使用
为了让游戏的内容更灵活,代码更改方便,我们在game.h头文件中定义几个变量
#define ROW 9//这是展示出来的数组的行数
#define COL 9//这是展示出来的数组的列数
#define ROWS ROW+2//这是实际需要的创建的数组的行数
#define COLS COL+2//这是实际需要的创建的数组的列数
#define EASY_COUNT 10//这是地雷的数量
在我们需要更改扫雷游戏的棋盘大小和地雷数量时,我们可以直接在这里更改。
2.3 创建两个二维数组
我们需要创建两个二维数组,一个二维数组用来存放‘0’和‘1’,表示该位置下有没有地雷。另一个数组用来展示给用户看,让用户知道自己查找了哪些位置,并显示那些查找过的位置的四周有几个地雷。
void game()
{
char mine[ROWS][COLS] = {0};//布置雷的数组
char show[ROWS][COLS] = {0};//展示给用户的数组
}
2.4初始化两个二维数组
创建好两个二维数组后,我们需要先初始化这两个二维数组,给它们填入对应的内容。
那么,我们在game.c文件中出创建一个InitBoard函数。
void InitBoard(char board[ROWS][COLS], int r, int c, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)//定位行
{
for (j = 0; j < c; j++)//定位列
{
board[i][j] = set;
}
}
}
这个函数的四个参数分别对应数组,需要初始化的行的数量,需要初始化的列的数量,填充的内容。
这样,我们就可以引用两次InitBoard函数来分别初始化这两个数组。
void game()
{
char mine[ROWS][COLS] = {0};//布置雷的数组
char show[ROWS][COLS] = {0};//展示给用户的数组
//初始化游戏区域
InitBoard(mine, ROWS, COLS, '0');//先全部用‘0’填充,后续再随机插入地雷
InitBoard(show, ROWS, COLS, '*');//用‘*’表示该位置没有翻开
}
3 打印出扫雷棋盘
将数组的内容填好后,我们就需要打印出数组让用户看见。我们创建一个DisplayBoard函数来实现这一功能。
void DisplayBoard(char board[ROWS][COLS], int r, int c)
{
int i = 0;
int j = 0;
for (j = 0; j <= c; j++)//打印列号,从0到c,0与行号对齐,避免错位
{
printf("%d ", j);
}
printf("\\n");//打印完列号后换行
for (i = 1; i <= r; i++)//i从1开始,<=r结束,不显示第0行和最后一行,那是外围的一圈
{
printf("%d ", i);//先打印行号,再打印这一行中每列的内容
for (j = 1; j <= c; j++)//j从1开始,<=c结束,不显示第0列和最后一列,那是外围的一圈
{
printf("%c ", board[i][j]);
}
printf("\\n");//每行结束后换行
}
printf("\\n");
}
这三个参数分别对应数组,需要打印的行的数量,需要打印的列的数量
这样,我们就可以应用该函数来打印mine数组。
void game()
{
char mine[ROWS][COLS] = {0};//布置雷的数组
char show[ROWS][COLS] = {0};//展示给用户看的数组
//初始化游戏区域
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROW, COL);
}
4 将雷布置到棋盘中
打印出棋盘后,我们需要在棋盘的随机位置填充地雷,我们可以创建SetMine函数来实现这一功能。
void SetMine(char mine[ROWS][COLS], int r, int c)
{
int count = EASY_COUNT;//布置得雷的数量
while (count)
{
int x = rand() % r + 1;//控制布置雷的行的范围
int y = rand() % c + 1;//控制布置雷的列的范围
if (mine[x][y] != '1')//当这个位置没有被布置过雷的条件下
{
mine[x][y] = '1';
count–;//每布置成功一次-1,使该循环的循环次数为count次
}
}
}
这三个参数分别对应被布置雷的数组,需要被布置雷的行的数量范围,需要被布置的列的数量范围。
rand()会随机生成一个数,这个随机数%r,那么得到的数字范围就是0 ~ r-1,+1后范围就是1~r。
使用rand函数需要在主函数中加入种子srand((unsigned int)time(NULL))。并加上#include <stdlib.h>和#include <time.h>这两个头文件。
这样我们就可以在数组中随机生成一定数量的雷了
void game()
{
char mine[ROWS][COLS] = {0};//布置雷的数组
char show[ROWS][COLS] = {0};//展示给用户看的数组
//初始化游戏区域
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
}
5 在棋盘中排查雷
将雷布置好后,就可以开始排查雷了。我们可以创建一个FindMine函数来实现这个功能。由于这个函数较复杂,我们还可以写一个简单的函数GetMineCount来帮助FindMine的实现
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
/*利用ASCII码值进行计算,'0'的ASCII码值是48,'1'的ASCII码值是49,四周八个位置的字符的ASCII码值加起来减8个'0'的ASCII码值就是周围'1'的个数*/
return (mine[x – 1][y] + mine[x – 1][y – 1] + mine[x][y – 1] + mine[x + 1][y – 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x – 1][y + 1] – 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int x = 0;
int y = 0;
int win = 0;
while (win < (r*c – EASY_COUNT))//当排雷的次数小于棋盘格子数减雷的数量,说明还没有排完,继续进入循环排雷
{
printf("请输入要排查的坐标:");
scanf("%d%d", &x, &y);
//判断坐标是否在棋盘范围内
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
//判断坐标是否被排查过
if(show[x][y] == '*')
{
//判断该位置是不是雷
if (mine[x][y] == '0')//不是雷
{
int count = GetMineCount(mine, x, y);//周围雷的数量
show[x][y] = (char)(count + '0');//将排查到的雷的数量转化为字符类型显示在棋盘上
DisplayBoard(show, ROW, COL);//打印排查后的棋盘信息
win++;//排查成功的次数+1
}
else//是雷
{
printf("很遗憾,你被炸死了\\n");
DisplayBoard(mine, ROW, COL);//展示雷的布局
break;//跳出循环,结束游戏
}
}
else//坐标被排查过的情况,结束后会重新进入循环选择坐标,由于win没有+1,不会影响使游戏结束需要的排雷成功的次数
{
printf("该坐标已经排查过了,请重新输入坐标\\n");
}
}
else//坐标不在棋盘范围的情况,结束后会重新进入循环选择坐标,由于win没有 + 1,不会影响使游戏结束需要的排雷成功的次数
{
printf("输入错误坐标,请重新输入\\n");
}
}
//结束循环后来到这里,成功或失败
if (win == r * c – EASY_COUNT)//成功的情况
{
printf("恭喜你,排雷成功\\n");
DisplayBoard(mine, ROW, COL);
}
}
这四个参数分别对应雷的布局的数组,展示给用户看的数组,排查雷的行的数量范围,排查雷的列的数量范围
在game函数中加上这个函数后,game函数的功能就完整了
void game()
{
char mine[ROWS][COLS] = {0};//布置雷的数组
char show[ROWS][COLS] = {0};//展示给用户看的数组
//初始化游戏区域
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
总结
这样,就用c语言写了一个简单的扫雷游戏。如有错误,欢迎指出。


