📑 目录
- [📖 1. 扫雷游戏分析和设计](#📖 1. 扫雷游戏分析和设计)
- [🎯 1.1 扫雷游戏的功能说明](#🎯 1.1 扫雷游戏的功能说明)
- [🧠 1.2 游戏的分析与设计](#🧠 1.2 游戏的分析与设计)
- [🏗️ 1.2.1 数据结构的分析](#🏗️ 1.2.1 数据结构的分析)
- [📁 1.2.2 文件结构设计](#📁 1.2.2 文件结构设计)
- [💻 2. 扫雷游戏的代码实现](#💻 2. 扫雷游戏的代码实现)
- [📄 game.h ------ 头文件与声明](#📄 game.h —— 头文件与声明)
- [⚙️ game.c ------ 核心函数实现](#⚙️ game.c —— 核心函数实现)
- [🚀 test.c ------ 游戏主逻辑](#🚀 test.c —— 游戏主逻辑)
- [🚀 3. 扫雷游戏的扩展](#🚀 3. 扫雷游戏的扩展)
- [📝 小总结](#📝 小总结)
📖 1. 扫雷游戏分析和设计
🎯 1.1 扫雷游戏的功能说明
扫雷是一款经典的单人益智游戏,我们将使用C语言在控制台中实现它。游戏具备以下核心功能:
- 🕹️ 菜单交互:游戏启动时显示菜单,玩家可以选择"开始游戏"或"退出游戏"。
- 🗺️ 标准棋盘 :游戏棋盘为 9×9 的格子。
- 💣 随机布雷 :系统会默认随机布置10个雷。
- 🔍 排查雷机制 :
- 如果玩家点击的位置不是雷,则显示该位置周围8个格子中雷的总数。
- 如果点击的位置是雷,则游戏结束,玩家被"炸死"。
- 如果玩家成功找出所有非雷格子(即排除了除10个雷之外的所有格子),则游戏胜利。
游戏界面示意:
- 初始界面 :棋盘上所有格子均显示为
*,代表未知区域。- 排雷界面:玩家输入坐标后,该位置会显示周围雷的数量(数字1-8),或显示空白(周围无雷)。
- 排雷失败界面:踩到雷后,游戏会展示所有雷的位置,并提示"很遗憾,你被炸死了"。



🧠 1.2 游戏的分析与设计
🏗️ 1.2.1 数据结构的分析
在扫雷过程中,我们需要存储两类信息:雷的布局信息 和 玩家排查出的雷的数量信息 。因此,我们需要设计合适的数据结构。

1. 基础棋盘设计
最直观的想法是创建一个 9×9 的二维数组 来存储信息。我们规定:
0:表示该位置没有雷。1:表示该位置有雷。

2. 边界问题与解决方案
当我们排查一个格子时,需要统计其周围8个格子的雷数。例如,排查坐标 (2,5) 时,访问其周围的黄色格子是安全的。但是,如果排查棋盘边缘的格子,比如 (8,6),访问其下方的格子时就会发生数组越界 。
排雷的假设:

周围加上一圈后的棋盘

为了解决这个问题,我们采用一个经典的技巧:将数组扩大一圈 。即创建一个 11×11 的数组 ,只在中间的 9×9 区域内布置雷,外围一圈全部初始化为 0(无雷)。这样,在统计任何中间格子的周围雷数时,都不会发生越界。
3. 信息分离:双数组策略
如果我们将雷的信息(0/1)和排查出的雷的数量信息(0-8)都放在同一个数组中,会造成数据混淆。例如,数组中的数字 1 到底代表"这里有1个雷"还是"周围有1个雷"?
为了解决这个问题,我们采用双数组方案:
mine数组 :专门用来存放布置好的雷的信息。初始化全为'0',布置雷的位置改为'1'。show数组 :专门用来存放排查出的雷的数量信息,并展示给玩家。初始化全为'*'(神秘符号),排查后显示对应的数字。
这样做的好处是:
- 职责单一,互不干扰。
- 两个数组类型一致(都是
char类型),可以使用同一套函数进行处理,提高代码复用性。
mine数组布置雷后的状态:

show输出初始化的状态:

对应的数组定义如下:
c
char mine[11][11] = {0}; // 用来存放布置好的雷的信息
char show[11][11] = {0}; // 用来存放排查出的雷的个数信息
📁 1.2.2 文件结构设计
为了代码的模块化和清晰性,我们采用多文件组织方式,将游戏代码分为三个文件:
test.c:游戏的测试逻辑,包含main函数和游戏菜单。game.c:游戏核心函数的实现,如初始化棋盘、打印棋盘、布雷、排雷等。game.h:头文件,包含所需库的引入、常量的定义以及所有函数的声明。
💻 2. 扫雷游戏的代码实现
📄 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
// 初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
// 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
// 布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
// 排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
⚙️ game.c ------ 核心函数实现
c
#include "game.h"
// 1. 初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
// 2. 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("-------- 扫雷游戏 -------\n");
// 打印列号
for (i = 0; i <= col; 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");
}
}
// 3. 布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
// 4. 统计某坐标周围雷的数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
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');
}
// 5. 排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; // 记录已排查的非雷格子数
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
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++;
}
}
else
{
printf("❌ 坐标非法,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("🎉 恭喜你,排雷成功!\n");
DisplayBoard(mine, ROW, COL);
}
}
🚀 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]; // 存放排查出的雷的信息
// 1. 初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
// 2. 打印棋盘
DisplayBoard(show, ROW, COL);
// 3. 布置雷
SetMine(mine, ROW, COL);
// 4. 排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); // 设置随机数种子
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("👋 退出游戏\n");
break;
default:
printf("❌ 选择错误,请重新选择!\n");
break;
}
} while (input);
return 0;
}
🚀 3. 扫雷游戏的扩展
以上实现了一个基础的扫雷游戏,但它还有很多可以优化的地方。以下是一些常见的扩展方向,也是面试中可能会被问到的问题:
- 🎚️ 游戏难度选择 :
- 简单:9×9 棋盘,10个雷。
- 中等:16×16 棋盘,40个雷。
- 困难:30×16 棋盘,99个雷。
- 🌊 展开功能:如果排查的位置不是雷,且周围也没有雷,可以自动展开周围的一片区域,提升游戏体验。
- 🚩 标记雷功能 :允许玩家对怀疑是雷的位置进行标记(例如用
!或?符号)。 - ⏱️ 计时功能:显示玩家完成游戏所用的时间,增加挑战性。
在线扫雷游戏参考 :http://www.minesweeper.cn/
📝 小总结
通过本次扫雷游戏的实战,我们深入实践了C语言中的以下核心知识点:
- 数组:使用二维数组来模拟游戏棋盘,并学习了通过"扩大一圈"的技巧来解决数组越界问题。
- 函数:将游戏的不同功能(初始化、打印、布雷、排雷)封装成独立的函数,提高了代码的可读性和可维护性。
- 多文件编程:将声明、实现和测试逻辑分离到不同的文件中,是大型项目开发的基础。
- 模块化设计思想:通过"双数组"策略,将雷的信息和排查信息分离,避免了数据混淆,体现了良好的设计思路。