:扫雷)
目录一、游戏功能预览二、设计思路与分析2.1 数据存储方案2.2 为什么要多开一圈2.3 两个数组各司其职三、文件结构四、代码实现逐块讲解4.1 头文件 game1.h4.2 主程序 test1.c4.3 游戏逻辑 game1.c五、总结一、游戏功能预览我们实现的是一个9×9的扫雷棋盘默认埋有10颗雷。玩家通过输入坐标行 列来翻开格子如果踩到雷 → 游戏结束被炸死如果没踩到雷 → 显示该位置周围 8 个格子中雷的总数如果把所有非雷格子全部翻开 → 排雷成功游戏胜利所有操作都在黑框控制台中进行界面清晰适合初学者理解数组和函数的综合运用。二、设计思路与分析2.1 数据存储方案我们需要存储两类信息雷的分布哪些格子有雷玩家已翻开格子的提示数字周围雷数最直观的想法是用一个9×9的二维数组用1表示雷0表示非雷。但这样做会带来两个问题当统计边缘格子周围雷数时可能会访问越界比如第 9 行第 9 列它的下方和右方都没有格子。如果把雷数数字和雷标记1/0混在一起显示时容易混淆。2.2 为什么要多开一圈为了避免越界我们故意把数组扩大成11×11但只在中间的9×9第 1~9 行第 1~9 列布置雷和进行游戏。外围的一圈第 0 行、第 10 行、第 0 列、第 10 列全部初始化为0无雷这样在统计边缘格子时即使访问到外围也不会越界而且外围没有雷不影响计数。2.3 两个数组各司其职我们使用两个独立的数组mine数组存放雷的分布0无雷1有雷——对玩家隐藏show数组存放玩家看到的界面初始全部为*翻开后显示数字——展示给玩家这样职责清晰互不干扰。三、文件结构按照模块化编程的习惯我们将代码拆分为三个文件文件作用game1.h头文件包含宏定义、函数声明game1.c游戏函数的实现初始化、打印、布置雷、排查雷test1.c主程序包含菜单和游戏流程控制四、代码实现逐块讲解4.1 头文件 game1.h#define_CRT_SECURE_NO_WARNINGS#includestdio.h#includestdlib.h#includetime.h#defineROW9// 实际棋盘行数#defineCOL9// 实际棋盘列数#defineROWSROW2// 数组行数多一圈#defineCOLSCOL2// 数组列数多一圈#defineEASY_COUNT10// 雷的数量// 函数声明voidInitBoard(charboard[ROWS][COLS],intr,intc,charset);voidDisplayBoard(charboard[ROWS][COLS],intr,intc);voidSetMine(charmine[ROWS][COLS],intr,intc);voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc);解释用#define定义常量方便日后修改棋盘大小或雷数。函数声明让其他.c文件可以调用这些函数。4.2 主程序 test1.c#define_CRT_SECURE_NO_WARNINGS#includegame1.hvoidmenu(){printf(------------------\n);printf(------1.play------\n);printf(------0.exit------\n);printf(------------------\n);}voidgame(){charmine[ROWS][COLS]{0};// 存放雷的数组charshow[ROWS][COLS]{0};// 展示给玩家的数组// 1. 初始化InitBoard(mine,ROWS,COLS,0);InitBoard(show,ROWS,COLS,*);// 2. 打印初始棋盘只显示 show// DisplayBoard(mine, ROW, COL); // 调试时可查看雷的位置DisplayBoard(show,ROW,COL);// 3. 布置雷SetMine(mine,ROW,COL);// 4. 排查雷FindMine(mine,show,ROW,COL);}intmain(){intinput0;srand((unsignedint)time(NULL));// 随机种子用于生成雷的位置do{menu();printf(请输入);scanf(%d,input);switch(input){case1:game();break;case0:printf(退出游戏\n);break;default:printf(输入错误请重新输入\n);break;}}while(input);return0;}流程显示菜单用户选择1开始游戏0退出。game()函数中定义两个二维数组。初始化mine全0show全*。打印show棋盘玩家看到的是星号。随机布置雷只布置在mine中。进入排雷循环。4.3 游戏逻辑 game1.c初始化棋盘voidInitBoard(charboard[ROWS][COLS],intr,intc,charset){for(inti0;ir;i){for(intj0;jc;j){board[i][j]set;}}}作用将整个数组包括外围一圈全部设置为同一个字符0或*。打印棋盘voidDisplayBoard(charboard[ROWS][COLS],intr,intc){// 打印列号0~9for(inti0;ic;i){printf(%d ,i);}printf(\n);// 打印行号 棋盘内容只打印 1~r 行1~c 列for(inti1;ir;i){printf(%d ,i);for(intj1;jc;j){printf(%c ,board[i][j]);}printf(\n);}printf(\n);}注意我们只打印中间的r行c列即1~ROW和1~COL外围一圈不打印。布置雷voidSetMine(charmine[ROWS][COLS],intr,intc){intcountEASY_COUNT;while(count){intxrand()%r1;// 随机行 1~rintyrand()%c1;// 随机列 1~cif(mine[x][y]!1)// 该位置还没有雷{mine[x][y]1;count--;}}}解释rand() % r 1生成1~r的随机数。避免重复布置如果已经是1就重新生成。统计周围雷数这是一个静态函数只在game1.c内部使用用于计算某个格子周围 8 个位置的雷数staticsize_tGetMineCount(charmine[ROWS][COLS],intx,inty){returnmine[x-1][y]mine[x-1][y-1]mine[x][y-1]mine[x1][y-1]mine[x1][y]mine[x1][y1]mine[x][y1]mine[x-1][y1]-8*0;}为什么减8 * 0因为mine中存储的是字符0或1它们的 ASCII 码值分别是 48 和 49。八个字符相加后需要减去 8 个0即8*48才能得到真正的数字雷数。返回值类型为size_t无符号整数。排查雷核心voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc){intx0;inty0;intwin0;// 已排查非雷的格子数while(winr*c-EASY_COUNT)// 当未翻开的非雷格子数 0{printf(请输入坐标);scanf(%d%d,x,y);// 1. 合法性检查if(x1xry1yc){// 2. 该位置是否已被翻开if(show[x][y]*){// 3. 判断是不是雷if(mine[x][y]0){size_tcountGetMineCount(mine,x,y);show[x][y](char)(count0);// 数字转字符DisplayBoard(show,ROW,COL);win;}else{printf(很遗憾你被炸死了\n);DisplayBoard(mine,ROW,COL);// 展示雷的位置break;}}else{printf(这里已经排查过请重新输入坐标\n);}}else{printf(请输入合法的坐标\n);}}// 如果 win 达到非雷总数则胜利if(winr*c-EASY_COUNT){printf(恭喜你排雷成功\n);DisplayBoard(mine,ROW,COL);}}逻辑玩家输入坐标先检查是否越界再检查是否重复翻开。如果该位置是0无雷则调用GetMineCount统计周围雷数并显示在show上。如果是雷1游戏结束。当win等于总格子数减雷数时表示所有非雷格子全部翻开胜利。五、总结整个代码约 150 行逻辑清晰非常适合作为 C 语言初学者的综合练习。如果你能完全理解并自己重写一遍那么数组、函数、指针后续扩展等知识点都会得到巩固但是这只是最基本的扫雷真正的扫雷还需要递归后续可以用各种智能体来改进成带界面的真正的扫雷。