整个扫雷的代码在文末。
扫雷涉及到的函数会进行逐个解析,哪里不会看哪里(doge)
扫雷游戏分为以下几个步骤:
进入游戏,布置棋盘和雷,排查雷,排到雷和未排到雷时的判定
游戏将由三个文件共同实现
test.c:专门测试游戏逻辑
game.c:游戏实现
game.h:对游戏中涉及到的函数等进行声明
当然,也可以不建立头文件,直接使用extern进行声明,但是这样的话会比较混乱,你在a文件里面定义一个函数,然后在b文件里面调用就要声明,然后在b里面定义另外一个函数,在a中使用又得声明一下,很凌乱无序。所以我们不妨就设一个头文件,它就相当于一个“目录”,你想实现什么功能,就先在一个源文件中定义对应的函数,然后在头文件中声明,想在其他源文件中使用就直接包含头文件,里面所有的函数就都可以使用。
先打印一个菜单,让玩家进行选择,然后创建一个变量input,这里先使用do while语句,再结合switch语句处理不同的输入情况。
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出游戏\n");
break;
case 1:
printf("开始\n");
game();
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
然后就可以进行游戏主体的设计了(通过game函数来实现)
注:排雷过程是以输入对应位置的坐标来进行的。
首先需要一个棋盘,默认棋盘规格为99,在扫雷过程中需要做到:若踩到雷则显示失败,若没有则显示周围八格雷的总数
也就是说我们需要设计一个二维数组mine,但需要注意,数组规格不可以为99,因为如果这样会出现数组越界的情况,即点到边缘部分格子的时候,无法判断雷的数量。所以我们将棋盘设为1111,并默认最外圈雷数为0。
棋盘设置好后,接下来需要区分一个格子有雷和无雷两种情况,我们规定,1为有雷,0为无雷。但这会出现一个问题,如果排查的位置周围雷数是1,那它也会显示1,这时候就会与表示有雷的“1”产生矛盾。
所以我们不妨这样,再设置一个一模一样的棋盘(二维数组),里面初始填满,当我们在第一个棋盘排查一个位置的时候,若无雷,就在第二张棋盘上对应的位置标上数字,将两个数组分别命名为mine和show,如图:
由于show数组里面的*为字符,而mine数组为数字,为了方便打印,我们输入mine数组时使用字符‘1’和‘0’,同理,show中的雷数也用数字字符表示
那么我们现在有了两个棋盘:mine和show,show是给玩家看的,mine供我们调试代码,测试游戏是否可以正常运行(比如后面要看所有雷都排查出来时是否会显示“恭喜你,排雷成功”,你直接看雷咋布置,把安全的位置排出来就行了,不用费时间一个一个位置去推理)。
初始化棋盘,要在mine里面布置雷,在show里面用*填满数组。
这个函数需要四个参数:数组,行,列,填充数组的符号
考虑到我们需要多处将行和列作为参数进行传递,所以我们这样处理:
game.h
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
定义后,ROW就是9,其他以此类推,这样做的好处在于:你如果后续想改变棋盘行和列大小的话,只需直接在头文件里面直接改个数字就行了,不用在game.c 和test.c 里面把所有数字都改一遍(因为二维数组传参时你在形参和实参都得标明数组大小,要改的话就都要改)。
函数内部就写一个for的嵌套循环,对棋盘每个格子进行赋值即可,注意这里棋盘大小是11*11
game.c
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
test.c
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS, '*');
mine棋盘是雷的位置信息,这个不要展示。只需展示show棋盘,我们要在进入游戏、每次排雷后展示下棋盘,让玩家看到雷数的情况。
上面已经初始化好了,接下来只要打印出来就行了。注意展示的棋盘是9*9
为了方便玩家输入排雷的坐标,我们把行数和列数也打印出来。
game.c
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
for (int k = 0; k <= row; k++) //打印列数
{
printf("%d ", k);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d", i); //每行打印棋盘之前打印行数
for (int j = 1; j <=col; j++)
{
printf(" %c", board[i][j]);
}
printf("\n"); //记得换行
}
}
test.c
DisplayBoard(show, ROW, COL);
我们要确保雷是随机布下的,所以这里需要用到rand函数,要用rand生成真正的随机数,我们要先调用srand来对它初始化,使用time函数的返回值作为srand的参数,time的参数要用NULL(空指针),这样time会返回时间戳,时间戳是一个随机值。(详见第四讲的课件)
在棋盘中,要使布雷坐标随机,就要使横、纵坐标都是随机数。
int x = rand() % row + 1; //控制布雷的范围在1-9,注意这里是row而不是ROW,row和下面的col是Setmine的形参,你等下看整个函数就明白了。
int y = rand() % col + 1;
然后我们要设定雷的数量,考虑到要方便修改雷的数量,且别的地方可能也要用到雷数这个变量,所以就跟前面定义ROW和COL一样,定义一个Easy_Count(因为真正的扫雷不同难度的雷数不一样,所以我们先设定一个“简单难度的雷数”),设置10个雷
game.h
#define Easy_Count 10
现在有10个雷,布雷我们采用循环,设雷数为count,每次循环后count就减1,减到0就停止。还记得前面说用字符 ‘1’ 来表示雷吧?注意布雷要布在原来没放过雷的地方,这里用一个条件语句进行判断。整个函数如下:
game.c
void Setmine(char board[ROWS][COLS], int row, int col)
{
int count = Easy_Count;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') //该位置没有雷
{
board[x][y] = '1';
count--;
}
}
}
test.c
Setmine(mine, ROW, COL);
首先明确我们在这个函数需要实现:输入坐标,排查该坐标后的反馈,胜利条件的判定
先初始化变量x,y表示行和列,使用scanf输入,因为要多次输入,所以要用到循环,那啥时候循环会结束呢?一种是被雷炸死,另外一种是把所有无雷的坐标都排出来了,所以这里来一个计数器,记为win,初始化为0,每排一个无雷的坐标win就加1,无雷的坐标总共有ROWCOL - Easy_Count,所以只要设定win
if (win == ROW * COL - Easy_Count)
{
printf("恭喜你,扫雷成功\n");
}
游戏不管赢了还是输了,都可以展示一下棋盘,让玩家看一下雷的分布情况,这时候可以再用一下之前展示棋盘的函数,放在这个if语句的后面。
若为雷,则显示“很遗憾,你被炸死”,然后退出循环。若不为雷,给这个坐标打印周围雷数。
这个函数返回类型为int,在mine棋盘里面统计雷数,然后将返回值打印在show对应位置。mine里面的数字是字符,要把它们转换为整型数字的话只需减掉 ’ 0 ',因为字符数字在加减运算的时候是用对应的ASCII码运算。比如‘3’-‘0’,因为它们的ASCII码相差3,相减之后以整型形式打印出来就是3,那现在有8个字符,就要减掉8个字符0咯。上代码:
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');
}
因为这个函数只在FindMine里面使用,所以它只需要在game.c里面定义(记得是在FindMine前面定义),再在FindMine里面调用就行了,没必要在头文件里面声明。
Q:GetMineCount为啥要减掉字符0转换为整型数字再求和?直接返回字符数字相加不是更好?
A:因为mine里面是字符1和字符0,你不减掉8个字符0,直接让它们相加不能得到正确结果,你要区分字符数字运算和整型数字运算的差别(比如字符1加字符1不等于字符2)。
现在我们已经得到了雷数(整型),但是show是个字符数组,所以再给雷数加上字符0就行了,然后展示棋盘。
综上,FindMine函数可以这样写:
void FindMine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - Easy_Count)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x,&y);
if (1 <= x && x <= ROW && 1 <= y && y <= COL)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = GetMineCount(mine, x, y);
show [x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入");
}
}
if (win == ROW * COL - Easy_Count)
{
printf("恭喜你,扫雷成功,雷的分布如下:\n");
//DisplayBoard(mine[ROWS][COLS], ROW, COL);
DisplayBoard(mine, ROW, COL);
}
}
你会发现,一直排查同一个无雷的坐标win也会一直++,这就是游戏的bug,所以我们要进行改进,改为已排查的坐标不能再排查。
最开始show里面会被初始化填满*,所以只要你排查的坐标不为*,就显示“该坐标已被排查,请重新输入”,改进后如下:
if (1 <= x && x <= ROW && 1 <= y && y <= COL)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死\n");
DisplayBoard(mine, ROW, COL);
break;
}
else if (show[x][y] != '*')
{
printf("该坐标已被排查,请重新输入\n");
}
else
{
int count = GetMineCount(mine, x, y);
show [x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入");
}
是否可以选择游戏难度
◦ 简单 99 棋盘,10个雷
◦ 中等 1616棋盘,40个雷
◦ 困难 30*16棋盘,99个雷
如果排查位置不是雷,周围也没有雷,可以展开周围的一片
是否可以标记雷
是否可以加上排雷的时间显示
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define Easy_Count 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//展示棋盘
void DisplayBoard(char board[ROWS][COLS], int rows, int cols);
//布雷 设定雷数,随机布雷
void Setmine(char board[ROWS][COLS], int row, int col);
//查雷
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);
//得到雷数
//int GetMineCount(char board[ROWS][COLS], int row, int col);
game.c
#include "game.h"
//将不同棋盘初始化填上不同的东西 mine:0 show:*
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
//展示棋盘 打印出来,并且还要打印坐标
//9*9棋盘这个二维数组中,第0,10行和第0,10列要空出来,后续才能实现计算周围雷个数的目标
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
for (int k = 0; k <= row; k++) //打印列数
{
printf("%d ", k);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d", i);
for (int j = 1; j <=col; j++)
{
printf(" %c", board[i][j]);
}
printf("\n");
}
}
void Setmine(char board[ROWS][COLS], int row, int col)
{
int count = Easy_Count;
while (count)
{
int x = rand() % row + 1; //控制布雷的范围在1-9
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[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');
}
void FindMine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - Easy_Count) //不断地扫雷
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x,&y);
if (1 <= x && x <= ROW && 1 <= y && y <= COL)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死\n");
DisplayBoard(mine, ROW, COL);
break;
}
else if (show[x][y] != '*')
{
printf("该坐标已被排查,请重新输入\n");
}
else
{
int count = GetMineCount(mine, x, y);
show [x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入");
}
}
if (win == ROW * COL - Easy_Count)
{
printf("恭喜你,扫雷成功,雷的分布如下:\n");
//DisplayBoard(mine[ROWS][COLS], ROW, COL);
DisplayBoard(mine, ROW, COL);
}
}
test.c
#include "game.h"
void menu()
{
printf("***********扫雷游戏***********\n");
printf("请选择>>开始游戏:1 退出游戏:0\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
printf("\n");
Setmine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
FindMine(show,mine, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出游戏\n");
break;
case 1:
printf("开始\n");
game();
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}