一、游戏设计与代码结构
1. 游戏核心机制
扫雷游戏的核心逻辑包含以下几个部分:
-
棋盘初始化:创建两个二维数组,一个存储地雷位置(
mine),另一个存储玩家可见区域(show)。 -
地雷布置:在
mine数组中随机生成指定数量的地雷。 -
递归展开:当玩家点击无雷区域时,自动展开相邻的所有安全格子。
-
时间管理:限制游戏时间,超时自动失败。
-
输入处理:支持排雷(
p)和标记(m)两种操作。
2. 代码文件分工
-
game.h:定义常量、函数声明。
-
game.c:实现游戏核心逻辑(初始化、打印、排雷、展开等)。
-
test.c:处理菜单交互、游戏启动和难度选择。
二、核心代码逐行解析
1. 棋盘初始化z
InitBoard函数
void InitBoard(char board[MAX_ROWS][MAX_COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
-
参数说明:
-
board:待初始化的二维数组。
-
rows/cols:棋盘的行数和列数。
-
set:填充字符(mine用'0',show用
'*')。
-
-
注意事项:
-
数组索引从0开始,但游戏逻辑中行列从1开始(避免边界检查复杂)。
-
DisplayBoard函数
void DisplayBoard(char board[MAX_ROWS][MAX_COLS], int row, int col)
{
printf("\n-----------扫雷游戏-----------\n");
// 打印列号
for (int i = 0; i <= col; i++)
{
printf("%2d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%2d ", i);
for (int j = 1; j <= col; j++)
{
printf("%2c ", board[i][j]);
}
printf("\n");
}
}
输出示例:
0 1 2 3 4 5 6 7 8
1 * * * * * * * * *
2 * * * * * * * * *
...
2. 地雷布置与计数
SetMine函数
void SetMine(char board[MAX_ROWS][MAX_COLS], int row, int col, int mine_count)
{
while (mine_count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
mine_count--;
}
}
}
-
关键点:
-
使用
rand()生成随机坐标,需在main函数中调用srand((unsigned)time(NULL))初始化随机种子。 -
地雷用字符
'1'表示,安全区域为'0'。
-
GetMineCount函数
static int GetMineCount(char mine[MAX_ROWS][MAX_COLS], int x, int y) {
int count = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
count += mine[i][j] - '0';
}
}
return count;
}
-
逻辑说明:
-
遍历周围8个格子,累加地雷数量。
-
字符
'1'减去'0'得到数值1,'0'则得到0。
-
3. 递归展开与排雷逻辑
Expand函数(递归核心)
static void Expand(char mine[MAX_ROWS][MAX_COLS], char show[MAX_ROWS][MAX_COLS], int x, int y, int row, int col, int* win)
{
if (x < 1 || x > row || y < 1 || y > col || show[x][y] != '*')
{
return;
}
int count = GetMineCount(mine, x, y);
show[x][y] = (count == 0) ? '0' : count + '0';
(*win)++;
if (count == 0)
{
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx != 0 || dy != 0)
{
Expand(mine, show, x + dx, y + dy, row, col, win);
}
}
}
}
}
-
递归终止条件:
-
超出棋盘边界。
-
当前格子已被探索(非
'*')。
-
FindMine函数(主逻辑)
void FindMine(char mine[MAX_ROWS][MAX_COLS], char show[MAX_ROWS][MAX_COLS], int row, int col, int mine_count, int time_limit)
{
int win = 0;
time_t start_time = time(NULL); //游戏开始时间戳
int remaining_time = time_limit; //剩余时间初始化
//主游戏循环:当未排完所有安全区域时继续
while (win < row * col - mine_count)
{
//时间计算与超时处理
remaining_time = time_limit - (int)(time(NULL) - start_time);
if (remaining_time <= 0)
{
printf("\n时间耗尽,游戏失败!\n");
DisplayBoard(mine, row, col);//显示完整雷区
return;
}
// 界面信息输出
printf("\n剩余时间: %02d:%02d", remaining_time / 60, remaining_time % 60);//格式化为MM:SS
printf("\n输入格式:操作(p排雷/m标记) 行 列 (如: p 3 4;m 3 4)\n");
// 清空输入缓冲区并读取命令
int c;
while ((c = getchar()) != '\n' && c != EOF); //清空输入缓冲区
char cmd;
int x, y;
int read_result = scanf(" %c %d %d", &cmd, &x, &y);//读取输入
if (read_result != 3)
{
printf("输入格式错误!\n");
while (getchar() != '\n');
continue;
}
// 坐标合法性检查
if (x < 1 || x > row || y < 1 || y > col)
{
printf("坐标非法!\n");
continue;
}
// 处理标记/排雷逻辑
if (cmd == 'm' || cmd == 'M')
{
show[x][y] = (show[x][y] == '*') ? 'F' : '*';//切换标记状态:'*'<->'F'
DisplayBoard(show, row, col);//更新显示
}
else if (cmd == 'p' || cmd == 'P')//排雷操作
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, row, col);//显示雷区
break;
}
//已标记位置保护
if (show[x][y] == 'F')
{
printf("请先取消标记!\n");
continue;
}
//重复排雷检查
if (show[x][y] != '*')
{
printf("该位置已排雷!\n");
continue;
}
//安全区域处理
int count = GetMineCount(mine, x, y);//获取周围雷数
if (count == 0) //周围无雷时自动展开
{
//递归展开连续空白区域,并更新排雷计数
Expand(mine, show, x, y, row, col, &win);
}
else //显示周围雷数
{
show[x][y] = count + '0';
win++;
}
DisplayBoard(show, row, col);//更新显示
}
else //无效命令处理
{
printf("无效命令!\n");
}
}
//胜利条件判定
if (win == row * col - mine_count)
{
printf("恭喜通关!剩余时间: %d秒\n", remaining_time);
}
}
-
胜利条件:已探索格子数等于总安全格子数(
row * col - mine_count)。
三、主函数与游戏流程(test.c)
-
菜单显示:提供简单、中等、困难三种模式。
-
初始化游戏:创建两个二维数组,分别存储雷的位置和玩家视图。
-
进入排雷循环:结合时间限制和用户输入更新游戏状态。
int main() {
int input = 0;
srand((unsigned)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game(9, 9, EASY_MINES, EASY_TIME); //简单难度
break;
case 2:
game(16, 16, MEDIUM_MINES, MEDIUM_TIME); //中等难度
break;
case 3:
game(16, 30, HARD_MINES, HARD_TIME); //困难难度
break;
case 0:
printf("游戏结束!\n");
break;
default:
printf("输入错误,请重新选择!\n");
break;
}
} while (input != 0);
return 0;
}
四、游玩示例




五、改进方向与总结
-
性能优化:递归展开可能引发栈溢出,可改用队列实现迭代展开。
-
界面增强:引入颜色库(如
ncurses)提升视觉效果。 -
功能扩展:支持保存/加载游戏、自定义难度等。
本扫雷游戏通过模块化设计实现核心功能,重点解决了递归展开、时间管理和输入处理等难点。代码结构清晰,适合初学者学习二维数组操作、递归逻辑及时间处理技巧。通过进一步优化,可扩展为功能更丰富的经典游戏复刻。
1358



被折叠的 条评论
为什么被折叠?



