扫雷是一个非常经典的小游戏,它的规则并不复杂:玩家在一个棋盘上选择坐标,如果踩到地雷,游戏结束;如果没有踩雷,则显示该位置周围 8 个格子中地雷的数量。当所有非雷区域都被成功排查后,玩家获胜。
整个项目由 main.c、game.h 和 game.c 三个文件组成。其中,main.c 负责游戏菜单和整体流程控制,game.h 负责宏定义和函数声明,game.c 负责棋盘初始化、显示、布雷和排雷等核心功能。
1 项目整体结构
这个扫雷项目采用了比较清晰的多文件结构:
c
main.c // 游戏入口、菜单、游戏流程
game.h // 头文件:宏定义、函数声明
game.c // 游戏核心逻辑:初始化、显示、布雷、排雷
main.c 只关心游戏什么时候开始、什么时候结束;game.c 负责具体游戏逻辑;game.h 则作为二者之间的"接口文件"。

在 game.h 中,程序定义了棋盘大小和地雷数量:
c
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define easyMine 10
这里真正展示给玩家的是 9 × 9 的棋盘,而程序内部数组大小是 11 × 11。这样设计的好处是:在统计某个格子周围 8 个方向的地雷数量时,可以避免边界越界问题。也就是说,棋盘外围额外加了一圈隐藏区域,用来简化边界判断。
2 双棋盘设置思想
扫雷游戏通常需要两个棋盘:
c
char mine[ROWS][COLS];
char board[ROWS][COLS];
其中:
c
mine // 存放真实地雷信息
board // 展示给玩家看的棋盘
mine 棋盘中,字符 '1' 表示该位置有雷,字符 '0' 表示该位置没有雷。
board 棋盘中,字符 '*' 表示该位置还没有被玩家排查。
这样做的好处是可以把"真实数据"和"玩家看到的数据"分开。玩家不能直接看到 mine 棋盘,只能看到 board 棋盘。程序根据玩家输入的坐标,去 mine 棋盘中判断是否踩雷,再把结果更新到 board 棋盘上。
在 Play() 函数中,程序首先创建两个棋盘,然后分别进行初始化:
c
char mine[ROWS][COLS] = { 0 };
char board[ROWS][COLS] = { 0 };
InitialBoard(mine, ROWS, COLS, '0');
InitialBoard(board, ROWS, COLS, '*');
这里 mine 被初始化为 '0',表示一开始没有雷;board 被初始化为 '*',表示所有格子都处于未排查状态。
3 菜单与游戏入口
程序入口在 main() 函数中。首先通过 srand((unsigned int)time(NULL)) 设置随机数种子,用来保证每次游戏布雷位置不同。然后通过 do...while 循环反复显示菜单:
c
do {
menu();
printf("Please enter 1 or 0: ");
scanf("%d", &input);
switch (input) {
case 1:
Play();
break;
case 0:
printf("Exit Game\n");
break;
}
} while (input);
当玩家输入1时,进入游戏;输入0时,退出游戏。这个结构比较适合初学者理解,因为游戏整体流程非常清晰:显示菜单、接收输入、判断选择、执行对应功能。
4 棋盘初始化函数
棋盘初始化函数如下:
c
void InitialBoard(char board[ROWS][COLS], int row, int col, char sign) {
int i = 0;
for (i = 0; i < ROWS; i++) {
int j = 0;
for (j = 0; j < COLS; j++) {
board[i][j] = sign;
}
}
}
这个函数的作用是把整个二维数组都初始化为指定字符。
如果传入的是 '0',就表示初始化真实地雷棋盘;如果传入的是 '*',就表示初始化玩家显示棋盘。
5 棋盘显示函数
棋盘显示函数DisplayBoard()用来打印玩家当前看到的棋盘:
c
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
int i = 0;
for (i = 0; i <= ROW; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= ROW; i++) {
printf("%d ", i);
int j = 0;
for (j = 1; j <= COL; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
这个函数打印时从下标1开始,到 ROW 和 COL 结束。也就是说,真正展示给玩家的是中间的9 × 9区域,而数组外围隐藏的一圈不会显示出来。
显示行号和列号的设计也很重要。玩家输入坐标时,可以根据棋盘上的数字更直观地选择位置。这种显示方式简单直观,适合控制台小游戏。
6 随机布雷函数
布雷函数SetMine()的作用是在真实棋盘mine中随机放置地雷:
c
void SetMine(char board[ROWS][COLS], int row, int cols) {
int mineNo = 0;
while (mineNo < easyMine) {
int x = rand() % 9 + 1;// 9---row
int y = rand() % 9 + 1;// 9---col
if (board[x][y] != '1') {
board[x][y] = '1';
mineNo++;
}
}
}
7 排雷函数核心逻辑
排雷函数是整个程序最核心的部分:
c
void FineMine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
这个函数不断接收玩家输入的坐标。如果玩家输入的位置是雷,游戏结束;如果不是雷,就统计该位置周围有多少个雷,并把结果显示到玩家棋盘上。
核心判断逻辑如下:
c
if (mine[x][y] == '1') {
printf("You hit a mine, game over\n");
break;
}
else {
// 统计周围雷的数量
}
如果玩家没有踩雷,程序会遍历当前位置周围的8个方向:
c
for (i = x - 1; i <= x + 1; i++) {
int j = 0;
for (j = y - 1; j <= y + 1; j++) {
c += mine[i][j] - 9 * '0';
}
}
把周围字符 '0' 和 '1' 转换成数字0和1,然后累加得到周围雷的数量。由于外围有一圈隐藏边界,所以即使玩家选择边缘位置,也可以安全遍历周围8个格子,不容易发生数组越界。
8 胜利条件判断
程序使用变量 win 记录玩家已经成功排查的安全格子数量:
c
int win = 0;
while (win < row * col - easyMine) {
...
}
整个棋盘有 row * col 个格子,其中有 easyMine 个雷。因此,只要玩家成功排查的格子数量达到:
c
row * col - easyMine
就说明所有非雷区域都已经被排查完毕,游戏胜利。
9 程序运行流程总结
整个扫雷游戏的执行流程可以总结为:
- 显示菜单
- 玩家输入 1 开始游戏
- 初始化 mine 棋盘和 board 棋盘
- 随机布置 10 个地雷
- 显示玩家棋盘
- 玩家输入坐标
- 判断是否踩雷
- 如果踩雷,游戏结束
- 如果没踩雷,显示周围雷数
- 判断是否排查完所有安全格子
- 胜利或继续游戏
这个项目虽然代码量不大,但已经包含了 C 语言中很多重要知识点,例如:二维数组、宏定义、函数封装、随机数、循环结构、条件判断、多文件编程字符与数字转换。
基于C语言实现了一个简易扫雷小游戏。项目采用多文件结构,将游戏入口、函数声明和核心逻辑分开,使代码更加清晰。程序使用双棋盘设计,一个棋盘保存真实地雷信息,另一个棋盘展示给玩家。通过随机数完成布雷,通过二维数组遍历完成周围雷数统计,并通过安全格子数量判断玩家是否胜利。
完整代码


