云计算百科
云计算领域专业知识百科平台

[C语言初阶]扫雷小游戏

目录

  • 一、原理及问题分析
  • 二、代码实现
    • 2.1 分文件结构设计
    • 2.2 棋盘初始化与打印
    • 2.3 布置雷与排查雷
    • 2.4 游戏主流程实现
  • 三、后期优化方向

在上一篇文章中,我们实现了我们的第二个游戏——三子棋小游戏。这次我们继续结合我们之前所学的所有内容,制作出我们的第三个项目——扫雷小游戏。 在这里插入图片描述

一、原理及问题分析

说起扫雷,这是一个非常经典的小游戏。扫雷游戏的核心逻辑是通过玩家输入的坐标排查雷的位置,若踩雷则游戏结束,否则显示周围雷的数量。接下来我们先以结构化的方式来宏观分析整个扫雷游戏中的关键要点:

  • 双棋盘设计

    • mine数组:存储雷的位置(1为雷,0为非雷)。
    • show数组:存储玩家可见的信息(初始为*,排查后显示周围雷数,因为是字符,所以两个数组都是char类型)。
    • 设计意义:若只创建一个数组,雷为1,不是雷为0,若这个坐标周围只有1个雷,则分不清是雷,还是排查出的雷的信息,有歧义,所以再创建一个额外数组,专门用来存放排查出的雷的信息,只打印这个数组即可,用%c打印。
  • 边界处理

    • 实际使用11×11的数组(通过ROWS和COLS定义),但只操作中间的9×9区域(通过ROW和COL定义)。
    • 目的:排查雷时边界容易越界,所以要实现9×9棋盘实际是创建11×11的数组才不会越界,。 但不要直接写数字,而是定义行和列的符号,方便后期修改。
  • 模块化设计

    • test.c:处理菜单、循环流程和用户输入。
    • game.c:实现游戏核心逻辑(初始化、布置雷、排查雷)。
    • game.h:声明函数和定义常量。
  • 游戏流程(在三子棋和之前的猜数字小游戏中已实现过)

    • 使用do-while循环支持重复游玩。
    • 玩家输入坐标后,通过递归展开无雷区域(进阶功能需自行实现)。

  • 二、代码实现

    2.1 分文件结构设计

    文件分工与核心函数:

    • game.h:定义常量、声明函数。

    // game.h
    #pragma once
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    // 常量定义:实际操作的棋盘大小为9×9,扩展为11×11避免越界
    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    #define EASY 10 // 默认雷的数量

    // 函数声明
    void Start(char arr[ROWS][COLS], int rows, int cols, char get); // 初始化棋盘
    void Display(char arr[ROWS][COLS], int row, int col); // 打印棋盘
    void Set(char arr[ROWS][COLS], int row, int col); // 布置雷
    int Choose(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); // 排雷逻辑

    • test.c:主流程和菜单逻辑。
    • game.c:核心功能实现。

    2.2 棋盘初始化与打印

    1. 初始化函数 Start

    • 功能:将棋盘所有位置初始化为指定字符(mine初始为'0',show初始为'*')因为一个函数要实现两种不同内容初始化,所以多加一个参数。

    // game.c
    void Start(char arr[ROWS][COLS], int rows, int cols, char get)
    {
    for (int i = 0; i < rows; i++)
    {
    for (int j = 0; j < cols; j++)
    {
    arr[i][j] = get; // 将每个元素设置为传入的字符('0'或'*')
    }
    }
    }

    2. 打印函数 Display

    • 功能:打印棋盘,为了美化棋盘显示,优化玩家体验,我们添加了行列号和分隔线。

    // game.c
    void Display(char arr[ROWS][COLS], int row, int col)
    {
    printf("——–扫雷游戏——–\\n");
    // 打印列号(顶部标签)
    printf(" "); // 对齐行号
    for (int i = 1; i <= col; i++)
    {
    printf("%d ", i);
    }
    printf("\\n");

    // 打印分隔线
    printf(" ");
    for (int i = 1; i <= col; i++)
    {
    printf("–");
    }
    printf("\\n");

    // 打印棋盘内容(带行号)
    for (int i = 1; i <= row; i++)
    {
    printf("%d |", i); // 行号+左侧竖线
    for (int j = 1; j <= col; j++)
    {
    printf("%c ", arr[i][j]); // 打印棋盘元素
    }
    printf("\\n");
    }
    printf("——–扫雷游戏——–\\n");
    }

    运行效果:

    ——–扫雷游戏——–
    1 2 3 4 5 6 7 8 9
    ——————-
    1 |* * * * * * * * *
    2 |* * * * * * * * *
    …(略)


    2.3 布置雷与排查雷

    1. 布置雷函数 Set

    • 功能:在9×9区域内随机生成雷。(关于rand函数的用法以及取余的技巧在之前的三子棋和猜数字小游戏中已经详细介绍过,这里不再赘述)

    // game.c
    void Set(char arr[ROWS][COLS], int row, int col)
    {
    int count = EASY; // 雷的数量
    while (count)
    {
    int x = rand() % row + 1; // 生成1~9的随机坐标
    int y = rand() % col + 1;
    if (arr[x][y] == '0') { // 仅当该位置无雷时布置
    arr[x][y] = '1'; // 标记为雷
    count;
    }
    }
    }

    2. 计算周围雷数 get_mine

    • 功能:计算坐标(x,y)周围8个位置的雷数总和。 这个函数可以不用在game.h中声明,因为只是为了在排查雷的函数中临时用的,不会用在其他地方。

    // game.c
    int get_mine(char arr[ROWS][COLS], int x, int y)
    {
    // 周围8个坐标的字符值相加('0'或'1'),再减去8*'0'得到实际数字
    return arr[x1][y1] + arr[x1][y] + arr[x1][y+1] +
    arr[x][y1] + arr[x][y+1] +
    arr[x+1][y1] + arr[x+1][y] + arr[x+1][y+1] 8 * '0';
    }

    3. 排雷逻辑 Choose

    • 功能:处理玩家输入的坐标,判断是否踩雷或显示周围雷数。

    // game.c
    int Choose(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    int x = 0, y = 0;
    int Win = 0; // 记录已排查的非雷区域数量
    while (Win < ROW * COL EASY) // 胜利条件:所有非雷区域均被排查
    {
    printf("请选择排雷坐标(如2 3表示第二行第三列):>");
    scanf("%d %d", &x, &y);
    if (x >= 1 && y >= 1 && x <= row && y <= col)// 坐标合法性检查
    {
    if (mine[x][y] == '1') // 踩雷
    {
    printf("很遗憾,你被炸死了!\\n");
    Display(mine, ROW, COL); // 展示雷的位置
    break; // 游戏结束
    }
    else // 安全坐标
    {
    int count = get_mine(mine, x, y); // 计算周围雷数
    show[x][y] = count + '0'; // 转换为字符存储(例如3 -> '3')
    Display(show, ROW, COL);
    Win++;
    }
    }
    else
    {
    printf("坐标非法!\\n");
    }
    }
    if (Win == ROW * COL EASY) // 胜利条件达成
    {
    printf("恭喜通关!\\n");
    Display(mine, ROW, COL); // 展示雷的位置
    }
    }


    2.4 游戏主流程实现

    test.c:菜单和主循环逻辑。

    // test.c
    #include "game.h"

    void menu() {
    printf("*********************************\\n");
    printf("*********************************\\n");
    printf("***********扫雷小游戏************\\n");
    printf("*********************************\\n");
    printf("***********1.开始游戏************\\n");
    printf("***********0.退出游戏************\\n");
    printf("*********************************\\n");
    printf("**********版本:Beta1.0***********\\n");
    printf("**********作者:Yang210***********\\n");
    printf("*********************************\\n");
    printf("*********************************\\n");
    printf("*********************************\\n");
    }

    void game() {
    char mine[ROWS][COLS]; // 存储雷的棋盘
    char show[ROWS][COLS]; // 显示给玩家的棋盘
    Start(mine, ROWS, COLS, '0'); // 初始化雷棋盘为全'0'
    Start(show, ROWS, COLS, '*'); // 初始化显示棋盘为全'*'
    Set(mine, ROW, COL); // 布置雷
    Display(show, ROW, COL); // 打印初始棋盘
    Choose(mine, show, ROW, COL); // 进入排雷逻辑
    }

    int main() {
    srand((unsigned int)time(NULL)); // 设置随机数种子
    int input = 0;
    int add = 0; // 记录游戏次数
    do {
    menu();
    if (add == 0) {
    printf("请选择(输入1开始游戏,输入0退出游戏):>");
    } else {
    printf("是否继续游玩(输入1继续游玩,输入0退出游戏):>");
    }
    scanf("%d", &input);
    switch (input) {
    case 1:
    game();
    add++;
    break;
    case 0:
    printf("已退出游戏\\n");
    break;
    default:
    printf("输入错误!\\n");
    }
    } while (input); // input为0时退出循环
    return 0;
    }


    三、后期优化方向

  • 递归展开:若排查坐标周围无雷(即雷数为0),自动展开相邻区域。
  • 标记雷:允许玩家输入特殊指令(如m 3 4)标记可能为雷的位置。
  • 难度调整:通过修改EASY的值实现不同难度的雷数设置。
  • 界面优化:使用Windows API或第三方库(如EasyX)添加图形界面。

  • 简单地总结一下,在这篇文章中,我们通过分文件设计和模块化的运用,将扫雷游戏的逻辑逐一地理清并且给出了后期扩展的方向。我们在代码中通过定义符号常量(如ROW、EASY)提高可维护性,方便后期修改,这是一种很重要的编程习惯。我们通过我们现阶段所学的知识,做出了这个经典的游戏,这是对我们所学知识的肯定,也是我们对经典跨越时间的致敬。最后,我想送给大家一句话:"人生没有白走的路,每一步都算数。"我们的决定,决定了我们。在介绍完了两个C语言的项目之后,下一章,我们将回归C语言的知识学习,介绍一下操作符的相关知识,敬请期待。 请添加图片描述

    作者其他文章链接: [C语言初阶]三子棋小游戏 [C语言初阶]数组 [C语言初阶]递归 Gitee详细使用教程

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » [C语言初阶]扫雷小游戏
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!