项目实战——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.运行效果展示

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

请给个三连支持一下哦

相关推荐
小道士写程序1 分钟前
Qt 5.12 上读取 .xlsx 文件(Windows 平台)
开发语言·windows·qt
ou.cs14 分钟前
c# :this() 和 :base()区别
开发语言·c#
才鲸嵌入式21 分钟前
01 Ubuntu20.04下编译QEMU8.2.4,交叉编译32位ARM程序,运行ARM程序的方法
linux·c语言·单片机·嵌入式·arm·qemu·虚拟机
Mikhail_G32 分钟前
Python应用函数调用(二)
大数据·运维·开发语言·python·数据分析
木木黄木木1 小时前
Python制作史莱姆桌面宠物!可爱的
开发语言·python·宠物
exploration-earth2 小时前
本地优先的状态管理与工具选型策略
开发语言·前端·javascript
苦学编程的谢2 小时前
Java网络编程API 1
java·开发语言·网络
寒山李白3 小时前
Java 依赖注入、控制反转与面向切面:面试深度解析
java·开发语言·面试·依赖注入·控制反转·面向切面
梓仁沐白3 小时前
【Kotlin】数字&字符串&数组&集合
android·开发语言·kotlin
Java菜鸟、3 小时前
设计模式(代理设计模式)
java·开发语言·设计模式