前面已经介绍了很多数组和函数的知识了,我们可以运用这些知识来写一个扫雷游戏。
一、扫雷游戏分析和设计
首先先创建好项目,为了避免将所有代码写在同一个文件中导致代码杂乱不堪,我们可以创建三个文件:
Minesweeper.c 这个文件写游戏的测试逻辑
function.c 这个文件写游戏中函数的实现
function.h 这个文件写函数声明和需要的数据
1、扫雷游戏的功能说明
• 使用控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格子 • 默认随机布置10个雷 • 可以排查雷 ◦ 如果位置不是雷,就显示周围有几个雷 ◦ 如果位置是雷,就炸死游戏结束 ◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
2、数据结构的设计
1、雷的个数信息存放
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息。然后规定一下如果这个位置有布置雷,我们就存放1,没有雷就存放0。看起来这么做是比较美妙的,但是,如果我们多想一步的话,就是我们开始游戏进行排雷了,就会发现不对劲
布置雷的棋盘
用一个实际例子来说吧

假如我们排查(2,5)这个坐标时,我们访问周围的⼀圈8个黄色位置,统计周围雷的个数是1,那我排查的这个(2,5)坐标的位置就应该显示 1 ,那这个1和存放雷的1是不是完全没有有区别,这样就会产生混淆,那我们该如何处理呢?
有人会想到把雷规定为别的数字或者字符,这样就不会混淆了,这的确是一个办法,但但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。
我们在这采用另一种处理方式:我们用两个棋盘,专门给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放查出的雷的信息。这样就互不⼲扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘,show数组开始时初始化为字符'*',为了保持两个数组的类型⼀致,可以使⽤同⼀套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。
mine数组布置雷后的状态
show数组初始化的状态
2、防止越界
上面图片中还有一个问题,当我们排查(8,6)这个坐标时,我们访问周围的⼀圈8个⻩⾊位置,统计周围雷的个数时,最下面三个会出现越界。为了防止越界,我们在设计的时候,给数组扩大一圈,也就是设计成11*11,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就⾏,这样就解决了越界的问题。所以我们将存放数据的数组创建成11*11 是⽐较合适哒

