当前位置: 首页 > ai >正文

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


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

扫雷游戏

  • 1.需求分析
  • 2.程序框架设计
  • 3.分函数实现
    • 打印游戏菜单界面
    • 游戏主逻辑函数
    • 程序主入口
    • 初始化游戏棋盘
    • 随机布置地雷
    • 显示当前棋盘状态
    • 计算指定位置周围的地雷数量
    • 玩家排雷主逻辑
  • 4.分文件实现
    • (1)test.c
    • (2)game.c
    • (3)game.h
  • 5.运行效果展示
  • 如果你觉得这篇文章对你有帮助
  • 请给个三连支持一下哦

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.分函数实现

打印游戏菜单界面

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;  // 程序正常退出
}


初始化游戏棋盘

// 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;  // 将当前格子设置为指定字符}}
}


随机布置地雷

// 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}}


显示当前棋盘状态

// 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: 雷区数组
// 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);  // 显示地雷位置}
}


4.分文件实现

这一次我们分三个文件来讲解

test.c
文件中写文件的测试逻辑
game.c
文件中写函数的实现
game.h
文件中写程序需要的数据类型和函数声明

(1)test.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

#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

#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.运行效果展示

在这里插入图片描述


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

请给个三连支持一下哦

在这里插入图片描述

http://www.xdnf.cn/news/11878.html

相关文章:

  • 【Spark征服之路-2.1-安装部署Spark(一)】
  • 【Windows开发】Windows 事件跟踪 (ETW)
  • 【conda配置深度学习环境】
  • 机器学习的数学基础:线性模型
  • HDFS分布式存储 zookeeper
  • 【Spec2MP:项目管理之项目成本管理】
  • 字节开源FlowGram:AI时代可视化工作流新利器
  • Promtail采集服务器本地日志存储到Loki
  • 《最长单调子序列》题集
  • 细说C语言将格式化输出到FILE *stream流的函数fprintf、_fprintf_I、fwprintf、_fwprintf_I
  • 轴承排列自动运行 定时器 外中断 PWM部分程序
  • 使用 systemctl 实现程序自启动与自动重启
  • RAG技术解析:实现高精度大语言模型知识增强
  • 【运维实战】Rsync将一台主Web服务器上的文件和目录同步到另一台备份服务器!
  • 数据库基础篇
  • 文件解读|检索页(附:新版知网国内刊检索页下载方法!)
  • cv::FileStorage用法
  • 多线程爬虫使用代理IP指南
  • Java面试题及答案整理( 2025年最新版,持续更新...)
  • PARADISE:用于新生儿缺氧缺血性脑病(HIE)疾病识别与分割的个性化和区域适应性方法|文献速递-深度学习医疗AI最新文献
  • GMS地下水数值模拟及溶质(包含反应性溶质)运移模拟技术
  • Python爬虫之数据提取
  • JavaScript性能优化实战技术
  • LeetCode-934. 最短的桥
  • 【uniapp开发】picker组件的使用
  • 二叉数-965.单值二叉数-力扣(LeetCode)
  • JavaWeb:前端工程化-Vue
  • 舵机在弹簧刀无人机中的作用是什么?
  • Linux 进程调度与管理:从内核管理到调度机制的深度解析
  • 【前端AI实践】泛谈AI在前端领域的应用场景