C语言结合EasyX图形库制作扫雷游戏的完整实现
使用C语言和EasyX图形库制作扫雷游戏,需要解决二维数组管理、内存安全及图形渲染问题。本文将核心逻辑分解为地图生成、初始化、鼠标交互及递归展开四个步骤,重点解析如何通过"保护区"设计规避数组越界风险,并利用递归算法实现空白区域的自动清理。
核心原理与规则
扫雷游戏的核心在于坐标映射与逻辑判断。玩家点击方块后,系统需判断该位置是否为地雷。若为空格,则显示周围八邻域内地雷的数量;若周围无雷(数值为0),则应自动递归展开相邻的空格,直至遇到数字边界。这一过程要求程序具备严谨的边界检查能力,以防止访问非法内存地址。
准备条件与环境配置
开发环境建议使用 Windows 平台下的 C/C++ 编译器(如 Visual Studio 或 Dev-C++),并集成 easyx.h 图形库以支持鼠标事件捕获和图像绘制。
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <graphics.h> // EasyX 图形库头文件 #include <conio.h> #include <windows.h> // 定义全局常量 #define WIDTH 10 // 游戏区域宽度 #define NUM 12 // 地雷总数 #define SIZE 70 // 单个格子图像的像素大小
第1步:构建带保护层的二维地图
目的: 解决边缘格子周围邻域计算时的数组越界问题。
入口: 定义二维数组。
动作: 将实际可视的 M x N 区域包裹在一层额外的空白边界内。例如,若游戏大小为 10x10,则定义数组大小为 12x12(即 [WIDTH+2][WIDTH+2])。
结果: 当遍历边缘格子时,其外围指向的“保护区”索引始终合法,从而避免崩溃。
int map[WIDTH + 2][WIDTH + 2];
第2步:初始化游戏地图数据
目的: 铺设背景网格并随机布雷,同时计算每个非雷格子的周围雷数。
入口: 主循环或初始化函数。
动作:
1. **清空背景**:遍历可视区域 (1,1) 到 (WIDTH, WIDTH),初始化为 0,并绘制默认闭合图片。
2. **随机布雷**:使用 rand() 在可视范围内生成 NUM 个坐标,若该处未被标记,则存入 -1(代表地雷)。
3. **统计雷数**:遍历所有格子,若某格为 -1,则将其周围 8 个邻居的值加 1(排除保护区且非雷位置)。
4. **状态加密**:为区分“未翻开”、“数字”和“地雷”,将所有可视区域数据加 20。此时 20 代表闭合方块,21-28 代表周围有 1-8 个雷。
代码实现:
// 1. 初始化空白区域并绘图
for (int i = 1; i <= WIDTH; i++)
{
for (int j = 1; j <= WIDTH; j++)
{
map[i][j] = 0;
putimage(i * SIZE, j * SIZE, &img[0]); // 绘制闭合图案
}
}
// 2. 随机布雷
srand(time(NULL)); // 设置随机种子
int n = 0;
while (n < NUM)
{
int r = rand() % WIDTH + 1;
int c = rand() % WIDTH + 1;
if (map[r][c] == 0) // 仅对空格布雷
{
map[r][c] = -1; // -1 标识地雷
n++;
}
}
// 3. 计算周围雷数
for (int i = 1; i <= WIDTH; i++)
{
for (int j = 1; j <= WIDTH; j++)
{
if (map[i][j] == -1)
{
for (int m = i - 1; m <= i + 1; m++)
{
for (int n = j - 1; n <= j + 1; n++)
{
// 确保不越界、不是雷本身、且在可视区域内
if (m != 0 && n != 0 && map[m][n] != -1)
{
map[m][n]++;
}
}
}
}
}
}
// 4. 给所有可视格子加密(加20)以便前端显示
for (int i = 1; i <= WIDTH; i++)
{
for (int j = 1; j <= WIDTH; j++)
{
map[i][j] += 20;
// 此时 map[i][j] 范围变为 20(闭合) ~ 28(周围8雷)
}
}第3步:处理鼠标交互逻辑
目的: 捕获用户操作并将屏幕坐标转换为逻辑坐标。
入口: 消息循环获取鼠标事件。
动作:
1. 坐标转换:将鼠标 X/Y 坐标除以 SIZE 并加 1,得到精确的数组索引(忽略保护区偏移)。
2. 左键点击:调用 ReturnT 判断是否可操作,若合法则调用 updateNum 展开格子并刷新画面。
3. 右键标记:调用标记函数,修改状态值(加 50 表示插旗,减 50 取消插旗)。
4. 中键退出:直接调用 exit(0)。
ExMessage m;
m = getmessage(EM_MOUSE | EM_KEY); // 等待输入
int heng = m.x / SIZE + 1;
int zong = m.y / SIZE + 1;
switch (m.message)
{
case WM_LBUTTONDOWN:// 左键:翻开
// ReturnT 检查该点是否未被标记(>10 且 <50)
if (ReturnT(heng, zong))
{
updateNum(heng, zong);
drawMap(); // 重绘整个地图
}
break;
case WM_RBUTTONDOWN:// 右键:插旗/取消
BiaoJi(heng, zong);
drawMap();
break;
case WM_MBUTTONDOWN:// 中键:退出
exit(0);
break;
}辅助函数解析:
// 判断是否可以点击(未被标记且是有效数字或空格)
bool ReturnT(int heng, int zong)
{
// 假设原始数据加密后,未翻开正常格子的值在 20~28 之间
// 这里根据原逻辑简化判断,通常需结合具体 img 数组索引
return (map[heng][zong] < 50 && map[heng][zong] >= 20);
}
// 右键标记逻辑:加50表示标记,减去50恢复
void BiaoJi(int heng, int zong)
{
if (map[heng][zong] > 20 && map[heng][zong] < 50)
{
map[heng][zong] += 50; // 标记状态
}
else
{
map[heng][zong] -= 50; // 取消标记
}
}第4步:递归展开空白区域
目的: 当点击到的格子周围雷数为 0 时,自动揭开其周围 8 个格子,形成连锁反应。
入口: updateNum 函数。
动作: 采用深度优先搜索(DFS)思想。若当前格子解密后值为 0,则递归调用其周围 8 个坐标的 updateNum。每次处理后,移除之前的“加密值(20)”,并绘制对应的数字图片或空白图。
void updateNum(int heng, int zong)
{
// 解密后的值:原值 - 20
// 若解密后为 0,说明周围无雷,需要递归展开
if (map[heng][zong] - 20 == 0)
{
for (int i = heng - 1; i <= heng + 1; i++)
{
for (int j = zong - 1; j <= zong + 1; j++)
{
// 递归调用,但需注意防止重复死循环(可通过判断状态值控制)
if (i >= 1 && i <= WIDTH && j >= 1 && j <= WIDTH)
{
updateNum(i, j);
}
}
}
}
// 解密并绘图:减去 20 还原真实数值,绘制对应图片
if (map[heng][zong] > 20 && map[heng][zong] < 50)
{
map[heng][zong] -= 20; // 此时值为 0~8
// 假设 img[] 数组中索引 0 为空白,1-8 为对应数字
putimage(heng * SIZE, zong * SIZE, &img[map[heng][zong]]);
}
}
总结
本实现通过简单的二维数组配合“保护区”技巧,巧妙解决了边界检测难题;利用递归算法高效处理了扫雷中核心的“点开一片”逻辑。结合 EasyX 的消息泵机制,实现了流畅的鼠标交互体验。掌握此案例有助于深入理解 C 语言中的数组操作、指针引用及递归思维在游戏开发中的应用。
以上就是C语言结合EasyX图形库制作扫雷游戏的详细内容,更多关于C语言图形库开发的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









