扫雷是经典的休闲益智小游戏,本文将基于 C 语言,从需求分析、设计思路、代码实现三个维度,手把手教你实现一个控制台版的 9*9 扫雷游戏,包含菜单选择、随机布雷、排雷判断、输赢检测等核心功能,同时也会讲解游戏的扩展思路,让你在实践中吃透 C 语言数组和函数的综合运用。
一、扫雷游戏核心需求分析
本次实现的控制台扫雷游戏,还原经典端游的核心玩法,具体功能要求如下:
- 排查位置是雷:游戏结束,玩家被炸死,展示所有雷的位置;
- 排查位置非雷:显示该位置周围 8 个格子的雷的数量;
- 排查出所有非雷位置(共 71 个):排雷成功,游戏胜利,展示雷的位置;
二、游戏整体设计思路
2.1 数据结构设计:双数组 + 扩大棋盘,解决核心问题
扫雷的核心是雷的存储和排雷信息的展示,同时要避免排查边缘位置时出现数组越界,因此采用双 11*11 数组的设计方案,而非直接使用 9*9 数组:
双数组分离职责
- mine[11][11]:雷盘数组,专门存放雷的信息,初始化为字符'0',布置雷的位置设为字符'1',非雷位置保持'0';
- show[11][11]:展示数组,专门存放玩家排雷的结果,初始化为字符'*'(保持神秘),排查非雷位置后,替换为周围雷的数量字符。双数组设计避免了雷的信息和排雷信息混杂,便于逻辑处理和页面展示。
11*11 数组解决越界问题实际游戏区域为 99(数组下标 1~9),将数组扩大为 1111(下标 0~10),让边缘位置的周围 8 个格子都能正常访问,无需额外的越界判断,简化代码逻辑。
2.2 文件结构设计:分文件编程,解耦代码
采用 C 语言分文件编程的思想,将代码分为 3 个文件,各司其职,便于后续维护和扩展,这也是实际项目开发的常用规范:
2.3 核心函数设计
结合游戏流程,设计 5 个核心函数,覆盖初始化、展示、布雷、排雷全流程:
三、扫雷游戏完整代码实现
本次实现的代码严格遵循分文件编程规范,需创建三个文件并分别编写代码,所有代码可直接复制编译运行(VS/Dev-C++/Clion 均可)。
3.1 头文件:game.h
该文件是游戏的 “全局配置中心”,定义了棋盘规格、雷的数量,声明了所有核心函数,同时包含所需的系统头文件。
c
运行
#pragma once
// 防止头文件重复包含
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 游戏核心配置宏定义
#define EASY_COUNT 10 // 简单难度雷的数量
#define ROW 9 // 棋盘实际行数
#define COL 9 // 棋盘实际列数
#define ROWS ROW+2 // 扩大后的行数(解决越界)
#define COLS COL+2 // 扩大后的列数(解决越界)
// 棋盘初始化函数:board-数组,rows/cols-数组规格,set-初始值
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
// 棋盘打印函数:board-数组,row/col-实际游戏棋盘规格
void DisplayBoard(char board[ROWS][COLS], int row, int col);
// 布雷函数:board-雷盘数组,row/col-实际游戏棋盘规格
void SetMine(char board[ROWS][COLS], int row, int col);
// 排雷函数:mine-雷盘数组,show-展示数组,row/col-实际游戏棋盘规格
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
3.2 功能实现文件:game.c
该文件实现game.h中声明的所有函数,是游戏的核心逻辑层,包含棋盘初始化、打印、随机布雷、雷数统计、排雷判断的具体代码。
c
运行
#include "game.h"
// 初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
// 遍历二维数组,设置初始值
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
// 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("——–扫雷游戏——–\\n");
// 打印列号(0~9),方便玩家输入坐标
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\\n");
// 打印行号(1~9)+ 棋盘内容
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (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;
// 循环布雷,直到雷的数量为0
while (count)
{
// 生成1~9的随机行/列坐标(对应实际游戏区域)
int x = rand() % row + 1;
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)
{
// 周围8个格子的字符'0'/'1'相加,减去8*'0',得到雷的数量(数字)
return (mine[x-1][y] + mine[x-1][y-1] + mine[x][y-1] + mine[x+1][y-1] +
mine[x+1][y] + mine[x+1][y+1] + mine[x][y+1] + mine[x-1][y+1]) – 8 * '0';
}
// 排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; // 记录玩家排查出的非雷数量,初始为0
// 排雷成功条件:排查出的非雷数量 = 总格子数 – 雷的数量
while (win < row * col – EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
// 判断坐标是否合法(在1~9范围内)
if (x >= 1 && x <= row && y >= 1 && 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++; // 非雷数量+1
}
}
// 坐标非法,提示重新输入
else
{
printf("坐标非法,请重新输入!\\n");
}
}
// 排雷成功,展示雷的位置
if (win == row * col – EASY_COUNT)
{
printf("恭喜你,排雷成功!\\n");
DisplayBoard(mine, ROW, COL);
}
}
3.3 测试逻辑文件:test.c
该文件控制游戏的整体流程,包含菜单函数和主函数,实现 “菜单选择→开始游戏→游戏循环→重新选择” 的逻辑,是玩家和游戏的交互入口。
c
运行
#include "game.h"
// 游戏菜单函数
void menu()
{
printf("***********************\\n");
printf("***** 1. play *****\\n");
printf("***** 0. exit *****\\n");
printf("***********************\\n");
}
// 游戏核心逻辑函数
void game()
{
// 定义雷盘数组和展示数组
char mine[ROWS][COLS];
char show[ROWS][COLS];
// 初始化棋盘:雷盘初始为'0',展示盘初始为'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
// 打印初始展示棋盘(玩家视角)
DisplayBoard(show, ROW, COL);
// 随机布置雷
SetMine(mine, ROW, COL);
// 开始排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
// 设置随机数种子,让布雷的随机坐标真正随机
srand((unsigned int)time(NULL));
// do-while循环:支持重复游玩游戏
do
{
// 打印菜单
menu();
printf("请选择:>");
scanf("%d", &input);
// 分支选择:开始游戏/退出游戏/选择错误
switch (input)
{
case 1:
game(); // 开始游戏
break;
case 0:
printf("退出游戏!\\n"); // 退出游戏
break;
default:
printf("选择错误,请重新选择!\\n"); // 非法选择
break;
}
} while (input); // input为0时退出循环,否则重新显示菜单
return 0;
}
四、游戏编译运行说明
4.1 编译运行步骤
4.2 游戏操作示例
五、扫雷游戏扩展思路
本次实现的是基础版 9*9 扫雷,基于该代码,可进行多种功能扩展,提升游戏的可玩性,也是锻炼 C 语言编程能力的好方式,推荐几个经典扩展方向:
5.1 增加游戏难度选择
支持简单、中等、困难三种难度,通过宏定义和分支判断实现,核心修改点:
- 简单:9*9 棋盘,10 个雷(基础版);
- 中等:16*16 棋盘,40 个雷;
- 困难:30*16 棋盘,99 个雷;
- 在菜单后增加难度选择界面,根据玩家选择设置对应的棋盘规格和雷数。
5.2 实现 “空白区域自动展开”
当玩家排查的位置非雷且周围雷数为 0 时,自动展开周围的所有空白区域(直到遇到有雷数的格子),核心思路:
- 采用递归实现,判断当前坐标周围雷数为 0 时,遍历周围 8 个坐标,依次递归排查;
- 递归时需增加去重判断,避免重复展开同一个坐标。
5.3 增加雷的标记功能
支持玩家用指定字符(如#)标记疑似雷的位置,避免误点,核心修改点:
- 增加坐标操作选择:“排查雷” 或 “标记雷”;
- 标记雷时,将 show 数组对应位置设为#,取消标记时恢复为*;
- 排雷成功条件需排除标记的位置,避免统计错误。
5.4 增加排雷计时功能
统计玩家的排雷时间,游戏结束时展示,核心思路:
- 利用time()函数,在游戏开始时记录开始时间start = time(NULL);
- 游戏结束(胜利 / 失败)时记录结束时间end = time(NULL);
- 计算时间差end – start,即为玩家的排雷耗时(秒)。
5.5 增加排雷步数统计
统计玩家排查雷 / 标记雷的步数,游戏结束时展示,核心思路:
- 定义步数变量step = 0,每次玩家有效输入坐标(排查 / 标记)时,step++;
- 游戏结束时打印步数,实现 “计时 + 计步” 的双重统计。
六、核心知识点总结
本次扫雷游戏的实现,综合运用了 C 语言的多个核心知识点,也是 C 语言入门阶段的经典综合实践案例,重点掌握:
七、写在最后
本次实现的控制台扫雷游戏,是 C 语言数组和函数的经典实践,从需求分析到代码实现,层层递进,核心是把复杂的游戏逻辑拆解为一个个简单的函数,逐个实现后再整合。
在此基础上,大家可以尝试上述扩展功能,比如空白区域自动展开、难度选择、计时计步等,在扩展的过程中,你会对 C 语言的递归、分支判断、变量作用域等知识点有更深刻的理解。
编程的核心是解决问题,扫雷游戏的实现过程,就是不断发现问题、分析问题、解决问题的过程,希望大家通过这个案例,提升自己的 C 语言编程思维和实战能力!




