项目实战——C语言扫雷游戏


这是一款9*9的扫雷游戏

扫雷游戏

1.需求分析

我们需要编写一款C语言扫雷游戏 ,包含9*9 自定义格式等游戏格式
注意: (由于创作者水平有限)**!!**本款游戏未能实现UI设计前端知识

  • 游戏可以通过菜单实现继续玩或者退出游戏

  • 游戏的棋盘格式是9x9

  • 默认随机布置10颗雷

  • 可以排查雷
    --如果不是雷,则显示周边有几颗雷
    --如果是雷,则炸死游戏结束
    --把除了雷之外的所有非雷格子都找出来,则排雷成功,游戏结束
    --游戏可以反复玩

  • 甚至我们可以拓展多种玩法,例如:11x1115x15计时模式

  • 游戏界面

2.程序框架设计

  • 扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构(C语言讲完之后会讲到)来存储这些信息。
  • 因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们首先想到的就是创建一个9*9的数组来存放布置雷的信息
  • 那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.

我们先看上面这个9x9的格子

我们访问(8,9)这个坐标的时候,周围的⼀圈8个位置,统计周围雷的个数时,最下面的三个坐标就会越界,为了防止越界,我们在设计的时候,给数组扩大⼀圈,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就行,这样就解决了越界的问题。所以我们将存放数据的数组创建成11*11是⽐较合适。

  • 再定义一个符号数组用来存放排查出的雷的信息

我们可以将数组初始化为*

3.分函数实现

打印游戏菜单界面

c 复制代码
void menu() {
    printf("***********************\n");
    printf("*****   1. play   *****\n");  // 选择1开始游戏
    printf("*****   0. exit   *****\n");  // 选择0退出游戏
    printf("***********************\n");
}

游戏主逻辑函数

c 复制代码
void game() {
    // 定义两个二维数组:
    // mine数组:存储地雷的真实分布('0'无雷,'1'有雷)
    // show数组:存储玩家可见的信息('*'未排查,数字表示周围雷数)
    char mine[ROWS][COLS];  // 实际地雷分布棋盘
    char show[ROWS][COLS];  // 玩家可见棋盘

    // 初始化棋盘:
    // mine数组全部初始化为'0'(表示初始时没有地雷)
    // show数组全部初始化为'*'(表示所有位置都未排查)
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    // 在mine数组中随机布置地雷(数量由EASY_COUNT决定)
    SetMine(mine, ROW, COL);

    // 打印玩家可见的棋盘(调试时可取消注释查看地雷分布)
    // DisplayBoard(mine, ROW, COL);  // 打印真实地雷分布(用于调试)
    DisplayBoard(show, ROW, COL);    // 打印玩家可见棋盘

    // 开始玩家排雷过程
    FindMine(mine, show, ROW, COL);
}

程序主入口

c 复制代码
int main() {
    int input = 0;  // 存储用户输入的菜单选择

    // 设置随机数种子(使用当前时间确保每次运行随机性不同)
    srand((unsigned int)time(NULL));

    // 主游戏循环(至少执行一次)
    do {
        menu();              // 打印菜单
        printf("请选择:>"); // 提示用户输入
        scanf_s("%d", &input); // 读取用户选择

        // 根据用户选择执行不同操作
        switch (input) {
        case 1:    // 选择1:开始游戏
            game(); // 调用游戏主函数
            break;
        case 0:    // 选择0:退出游戏
            printf("退出游戏\n");
            break;
        default:   // 其他输入:提示错误
            printf("选择错误,请重新选择\n");
            break;
        }
    } while (input);  // 当input不为0时继续循环

    return 0;  // 程序正常退出
}

初始化游戏棋盘

c 复制代码
// board: 二维数组表示的棋盘
// rows: 棋盘的行数
// cols: 棋盘的列数
// set: 初始化时填充的字符(通常'0'表示无雷,'1'表示有雷)
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    int i = 0;
    for (i = 0; i < rows; i++)  // 遍历每一行
    {
        int j = 0;
        for (j = 0; j < cols; j++)  // 遍历每一列
        {
            board[i][j] = set;  // 将当前格子设置为指定字符
        }
    }
}

