C语言实现经典扫雷游戏:从零构建完整逻辑与代码详解

小石头
小石头
发布时间:2026-10-13 | 2人浏览过
原创

本文详细介绍如何使用C语言开发控制台版扫雷游戏,涵盖从模块拆分、核心算法(布雷、排雷、展开)到完整代码实现的每一步骤。

游戏架构与核心模块

扫雷游戏的编写需要清晰的逻辑结构。在开始编码前,需明确程序包含的功能模块,并通过主函数将这些模块串联。一个完整的C语言扫雷程序通常包含以下核心功能:

  • 菜单与交互:启动界面及选项选择。
  • 棋盘初始化:准备两张棋盘,一张展示给玩家(显示*),一张记录真实雷位信息。
  • 布雷逻辑:在指定范围内随机生成固定数量的地雷。
  • 安全检查:确保玩家第一次点击不会踩雷。
  • 统计与展开:计算坐标周围8个邻居的雷数;若周围无雷,则自动展开相邻空白区域。
  • 胜负判断:当非雷区域全部被揭开时获胜,踩雷则失败。

第一步:定义头文件与全局变量

为了封装游戏逻辑,首先创建头文件game.h。这里定义了棋盘的行(row)和列(col)以及雷的总数(COUNT),并声明了外部使用的两个二维数组:show_mine用于展示给玩家的界面,real_mine用于存储雷的实际分布位置。同时列出所有功能函数的原型。

#ifndef __GAME_H__
#define __GAME_H__
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

// 定义棋盘大小和雷的数量
#define row 12 // 实际有效游戏区域为中间的10x10
#define col 12
#define COUNT 10

extern char show_mine[row][col]; // 展示数组,初始化为'*'
extern char real_mine[row][col]; // 布雷数组,'0'表示无雷,'1'表示有雷

void muen();           // 菜单函数
void init_mine();      // 初始化数组
void set_mine();       // 布雷函数
int count_mine(int x, int y); // 统计周围雷的个数
void print_player();   // 打印玩家棋盘
void print_mine();     // 打印设计者棋盘(调试用)
int sweep_mine();      // 扫雷函数
void safe_mine();      // 避免第一次被雷炸死
void open_mine(int x, int y);// 展开函数(简化版,针对直接相邻的8格)
int count_show_mine(); // 判断玩家棋盘剩余未知区域的个数

#endif  //__GAME_H__

第二步:实现主函数与游戏循环

主函数负责生成随机种子、调用菜单并处理用户输入。游戏核心逻辑封装在game()函数中。流程如下:

  1. 初始化:清空并初始化两张棋盘。
  2. 布雷:随机放置地雷。
  3. 首步安全:调用safe_mine处理第一次落子,若踩雷则移动雷的位置并确保当前点周围无雷。
  4. 循环扫雷:获取用户坐标,检查是否胜利或失败,更新棋盘状态。
#include "game.h"

void game()
{
    init_mine();      // 初始化玩家棋盘和设计者棋盘
    set_mine();       // 给设计者棋盘布雷
    
    // 可选:打印设计者棋盘查看雷的分布进行调试
    // print_mine(); 
    printf("\n");
    print_player();   // 打印玩家棋盘

    safe_mine();      // 确保第一次不被炸死

    // 检查一步胜利的特殊情况
    if (count_show_mine() == COUNT)
    {
        print_mine();
        printf("玩家赢!\n\n");
        return;
    }
    print_player();

    while (1)         // 循环扫雷
    {
        int ret = sweep_mine(); // 执行扫雷动作,踩到雷返回1,未踩到返回0
        
        // 判断胜利条件:当剩余未打开的非雷格子数量等于总雷数时
        if (count_show_mine() == COUNT)
        {
            print_mine();
            printf("玩家赢!\n\n");
            break;
        }
        
        if (ret) // 如果返回1,说明踩雷
        {
            printf("被雷炸死\n");
            print_mine(); // 展示雷阵位置
            break;
        }
        print_player(); // 打印最新状态的棋盘
    }
}

