目录:
- 项目结构:文件划分
- 菜单和棋盘初始化:为什么用 11×11 存 9×9
- 打印棋盘:雷区与显示区
- 随机埋雷:生成
- 玩家操作:输入、判断、输赢
- 游戏主循环:菜单与流程控制
- 踩坑记录
- 完整代码
- 总结
1.项目结构
本项目采用多文件编程,将声明、实现和入口分离,让每个文件职责清晰,方便后续维护和调试。
| 文件 | 职责 |
|---|---|
game.h | 定义棋盘大小等宏,声明所有函数 |
game.c | 实现初始化、埋雷、打印、玩家操作等逻辑 |
test.c | 编写main()函数,调用菜单和游戏流程 |
2.菜单和棋盘初始化
打印菜单的代码如下:
test.c
voidmenu(){printf("- - - - - - - - - - - -\n");printf("- - - - 1.play - - - -\n");printf("- - - - 0.exit - - - -\n");printf("- - - - - - - - - - - -\n");}intmain(){intn=0;do{menu();printf("the number is: ");scanf("%d",&n);switch(n){case1://game();break;case0:printf("exit");break;default:printf("error");break;}}while(n);return0;}在这里我把玩游戏的区域设为 9×9 ,但对于整个棋盘需设置为 11×11 , 因为在后续计算每个格子周围 8 个方向的雷数时,边缘格子会访问到 [-1] 或 [9] 这样的越界下标。多出一圈"围墙"后,所有合法格子的周围 8 个方向都在数组范围内,不需要额外写边界判断。
game.h
#defineROWS11#defineCOLS11#defineROW9#defineCOL9voidInitboard(charboard[ROWS][COLS],intr,intc,charset);test.c
voidgame(){charmine[ROWS][COLS]={0};charshow[ROWS][COLS]={0};Initboard(show,ROWS,COLS,'?');Initboard(mine,ROWS,COLS,'0');}game.c
voidInitBoard(charboard[ROWS][COLS],intr,intc,charset){for(inti=0;i<r;i++){for(intj=0;j<c;j++){board[i][j]=set;}}}3. 打印棋盘:雷区与显示区
game.h
voidDislayBoard(charboard[ROWS][COLS],intr,intc);game.c
voidDisplayBoard(charboard[ROWS][COLS],intr,intc){inti=0;intj=0;for(j;j<=r;j++){printf("%d ",j);}printf("\n");for(i=1;i<=r;i++){printf("%d ",i);for(j=1;j<=c;j++){printf("%c ",board[i][j]);}printf("\n");}printf("\n");}test.c
voidgame(){DisplayBoard(show,ROW,COL);DisplayBoard(mine,ROW,COL);}4. 随机埋雷:生成
game.h
#include<stdlib.h>#include<time.h>#defineSET10voidSetmine(charboard[ROWS][COLS],intr,intc);game.c
voidSetmine(charboard[ROWS][COLS],intr,intc){intcount=SET;while(count){intx=rand()%r+1;inty=rand()%c+1;if(board[x][y]=='0'){board[x][y]='1';count--;}}}test.c
voidgame(){Setmine(mine,ROW,COL);}intmain(){srand((unsignedint)time(NULL));}5. 玩家操作:输入、判断、输赢
这个模块是玩游戏的重要部分,下面我讲拆成一下六个部分来讲解:
输入坐标
通过输入行号和列号来选择要翻开的格子,比如输入 2 3 表示翻开第 2 行第 3 列。合法性判断
输入的坐标不能越界并且不能是已经翻过的格子,这里需要判断合不合法,不合法时提示重新输入,合法才继续。踩雷判断
翻开格子后,需根据mine棋盘,看是 ‘1’ 还是 ‘0’ , 是 ‘1’ 结束游戏; 是 ‘0’ 继续游戏。计算周围的雷数
如果没踩雷,统计该格子周围 8 个方向有多少个 ‘1’,把结果写入 show 棋盘对应位置。
关于计算雷数的方法有多种,这里我介绍两个:
方法1:
returnboard[x+1][y]+board[x+1][y-1]+board[x+1][y+1]+board[x][y+1]+board[x][y-1]+board[x-1][y]+board[x-1][y+1]+board[x-1][y-1]-8*'0';思路:把 8 个方向的字符全部加起来,最后统一减去 8 * ‘0’,把 8 个字符都转成数字。
方法2:
for(i=-1;i<=1;i++){for(j=-1;j<=1;j++){count+=(mine[x+i][y+j]-'0');}}returncount;思路:用 i 和 j 从 -1 到 1 遍历,刚好覆盖周围 8 个方向加上自身。每个格子单独减 ‘0’ 转成数字后累加。
胜利判断
每次成功翻开一个格子后,检查是否所有非雷格子都已被翻开。如果是,则玩家获胜。循环控制
把以上步骤放在一个循环里,玩家每回合操作一次,直到踩雷(失败)或全部翻开(胜利)才退出循环。
结合以上思路,得到以下代码:
game.h
voidSearchmine(charshow[ROWS][COLS],charmine[ROWS][COLS],intr,intc);game.c
inttne_number_of_mine(charboard[ROWS][COLS],intx,inty){returnboard[x+1][y]+board[x+1][y-1]+board[x+1][y+1]+board[x][y+1]+board[x][y-1]+board[x-1][y]+board[x-1][y+1]+board[x-1][y-1]-8*'0';}voidSearchmine(charshow[ROWS][COLS],charmine[ROWS][COLS],intr,intc){intx,y;intamount=0;printf("site : ");scanf("%d %d",&x,&y);while(amount<r*c-SET){if(x>0&&x<=r&&y>0&&y<=c){if(show[x][y]=='?'){intcount=tne_number_of_mine(mine,x,y);show[x][y]='0'+count;DisplayBoard(show,ROW,COL);amount++;}else{printf("bomb! you dead\n");DisplayBoard(mine,ROW,COL);break;}}else{printf("the address is illegal\n");break;}}if(amount==r*c-SET){printf("congratulation! you make it\n");DisplayBoard(mine,ROW,COL);}}test.c
voidgame(){Searchmine(show,mine,ROW,COL);}6. 踩坑记录
再写代码时需注意这两个函数的顺序,SetMine(mine, ROW, COL); 在前,
DisplayBoard(mine, ROW, COL);在后。
SetMine(mine,ROW,COL);DisplayBoard(mine,ROW,COL);7. 完整代码
game.h
#include<stdio.h>#include<stdlib.h>#include<time.h>#defineROWS11#defineCOLS11#defineROW9#defineCOL9#defineSET10voidInitboard(charboard[ROWS][COLS],intr,intc,charset);voidDislayBoard(charboard[ROWS][COLS],intr,intc);voidSetmine(charboard[ROWS][COLS],intr,intc);voidSearchmine(charshow[ROWS][COLS],charmine[ROWS][COLS],intr,intc);game.c
#define_CRT_SECURE_NO_WARNINGS#include"game.h"voidInitboard(charboard[ROWS][COLS],intr,intc,charset){inti=0;intj=0;for(i=1;i<r;i++){for(j=1;j<c;j++){board[i][j]=set;}}}voidDisplayBoard(charboard[ROWS][COLS],intr,intc){inti=0;intj=0;for(j;j<=r;j++){printf("%d ",j);}printf("\n");for(i=1;i<=r;i++){printf("%d ",i);for(j=1;j<=c;j++){printf("%c ",board[i][j]);}printf("\n");}printf("\n");}voidSetmine(charboard[ROWS][COLS],intr,intc){intcount=SET;while(count){intx=rand()%r+1;inty=rand()%c+1;if(board[x][y]=='0'){board[x][y]='1';count--;}}}inttne_number_of_mine(charboard[ROWS][COLS],intx,inty){returnboard[x+1][y]+board[x+1][y-1]+board[x+1][y+1]+board[x][y+1]+board[x][y-1]+board[x-1][y]+board[x-1][y+1]+board[x-1][y-1]-8*'0';}voidSearchmine(charshow[ROWS][COLS],charmine[ROWS][COLS],intr,intc){intx,y;intamount=0;printf("site : ");scanf("%d %d",&x,&y);while(amount<r*c-SET){if(x>0&&x<=r&&y>0&&y<=c){if(show[x][y]=='?'){intcount=tne_number_of_mine(mine,x,y);show[x][y]='0'+count;DisplayBoard(show,ROW,COL);amount++;}else{printf("bomb! you dead\n");DisplayBoard(mine,ROW,COL);break;}}else{printf("the address is illegal\n");break;}}if(amount==r*c-SET){printf("congratulaton! you make it\n");DisplayBoard(mine,ROW,COL);}}test.c
#define_CRT_SECURE_NO_WARNINGS#include"game.h"voidmenu(){printf("- - - - - - - - - - - -\n");printf("- - - - 1.play - - - -\n");printf("- - - - 0.exit - - - -\n");printf("- - - - - - - - - - - -\n");}voidgame(){charmine[ROWS][COLS]={0};charshow[ROWS][COLS]={0};Initboard(show,ROWS,COLS,'?');Initboard(mine,ROWS,COLS,'0');DisplayBoard(show,ROW,COL);Setmine(mine,ROW,COL);DisplayBoard(mine,ROW,COL);Searchmine(show,mine,ROW,COL);}intmain(){intn=0;srand((unsignedint)time(NULL));do{menu();printf("the number is: ");scanf("%d",&n);switch(n){case1:game();break;case0:printf("exit");break;default:printf("error");break;}}while(n);return0;}8. 总结
本文用 C 语言实现了一个 9×9 的扫雷小游戏,核心要点如下:
- 多文件结构:
game.h声明、game.c实现、test.c入口,职责清晰。 - 11×11 存 9×9:多出一圈"围墙",避免统计周围雷数时数组越界。
- 随机埋雷:用
rand()在非雷位置生成 10 颗雷。 - 玩家操作:输入坐标 → 合法性判断 → 踩雷判断 → 统计周围雷数 → 胜利判断,循环直到输赢。
- 踩坑提醒:先
SetMine埋雷,再DisplayBoard打印,顺序不能反。