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

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

在这里插入图片描述


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

请给个三连支持一下哦

在这里插入图片描述

相关文章:

  • MySQL ACID 面试深度解析:原理、实现与面试实战
  • SARIMA时间序列分析:三大模型对比
  • Python网页数据抓取常用的库及方法介绍
  • SpringBoot+Mybatisplus配置多数据源(超级简单!!!!)
  • C# 一个解决方案放一个dll项目,一个dll测试项目 ,调试dll项目的源码
  • NLP学习路线图(二十三):长短期记忆网络(LSTM)
  • BERT vs Rasa 如何选择 Hugging Face 与 Rasa 的区别 模型和智能体的区别
  • 祝贺XC3576H通过银河麒麟桌面操作系统的兼容性测试,取得麒麟软件互认证证书
  • tensorflow image_dataset_from_directory 训练数据集构建
  • 力扣刷题Day 70:在排序数组中查找元素的第一个和最后一个位置(34)
  • python3GUI--车牌、车牌颜色识别可视化系统 By:PyQt5(详细介绍)
  • 【更新至2024年】1991-2024年上市公司信息披露质量KV指数数据(含原始数据+计算过程+结果)
  • Vue跨层级通信
  • c++中char *p指针指向字符串输出问题
  • 2D 写实交互数字人:多终端实时交互,引领数字化浪潮
  • 软件工程:如何在项目中把软件做好
  • 数学复习笔记 25
  • 神经符号AI的企业应用:结合符号推理与深度学习的混合智能
  • 虚拟机CentOS 7 网络连接显示“以太网(ens33,被拔出)“、有线已拔出、CentOS7不显示网络图标
  • Redis中的setIfAbsent方法和execute
  • 微网站的好处/如何将网站的关键词排名优化
  • 济南城市建设学院网站/网络推广业务
  • 嵊州门户网站/杭州网站排名提升
  • 旅行社网站的建设开题报告/网站可以自己建立吗
  • 零遁nas做网站/网络营销策划书封面
  • 在哪个网站上可以找兼职做/谷歌官方网站登录入口