当前位置:网站首页>赶紧进来!!!教你C语言实现扫雷小游戏(文章最后有源码!!!)
赶紧进来!!!教你C语言实现扫雷小游戏(文章最后有源码!!!)
2022-08-04 22:18:00 【牛牛要坚持】
本文讲解了扫雷小游戏,用C语言从0到1模拟实现网页版扫雷小游戏,将所有设计过程分解成一个个小点,并展示对应的代码,文章最后有源码!!!
从0到1实现扫雷小游戏
一、扫雷游戏介绍
《扫雷》是一款大众类的益智小游戏,于1992年发行。 游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
扫雷的规则
我们随便点一个格子,方格即被打开并显示出方格中的数字,方格中数字则表示其周围的8个方格隐藏了几颗雷。
如下图红色圆圈中是数字是1,就代表黑色方框中的其他8个方格中有1颗雷,所有左下角那个格子一定是雷了。
同理,如果数字是3,就表示周围的8个方格隐藏了3颗雷。
按照这个规则推敲下去,最后就能找出所有的雷了。
如果没玩过扫雷,单击右边蓝色字体即可体验一把扫雷游戏→ 扫雷游戏网页版。
二、实现扫雷游戏设计思路
根据这张网页扫雷图,可以看出开始有9行9列格子构成的扫雷棋盘,中间的数字1表示该位置周围八个格子中有1个是雷空白区域,表示周围没有雷,根据此特性我们可以设置当选的格子没有雷时统计周围雷的个数显示出来,但在靠近边缘或对角线的时候超出棋盘区域不够八个格子,但根据图可以看出超出的区域位置没有雷,根据这一特性,我们在边缘排雷为了好设计,原本99的格子设计11*11的格子,即整个边缘拓展了一圈格子,这一圈格子里雷的个数为0,便于边缘格子统计周围雷的个数。
综上设计,可以设计两个棋盘,一个为布置雷的棋盘,一个为排查雷的棋盘,布置雷棋盘是系统内部布置雷所用,在布置雷棋盘里设置雷的样式为字符1,非雷的位置为字符0,排查雷棋盘是玩家所看到的,刚开始应未知区域用字符表示,玩家排雷实际在布置雷棋盘里判断,当排到的是字符1则为踩中雷,被炸死了,当为字符0,此时再统计当前排雷位置周围字符1的个数,将此个数传给对应排查雷的棋盘替代对应的字符展示给玩家看。
三、实现扫雷游戏需要学习的知识点
写完扫雷能学习这些知识点或者对知识点加以巩固。
1.分支if–else Switch—case结构 循环 while for do-while结构
不熟悉的可以看这篇博客->c语言三大结构
2.自定义函数、库函数、函数递归
不熟悉的点击这里->c语言函数
全局变量 ,define定义的标识符常量
3.不熟悉的点这里->c语言变量和常量
一些基本数据类型,二维数组,数组传参等
4.不熟悉的看这里->c语言的基本数据类型
四、实现扫雷游戏前的准备
1.扫雷游戏多文件
首先,实现扫雷游戏需要涵盖多个函数,库函数,为了便于维护,选择使用多文件,game.h用来放实现扫雷游戏函数的声明,game.c用来放实现扫雷游戏功能的函数定义,text.c是用来测试扫雷游戏的源文件。
不熟悉多文件的可以看我这篇博客->多文件概念
2.构建游戏整体运行框架
基本框架为玩家打开程序,显示出菜单供玩家选择(一般是玩游戏,或者退出游戏两个选项),玩家选择玩游戏则运行玩游戏分支,结束一盘游戏继续让用户选择,当选择退出游戏时则结束程序。
下面是设计代码↓
#include"game.h"
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");
}
}
while (input);
return 0;
}
五、扫雷游戏的实现
1.游戏菜单功能
游戏菜单主要是封装一个菜单函数,运行该函数在屏幕终端显示菜单信息,供用户选择。
这里设计选择1为玩游戏,选择0为退出游戏。
下面是代码实现↓↓
void menu()
{
printf("********** 扫雷游戏 **********\n");
printf("********** 1.play **********\n");
printf("********** 0.exit **********\n");
}
2.扫雷游戏功能实现
当我们运行设计的game()函数 则表示开始扫雷游戏,在函数设计里我们要涵盖所有扫雷游戏的实现过程,里面涵盖多个不同模块的函数。
创建布置雷和排查雷棋盘
首先构建棋盘,根据上面的游戏设计思路,定义两个二维数组表示两个扫雷棋盘一个布置雷set棋盘,一个排查雷show棋盘,二维数组的行列即棋盘的行列,为了便于维护,选择在game.h头文件中define 定义 标识符 ROW 9 COL 9 ROWS 11 COLS 11表示 实际行列 跟排雷行列,这样做可以在后期修改 对应的标识符常量 实际行 即可改变 棋盘的行列 。代码如下↓
#define ROW 9 //实际行
#define COL 9 //实际列
#define ROWS ROW+2 //排雷行
#define COLS COL+2 //排雷列
char set[ROWS][COLS]; //布置雷 的棋盘 (玩家不可见)
char show[ROWS][COLS]; //排查雷的棋盘 (玩家可见)
棋盘初始化
创建二维数组后要为这两个数组初始化,首先布置雷的棋盘全初始化为字符0,排查雷的棋盘全初始化为字符*,下面设计的为初始化布置雷 和排查雷的棋盘函数,因为要为两个棋盘初始化不同的字符,所以函数调用设置有四个参数第一个为需要初始化的棋盘,第二三个 为棋盘的行列,第四个为要初始化成的字符,这样设计一个函数就可以把两个棋盘内都初始化为相应的字符。
下面是代码↓
init_board(set, ROWS, COLS, '0'); //初始化布置雷棋盘
init_board(show, ROWS, COLS, '*'); //初始化排查雷棋盘
void init_board(char board[ROWS][COLS], int rows, int cols, char set) //初始化棋盘函数定义
{
int i,j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
实现随机布置雷
初始化两个棋盘后,开始在布置雷棋盘里布置相应的雷,这里设置的雷数是10,为了便于维护修改雷数,在game.h文件里define LEI 10 表示 LEI为常量10代表雷数 然后定义布置雷的函数,第一个参数为布置雷的二维数组名,第二三个参数为实际行列,因为最终是在99的格子里排雷周围扩充的行列不能有雷,而布雷要用到rand()随机函数 %行列 +1 使得最后布置雷的行列会在 中间99的格子里 ,用一个循环 直到10个雷都放置好,下面是代码实现↓
#define LEI 10 //雷数
set_board(set, ROW, COL); //在布置雷棋盘里 布置雷
void set_board(char board[ROWS][COLS], int row, int col) //布置雷棋盘函数定义
{
int lei = LEI;
int i,j;
while (lei)
{
i = rand() % row+1; //1~9 随机生成行列 放置雷
j = rand() % col + 1; //1~9
if (board[i][j] == '0')
{
board[i][j] = '1';
lei--;
}
}
}
开始排雷和判断输赢
下面是开始排雷的过程,分打印开始棋盘函数,排雷函数,打印最后结果的函数
while (1) //循环开始排雷
{
printf("扫雷游戏开始,游戏规则:输入对应的行列进行排雷,排雷得到的数字表示周围8个区域中有这个数量的雷\n");
display_board(show, ROW, COL); // 打印排查刚开始的雷棋盘
find_lei(set, show, ROW, COL); // 正式 开始 扫雷的 棋盘
printf("下面是是整个雷区图,1表示雷!! \n");//给用户展示最终的雷区
display_board(set, ROW, COL); //游戏结束 打印出内部布置雷的棋盘 给玩家看
break;
}
首先我们刚开始排雷要给用户看到排查雷棋盘的样子,此时都是*然后再设置相应的行列让玩家好定位各个棋子
具体效果和代码如下↓
display_board(show, ROW, COL); // 打印排查刚开始的雷棋盘
> void display_board(char board[ROWS][COLS], int row, int col) //打印棋盘函数定义
{
int i, j;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= row; i++)
{
printf("—");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d", i);
printf("|");
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
打印完布置雷棋盘后需要实现扫雷函数,基本实现为玩家根据布置雷棋盘 选择行列进行排雷,当选择的位置对应的布置雷棋盘是字符1则表示踩到雷被炸死了,当是字符0则统计该位置周围八个区域雷的个数,因为设计的0不是雷,1是雷,统计雷的个数只需要将周围八个区域的字符相加最后减去7字符0得到的就是周围雷的个数用字符表示,最后将这个字符赋给对应排查雷的位置,然后打印出扫一次雷后的结果
游戏结束条件为踩到雷 或者扫完全部的雷(找到所有不是雷的区域)。实现扫完全部雷我们可以定义一个 #define WIN ((ROWCOL)-LEI) //排雷胜利要达到的次数 表示最后要扫的次数,此时再定义一个全局变量 win 接受这个 数 ,当扫完一次雷后 win次数减1 ,直到0时 说明排完了所有雷,游戏结束
void find_lei(char board[ROWS][COLS], char board1[ROWS][COLS], int row, int col) // 扫雷 函数定义
{
int i, j;
while (win) //win 即扫雷最大次数为0时结束循环 游戏胜利
{
printf("请输入你要排雷的行:");
scanf("%d", &i);
printf("请输入你要排雷的列:");
scanf("%d", &j);
if (i >= 1 && i <= row && j >= 1 && j <= col)
{
if (board1[i][j] == '*') //为*时才可以是有效坐标,否则为重复选择
{
if (board[i][j] == '1') //为字符1时 踩到雷 游戏结束
{
printf("你踩到雷了被炸死了,游戏结束\n");
return ;
}
else
{
board1[i][j]= countlei(board, i, j); // 当 此位置没有排雷且是规定范围 ,此时统计当前位置周围雷数 给排查雷棋盘对应的位置
//排雷次数减1
system("cls"); //排一次雷后清一次空屏幕
display_board(board1, ROW, COL); // 打印排查雷棋盘
}
}
else
{
printf("该位置已经排过雷了,请重新选择\n");
}
}
else
{
printf("行列非法,请重新输入\n");
}
}
if (win == 0)
{
printf("你排完了所有雷,游戏胜利!!\n");
return ;
}
}
char countlei(char board[ROWS][COLS], int i, int j)
{
return (board[i - 1][j - 1] + board[i - 1][j] // 将布置雷棋盘该位置周围八个对应的字符相加减去 7*‘0’ 得到的是周围雷的个数 赋给排查雷的棋盘
+ board[i - 1][j + 1] + board[i][j - 1]
+ board[i][j + 1] + board[i + 1][j - 1]
+ board[i + 1][j] + board[i + 1][j + 1]) - (7 * '0');
}
六、扫雷游戏优化(递归展开)
经过上面的设计,扫雷游戏已经初步成形。
但是,根据正常的扫雷游戏设计我们选择的行列排雷后显示的周围雷是0的话会自动将周围的区域也排开,这样就节省了排雷成本,更具有人性化。
上面图空白区域表示周围有0个雷,
为此,可以对扫雷进行优化,当选择的位置周围没有雷时,此时该区域设为空白,并展开周围八个区域,当周围每个区域的周围也没有雷时也会一一展开。具体优化代码如下↓
1.优化棋盘的显示效果↓
void display_board(char board[ROWS][COLS], int row, int col) //打印棋盘函数定义
{
int i, j;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= row; i++)
{
printf("—");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d", i);
printf("|");
for (j = 1; j <= col; j++)
{
if (board[i][j] == '0')
{
printf(" "); //当打印棋盘时对应的区域 为字符 0时 将其打印为 空格
}
else
printf("%c ", board[i][j]);
}
printf("|"); //打印完一行后 后面加上竖线 增加棋盘可视化
printf("\n");
}
}
2.周围没有雷时将周围区域展开
void find_lei(char board[ROWS][COLS], char board1[ROWS][COLS], int row, int col) // 扫雷 函数定义
{
int i, j;
while (win) //win 即扫雷最大次数为0时结束循环 游戏胜利
{
printf("请输入你要排雷的行:");
scanf("%d", &i);
printf("请输入你要排雷的列:");
scanf("%d", &j);
if (i >= 1 && i <= row && j >= 1 && j <= col)
{
if (board1[i][j] == '*') //为*时才可以是有效坐标,否则为重复选择
{
if (board[i][j] == '1') //为字符1时 踩到雷 游戏结束
{
printf("你踩到雷了被炸死了,游戏结束\n");
return ;
}
else
{
pailei(board, board1, i, j); //递归排雷展开当前排雷位置周围没有雷的区域
//排雷次数减1
system("cls"); //排一次雷后清一次空屏幕
display_board(board1, ROW, COL); // 打印排查雷棋盘
}
}
else
{
printf("该位置已经排过雷了,请重新选择\n");
}
}
else
{
printf("行列非法,请重新输入\n");
}
}
if (win == 0)
{
printf("你排完了所有雷,游戏胜利!!\n");
return ;
}
}
char countlei(char board[ROWS][COLS], int i, int j)
{
return (board[i - 1][j - 1] + board[i - 1][j] // 将布置雷棋盘该位置周围八个对应的字符相加减去 7*‘0’ 得到的是周围雷的个数 赋给排查雷的棋盘
+ board[i - 1][j + 1] + board[i][j - 1]
+ board[i][j + 1] + board[i + 1][j - 1]
+ board[i + 1][j] + board[i + 1][j + 1]) - (7 * '0');
}
void pailei(char board[ROWS][COLS], char board1[ROWS][COLS], int i, int j)
{
if ((i <1 || j< 1 || i > ROW || j >COL)) //当前i j 不在规定范围内 则返回
{
return;
}
if (board1[i][j] != '*') //当前i j 对应的 不是'*'则已经排了雷 不需要再排 此时返回
{
return;
}
board1[i][j]= countlei(board, i, j); // 当 此位置没有排雷且是规定范围 ,此时统计当前位置周围雷数 给排查雷棋盘对应的位置
win--;
if (board1[i][j]>'0') //当周围有雷 此时返回
{
return;
}
else if (board1[i][j] =='0') //当周围没有雷时,递归展开周围的区域
{
pailei(board, board1, i-1, j-1);
pailei(board, board1, i - 1, j );
pailei(board, board1, i - 1, j + 1);
pailei(board, board1, i , j - 1);
pailei(board, board1, i , j + 1);
pailei(board, board1, i + 1, j - 1);
pailei(board, board1, i +1, j +1);
pailei(board, board1, i +1, j );
}
}
七、扫雷游戏效果展示
八、扫雷游戏源代码展示
game.c源文件下代码
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
int win = WIN; //定义一个全局变量 接受 最终胜利 要排的雷数
void menu()
{
printf("********** 扫雷游戏 **********\n");
printf("********** 1.play **********\n");
printf("********** 0.exit **********\n");
}
void game()
{
srand((unsigned)time(NULL)); //设置随机函数起点值
char set[ROWS][COLS]; //布置雷 的棋盘 (玩家不可见)
char show[ROWS][COLS]; //排查雷的棋盘 (玩家可见)
init_board(set, ROWS, COLS, '0'); //初始化布置雷棋盘
init_board(show, ROWS, COLS, '*'); //初始化排查雷棋盘
//display_board(set, ROW, COL); //测试 打印布置雷棋盘
set_board(set, ROW, COL); //在布置雷棋盘里 布置雷
/*display_board(set, ROW, COL); */ //用于测试 游戏时 打印布雷棋盘 正式排雷时因注释掉
while (1) //循环开始排雷
{
printf("扫雷游戏开始,游戏规则:输入对应的行列进行排雷,排雷得到的数字表示周围8个区域中有这个数量的雷\n");
display_board(show, ROW, COL); // 打印排查刚开始的雷棋盘
find_lei(set, show, ROW, COL); // 正式 开始 扫雷的 棋盘
printf("下面是是整个雷区图,1表示雷!! \n");//给用户展示最终的雷区
display_board(set, ROW, COL); //游戏结束 打印出内部布置雷的棋盘 给玩家看
break;
}
}
void init_board(char board[ROWS][COLS], int rows, int cols, char set) //初始化棋盘函数定义
{
int i,j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void display_board(char board[ROWS][COLS], int row, int col) //打印棋盘函数定义
{
int i, j;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= row; i++)
{
printf("—");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d", i);
printf("|");
for (j = 1; j <= col; j++)
{
if (board[i][j] == '0')
{
printf(" "); //当打印棋盘时对应的区域 为字符 0时 将其打印为 空格
}
else
printf("%c ", board[i][j]);
}
printf("|"); //打印完一行后 后面加上竖线 增加棋盘可视化
printf("\n");
}
}
void set_board(char board[ROWS][COLS], int row, int col) //布置雷棋盘函数定义
{
int lei = LEI;
int i,j;
while (lei)
{
i = rand() % row+1; //1~9 随机生成行列 放置雷
j = rand() % col + 1; //1~9
if (board[i][j] == '0')
{
board[i][j] = '1';
lei--;
}
}
}
void find_lei(char board[ROWS][COLS], char board1[ROWS][COLS], int row, int col) // 扫雷 函数定义
{
int i, j;
while (win) //win 即扫雷最大次数为0时结束循环 游戏胜利
{
printf("请输入你要排雷的行:");
scanf("%d", &i);
printf("请输入你要排雷的列:");
scanf("%d", &j);
if (i >= 1 && i <= row && j >= 1 && j <= col)
{
if (board1[i][j] == '*') //为*时才可以是有效坐标,否则为重复选择
{
if (board[i][j] == '1') //为字符1时 踩到雷 游戏结束
{
printf("你踩到雷了被炸死了,游戏结束\n");
return ;
}
else
{
pailei(board, board1, i, j); //递归排雷展开当前排雷位置周围没有雷的区域
//排雷次数减1
system("cls"); //排一次雷后清一次空屏幕
display_board(board1, ROW, COL); // 打印排查雷棋盘
}
}
else
{
printf("该位置已经排过雷了,请重新选择\n");
}
}
else
{
printf("行列非法,请重新输入\n");
}
}
if (win == 0)
{
printf("你排完了所有雷,游戏胜利!!\n");
return ;
}
}
char countlei(char board[ROWS][COLS], int i, int j)
{
return (board[i - 1][j - 1] + board[i - 1][j] // 将布置雷棋盘该位置周围八个对应的字符相加减去 7*‘0’ 得到的是周围雷的个数 赋给排查雷的棋盘
+ board[i - 1][j + 1] + board[i][j - 1]
+ board[i][j + 1] + board[i + 1][j - 1]
+ board[i + 1][j] + board[i + 1][j + 1]) - (7 * '0');
}
void pailei(char board[ROWS][COLS], char board1[ROWS][COLS], int i, int j)
{
if ((i <1 || j< 1 || i > ROW || j >COL)) //当前i j 不在规定范围内 则返回
{
return;
}
if (board1[i][j] != '*') //当前i j 对应的 不是'*'则已经排了雷 不需要再排 此时返回
{
return;
}
board1[i][j]= countlei(board, i, j); // 当 此位置没有排雷且是规定范围 ,此时统计当前位置周围雷数 给排查雷棋盘对应的位置
win--;
if (board1[i][j]>'0') //当周围有雷 此时返回
{
return;
}
else if (board1[i][j] =='0') //当周围没有雷时,递归展开周围的区域
{
pailei(board, board1, i-1, j-1);
pailei(board, board1, i - 1, j );
pailei(board, board1, i - 1, j + 1);
pailei(board, board1, i , j - 1);
pailei(board, board1, i , j + 1);
pailei(board, board1, i + 1, j - 1);
pailei(board, board1, i +1, j +1);
pailei(board, board1, i +1, j );
}
}
text.c源文件代码
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
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");
}
}
while (input);
return 0;
}
game.h源文件代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9 //实际行
#define COL 9 //实际列
#define ROWS ROW+2 //排雷行
#define COLS COL+2 //排雷列
#define LEI 10 //雷数
#define WIN ((ROW*COL)-LEI) //排雷胜利要达到的次数
void menu();
void game();
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
void display_board(char board[ROWS][COLS], int row, int col);
void set_board(char board[ROWS][COLS], int row, int col);
void find_lei(char board[ROWS][COLS], char board1[ROWS][COLS], int row, int col);
char countlei(char board[ROWS][COLS], int i, int j);
void pailei(char board[ROWS][COLS], char board1[ROWS][COLS], int i, int j);
九、扫雷游戏实现总结
经过扫雷游戏的设计,可以对上面我提到的知识的进行了解,并加以巩固,体验从0到1用c语言知识实现一个游戏,不但能有满满成就感,还能拓展自己思维,有个不错的收获,希望我们能在编程的路上越走越远,共勉 ~
制作不易,给个一键三连支持下吧~~
边栏推荐
- 论文解读(PPNP)《Predict then Propagate: Graph Neural Networks meet Personalized PageRank》
- Exploration and Practice of Database Governance
- [Linear Algebra 03] Elimination method display and 4 solutions of AX=b
- CountDownLatch使用及原理
- 【项目实战】仿照Room实现简单管理系统
- 一招包治pycharm DEBUG报错 UnicodeDecodeError: ‘utf-8‘ codec can‘t decode
- idea 仓库地址连接不上问题
- the warmest home
- 祝福一路顺风
- QT 子窗口—>主窗口 信号和槽的交互
猜你喜欢
随机推荐
BUG | The interface returns abnormal data
Unknown point cloud structure file conversion requirements
The Record of Reminding myself
快速web开发框架——learun framework
[larave]关于laravel使用form submit()不能获取值问题
Cocoa Application-基础
1、网页结构
OC-拷贝
最温馨的家园
老叶的三束玫瑰
【模拟面试-10年工作】项目多一定是优势吗?
SSM整合完整流程讲解
ES 数据聚合、数据同步、集群
【社媒营销】WhatsApp Business API:您需要知道的一切
开源一夏 | 云服务器ECS安装Mysql、JDK、RocketMQ
Analysis and treatment of Ramnit infectious virus
UDP通信
使用cpolar优化树莓派上的网页(2)
后排乘客不系安全带?事故瞬间被甩出
【组成原理 六 存储器类型】