C语言扫雷游戏:二维数组与递归算法完整实现指南

沈知远
沈知远
发布时间:2026-10-15 | 6人浏览过
原创

利用 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)

这是实现自动翻页效果的核心。检查目标点的周围雷数:

  1. 若雷数为 0,说明该点及其周边均是空旷区域。将其显示为空格符 ' ',并将胜利计次 *win 自增 1。随后对周围的 8 个方向坐标进行独立的条件检测,如果对应位置尚未被翻开(值为 '*'),则对其发起递归调用,继续向外探索。
  2. 若雷数不为 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';
  }
}

工程化架构与完整源码

在实际开发中,良好的文件划分能极大提升维护效率。建议按职责划分为三个部分:

  1. game.h (头文件):放置所有的宏定义、函数声明以及必要的标准库引用(如 #include <time.h>、#include <stdlib.h>),方便跨文件共享。
  2. game.c (逻辑层):实现具体函数的底层逻辑,包括初始化、布阵、打印、查找与展开等功能。
  3. 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语言算法实战的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表