随机布置地雷

c 复制代码
// mine: 表示雷区的二维数组
// row: 有效行数
// col: 有效列数
void SetMine(char mine[ROWS][COLS], int row, int col)
{
    // 需要布置的地雷数量(EASY_COUNT可能是头文件中定义的常量,如10)
    int count = EASY_COUNT;

    while (count) {  // 循环直到布置完所有地雷
        // 生成随机坐标(1-row和1-col范围内)
        int x = rand() % row + 1;  // 1到row的随机数
        int y = rand() % col + 1;  // 1到col的随机数

        // 检查该位置是否已经有雷
        if (mine[x][y] == '0')  // '0'表示无雷
        {
            mine[x][y] = '1';  // '1'表示有雷
            count--;  // 成功布置一个雷,计数器减1
        }
    }

显示当前棋盘状态

c 复制代码
// board: 要显示的棋盘(可能是雷区或玩家可见区)
// row: 棋盘的有效行数(通常从1开始)
// col: 棋盘的有效列数(通常从1开始)
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
    printf("-------扫雷-------\n");

    // 打印列号(0-col)
    for (int j = 0; j <= col; j++) {
        printf("%d ", j);  // 打印列索引(0到col)
    }
    printf("\n");

    // 打印每一行内容(行号+棋盘内容)
    for (int i = 1; i <= row; i++) {
        printf("%d ", i);  // 打印行号(1到row)

        // 打印该行每个格子的内容
        for (int j = 1; j <= col; j++) {
            printf("%c ", board[i][j]);  // 打印棋盘内容
        }
        printf("\n");  // 换行到下一行
    }
}

计算指定位置周围的地雷数量

c 复制代码
// mine: 雷区数组
// x: 行坐标
// y: 列坐标
// 返回:周围8个格子的地雷总数
int GetMineCount(char mine[ROWS][COLS], int x, int y) {
    // 将周围8个格子的字符值相加('0'=48,'1'=49),然后减去8*'0'得到实际数字
    return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
        mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
        mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}

玩家排雷主逻辑

c 复制代码
// mine: 真实的雷区数组
// show: 玩家看到的棋盘(显示已排查区域)
// row: 有效行数
// col: 有效列数
void FindMine(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_COUNT) {
        printf("请输入要排查的坐标:");
        scanf_s("%d %d", &x, &y);  // 读取玩家输入

        // 检查坐标是否合法
        if (x >= 1 && x <= row && y >= 1 && y <= col) {
            // 如果踩中地雷
            if (mine[x][y] == '1') {
                printf("很遗憾,你被炸死了\n");
                DisplayBoard(mine, row, col);  // 显示全部地雷位置
                break;  // 结束游戏
            }
            else {
                // 计算并显示周围地雷数量
                int count = GetMineCount(mine, x, y);
                show[x][y] = count + '0';  // 将数字转换为ASCII字符(如1→'1')
                DisplayBoard(show, row, col);
                win++;  // 成功排查一个安全格子,计数器加1
            }
        }
        else {
            printf("输入的坐标非法,请重新输入\n");  // 坐标超出范围提示
        }
    }
    // 胜利条件判断:当所有安全格子都被排查
    if (win == row * col - EASY_COUNT) {
        printf("恭喜你,排雷成功!\n");
        DisplayBoard(mine, row, col);  // 显示地雷位置
    }
}

4.分文件实现

这一次我们分三个文件来讲解
test.c 文件中写文件的测试逻辑 game.c 文件中写函数的实现 game.h 文件中写程序需要的数据类型和函数声明

(1)test.c

c 复制代码
#include <stdio.h>      // 标准输入输出库
#include "game.h"       // 包含自定义的游戏头文件(定义常量、函数声明等)
#include <stdlib.h>     // 包含rand()和srand()函数
#include <time.h>       // 包含time()函数用于生成随机数种子

// 打印游戏菜单界面
void menu() {
    printf("***********************\n");
    printf("*****   1. play   *****\n");  // 选择1开始游戏
    printf("*****   0. exit   *****\n");  // 选择0退出游戏
    printf("***********************\n");
}

