目录
- 一、游戏功能预览
- 二、设计思路与分析
- 2.1 数据存储方案
- 2.2 为什么要多开一圈
- 2.3 两个数组各司其职
- 三、文件结构
- 四、代码实现(逐块讲解)
- 4.1 头文件 game1.h
- 4.2 主程序 test1.c
- 4.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#include<stdio.h>#include<stdlib.h>#include<time.h>#defineROW9// 实际棋盘行数#defineCOL9// 实际棋盘列数#defineROWSROW+2// 数组行数(多一圈)#defineCOLSCOL+2// 数组列数(多一圈)#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#include"game1.h"voidmenu(){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(){intinput=0;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全'0',show全'*'。 - 打印
show棋盘(玩家看到的是星号)。 - 随机布置雷(只布置在
mine中)。 - 进入排雷循环。
4.3 游戏逻辑 `game1.c`
初始化棋盘
voidInitBoard(charboard[ROWS][COLS],intr,intc,charset){for(inti=0;i<r;i++){for(intj=0;j<c;j++){board[i][j]=set;}}}作用:将整个数组(包括外围一圈)全部设置为同一个字符('0'或'*')。
打印棋盘
voidDisplayBoard(charboard[ROWS][COLS],intr,intc){// 打印列号(0~9)for(inti=0;i<=c;i++){printf("%d ",i);}printf("\n");// 打印行号 + 棋盘内容(只打印 1~r 行,1~c 列)for(inti=1;i<=r;i++){printf("%d ",i);for(intj=1;j<=c;j++){printf("%c ",board[i][j]);}printf("\n");}printf("\n");}注意:我们只打印中间的r行c列(即1~ROW和1~COL),外围一圈不打印。
布置雷
voidSetMine(charmine[ROWS][COLS],intr,intc){intcount=EASY_COUNT;while(count){intx=rand()%r+1;// 随机行 1~rinty=rand()%c+1;// 随机列 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[x+1][y-1]+mine[x+1][y]+mine[x+1][y+1]+mine[x][y+1]+mine[x-1][y+1]-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){intx=0;inty=0;intwin=0;// 已排查(非雷)的格子数while(win<r*c-EASY_COUNT)// 当未翻开的非雷格子数 > 0{printf("请输入坐标:");scanf("%d%d",&x,&y);// 1. 合法性检查if(x>=1&&x<=r&&y>=1&&y<=c){// 2. 该位置是否已被翻开if(show[x][y]=='*'){// 3. 判断是不是雷if(mine[x][y]=='0'){size_tcount=GetMineCount(mine,x,y);show[x][y]=(char)(count+'0');// 数字转字符DisplayBoard(show,ROW,COL);win++;}else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine,ROW,COL);// 展示雷的位置break;}}else{printf("这里已经排查过,请重新输入坐标\n");}}else{printf("请输入合法的坐标\n");}}// 如果 win 达到非雷总数,则胜利if(win==r*c-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}逻辑:
- 玩家输入坐标,先检查是否越界,再检查是否重复翻开。
- 如果该位置是
'0'(无雷),则调用GetMineCount统计周围雷数,并显示在show上。 - 如果是雷(
'1'),游戏结束。 - 当
win等于总格子数减雷数时,表示所有非雷格子全部翻开,胜利。
五、总结
整个代码约 150 行,逻辑清晰,非常适合作为 C 语言初学者的综合练习。如果你能完全理解并自己重写一遍,那么数组、函数、指针(后续扩展)等知识点都会得到巩固,但是这只是最基本的扫雷,真正的扫雷还需要递归,后续可以用各种智能体来改进成带界面的真正的扫雷。