公司动态

【C语言实战】扫雷游戏|多文件、双棋盘、11*11数组原理深度剖析

📅 2026/8/22 9:18:25
【C语言实战】扫雷游戏|多文件、双棋盘、11*11数组原理深度剖析
1. 项目整体介绍3个文件分工本项目采用多文件模块化开发一共3个文件各司其职不会把全部代码堆在main函数中。文件名作用说明game.h头文件1.引入头文件stdio.h、stdlib.h、time.h2.宏定义棋盘大小、地雷数量3.所有功能函数声明供其他.c文件调用4.#pragma once防止头文件重复包含game.c功能实现文件全部功能函数的具体实现代码初始化棋盘、打印棋盘、随机布雷、统计周围雷数、排雷游戏逻辑全部写在这里test.c主程序文件程序入口main函数打印游戏菜单提供选项1开始游戏 / 0退出游戏调用game()函数开启一局扫雷游戏模块化好处代码解耦(结构改变内里不变)。修改功能只改动对应文件阅读调试方便是C语言项目标准写法。2. 扫雷游戏完整运行效果文本模拟控制台截图️图1游戏初始界面*********************** ***** 1. play ***** ***** 0. exit ***** *********************** 请选择:1 --------扫雷游戏------- 0 1 2 3 4 5 6 7 8 9 1 * * * * * * * * * 2 * * * * * * * * * 3 * * * * * * * * * 4 * * * * * * * * * 5 * * * * * * * * * 6 * * * * * * * * * 7 * * * * * * * * * 8 * * * * * * * * * 9 * * * * * * * * * 请输入要排查的坐标:全部格子显示*地雷完全隐藏等待玩家输入坐标。️图2排雷过程界面--------扫雷游戏------- 0 1 2 3 4 5 6 7 8 9 1 0 0 0 0 1 * * * * 2 0 0 0 0 1 * * * * 3 * * * * * * * * * 4 * * * * * * * * * 5 * * * * * * * * * 6 * * * * * * * * * 7 * * * * * * * * * 8 * * * * * * * * * 9 * * * * * * * * * 请输入要排查的坐标:输入安全坐标之后翻开格子显示周围地雷数字。️图3踩雷失败界面很遗憾,你被炸死 --------扫雷游戏------- 0 1 2 3 4 5 6 7 8 9 1 0 0 0 0 0 0 1 0 0 2 0 0 0 0 0 0 0 0 1 3 0 0 0 0 0 0 0 0 0 4 0 0 1 1 1 0 0 0 0 5 0 0 0 0 0 1 0 0 0 6 0 0 0 0 0 0 0 0 0 7 0 0 1 1 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 1 1 0 0踩到地雷完整打印mine地雷棋盘展示全部地雷位置。✨CSDN上传真实截图操作编辑器工具栏点图片图标CtrlG上传你本地运行截图复制生成图片链接替换上面文本块即可。3. 为什么选择数组函数实现扫雷数组的优势在哪里扫雷本质逻辑不断接收玩家输入坐标存储每个格子状态有雷/无雷/周围雷数量✅二维数组优势二维数组完美模拟棋盘board[i][j]直接对应棋盘i行j列坐标一一对应逻辑直观。批量存储大量格子状态方便读取、修改每一格的数据。配合嵌套循环轻松遍历整张棋盘完成打印、统计地雷等操作。✅函数封装优势功能拆分初始化棋盘、打印棋盘、布雷、排雷各自封装独立函数。代码复用需要功能直接调用函数不用重复复制代码。逻辑解耦出现bug可以快速定位到对应函数。如果不使用数组9*9棋盘一共81个格子就要定义81个独立变量保存状态代码极度臃肿几乎无法维护。4. 游戏整体业务逻辑、分支流程flowchart TD A[启动程序] -- B[打印菜单 1.play 0.exit] B --|输入0| C[退出游戏程序结束] B --|输入1| D[调用game()开启一局游戏] B --|其他数字| B D -- D1[初始化mine棋盘为0] D1 -- D2[初始化show棋盘为*] D2 -- D3[mine棋盘随机布置10颗雷1] D3 -- D4[打印show棋盘给玩家看] D4 -- E[等待输入排查坐标x y] E --|坐标不在1~9| E E --|mine[x][y] 1 踩到雷| F[游戏失败打印全部地雷结束本局] E --|安全无雷| G[GetMineCount计算周围雷数] G -- H[更新show棋盘win计数1] H -- I{win 总安全格子?} I --|否| E I --|是| J[游戏胜利排雷成功]程序启动打印菜单1.play开始游戏0.exit退出用户输入选择进入三大分支分支①输入0退出循环程序结束分支②输入1调用game()开启一局扫雷游戏分支③输入其他数字提示输入错误回到菜单重新选择开启一局游戏内部流程创建mine、show两个二维数组初始化棋盘mine全部赋值字符0show全部赋值字符*在mine棋盘随机布置10颗地雷地雷位置赋值1打印show棋盘给玩家查看循环接收玩家输入排查坐标分支A坐标不在1~9范围 →坐标非法重新输入分支B当前坐标踩到雷mine[x][y] 1→游戏失败打印完整地雷棋盘结束本局分支C坐标安全无雷 →计算周围8格地雷数量更新show棋盘翻开格子win计数判断胜利翻开安全格子总数 总格子数 - 地雷数量游戏胜利。5. 核心重难点解析重点5.1 棋盘为什么是11×11而不是9×9数组越界问题️文本示意图11×11棋盘缓冲区原理0 1 2 ... 9 10 0 ┌────────────────┐ ---缓冲区永远不布雷全部0 1 │ * * * ... * │ 2 │ * * * ... * │ … │ │ 9 │ * * * ... * │ 10└────────────────┘ ---缓冲区永远不布雷全部0 ✅真正游戏有效区域行1~9列1~99*9 ✅第0行、0列、10行、10列充当安全缓冲区实际玩家操作的有效棋盘是中间9行9列。❗问题 当排查棋盘角落、边缘格子比如坐标(9,9)要统计该点周围8个格子地雷。 如果数组定义为9*9下标范围是0~8。访问x1、y1下标就变成10发生数组越界。数组越界属于非法内存访问会造成程序随机崩溃。✅解决方案数组向外扩大一圈定义11 × 11。 游戏只使用下标1~9行1~9列第0行、第0列、第10行、第10列作为缓冲区不布置地雷全部初始化为字符0。宏定义代码#define ROW 9 //实际游戏有效行 #define COL 9 //实际游戏有效列 #define ROWS ROW2 //9211 #define COLS COL2 //9211缓冲区全部是0统计雷数不会干扰计算访问边缘格子周围坐标不会越界。5.2 mine棋盘 和 show棋盘双棋盘设计只用一个数组会出现什么BUG️两张棋盘对比示意mine棋盘程序内部玩家看不见 存0无雷1代表地雷 show棋盘打印给玩家看 *未翻开1~8周围地雷数量mine[ROWS][COLS]地雷真实棋盘程序内部使用玩家看不到0该位置没有地雷1该位置存在地雷show[ROWS][COLS]展示棋盘打印输出给玩家看*格子尚未翻开0 ~ 8翻开后代表格子周围地雷数量❌只用一个数组的致命BUG 地雷标记1和「周围存在1颗雷」显示字符1完全一样。程序分不清这个1到底代表地雷还是统计出来的雷数逻辑直接错乱。✅解决思路两套数组分开mine保存地雷真实数据判断是否踩雷全部读取mine计算得到的周围雷数只写入show数组打印show给玩家玩家输入坐标要去mine排查地雷同时更新show做界面显示。5.3 字符0 和数字0的区别为什么数组选用char字符类型⚠️一定要区分0整型数字数值00字符ASCII码值等于48mine数组是char类型无雷存字符0有雷存字符1。为什么不用int数组存数字0、1 show棋盘最后需要打印数字字符给玩家看。char类型可以直接存放字符使用%c格式化输出。数字转字符核心公式字符 整数数字 0举例周围雷数count3 →show[x][y] count 0存入字符3。如果混用int和char打印输出会出现乱码。5.4 8方向统计周围地雷原理上下左右四个斜角️8个方向坐标示意图以(x,y)为中心点(x‑1,y‑1) (x‑1,y) (x‑1,y1) (x ,y‑1) (x,y) (x ,y1) (x1,y‑1) (x1,y) (x1,y1)玩家输入坐标(x,y)需要扫描周围一圈8个相邻坐标。GetMineCount函数直接累加8个位置字符减去8*0得到地雷整数数量。int GetMineCount(char mine[ROWS][COLS], int x, int y) { 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; }原理1的ASCII码比0大1相加后减去8倍0结果就是地雷总个数。6. 全部完整源代码VS编译器提示scanf报错需要在game.c最顶部加上#define _CRT_SECURE_NO_WARNINGS 1game.h#pragma once #include stdio.h #include stdlib.h #include time.h #define EASY_COUNT 10 //简单模式地雷数量 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 //初始化棋盘 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); //打印棋盘 void DisplayBoard(char board[ROWS][COLS], int row, int col); //布置地雷 void SetMine(char board[ROWS][COLS], int row, int col); //统计坐标周围8格地雷数量 int GetMineCount(char mine[ROWS][COLS], int x, int y); //排查雷游戏主逻辑 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);game.c#define _CRT_SECURE_NO_WARNINGS 1 #include game.h //初始化棋盘把棋盘所有位置赋值set字符 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; } } } //打印棋盘row col是实际9*9游戏大小 void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i 0; printf(--------扫雷游戏-------\n); //打印顶部列号 for (i 0; i col; i) { printf(%d , i); } printf(\n); for (i 1; i row; i) { printf(%d , i); //打印行号 int j 0; for (j 1; j col; j) { printf(%c , board[i][j]); } printf(\n); } } //随机布雷生成不重复的坐标放置地雷1 void SetMine(char board[ROWS][COLS], int row, int col) { int count EASY_COUNT; while (count) { int x rand() % row 1; int y rand() % col 1; //只有当前位置没有雷0才放雷防止同一个位置重复放雷 if (board[x][y] 0) { board[x][y] 1; count--; } } } //计算(x,y)周围8个格子地雷总数核心算法 int GetMineCount(char mine[ROWS][COLS], int x, int y) { 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; } //排雷主逻辑 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x 0; int y 0; //统计翻开安全格子的数量 int win 0; //总安全格子 row*col - EASY_COUNT while (win row * col - EASY_COUNT) { printf(请输入要排查的坐标:); scanf(%d %d, x, y); //判断坐标是否在合法1~9范围 if (x 1 x row y 1 y col) { //分支踩到地雷游戏失败 if (mine[x][y] 1) { printf(很遗憾,你被炸死\n); DisplayBoard(mine, ROW, COL); break; } else { //分支安全计算周围雷数更新show棋盘 int count GetMineCount(mine, x, y); show[x][y] count 0; DisplayBoard(show, ROW, COL); win; } } else { //分支坐标输入越界非法 printf(坐标输入错误请输入1~9的坐标\n); } } //循环结束如果win等于全部安全格子代表胜利 if (win row * col - EASY_COUNT) { printf(恭喜你排雷成功\n); DisplayBoard(mine, ROW, COL); } }test.c#include game.h //菜单打印 void menu() { printf(***********************\n); printf(***** 1. play *****\n); printf(***** 0. exit *****\n); printf(***********************\n); } //开启一局游戏 void game() { char mine[ROWS][COLS]; //存地雷真相 char show[ROWS][COLS]; //给玩家展示棋盘 InitBoard(mine, ROWS, COLS, 0); //地雷棋盘初始化为字符0 InitBoard(show, ROWS, COLS, *); //显示棋盘全部初始化为* DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); //布置地雷 FindMine(mine, show, ROW, COL); //开始排雷 } int main() { int input 0; srand((unsigned int)time(NULL)); //设置随机数种子 do { menu(); printf(请选择:); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(游戏退出\n); break; default: printf(输入错误请重新选择\n); break; } } while (input); return 0; }7. 每个函数逐行讲解InitBoard双重for循环遍历二维数组统一赋值棋盘每一格。同一套函数复用初始化mine和show两块棋盘。DisplayBoard打印棋盘打印顶部列索引循环打印每一行并带上行号控制台可视化棋盘。SetMinerand()生成1‑9随机坐标判断位置无雷赋值1布雷直到放满设定数量地雷。GetMineCount核心算法访问周围8方向坐标利用ASCII码运算直接得到周围地雷数量。FindMine游戏循环主体接收玩家输入三层分支判断非法坐标 /踩雷 /安全翻开格子win计数判断游戏胜利。menu打印菜单game()函数封装一局游戏完整流程main函数中do‑while循环支持重复游玩多局。8. 现有代码不足与后续扩展方向当前代码没有实现递归展开空白格子点开数字0不会自动展开一大片区域没有插旗子标记地雷功能难度固定可以扩展简易/中等/困难多难度模式自定义棋盘大小增加计时器统计玩家排雷耗时。