// 游戏主逻辑函数
void game() {
    // 定义两个二维数组:
    // mine数组:存储地雷的真实分布('0'无雷,'1'有雷)
    // show数组:存储玩家可见的信息('*'未排查,数字表示周围雷数)
    char mine[ROWS][COLS];  // 实际地雷分布棋盘
    char show[ROWS][COLS];  // 玩家可见棋盘

    // 初始化棋盘:
    // mine数组全部初始化为'0'(表示初始时没有地雷)
    // show数组全部初始化为'*'(表示所有位置都未排查)
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    // 在mine数组中随机布置地雷(数量由EASY_COUNT决定)
    SetMine(mine, ROW, COL);

    // 打印玩家可见的棋盘(调试时可取消注释查看地雷分布)
    // DisplayBoard(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_s("%d", &input); // 读取用户选择

        // 根据用户选择执行不同操作
        switch (input) {
        case 1:    // 选择1:开始游戏
            game(); // 调用游戏主函数
            break;
        case 0:    // 选择0:退出游戏
            printf("退出游戏\n");
            break;
        default:   // 其他输入:提示错误
            printf("选择错误,请重新选择\n");
            break;
        }
    } while (input);  // 当input不为0时继续循环

    return 0;  // 程序正常退出
}

(2)game.c

c 复制代码
#include "game.h"  // 包含自定义的游戏头文件,可能定义了ROWS、COLS、EASY_COUNT等常量

// 初始化游戏棋盘
// board: 二维数组表示的棋盘
// rows: 棋盘的行数
// cols: 棋盘的列数
// set: 初始化时填充的字符(通常'0'表示无雷,'1'表示有雷)
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    int i = 0;
    for (i = 0; i < rows; i++)  // 遍历每一行
    {
        int j = 0;
        for (j = 0; j < cols; j++)  // 遍历每一列
        {
            board[i][j] = set;  // 将当前格子设置为指定字符
        }
    }
}

// 显示当前棋盘状态
// board: 要显示的棋盘(可能是雷区或玩家可见区)
// row: 棋盘的有效行数(通常从1开始)
// col: 棋盘的有效列数(通常从1开始)
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
    printf("-------扫雷-------\n");

    // 打印列号(0-col)
    for (int j = 0; j <= col; j++) {
        printf("%d ", j);  // 打印列索引(0到col)
    }
    printf("\n");

    // 打印每一行内容(行号+棋盘内容)
    for (int i = 1; i <= row; i++) {
        printf("%d ", i);  // 打印行号(1到row)

        // 打印该行每个格子的内容
        for (int j = 1; j <= col; j++) {
            printf("%c ", board[i][j]);  // 打印棋盘内容
        }
        printf("\n");  // 换行到下一行
    }
}

// 随机布置地雷
// mine: 表示雷区的二维数组
// row: 有效行数
// col: 有效列数
void SetMine(char mine[ROWS][COLS], int row, int col)
{
    // 需要布置的地雷数量(EASY_COUNT可能是头文件中定义的常量,如10)
    int count = EASY_COUNT;

    while (count) {  // 循环直到布置完所有地雷
        // 生成随机坐标(1-row和1-col范围内)
        int x = rand() % row + 1;  // 1到row的随机数
        int y = rand() % col + 1;  // 1到col的随机数

        // 检查该位置是否已经有雷
        if (mine[x][y] == '0')  // '0'表示无雷
        {
            mine[x][y] = '1';  // '1'表示有雷
            count--;  // 成功布置一个雷,计数器减1
        }
    }
}

// 计算指定位置周围的地雷数量
// mine: 雷区数组
// x: 行坐标
// y: 列坐标
// 返回:周围8个格子的地雷总数
int GetMineCount(char mine[ROWS][COLS], int x, int y) {
    // 将周围8个格子的字符值相加('0'=48,'1'=49),然后减去8*'0'得到实际数字
    return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
        mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
        mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}

