C语言扫雷游戏:二维数组与递归算法完整实现指南
利用 C 语言开发扫雷游戏是深入理解二维数组操作与控制流程的经典练习。本文详细拆解了从初始化棋盘到递归排查地雷的全套实现逻辑,重点解决数组越界问题并展示如何高效统计周围地雷数量。
核心设计思路
要实现一个结构清晰的扫雷程序,关键在于数据结构的设计与管理。主要涉及两个核心二维数组:mine 用于存储地雷的实际分布数据,在游戏中全程保持不变;show 用于呈现给玩家当前排查出的安全信息及标记状态。
在基础功能上,需完成以下六个步骤:
- 对
mine和show数组进行内存初始化。 - 具备打印二维数组以展示盘面状态的能力。
- 利用随机种子在盘面内均匀散布指定数量的地雷。
- 接收玩家输入坐标并进行合法校验。
- 编写逻辑判断某点周围八个邻接坐标中存在的雷的总数。
- 当点击无雷安全区时,递归式地向四周展开所有相邻的安全区域,直到触碰到边缘或带有数字提示的方块。
在算法设计中,为了防止玩家点击边界(如第 0 行或最后一列)导致访问数组外部非法内存(即越界),通常采用“填充法”扩展矩阵。将显示区域行列数各加 2,使得真实的游戏盘面向外多出两圈空行/列。这样无论玩家在哪个位置排查,其周边的 8 个邻接坐标均存在于数组内,无需额外编写防越界的条件判断分支。
基于这一设计思路,定义宏常量以规范维度:
#define ROW 9//实际游戏盘面的行数 #define COL 9//实际游戏盘面的列数 #define ROWS (ROW + 2)//包含冗余边界的数组总行数 #define COLS (COL + 2)//包含冗余边界的数组总列数 #define MINE 10//预设的地雷总数
关键算法解析
第1步:数组初始化与信息打印
为了区分地雷与未知区域,初始化需分别设定不同字符。调用通用初始化函数时,传入目标数组名、尺寸参数及填充字符。通常将 mine 填充为 '0',将 show 填充为 '*'。此外,需要一个专门的循环遍历函数将二维数组以网格形式输出至控制台,并在每行首尾添加辅助的行号与列号标识。
第2步:布置地雷
地雷的分布依靠伪随机数发生器实现。为避免每次启动程序产生相同的布局,需在主程序入口处调用 srand() 绑定系统时间作为随机种子。该操作仅执行一次即可。
在具体实现时,使用 while(count) 循环不断生成随机坐标。由于内部预留了一圈边距,生成的 x 和 y 坐标范围必须严格落在 [1, ROW] 和 [1, COL] 区间内,以确保地雷不会落在冗余边距上。若生成的坐标已被占用则跳过重新生成,直至填满 MINE 个地雷。
int x = rand() % row + 1;//取模限制横坐标范围在1到row之间
int y = rand() % col + 1;//取模限制纵坐标范围在1到col之间
第3步:标记判定与交互逻辑
游戏中需支持两种操作:标记地雷(Flag)与排查区域(Find)。可通过分支菜单引导用户选择模式。针对标记功能,系统应当维护一个计数器,限制玩家最多标记的地雷数不能超过预设的 MINE。已标记的位置再次点击可取消标记释放额度,未标记位置且在额度范围内则可更新状态。
当玩家进入排查模式后,根据所选坐标是否踩雷做出不同响应。若踩雷直接宣告失败;若为安全区,则调用递归算法展开周围空白地带。
第4步:统计周围地雷个数 (CountMine)
统计某个坐标周边八个方向的雷数之和。由于数组内存储的是 ASCII 字符型数值,可以直接将相邻八个元素的字节值相加,随后减去 8 倍的字符 '0' 的 ASCII 值,便可获得真实的整数型雷数。
int CountMine(char board[ROWS][COLS], int row, int col)
{
int num = 0;
num = board[row - 1][col + 1] + board[row - 1][col] + board[row - 1][col - 1] + board[row][col - 1] +
board[row + 1][col - 1] + board[row + 1][col] + board[row + 1][col + 1] + board[row][col + 1] - 8 * '0';
return num;
}
第5步:递归展开安全区 (ExpandBoard)
这是实现自动翻页效果的核心。检查目标点的周围雷数:
- 若雷数为 0,说明该点及其周边均是空旷区域。将其显示为空格符
' ',并将胜利计次*win自增 1。随后对周围的 8 个方向坐标进行独立的条件检测,如果对应位置尚未被翻开(值为'*'),则对其发起递归调用,继续向外探索。 - 若雷数不为 0,代表此处存在提示信息,只需将该坐标的值修改为对应的数字字符即可。
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS],int x, int y,int *win)
{
int count = CountMine(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';//清空无雷区域的显示
(*win)++;
//依次向8个邻居发起探测
if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
ExpandBoard(mine, show, x - 1, y - 1,win);
if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 < ROWS && y > 0 && y < COLS)
ExpandBoard(mine, show, x - 1, y,win);
if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
ExpandBoard(mine, show, x - 1, y + 1,win);
if (show[x][y - 1] == '*' && x > 0 && x < ROWS && y - 1 > 0 && y - 1 < COLS)
ExpandBoard(mine, show, x, y - 1,win);
if (show[x][y + 1] == '*' && x > 0 && x < ROWS && y + 1 > 0 && y + 1 < COLS)
ExpandBoard(mine, show, x, y + 1,win);
if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
ExpandBoard(mine, show, x + 1, y - 1,win);
if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 < ROWS && y > 0 && y < COLS)
ExpandBoard(mine, show, x + 1, y,win);
if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
ExpandBoard(mine, show, x + 1, y + 1,win);
}
else
{
show[x][y] = count + '0';
}
}
工程化架构与完整源码
在实际开发中,良好的文件划分能极大提升维护效率。建议按职责划分为三个部分:
- game.h (头文件):放置所有的宏定义、函数声明以及必要的标准库引用(如
#include <time.h>、#include <stdlib.h>),方便跨文件共享。 - game.c (逻辑层):实现具体函数的底层逻辑,包括初始化、布阵、打印、查找与展开等功能。
- test.c (测试/入口层):负责主控流程,如循环打印菜单、处理用户选项、调用
srand()并衔接各个游戏模块。
以下是整合后的 test.c 入口代码示例:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("***************************\n");
printf("*** 1. play ***\n");
printf("*** 0. exit ***\n");
printf("***************************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 }; //存放雷的信息
char show[ROWS][COLS] = { 0 }; //显示给用户的信息
InitBoard(mine, ROWS, COLS,'0'); // 初始化为'0'
InitBoard(show, ROWS, COLS, '*'); // 初始化为'*'
SetMine(mine, ROW, COL); //布置地雷
DisplayBoard(show, ROW, COL); // 显示初始界面
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;
}
总结
通过引入扩充矩阵的技巧规避了复杂的边界判断,结合递归思想实现了高效的连锁开方机制,这便是使用C语言重构扫雷游戏背后的核心逻辑。以上就是利用C语言开发扫雷小游戏的详细内容,更多关于C语言算法实战的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













