项目实战——C语言扫雷游戏
这是一款9*9的扫雷游戏
扫雷游戏
- 1.需求分析
- 2.程序框架设计
- 3.分函数实现
- 打印游戏菜单界面
- 游戏主逻辑函数
- 程序主入口
- 初始化游戏棋盘
- 随机布置地雷
- 显示当前棋盘状态
- 计算指定位置周围的地雷数量
- 玩家排雷主逻辑
- 4.分文件实现
- (1)test.c
- (2)game.c
- (3)game.h
- 5.运行效果展示
- 如果你觉得这篇文章对你有帮助
- 请给个三连支持一下哦
1.需求分析
我们需要编写一款C语言扫雷游戏,包含
9*9
自定义格式
等游戏格式
注意:(由于创作者水平有限)**!!**本款游戏未能实现UI设计前端知识
。
- 游戏可以通过菜单实现继续玩或者退出游戏
- 游戏的棋盘格式是
9x9
- 默认随机布置10颗雷
- 可以排查雷
–如果不是雷,则显示周边有几颗雷
–如果是雷,则炸死游戏结束
–把除了雷之外的所有非雷格子都找出来,则排雷成功,游戏结束
–游戏可以反复玩
-
甚至我们可以拓展多种玩法,例如:
11x11
、15x15
、计时模式
-
游戏界面
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.分文件实现
这一次我们分三个文件来讲解
(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);