本篇给大家带来扫雷小游戏,有简单,中等,和困难三种模式供玩家选择,读者如有更好的建议,可以在评论区留言
代码模块将分为3个部分,游戏页面逻辑书写写在 test.c 文件中,游戏所需要的声明书写在 game.h 文件中,实现游戏的函数写在 game.c 文件中
文章目录
一、test.c 文件
1. 游戏界面的书写
如果小伙伴不喜欢该游戏界面,可以自行更改菜单 mnue 函数
#include "game.h"; //需要用到 game.h 中的声明
void mnue()
{
printf("******************\n");
printf("*** 1.play ***\n");
printf("*** 0.exit ***\n");
printf("******************\n");
}
为了让玩家有更好的游戏体验,游戏玩了一局之后我们可以选择再来一局,或者退出游戏,这里采用 do while 循环
int main()
{
int input = 0;
//设置随机数的生成起点
//需要的头文件是 stdlib.h 和 time.h
//在布置雷的函数实现中说明
srand((unsigned int)time(NULL));
do
{
//打印菜单
mnue();
printf("请输入:>");
scanf("%d", &input);
system("cls"); //将屏幕已经打印的内容清理掉,
//需要头文件是 stdlib.h
//用户选择
switch (input)
{
case 1: //用户选择 1 开始游戏,进入到 game 函数中
printf("欢迎来到扫雷游戏\n");
game();
break;
case 0: //用户选择 0 退出游戏
printf("退出游戏\n");
break;
default : //如果选择的不是 1 则需要重新输入
printf("输入错误,请重新输入\n");
break;
}
} while (input); //选择 0 条件不成立会退出循环,已结束游戏
//选择 1 或输入了其他数字,判断为真
//进入循环,打印菜单,让用户重新选择
return 0;
}
2. 游戏逻辑的书写
用户选择 1 之后来到 game 函数
void game_mnue()
{
printf("************************************\n");
printf("*** 1.easy 2.advn 3.diff ***\n");
printf("************************************\n");
}
//代码中字母是全大写的字符放在 game.h 中声明
void game()
{
int input = 0;
int dgree_row = 0; //接收难度的行数
int dgree_col = 0; //接收难度的列数
int mine_num = 0; //接收雷的数目
//数组长度定义为困难模式的行和列,已便可以容纳3种模式
char mine[DIFF_ROW][DIFF_COL]; //放地雷
char show[DIFF_ROW][DIFF_COL]; //排地雷
do
{
//选择游戏模式
game_mnue();
printf("请输入难度:>");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1: //简单模式
dgree_row = EASY_ROW;
dgree_col = EASY_COL;
mine_num = EASY_MINE;
break;
case 2: //中等模式
dgree_row = ADVN_ROW;
dgree_col = ADVN_COL;
mine_num = ADVN_MINE;
break;
case 3: //困难模式
dgree_row = DIFF_ROW;
dgree_col = DIFF_COL;
mine_num = DIFF_MINE;
break;
default : //如果输入错误,重新输入
printf("输入错误请重新输入:\n");
break;
}
//用户输入不在选择范围,可以重新选择
} while (input < 1 || input > 3);
//初始化地雷数组和排地雷数组
//第四个参数表示初始化的内容
init(mine, dgree_row, dgree_col, '0');
init(show, dgree_row, dgree_col, '*');
//检测程序是否符合预期(实际代码中不需要运行)
//print(mine, dgree_row, dgree_col);
//print(show, dgree_row, dgree_col);
//布置雷,第四个参数表示雷的数目
set_mine(mine, dgree_row, dgree_col, mine_num);
//检测程序是否符合预期(实际代码中不需要运行)
//print(mine, dgree_row, dgree_col);
//排雷并判断是否胜利
find_mine(mine, show, dgree_row, dgree_col, mine_num);
Sleep(5000); //让输出结果停留 5s 需要的头文件是 Windows.h
system("cls");
}
二、game.h 文件
用来存放函数的声明,需要的头文件,以及不同模式下棋盘的大小和雷的数目
该文件中不同模式下的数据是参照扫雷游戏的数据设计的,但是行和列会多上二,具体原因参考本篇目录中 三、 game.c 文件中 1. 设计思路
该文件可以更改对应模式下的棋盘大小和雷的数目,应当注意行数和列数都应当设置为想要更改的数目 + 2
#define _CRT_SECURE_NO_WARNINGS 1 //在VS编译器用 scanf 等函数
//需要定义该符号
//需要包含的头文件
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>
//这里的行数和列数设计为比实际运行出来的行和列多二
//简单模式
#define EASY_ROW 11
#define EASY_COL 11
#define EASY_MINE 10
//中等模式
#define ADVN_ROW 18
#define ADVN_COL 18
#define ADVN_MINE 40
//困难模式
#define DIFF_ROW 18
#define DIFF_COL 32
#define DIFF_MINE 99
//初始化,第四个参数表示初始化的内容
void init(char arr[DIFF_ROW][DIFF_COL],
int row, int col, char piece);
//打印
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col);
//布置雷,第四个参数表示雷的数目
void set_mine(char mine[DIFF_ROW][DIFF_COL],
int row, int col,int mine_num);
//排雷并判断是否胜利
void find_mine(char mine[DIFF_ROW][DIFF_COL],
char show[DIFF_ROW][DIFF_COL],
int row, int col, int mine_num);
三、game.c 文件
1. 设计思路
(1)为什么用字符 ‘1’ 表示雷
(2)为什么需要采用两个数组
2. 初始化
根据设计思路:mine 数组初始化为全 ‘0’ ,布置雷时改为字符 ‘1’ 即可
show 数组为了达到视觉效果初始化为全 ‘*’,排查雷时改为相应的信息
#include "game.h"; //需要用到 game.h 中的声明
void init(char arr[DIFF_ROW][DIFF_COL],
int row, int col, char piece)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = piece;
}
}
}
3. 打印
根据设计思路:只需打印第二行到倒数第二行,以及第二列到倒数第二列
注意:数组下标是从零开始的,在该函数中数组下标的范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2
为了让玩家能容易的找到需要排查的坐标,这里打印行号和列号
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col)
{
int i = 0;
int j = 0;
//打印列号,为了对齐,这里从 0 开始打印
for (i = 0; i < col - 1; i++)
{
printf("%d", i);
//为了对齐,10 以下的数字打印两个空格
//10 及以上的数字会占用两个位置,只打印一个空格
i >= 10 ? printf(" ") : printf(" ");
}
printf("\n");
//打印数组内容
for (i = 1; i < row - 1; i++)
{
//打印行号
printf("%d", i);
//按照列号的对齐方式
i >= 10 ? printf(" ") : printf(" ");
for (j = 1; j < col - 1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
4. 布置雷
根据设计思路:雷只能放在第二行到倒数第二行,以及第二列到倒数第二列
注意:数组下标是从零开始的,该函数雷位置的下标范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2
- rand 函数可以随机产生 0 - 32767 之间的整数
- 但是在使用 rand 函数之前需要调用 srand 函数设置随机数的生成起点
- 为了让 rand 函数每次运行时产生的随机数不一样,需要给 srand 函数的参数设置为1 个可以变化的值
- 由于时间戳是不断变化的,可以将时间戳作为 srand 的参数
- time 函数可以获取当前的时间
void set_mine(char mine[DIFF_ROW][DIFF_COL],
int row, int col, int mine_num)
{
//循环次数不能确定
while (mine_num)
{
//生成雷的坐标
//rand 函数返回的数字是 0 - 32767 之间的数字
//模上 (row - 2),则会产生 0 - (row - 3) 的数字
//加 1 产生 0 - (row - 2) 的数字
int x = rand() % (row - 2) + 1;
int y = rand() % (col - 2) + 1;
//判断雷坐标是否重复
if (mine[x][y] == '0')
{
mine[x][y] = '1'; //'1' 表示该位置是雷
mine_num--; //成功布置雷后,雷的数目才减 1
}
}
}
5. 判断游戏是否结束以及存储排查雷的信息
根据设计思路:玩家输入的坐标范围只能放在第二行到倒数第二行,以及第二列到倒数第二列
注意:数组下标是从零开始的,玩家排查坐标的范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2
(1)判断游戏胜利还是失败
void find_mine(char mine[DIFF_ROW][DIFF_COL],
char show[DIFF_ROW][DIFF_COL],
int row, int col, int mine_num)
{
int x = 0;
int y = 0;
int win = 0;
//找出所有非雷的位置,游戏胜利
//非雷位置的数目 = 位置总数目 - 雷的数目
while (win < (row - 2) * (col - 2) - mine_num)
{
//打印 show 数组让玩家可以根据已经排查的坐标
//(坐标的内容为周围雷的信息)来判断其他坐标是否是雷
print(show, row, col);
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
system("cls");
//坐标合法
if (x >= 1 && x <= row - 2 && y >= 1 && y <= col - 2)
{
//坐标是否被排查过,(show 数组中存放排雷的信息)
if (show[x][y] == '*')
{
//是否踩雷,(mine 数组中存放雷的信息)
if (mine[x][y] == '1')
{
printf("很遗憾,游戏失败,你被炸死了\n");
break;
}
else
{
//conut_mine 函数计算该坐标周围的雷数并存储到 show 数组中
//如果该坐标周围雷数为 0,则完成相应的展开
count_mine(mine, show, row, col, x, y);
//count 是在 count_mine 函数前定义的全局静态变量
//用来记录当前坐标在 count_mine 函数执行完成后排查的位置个数
win += count;
//避免影响下一次 count_mine 函数执行后排查的位置个数
count = 0;
}
}
else
{
printf("输入错误,请重新输入:>\n");
}
}
else
{
printf("坐标非法,请重新输入:>\n");
}
}
//判断游戏是否胜利
if (win == (row - 2) * (col - 2) - mine_num)
printf("恭喜你,游戏胜利,排雷成功\n");
print(show, row, col);
}
(2)计算并存储排查雷的信息
当计算该坐标的雷数为 0 时,需要判断该坐标周围的 8 个坐标的信息,如果存在周围的坐标的雷数也为 0 时,就继续判断该周围坐标的周围的 8 个坐标,以此类推,其中如果坐标已被判断过,便直接退出当前所在的函数
由于计算一个坐标周围的雷数的方法是一样的,只是坐标不同,这里采用函数的递归来实现
static int count = 0; //用来记录排查的位置个数
//计算并存储雷的个数,如果该坐标的雷数为 0 则递归判断周围 8 个坐标的雷数
static void count_mine(char mine[DIFF_ROW][DIFF_COL],
char show[DIFF_ROW][DIFF_COL],
int row, int col, int x, int y)
{
//在递归过程中坐标可能会越界
//坐标也可能已经判断过了,此时直接退出当前所在的函数
if (x < 1 || x > row - 2 ||
y < 1 || y > col - 2 ||
show[x][y] != '*')
return; //函数返回类型为空时,return 后面不可以带数值
count++; //坐标合法,并且未被排查过,
//则进入函数一次,加一,表示排查坐标的的数目
//计算当前坐标周围的雷数,mine 数组中 '0' 表示非雷,'1' 表示雷
//将周围 8 个坐标相加后减去 8 个'0'
int mine_num = mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y - 1] + mine[x][y + 1] - 8 * '0';
//坐标周围雷数是 0 时,递归判断周围 8 个坐标周围雷的信息
if (mine_num == 0)
{
//将该坐标设置位空格,表示该坐标周围雷数为 0
//并且可以表示该坐标已被排查过
show[x][y] = ' ';
count_mine(mine, show, row, col, x - 1, y - 1);
count_mine(mine, show, row, col, x - 1, y);
count_mine(mine, show, row, col, x - 1, y + 1);
count_mine(mine, show, row, col, x, y - 1);
count_mine(mine, show, row, col, x, y + 1);
count_mine(mine, show, row, col, x + 1, y - 1);
count_mine(mine, show, row, col, x + 1, y);
count_mine(mine, show, row, col, x + 1, y + 1);
}
else
{
//坐标周围雷数不是 0 时,将计算周围雷数加上 '0'
//便可设置为雷数对应的字符
show[x][y] = mine_num + '0';
}
}
四、源码
test.c 文件
#include "game.h"; //需要用到 game.h 中的声明
void mnue()
{
printf("******************\n");
printf("*** 1.play ***\n");
printf("*** 0.exit ***\n");
printf("******************\n");
}
void game_mnue()
{
printf("************************************\n");
printf("*** 1.easy 2.advn 3.diff ***\n");
printf("************************************\n");
}
void game()
{
int input = 0;
int dgree_row = 0; //接收难度的行数
int dgree_col = 0; //接收难度的列数
int mine_num = 0; //接收雷的数目
//定义为最大难度的数组长度,便可以容纳3种模式
char mine[DIFF_ROW][DIFF_COL]; //放地雷
char show[DIFF_ROW][DIFF_COL]; //排地雷
do
{
//选择游戏模式
game_mnue();
printf("请输入难度:>");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1: //简单模式
dgree_row = EASY_ROW;
dgree_col = EASY_COL;
mine_num = EASY_MINE;
break;
case 2: //中等模式
dgree_row = ADVN_ROW;
dgree_col = ADVN_COL;
mine_num = ADVN_MINE;
break;
case 3: //困难模式
dgree_row = DIFF_ROW;
dgree_col = DIFF_COL;
mine_num = DIFF_MINE;
break;
default : //如果输入错误,重新输入
printf("输入错误请重新输入:\n");
break;
}
} while (input < 1 || input > 3);
//初始化地雷数组和排地雷数组
init(mine, dgree_row, dgree_col, '0');
init(show, dgree_row, dgree_col, '*');
//检测程序是否符合预期
//print(mine, dgree_row, dgree_col);
//print(show, dgree_row, dgree_col);
//布置雷
set_mine(mine, dgree_row, dgree_col, mine_num);
//检测程序是否符合预期
//print(mine, dgree_row, dgree_col);
//排雷并判断是否胜利
find_mine(mine, show, dgree_row, dgree_col, mine_num);
Sleep(5000);
system("cls");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); //设置随机数的生成起点
do
{
//打印菜单
mnue();
printf("请输入:>");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
printf("欢迎来到扫雷游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default :
printf("输入错误,请重新输入:>\n");
break;
}
} while (input);
return 0;
}
game.h 文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>
//简单模式
#define EASY_ROW 11
#define EASY_COL 11
#define EASY_MINE 10
//中等模式
#define ADVN_ROW 18
#define ADVN_COL 18
#define ADVN_MINE 40
//困难模式
#define DIFF_ROW 18
#define DIFF_COL 32
#define DIFF_MINE 99
//初始化
void init(char arr[DIFF_ROW][DIFF_COL], int row, int col, char piece);
//打印
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col);
//布置雷
void set_mine(char mine[DIFF_ROW][DIFF_COL], int row, int col,int mine_num);
//排雷并判断是否胜利
void find_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col, int mine_num);
game.c 文件
#include "game.h"
void init(char arr[DIFF_ROW][DIFF_COL], int row, int col, char piece)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = piece;
}
}
}
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i < col - 1; i++)
{
printf("%d", i);
i >= 10 ? printf(" ") : printf(" "); //为了对齐
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
//打印行号
printf("%d", i);
i >= 10 ? printf(" ") : printf(" "); //为了对齐
for (j = 1; j < col - 1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
void set_mine(char mine[DIFF_ROW][DIFF_COL], int row, int col,int mine_num)
{
while (mine_num)
{
//生成雷的坐标(打印时,只打印行:1 - (row - 2) ,列:1 - (col - 2) 之间的内容)
int x = rand() % (row - 2) + 1; //产生 1 - (row - 2) 之间的数字
int y = rand() % (col - 2) + 1; //产生 1 - (col - 2) 之间的数字
//判断雷坐标是否重复
if (mine[x][y] == '0')
{
mine[x][y] = '1';
mine_num--;
}
}
}
//排查雷的个数,如果该坐标的雷数为 0 则递归判断周围的雷数
static int count = 0; //用来记录排查的位置个数
static void count_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col, int x, int y)
{
//在递归过程中可能会越界,并且有些已经判断过了
if (x < 1 || x > row - 2 || y < 1 || y > col - 2 || show[x][y] != '*')
return;
count++;
int mine_num = mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y - 1] + mine[x][y + 1] - 8 * '0';
if (mine_num == 0)
{
show[x][y] = ' ';
count_mine(mine, show, row, col, x - 1, y - 1);
count_mine(mine, show, row, col, x - 1, y);
count_mine(mine, show, row, col, x - 1, y + 1);
count_mine(mine, show, row, col, x, y - 1);
count_mine(mine, show, row, col, x, y + 1);
count_mine(mine, show, row, col, x + 1, y - 1);
count_mine(mine, show, row, col, x + 1, y);
count_mine(mine, show, row, col, x + 1, y + 1);
}
else
{
show[x][y] = mine_num + '0';
}
}
void find_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col ,int mine_num)
{
int x = 0;
int y = 0;
int win = 0;
//找出所有非雷的位置,游戏胜利
while (win < (row - 2) * (col - 2) - mine_num)
{
print(show, row, col);
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
system("cls");
//坐标合法
if (x >= 1 && x <= row - 2 && y >= 1 && y <= col - 2)
{
//坐标是否被占用
if (show[x][y] == '*')
{
//是否踩雷
if (mine[x][y] == '1')
{
printf("很遗憾,游戏失败,你被炸死了\n");
break;
}
else
{
//判断坐标周围的雷数
count_mine(mine, show, row, col, x, y);
win += count;
count = 0;
}
}
else
{
printf("输入错误,请重新输入:>\n");
}
}
else
{
printf("坐标非法,请重新输入:>\n");
}
}
if(win == (row - 2) * (col - 2) - mine_num)
printf("恭喜你,游戏胜利,排雷成功\n");
print(show, row, col);
}
本代码并未开发标记雷的功能,有兴趣的读者可以自行开发
代码亲测有效,如有建议,可以评论留言哦