int main()
{
    srand((unsigned int)time(NULL)); // 初始化随机数生成器
    int input = 0;
    
    do
    {
        muen();                   // 显示菜单
        scanf_s("%d", &input);  // 读取输入
        switch (input)
        {
        case 1:
            game();               // 开始游戏
            break;
        case 0:
            exit(1);              // 退出程序
            break;
        default:
            printf("输入错误,重新输入\n");
            break;
        }
        printf("Continue? (Y/N): ");
        // 注意:原文此处逻辑简单,实际项目中建议增加更严谨的回车符清理和继续判定
    } while (1);
    
    return 0;
}

第三步:实现核心功能函数

以下是各模块的具体实现细节。代码中使用了scanf_s以适应部分编译器环境,标准C也可使用scanf。

1. 初始化与打印

init_mine将展示数组填充为'*',将真实数组填充为'0'。打印函数需注意坐标轴的对齐,通常在中间10x10的有效区域外保留一圈边界,以便后续处理边界坐标溢出问题(虽然本示例主要通过输入判断处理边界)。

2. 布雷函数 set_mine

利用rand() % 10 + 1生成1-10之间的随机坐标。当目标位置尚未埋雷时(值为'0'),将其设为'1',并递减计数,直到埋够COUNT个雷。

3. 统计周围雷数 count_mine

给定坐标(x, y),遍历其周围8个邻居(即x-1~x+1, y-1~y+1)。统计其中值为'1'的数量。这是扫雷显示数字的核心逻辑。

4. 首次安全策略 safe_mine

此函数处理第一次输入。若玩家第一次就踩到雷(real_mine[x][y] == '1'),程序会做两件事:

  1. 将当前雷移除,改为'0'。
  2. 在当前点周围(及该点本身)计算周围雷数并填入show_mine。
  3. 在其他任意空白位置新布一枚雷,保证总雷数不变且不影响当前局面的公平性。

这样确保了“第一次绝不死”的用户体验。

5. 扫雷与展开 sweep_mine 和 open_mine

sweep_mine接收坐标,若为安全区,则调用count_mine将结果转为字符存入show_mine,并调用open_mine。在此简化版本中,open_mine仅处理直接相邻的8个格子(非全递归泛洪填充),若周围为'0'则展开显示,否则保留数字。更完善的递归展开通常需要单独的DFS或BFS函数,但本例展示了基本的展开概念。

6. 胜利判断 count_show_mine

遍历展示数组,统计剩余仍为'*'的数量。当这个数量恰好等于总雷数时,意味着所有非雷格子都已被翻开,玩家获胜。

完整源代码汇总

将上述逻辑整合,以下是可编译运行的完整.c文件代码结构(假设头文件已按上文定义):

#include "game.h"
#include <time.h>

char show_mine[row][col] = { 0 };
char real_mine[row][col] = { 0 };

void muen()
{
    printf("*******************************\n");
    printf("*****1.Play          0.Exit****\n");
    printf("*******************************\n");
}

void init_mine()
{
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            show_mine[i][j] = '*';
            real_mine[i][j] = '0';
        }
    }
}

void print_player()
{
    // 省略具体打印逻辑以节省篇幅,参考上文step 2代码中的print_player实现
    int i, j;
    printf("   ");
    for (i = 1; i < row - 1; i++) printf("%d  ", i);
    printf("\n");
    for (i = 1; i < row - 1; i++)
    {
        printf("%d ", i);
        for (j = 1; j < col - 1; j++)
        {
            printf("%c  ", show_mine[i][j]);
        }
        printf("\n");
    }
}

void print_mine()
{
    // 类似print_player,但输出real_mine
    int i, j;
    printf("\n--- Mine Map ---\n");
    printf("   ");
    for (i = 1; i < row - 1; i++) printf("%d  ", i);
    printf("\n");
    for (i = 1; i < row - 1; i++)
    {
        printf("%d ", i);
        for (j = 1; j < col - 1; j++)
        {
            printf("%c  ", real_mine[i][j]);
        }
        printf("\n");
    }
}

