C语言结合EasyX图形库制作扫雷游戏的完整实现

Ada
Ada
发布时间:2026-10-16 | 2人浏览过
原创

使用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语言图形库开发的资料请关注本站其它相关文章!

本站声明

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

Python 相关产品和应用

全部下载

专题列表