今天给大家详细介绍一个C语言入门小游戏——扫雷。
鉴于论坛内同样的文章过多且大多都是简化版的扫雷游戏,本文将使用递归的方式实现“一键清空”版本的扫雷游戏。
废话不多说,开整!
- 游戏运行逻辑
- 整体思路
- 游戏主体的实现
- 代码分析
- 初始化棋盘:inite_board(char board[ROWS][COLS], int rows, int cols, char c)
- 打印棋盘:display_board(char board[ROWS][COLS], int row, int col)
- 布置雷:void set_mine(char board[ROWS][COLS], int row, int col)
- 扫雷:void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col)
- 主函数实现
- 递归实现“一键清空”
为了实现三子棋游戏,并且有一个良好的编程习惯,我们需要三个文件分辨存储我们需要的代码:
- game.c —— 游戏主体结构
- function.c —— 游戏函数的实现
- game.h —— 头文件,函数的声明
这里我们先给出游戏整体的代码,大家可以先看一看,我们在下面一一分解做出详细介绍:
主函数(main.c)代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//菜单
void menu()
{
printf("------------------------------------------n");
printf("----------- 1.game 0.exit ---------------n");
printf("------------------------------------------n");
}
void game()
{
char mine_board[ROWS][COLS] = { 0 };
char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化
inite_board(mine_board, ROWS, COLS, '0');
inite_board(show_board, ROWS, COLS, '*');
set_mine(mine_board, ROW, COL);
display_board(show_board, ROW, COL);
find_mine(mine_board, show_board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("要来一局扫雷游戏吗?n");
scanf("%d", &input);
printf("n");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误,请重新选择n");
break;
}
} while (input);
}
功能函数(function.c)代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void inite_board(char board[ROWS][COLS], int rows, int cols, char c)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = c;
}
}
}
void display_board(char board[ROWS][COLS], int row, int col)
{
printf("----------------扫雷游戏-----------------n");
for (int i = 0; i <= row; i++)
{
printf("%d ", i);
}
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");
}
printf("----------------扫雷游戏-----------------n");
}
void set_mine(char board[ROWS][COLS], int row, int col)
{
int mines = EASY_METHOD;
while (mines)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] != '1')
{
board[x][y] = '1';
mines--;
}
}
}
}
int round_mines(char mine_board[ROWS][COLS],int x,int y)
{
return (
mine_board[x - 1][ y - 1] +
mine_board[x - 1][y] +
mine_board[x - 1][y + 1] +
mine_board[x][y - 1] +
mine_board[x][y + 1] +
mine_board[x + 1][y - 1] +
mine_board[x + 1][y] +
mine_board[x + 1][y + 1] - 8 * '0'
);
}
void remove_board(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col, int x, int y)
{
if (round_mines(mine_board, x, y) == 0)
{
//如果要位置在棋盘的四周就不递归了
if ((x == 9 && y >= 1 && y <= 9) || (x == 1 && y >= 1 && y <= 9) || (y == 1 && x >= 1 && x <= 9) || (y == 9 && x >= 1 && x <= 9))
{
show_board[x][y] = ' ';
}
else
{
show_board[x][y] = ' '; //周围没有雷,就把该位置设为空
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (show_board[x + i][y + j] == '*')//找到没有被开的位置,进入递归,继续设置空
{
remove_board(mine_board, show_board, row, col, x + i, y + j);
}
}
}
}
}
else
{
show_board[x][y] = round_mines(mine_board, x, y)+'0';//周围有雷的位置返回标记为雷的数量
}
}
void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_METHOD)
{
printf("请输入你要排查的坐标:n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show_board[x][y] != '*')
{
printf("此位置已经排除,请输入其他位置n");
}
else
{
win++;
//如果是雷
if (mine_board[x][y] == '1')
{
printf("很遗憾,你被炸到了n");
display_board(mine_board, ROW, COL);
break;
}
else//如果不是雷
{
//递归实现如果周围没有雷就消失一片
remove_board(mine_board, show_board, row, col, x, y);
统计mine_board数组x,y坐标周围有多少个雷
//int count = round_mines(mine_board, x, y);
//show_board[x][y] = count + '0';
display_board(show_board, row, col);
}
}
}
else
{
printf("输入的坐标非法,请重新输入n");
}
}
if (win == row * col - EASY_METHOD)
{
printf("扫雷结束,你赢了!n");
}
}
头文件(game.h)代码:
#pragma once #include#include #include #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define EASY_METHOD 10 //初始化棋盘 void inite_board(char board[ROWS][COLS], int rows, int cols, char c); //显示棋盘,显示数据内容,所以传进来row和col void display_board(char board[ROWS][COLS], int row, int col); //设置雷 void set_mine(char board[ROWS][COLS], int row, int col); //排查雷 void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//菜单
void menu()
{
printf("------------------------------------------n");
printf("----------- 1.game 0.exit ---------------n");
printf("------------------------------------------n");
}
void game()
{
char mine_board[ROWS][COLS] = { 0 };
char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化
inite_board(mine_board, ROWS, COLS, '0');
inite_board(show_board, ROWS, COLS, '*');
set_mine(mine_board, ROW, COL);
display_board(show_board, ROW, COL);
find_mine(mine_board, show_board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("要来一局扫雷游戏吗?n");
scanf("%d", &input);
printf("n");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误,请重新选择n");
break;
}
} while (input);
}
当我们开始一局游戏时,首先出现在界面上的应该是游戏菜单,在这里我们使用menu()函数实现该功能。
当程序开始运行后,先打印出菜单界面:
输入我们的选择(1 or 0)进入switch语句,如果选择为0则退出游戏程序,如果选择为1则进入case 1 开始运行游戏程序。如果输入其他的字符,则会提示重新输入!
整体思路在game()函数里,我们需要创建两个二维数组来保存数据,这是因为一个9*9的二位数组在逻辑结构上和我们的扫雷棋盘形状是一样的,方便我们进行代码的编写。
为了防止越界,我们数组创建为11*11,相对于9*9的棋盘多了两行两列,但数据放在数组中间9*9的位置里面。
一个数组mine_board放布置好的雷的信息,先都初始化为字符0,然后随机放置雷,有雷的地方布置为字符1。雷的地方布置为字符1是为了方便我们后续统计每个位置附近雷的数量有多少。
另一个数组show_board放排查出的雷的信息,先都初始化为“*”,表示没有被排查的地方,当玩家选择要排查的位置坐标后,显示该位置坐标的信息:
- 如果这个位置没有雷且周围也没有雷则将该位置的数组空间设置为空。
- 如果该位置没有雷但周围有雷则将周围雷的数量保存进该位置对应的数组空间内。
- 如果该位置有雷,则游戏结束,玩家扫雷失败。
当我们的选择为1(来一局游戏)后,程序就会进入到game函数里。
头文件(game.h)代码:
#pragma once #include#include #include #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define EASY_METHOD 10 //初始化棋盘 void inite_board(char board[ROWS][COLS], int rows, int cols, char c); //显示棋盘,显示数据内容,所以传进来row和col void display_board(char board[ROWS][COLS], int row, int col); //设置雷 void set_mine(char board[ROWS][COLS], int row, int col); //排查雷 void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);
在头文件里,我们不仅要对函数进行声明,还要进行宏定义的设置,这样方便我们在日后将代码更改为其他规格的棋盘。头文件里,ROW和COL代表扫雷数组的数据范围,EASY_METHOD代表简单模式下一局游戏中雷的数量。
然后我们来看一看game函数的实现思路:
void game()
{
char mine_board[ROWS][COLS] = { 0 };//
char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化
inite_board(mine_board, ROWS, COLS, '0');
inite_board(show_board, ROWS, COLS, '*');
set_mine(mine_board, ROW, COL);
display_board(show_board, ROW, COL);
find_mine(mine_board, show_board, ROW, COL);
}
我们的数组在创建好后,需要先对两个数组棋盘进行初始化inite_board(mine_board, ROWS, COLS, '0');,该函数的最后一个参数代表被传进数组的值将被初始化为该参数。初始化之后,我们先在mine_board数组里面设置雷的信息set_mine(mine_board, ROW, COL);,然后我们将show_board棋盘打印出来 display_board(show_board, ROW, COL); ,display_board函数的第一个参数也可以传入mine_board数组,这样就能展示游戏中雷的分布了。此时游戏运行状况为:
然后我们将两个数组传入find_mine(mine_board, show_board, ROW, COL)函数内,进行扫雷排查,当show_board棋盘内所有没有雷的位置都被找到时,游戏结束,玩家获胜。
代码分析 初始化棋盘:inite_board(char board[ROWS][COLS], int rows, int cols, char c)void inite_board(char board[ROWS][COLS], int rows, int cols, char c)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = c;
}
}
}
这里较为容易,只需要传参,使用两层循环,将数组内的值都赋值为所传入的参数c即可。
打印棋盘:display_board(char board[ROWS][COLS], int row, int col)void display_board(char board[ROWS][COLS], int row, int col)
{
printf("----------------扫雷游戏-----------------n");
for (int i = 0; i <= row; i++)
{
printf("%d ", i);
}
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");
}
printf("----------------扫雷游戏-----------------n");
}
为了方便玩家进行位置的选择,我们要在棋盘的最上方和最左列打印对印的序号位置,然后再其他位置打印我们所保存的数组数据。效果如下:
布置雷:void set_mine(char board[ROWS][COLS], int row, int col)void set_mine(char board[ROWS][COLS], int row, int col)
{
int mines = EASY_METHOD;//mines为雷的数量,EASY_METHOD来源于先前的宏定义
while (mines)
{
int x = rand() % row + 1;//x的取值为1-9
int y = rand() % col + 1;//y的取值为1-9
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] != '1')
{
board[x][y] = '1';
mines--;
}
}
}
}
随机取的x和y的坐标,然后在不是雷的位置布下雷,用字符1代表该位置有雷是为了方便后续统计某个位置周围雷的数量。
ps:这就是所谓的作弊模式
扫雷:void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col) 主函数实现void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//用win来计入已经排查出的非雷位置的数量
while (win < row * col - EASY_METHOD)
{
printf("请输入你要排查的坐标:n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//检测输入位置是否合法
{
if (show_board[x][y] != '*')
{
printf("此位置已经排除,请输入其他位置n");
}
else
{
win++; //该位置被排查到,所以数量增加,若是雷,则游戏直接结束,无需判断
//如果是雷
if (mine_board[x][y] == '1')
{
printf("很遗憾,你被炸到了n");
display_board(mine_board, ROW, COL);//向玩家展示棋盘
break;
}
else//如果不是雷
{
//递归实现如果周围没有雷就消失一片
remove_board(mine_board, show_board, row, col, x, y);
display_board(show_board, row, col);
}
}
}
else
{
printf("输入的坐标非法,请重新输入n");
}
}
if (win == row * col - EASY_METHOD)//win的数量刚好为非雷的数量,玩家获胜
{
printf("扫雷结束,你赢了!n");
}
}
该函数的设计思路较为简单,大家阅读便知,核心代码在于其中的递归实现一键清空周围不是雷的位置。
递归实现“一键清空”int round_mines(char mine_board[ROWS][COLS],int x,int y)
{
return (
mine_board[x - 1][ y - 1] +
mine_board[x - 1][y] +
mine_board[x - 1][y + 1] +
mine_board[x][y - 1] +
mine_board[x][y + 1] +
mine_board[x + 1][y - 1] +
mine_board[x + 1][y] +
mine_board[x + 1][y + 1] - 8 * '0'
);
}
void remove_board(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col, int x, int y)
{
if (round_mines(mine_board, x, y) == 0)
{
//如果要位置在棋盘的四周就不递归了
if ((x == 9 && y >= 1 && y <= 9) || (x == 1 && y >= 1 && y <= 9) || (y == 1 && x >= 1 && x <= 9) || (y == 9 && x >= 1 && x <= 9))
{
show_board[x][y] = ' ';
}
else
{
show_board[x][y] = ' '; //周围没有雷,就把该位置设为空
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (show_board[x + i][y + j] == '*')//找到没有被开的位置,进入递归,继续设置空
{
remove_board(mine_board, show_board, row, col, x + i, y + j);
}
}
}
}
}
else
{
show_board[x][y] = round_mines(mine_board, x, y)+'0';//周围有雷的位置返回标记为雷的数量
}
}
逐层递归,当该位置没有雷的时候,继续递归附近的位置直到遇到附近有雷的位置,然后将该位置的数据保存为周围雷的数量,然后返回该层递归,当递归到棋盘的四周时同样也要结束递归,防止出现探查一个位置就几乎将整个棋盘都递归完了的情况。
游戏运行状况:
以上就是扫雷游戏的全部内容,相信大家看到这里应该也能够独立的完成属于自己专有的“扫雷游戏”,也相信大家在这过程中对于C语言,对于编程有了更进一步的看法。
很高兴能够为大家带来帮助,码字不易,如果大家觉得我这篇文章有帮助的话,可以支持。