二、代码
function.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//真正用到的棋盘
#define ROW 9
#define COL 9
//防止越界的大棋盘
#define ROWS ROW+2
#define COLS COL+2
//雷的个数
#define MINE_COUNT 10
//下面是函数的声明
//棋盘初始化函数
void initBoard(char arr[ROWS][COLS], int rows, int cols, char ch);
//打印棋盘的函数
void displayBoard(char arr[ROWS][COLS], int row, int col);
//布置雷的函数
void setMine(char arr[ROWS][COLS], int row, int col);
//排查雷的函数
void findMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
function.c
#define _CRT_SECURE_NO_WARNINGS
#include "function.h"
//初始化棋盘函数
void initBoard(char arr[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0;//控制行
int j = 0;//控制列
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = ch;
}
}
}
//打印棋盘的函数
void displayBoard(char arr[ROWS][COLS], int row, int col)
{
printf("———–扫雷游戏———–\\n");
int i = 0;//控制行
int j = 0;//控制列
//我们只需要打印中间的9*9的即可。
//同时为了方便排查时写坐标,可以在棋盘边上打印出行号和列号
for (i = 0; i <= col; i++)
{
printf("%2d ", i);
}
printf("\\n");
for(i = 1; i <= row; i++)
{
printf("%2d ", i);
for (j = 1; j <= col; j++)
{
printf("%2c ", arr[i][j]);
}
printf("\\n");
}
}
//随机布置雷的函数
void setMine(char arr[ROWS][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)//循环条件是雷的个数,布置一个减一个,只要count不是0个,循环就会继续下去
{
//棋盘横纵坐标都是1-9,所以我们要生成1-9的随机数
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
//因为是随机生成,有可能这个位置已经是雷了
//所以我们在布雷之前应该要判断该位置是不是雷
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count–;
}
}
}
//排查雷的函数(里面还有一个统计雷个数的函数)
//统计雷的个数(这是一种写法)
//int getMineCount(char mine[ROWS][COLS], int x, int y)
//{
//return mine[x – 1][y – 1] +
//mine[x – 1][y] +
//mine[x – 1][y + 1] +
//mine[x][y – 1] +
//mine[x][y + 1] +
//mine[x + 1][y – 1] +
//mine[x + 1][y] +
//mine[x + 1][y + 1] – 8 * '0';
//}
//统计雷的个数(另一种写法)
int getMineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x – 1; i <= x + 1; i++)
{
for (j = y – 1; j <=y + 1; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
void findMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
//排雷肯定不止一次,我们用循环来实现多次排雷
while (win < row * col – MINE_COUNT)
{
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
//为了防止有玩家非法输入,我们要用if语句来判断输入坐标的合法性
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
//下面这个if语句是为了防止重复判断同一个坐标
if (show[x][y] == '*')
{
//判断该位置是否为雷
//为雷直接杀死比赛
//不为雷统计周围雷的个数
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死喽\\n");
printf("雷的位置:\\n");
displayBoard(mine, ROW, COL); //展示一下雷在哪,主打死得明白
printf("——————————\\n");
break;
}
else
{
int count = getMineCount(mine, x, y);//这个地方我们可以单独用一个函数来统计
show[x][y] = count + '0';
displayBoard(show, ROW, COL);//排查完一次后,要展示一下show棋盘
win++;
}
}
else
{
printf("该坐标已被排查,请重新输入\\n");
}
}
else
{
printf("输入非法,请重新输入\\n");
}
}
if (win == row * col – MINE_COUNT)
{
printf("——————————\\n");
printf("恭喜你,排雷成功啦!\\n");
displayBoard(mine, ROW, COL);//给成功的人看看胜利成果
}
}
Minesweeper.c
#define _CRT_SECURE_NO_WARNINGS
#include "function.h"
//这是一个菜单
void menu()
{
printf("*******************\\n");
printf("***** 1、play *****\\n");
printf("***** 0、exit *****\\n");
printf("*******************\\n");
printf("请选择:");
}
//这是函数的核心
void game()
{
char mine[ROWS][COLS] ;//全部初始化为'0',存放布置好的雷的信息
char show[ROWS][COLS] ;//全部初始化为'*',存放排查出的雷的信息
//为了完成两个数组的初始化,我们写一个初始化棋盘的函数
initBoard(mine, ROWS, COLS, '0');
initBoard(show, ROWS, COLS, '*');
//打印棋盘,也写一个函数(这个函数会在很多地方用到,所以先写一个打印函数)
//displayBoard(mine, ROW, COL);
//布置雷函数
//在9*9的棋盘上随机布置10个雷
setMine(mine,ROW,COL);
//displayBoard(mine, ROW, COL);//在这个地方可以打印一下mine棋盘来看一看雷是否布置好
displayBoard(show, ROW, COL);//展示初始棋盘
findMine(mine, show, ROW, COL);
}
//这是函数的主体
int main()
{
srand((unsigned int)time(NULL));//随机生成雷需要用到
int input = 0;
//用do-while循环实现继续玩或者退出
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();//这是扫雷游戏的核心,是真正实现扫雷游戏的部分
break;
case 0:
printf("游戏结束\\n");
break;
default:
printf("输入错误,请重新输入\\n");
break;
}
} while (input);
return 0;
}
他们说,人类不适合在中年痛哭,那样会太懦弱,也不能在晚年痛哭,那样太凄苦。 所以在诞生之时,人才会哭得嘹亮,感人肺腑。 因此我想,我的第一颗泪滴,一定是献给我的生命,庆祝我的降临。 谁也不能更改它的日期,曲解它的意义。 未来的道路上,我会为许多的人和物倾注情绪,会为许多场景瞬间泪目。 但是,这第一捧热泪,一定是属于自己。 我要用第一声啼哭,让这世界与我同时睁目。