void set_mine()
{
    int x, y, count = COUNT;
    while (count)
    {
        x = rand() % 10 + 1;
        y = rand() % 10 + 1;
        if (real_mine[x][y] == '0')
        {
            real_mine[x][y] = '1';
            count--;
        }
    }
}

int count_mine(int x, int y)
{
    int c = 0;
    // 检查周围8个位置
    if (real_mine[x-1][y-1] == '1') c++;
    if (real_mine[x-1][y]   == '1') c++;
    if (real_mine[x-1][y+1] == '1') c++;
    if (real_mine[x][y-1]   == '1') c++;
    if (real_mine[x][y+1]   == '1') c++;
    if (real_mine[x+1][y-1] == '1') c++;
    if (real_mine[x+1][y]   == '1') c++;
    if (real_mine[x+1][y+1] == '1') c++;
    return c;
}

void safe_mine()
{
    int x, y;
    printf("Input first coordinate: ");
    while (1)
    {
        scanf_s("%d%d", &x, &y);
        if (x >=1 && x <=10 && y >=1 && y <=10)
        {
            if (real_mine[x][y] == '1')
            {
                // 补救:移走当前雷
                real_mine[x][y] = '0';
                // 计算周围雷数并填入show_mine
                char val = count_mine(x, y);
                show_mine[x][y] = val + '0';
                open_mine(x, y);
                
                // 在其他地方补一颗雷
                int rx, ry;
                do {
                    rx = rand() % 10 + 1;
                    ry = rand() % 10 + 1;
                } while(real_mine[rx][ry] == '1');
                real_mine[rx][ry] = '1';
            }
            else
            {
                char val = count_mine(x, y);
                show_mine[x][y] = val + '0';
                open_mine(x, y);
            }
            break;
        }
        printf("Invalid input. Try again:\n");
    }
}

int sweep_mine()
{
    int x, y;
    printf("Input coordinate: ");
    scanf_s("%d%d", &x, &y);
    
    if (x <1 || x >10 || y <1 || y >10)
    {
        printf("Out of bounds!\n");
        return 0;
    }
    
    if (real_mine[x][y] == '1')
    {
        return 1; // 踩雷
    }
    else
    {
        char val = count_mine(x, y);
        show_mine[x][y] = val + '0';
        open_mine(x, y);
        return 0;
    }
}

void open_mine(int x, int y)
{
    // 此处为简化版的相邻展开,非递归泛洪
    // 仅处理紧邻的8个格子
    struct offset {
        int dx, dy;
    } offsets[] = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}};
    
    for(int i=0; i<8; i++) {
        int nx = x + offsets[i].dx;
        int ny = y + offsets[i].dy;
        if (nx >=1 && nx <=10 && ny >=1 && ny <=10) {
            if (show_mine[nx][ny] == '*' && real_mine[nx][ny] == '0') {
                 show_mine[nx][ny] = count_mine(nx, ny) + '0';
            }
        }
    }
}

int count_show_mine()
{
    int count = 0;
    for (int i = 1; i <= row - 2; i++)
        for (int j = 1; j <= col - 2; j++)
            if (show_mine[i][j] == '*')
                count++;
    return count;
}

注意事项与扩展

上述代码实现了扫雷的基本骨架。在实际应用中,可能存在以下改进空间:
1. 递归展开:当前open_mine仅为一次级联,若遇到空格应递归调用自身以实现大面积白块翻开。
2. 内存布局:使用更大的数组(如13x13)并在外围填充'0',可以省去大量的边界判断代码,使count_mine逻辑更加简洁统一。
3. 界面优化:使用Windows API或ncurses库可以实现彩色显示和鼠标支持,提升控制台体验。

以上就是C语言实现扫雷小程序的详细内容,更多关于C语言游戏开发入门的资料请关注本站其它相关文章!

相关标签:

本站声明

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

Python 相关产品和应用

全部下载

专题列表