// 玩家排雷主逻辑
// mine: 真实的雷区数组
// show: 玩家看到的棋盘(显示已排查区域)
// row: 有效行数
// col: 有效列数
void FindMine(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_COUNT) {
        printf("请输入要排查的坐标:");
        scanf_s("%d %d", &x, &y);  // 读取玩家输入

        // 检查坐标是否合法
        if (x >= 1 && x <= row && y >= 1 && y <= col) {
            // 如果踩中地雷
            if (mine[x][y] == '1') {
                printf("很遗憾,你被炸死了\n");
                DisplayBoard(mine, row, col);  // 显示全部地雷位置
                break;  // 结束游戏
            }
            else {
                // 计算并显示周围地雷数量
                int count = GetMineCount(mine, x, y);
                show[x][y] = count + '0';  // 将数字转换为ASCII字符(如1→'1')
                DisplayBoard(show, row, col);
                win++;  // 成功排查一个安全格子,计数器加1
            }
        }
        else {
            printf("输入的坐标非法,请重新输入\n");  // 坐标超出范围提示
        }
    }

    // 胜利条件判断:当所有安全格子都被排查
    if (win == row * col - EASY_COUNT) {
        printf("恭喜你,排雷成功!\n");
        DisplayBoard(mine, row, col);  // 显示地雷位置
    }
}

(3)game.h

c 复制代码
#pragma once  // 防止头文件被重复包含

// 标准库头文件
#include <stdio.h>   // 提供输入输出函数(如printf、scanf)
#include <stdlib.h>  // 提供内存分配、随机数等函数(如rand、srand)
#include <time.h>    // 提供时间相关函数(如time用于随机数种子)

/***********************
 * 游戏难度配置宏定义
 * 通过地雷数量控制难度级别
 ***********************/
#define EASY_COUNT 10   // 简单难度 - 10个地雷
#define MIDIEA_COUNT 30 // 中等难度 - 30个地雷(注意拼写应为MEDIUM)
#define HIGH_COUNT 50   // 困难难度 - 50个地雷

 /***********************
  * 棋盘尺寸宏定义
  * 采用"大一圈"的数组设计便于边界处理
  ***********************/
#define ROW 9    // 游戏可见区域的行数(玩家实际操作区域)
#define COL 9    // 游戏可见区域的列数
#define ROWS ROW+2  // 实际数组行数 = 可见行 + 2(上下各多一行边界)
#define COLS COL+2  // 实际数组列数 = 可见列 + 2(左右各多一列边界)

  /***********************
   * 函数声明部分
   * 描述各模块的核心功能
   ***********************/

   /**
	* @brief 初始化游戏棋盘
	* @param board 目标二维数组
	* @param rows 数组总行数(应使用ROWS)
	* @param cols 数组总列数(应使用COLS)
	* @param set 初始化填充字符
	* @note 将整个数组(包括边界)初始化为set字符
	*       通常:mine数组用'0',show数组用'*'
	*/
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

/**
 * @brief 打印游戏棋盘
 * @param board 要打印的二维数组
 * @param row 可见区域行数(应使用ROW)
 * @param col 可见区域列数(应使用COL)
 * @note 会显示行列坐标(1-9)
 *       只打印内部9x9区域(忽略最外圈边界)
 */
void DisplayBoard(char board[ROWS][COLS], int row, int col);

/**
 * @brief 随机布置地雷
 * @param board 地雷分布数组(通常为mine数组)
 * @param row 可见区域行数(应使用ROW)
 * @param col 可见区域列数(应使用COL)
 * @note 在1-9行/列范围内随机布置地雷
 *       地雷用'1'表示,安全格用'0'
 *       实际使用时应配合EASY_COUNT等难度常量
 */
void SetMine(char board[ROWS][COLS], int row, int col);

/**
 * @brief 玩家排雷核心逻辑
 * @param mine 真实地雷分布数组
 * @param show 玩家可见的棋盘数组
 * @param row 可见区域行数(应使用ROW)
 * @param col 可见区域列数(应使用COL)
 * @note 处理玩家输入、胜负判断、棋盘更新
 *       包含游戏主循环,直到胜利或踩雷
 */
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

5.运行效果展示

如果你觉得这篇文章对你有帮助

请给个三连支持一下哦

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
FairGuard手游加固9 小时前
2026年7月份国产游戏审批信息
游戏
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript