首先确定要实现的目标
1. 9*9的棋盘
2.菜单选择游戏或退出 并打印规则
3.实现基础的排雷功能 :1.能判断所选坐标是否为雷 2.不是雷 查找坐标周围雷个数并在棋盘上显示
4.制定一个基本规则:9*9的棋盘上布置25个雷 排查10次(不包含重复排查)没被炸死则胜利 被炸死则失败
我们要实现的功能:打印菜单 初始化棋盘(布置地雷) 打印棋盘 排查地雷
实现功能较多 故使用函数封装功能便于 后续调用 以及main函数简洁可读
下面一 一 实现各个功能
一.菜单功能
目标:打印选择与规则
定义一个函数menu来打印菜单
代码实现:
void menu()
{
printf("**************************\n");
printf("********1.开始游戏********\n");
printf("********0.退出游戏********\n");
printf("**************************\n");
printf("****十次没被炸死就是赢****\n");
printf("*******重复不算次数*******\n");
printf("**************************\n");
printf("请选择:");
}
二.初始化棋盘
要实现扫雷棋盘的效果 我们可以分为两个棋盘(防止棋盘上地雷信息与排雷信息混淆) 一个是展示给玩家看的棋盘称为ShowBoard 一个是布置地雷的棋盘称为MineBoard (1为地雷0为非地雷) 注:我们使用的是字符0和1
因为我们是通过二维坐标实现扫雷
我们可以用二维数组来存放棋盘上每一个位置对应的元素
定义一个函数来初始化棋盘
函数无需返回值 函数参数有 二维数组,行数,列数,初始化元素
二维数组则可使用嵌套for循环来初始化每个元素
代码实现:
void InitShowBoard(char arr[11][11], int m, int n, char init)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arr[i][j] = init;
}
}
}
注:这里展示的是ShowBoard初始化函数 因为MineBoard初始化略有不同
函数定义完成
那么我们要调用函数来实现初始化功能
首先创建两个二维数组
char MineBoard[11][11];
char ShowBoard[11][11];
那么问题来了:为什么在这里定义的数组是11*11的呢,我们不是需要打印9*9的棋盘吗?后续将会解答
我们先继续完成棋盘的初始化
调用函数:
InitShowBoard(ShowBoard, m, n, '*');
在此m为行n为列
这行代码的功能就是将ShowBoard中元素全部初始化为*
那么MineBoard呢?
代码展示:
void InitMineBoard(char arr[11][11], int m, int n,char init)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arr[i][j] = init;
}
}
SetMines(arr, m, n);
}
我们会发现多了一个SetMines
这便是布置地雷函数
我们在此用到了函数的嵌套调用
初始化地雷棋盘:先将所有元素初始化为0再在棋盘上布置地雷1
那么我们来定义SetMines 首先确定参数:二维数组 行数m 列数n
实现功能:随机布置地雷在9*9的范围内 25个地雷
注意是随机 也就是说我们在设置地雷坐标(x,y)时 x与y都是随机的 那么我们可以让它们根据系统时间不同而改变随机值 那么我们首先需要在main 函数中使用
srand((unsigned int)time(NULL));
来根据不同系统时间来设定随机数种子
然后在函数中使用rand()%9+1 来初始化x与y
注:rand()%9范围是0-8故+1使范围变为1-9
有了随机坐标那么我们就可以按坐标来布置地雷 因为我们要布置25个所以我们可以用for循环来实现获取25次坐标并布雷(这边会产生一个问题:两次获取的随机坐标可能会是同一个,解决这个问题我们完全可以使用if语句判断一下:如果已经为1那么我们可以让i-1意味着我们将多循环一次获取坐标布雷的操作)那么又会产生一个问题 我们怎么设置条件呢由于我们使用的是字符0与1比较时我们可以比较ASCII码值 if(arr[x][y]-'0'==1) i--;
SetMines代码实现
static void SetMines(char arr[11][11], int m, int n) //用static修饰防止其他.c文件错误调用
{
for (int i = 0; i < 25; i++) //循环布置25个雷
{
m = rand() % 9 + 1;
n = rand() % 9 + 1;
if (arr[m][n] - '0' == 1)
{
i--;
}
arr[m][n] = '1';
}
}
这里的static我会在文章后续解释
那么到此我们终于实现了两个棋盘的初始化功能
三.打印棋盘
设置函数实现二维数组的打印
参数:二维数组
我们可以使用嵌套for循环来打印元素
但是我们在这不仅仅是打印9*9的棋盘 还有外围的坐标轴
如图:
所以我们可以先打印第一行横坐标
再循环打印其余行
代码如下:
void PrintShowBoard(char arr[11][11], int m, int n)
{
for (int i = 0; i <= m-2; i++)
{
printf("%-3d", i);
}
printf("\n");
for (int i = 1; i <= m-2; i++)
{
printf("%-3d", i );
for (int j = 1; j <= n-2; j++)
{
printf("%-3c", arr[i][j]);
}
printf("\n");
}
}
关于为什么是%-3d 是为了棋盘元素对齐美观 每个元素向左对齐并占三格位置
下面是MineBoard的打印函数 便于后续检验布雷与排雷
void PrintMineBoard(char arr[11][11], int m, int n)
{
for (int i = 0; i <= m-2; i++)
{
printf("%-3d", i);
}
printf("\n");
for (int i = 1; i <= m-2; i++)
{
printf("%-3d", i);
for (int j = 1; j <= n-2; j++)
{
printf("%-3c", arr[i][j]);
}
printf("\n");
}
}
不能说很像只能说一模一样 可以合并成一个函数如PrintBoard
四.排查地雷
排查地雷分两种结果:1.是地雷 被炸死 2.不是地雷 将排查位置元素改为周边地雷数
定义一个函数FindMines
首先提示用户输入坐标
然后判断是雷输出被炸死游戏结束回到菜单选择
不是雷 排查周边地雷记录个数并替换ShowBoard中被排查的那个坐标位置的元素
参数:二维数组,二维数组,行数m,列数n
代码展示:
void FindMines(char MineBoard[11][11],char ShowBoard[11][11],int m,int n)
{
int out = 0;
while (out < 10)
{
int x = 0;
int y = 0;
printf("请输入你要查找的坐标:");
scanf_s("%d,%d", &x, &y);
if (MineBoard[y][x]=='1')
{
printf("你被炸死了,弟中之弟\n");
break;
}
else
{
if (ShowBoard[y][x] !='*')
{
out--;
}
/*system("cls");*/
GetMineCount(MineBoard, ShowBoard, x, y);
PrintShowBoard(ShowBoard, m, n);
out++;
}
}
最后加上胜利条件
if (out == 10)
{
printf("恭喜你赢了,有东西但不多\n");
}
注:图文中的system("cls");
是清屏操作 需包含头文件 stdlib.h才可使用
out 变量用来记录次数
if (ShowBoard[y][x] !='*')
{
out--;
}
防止重复排查一个坐标 捡漏通关
GetMineCount嵌套函数调用 用来排查周围地雷个数
下面我们来实现排雷数函数的定义
首先我们排雷是排查坐标周围的八个元素那么问题来了 边缘坐标位置特殊不用排查八个但是如果单独作为情况的话显然比较麻烦 但是直接排查就会出现越界访问的问题 如何解决呢?
其实很简单 我们再给棋盘外面套一圈 反正存放的元素都是‘0’不会影响排雷个数
请看图:
这样就轻松解决了越界访问的问题
也就解释了文章前部分的问题:为什么在这里定义的数组是11*11的呢,我们不是需要打印9*9的棋盘吗?和 为什么布雷的坐标要限定范围为1-9
下面是函数定义的代码
static void GetMineCount(char MineBoard[11][11],char ShowBoard[11][11],int x,int y)
{
int MineCount = 0;
MineCount = (MineBoard[y - 1][x + 1] + MineBoard[y][x + 1] + MineBoard[y + 1][x + 1] + MineBoard[y - 1][x]
+ MineBoard[y + 1][x] + MineBoard[y - 1][x - 1] + MineBoard[y][x - 1] + MineBoard[y + 1][x - 1]) - '0' * 8;
ShowBoard[y][x] = MineCount + '0';
}
ShowBoard[y][x] = MineCount + '0'; 利用的是ASCII码值
MineCount的计算利用的是字符数字减去字符零等于数字本身计算的
注:对于前文提到的statuc
static 是静态的意思
用static修饰的函数的外部链接属性变为内部链接属性 故在其他分文件中不可调用
这里使用是防止其被错误调用
game.h中是函数的声明
game.c中放的是函数定义
扫雷.c中是main函数
需要具体文件可以访问我的gitee仓库