C语言实现扫雷——递归展开周围没有雷(realize minesweeping by C - Recursively unfolded if there were no mines around)
继续实现 比特C语言视频 中第二个项目实例:扫雷 的升级版本,该版本实现了视频中代码的升级——当周围没有一个雷的时候,递归展开直至有雷
扫雷思路:
1. 初始化存放雷信息的棋盘mine;
2. 初始化存放玩家排查出雷的情况的棋盘show;
3. 随机布置雷的位置;
4. 显示棋盘;
5. 玩家输入坐标排查雷。
(1)主代码:test.c 源文件,主要写程序的主要架构
1 #define _CRT_SECURE_NO_WARNINGS 1
2 #include "game.h"
3
4 void menu() {
5 printf("*********************************\n");
6 printf("******** 1. play **********\n");
7 printf("******** 0. exit **********\n");
8 printf("*********************************\n");
9 }
10
11 void game() {
12 // 雷的存储信息
13 // 1. 存放布置好的雷的信息
14 char mine[ROWS][COLS] = { 0 };
15 // 2. 存放排查出的雷的信息
16 char show[ROWS][COLS] = { 0 };
17 // 初始化
18 InitBoard(mine, ROWS, COLS, '0');
19 InitBoard(show, ROWS, COLS, UserSee);
20 // 打印棋盘
21 DisplayBoard(show, ROW, COL);
22 // 布置雷
23 SetMine(mine, ROW, COL);
24 DisplayBoard(mine, ROW, COL);
25 // 扫雷
26 FindMine(mine, show, ROW, COL);
27 }
28
29 void test() {
30 int input = 0;
31 srand((unsigned int)time(NULL));
32 do
33 {
34 menu();
35 printf("请选择: > ");
36 scanf("%d", &input);
37 switch (input)
38 {
39 case 1:
40 game();
41 break;
42 case 0:
43 printf("退出游戏 \n");
44 break;
45 default:
46 printf("选择错误,重新选择!\n");
47 break;
48 }
49 } while (input);
50 }
51
52 int main() {
53 test();
54 return 0;
55 }
(2)game.h 头文件:主要存放和game.c相关的参数以及函数声明
1 #pragma once 2 #include <stdio.h> 3 #include <stdlib.h> // 引用srand和rand函数需要 4 #include <time.h> 5 6 #define ROW 9 // 棋盘的行数 7 #define COL 9 // 棋盘的列数 8 9 #define ROWS ROW+2 // 扩展后棋盘的行数(定义这个是为了计算周围8个位置的时候不出边) 10 #define COLS COL+2 // 扩展后棋盘的列数(定义这个是为了计算周围8个位置的时候不出边) 11 12 #define EASY_COUNT 2 // 雷的个数 13 #define UserSee '*' // 用户玩游戏的时候可以操作的位置符号 14 15 // 初始化棋盘 16 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); 17 // 显示棋盘 18 void DisplayBoard(char board[ROWS][COLS], int row, int col); 19 // 随机设置雷 20 void SetMine(char board[ROWS][COLS], int row, int col); 21 // 找雷 22 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); 23 // 判断游戏是否结束 24 int if_win(char show[ROWS][COLS], int row, int col);
(3)game.c 源文件:主要存放 game() 函数需要调用到的函数
帮助理解代码的知识 —— 图像的八邻域图
1 #define _CRT_SECURE_NO_WARNINGS 1
2 #include "game.h"
3
4 // 初始化棋盘信息,把数组中所有值都赋值set。这时传入的要是扩大的rows和cols(初始化需要连扩大的部分一起初始化)
5 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) {
6 int i = 0;
7 int j = 0;
8 for (i = 0;i < rows;i++) {
9 for (j = 0;j < cols;j++) {
10 board[i][j] = set;
11 }
12 }
13 }
14
15 // 显示棋盘,这时要传入的是正常的row和col(因为显示用的是正常的尺寸)
16 void DisplayBoard(char board[ROWS][COLS], int row, int col) {
17 int i = 0;
18 int j = 0;
19 printf("===================================== \n");
20 printf("==== **** =============== **** ==== \n");
21 printf("===================================== \n");
22 printf("\033[33m共有 %d 个雷\n\033[0m", EASY_COUNT);
23 // 打印列号
24 for (i = 0;i <= col;i++) {
25 printf("\033[36m%d\033[0m ", i);
26 }
27 printf("\n");
28 // 打印行号和每行数据
29 for (i = 1;i <= row;i++) {
30 printf("\033[36m%d\033[0m ", i);
31 for (j = 1;j <= col;j++) {
32 printf("%c ", board[i][j]);
33 }
34 printf("\n");
35 }
36 }
37
38 // 把雷通过随机的方式布置在棋盘上,因为布雷的地方是正常的尺寸,不能把雷布在超出正常尺寸的地方,否则玩家看不到
39 void SetMine(char board[ROWS][COLS], int row, int col) {
40 // 指定产生EASY_COUNT个雷,并且位置不重复
41 int count = EASY_COUNT;
42 while (count)
43 {
44 int x = rand() % row + 1; // rand()%row产生的数值是0 ~ row-1, +1之后就可以产生1 ~ row
45 int y = rand() % col + 1; // rand()%col产生的数值是0 ~ col-1, +1之后就可以产生1 ~ col
46 if (board[x][y] == '0') {
47 board[x][y] = '1';
48 count--;
49 }
50 }
51 }
52
53 // 思路:根据ASCII表可以知道,'0'-'0'= 0(即符号0的ASCII-符号0的ASCII=数字0)
54 // 根据ASCII表可以知道,'3' - '0' = 3(即符号3的ASCII - 符号0的ASCII = 数字3)
55 // 所以这里使用这8个数的ASCII总和减去8个'0'ASCII总和的方法来统计总的雷数,可以保证返回类型是整型
56 int get_mine_count(char mine[ROWS][COLS], int x, int y) {
57 return mine[x - 1][y] +
58 mine[x - 1][y - 1] +
59 mine[x][y - 1] +
60 mine[x + 1][y - 1] +
61 mine[x + 1][y] +
62 mine[x + 1][y + 1] +
63 mine[x][y + 1] +
64 mine[x - 1][y + 1] - 8 * '0';
65 }
66
67 /*
68 递归的两个必要条件:
69 1. 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
70 2. 每次递归调用之后越来越接近这个限制条件。
71 */
72 // 当玩家点到x,y坐标并且该坐标不是雷,那么就判断该位置是否是可以展开的0的,如果是则通过递归的方法扩展展开,直至没有碰到0为止
73 // 否则就直接展现数字。写递归一定要注意,一定要给出递归终止的条件。
74 void get_mine_count_recursion(char board_record[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
75 // 如果这个位置的值不是0且等于UserSee(即说明没有显示过),那么show中相应位置就显示它在board_record中的数
76 if (board_record[x][y] != '0') {
77 if (show[x][y] == UserSee) {
78 show[x][y] = board_record[x][y];
79 }
80 }
81 else {
82 // 如果是0,那么show中相应位置先显示0,然后再判断四周8个位置情况
83 // 注意,每次的判断一定要加上判断该位置是否等于UserSee(即已经被展开过),如果没有加这条判断,只是在一直判断是否坐标合法,
84 // 是否等于0,那么就会陷入死循环中,因为如果四周8个位置已经展开了,那么就算碰到0也不需要再检测
85 show[x][y] = '0';
86 // 正上方:检测正上方坐标是否合理以及是否没有被检测过,是的或那么就判断该位置是否为'0',
87 // 是的话就继续展开这个正上方这个数的8个位置,否则就直接填上数字
88 if (x - 1 >= 1 && x - 1 <= ROW && y >= 1 && y <= COL && show[x - 1][y] == UserSee) {
89 if (board_record[x - 1][y] == '0') {
90 show[x - 1][y] = '0';
91 get_mine_count_recursion(board_record, show, x - 1, y);
92 }
93 else {
94 show[x - 1][y] = board_record[x - 1][y];
95 }
96 }
97 // 左上角
98 if (x - 1 >= 1 && x - 1 <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x - 1][y - 1] == UserSee) {
99 if (board_record[x - 1][y - 1] == '0') {
100 show[x - 1][y - 1] = '0';
101 get_mine_count_recursion(board_record, show, x - 1, y - 1);
102 }
103 else {
104 show[x - 1][y - 1] = board_record[x - 1][y - 1];
105 }
106 }
107 // 正左方
108 if (x >= 1 && x <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x][y - 1] == UserSee) {
109 if (board_record[x][y - 1] == '0') {
110 show[x][y - 1] = '0';
111 get_mine_count_recursion(board_record, show, x, y - 1);
112 }
113 else {
114 show[x][y - 1] = board_record[x][y - 1];
115 }
116 }
117 // 左下角
118 if (x + 1 >= 1 && x + 1 <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x + 1][y - 1] == UserSee) {
119 if (board_record[x + 1][y - 1] == '0') {
120 show[x + 1][y - 1] = '0';
121 get_mine_count_recursion(board_record, show, x + 1, y - 1);
122 }
123 else {
124 show[x + 1][y - 1] = board_record[x + 1][y - 1];
125 }
126 }
127 // 正下方
128 if (x + 1 >= 1 && x + 1 <= ROW && y >= 1 && y <= COL && show[x + 1][y] == UserSee) {
129 if (board_record[x + 1][y] == '0') {
130 show[x + 1][y] = '0';
131 get_mine_count_recursion(board_record, show, x + 1, y);
132 }
133 else {
134 show[x + 1][y] = board_record[x + 1][y];
135 }
136 }
137 // 右下角
138 if (x + 1 >= 1 && x + 1 <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x + 1][y + 1] == UserSee) {
139 if (board_record[x + 1][y + 1] == '0') {
140 show[x + 1][y + 1] = '0';
141 get_mine_count_recursion(board_record, show, x + 1, y + 1);
142 }
143 else {
144 show[x + 1][y + 1] = board_record[x + 1][y + 1];
145 }
146 }
147 // 正右方
148 if (x >= 1 && x <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x][y + 1] == UserSee) {
149 if (board_record[x][y + 1] == '0') {
150 show[x][y + 1] = '0';
151 get_mine_count_recursion(board_record, show, x, y + 1);
152 }
153 else {
154 show[x][y + 1] = board_record[x][y + 1];
155 }
156 }
157 // 右上角
158 if (x - 1 >= 1 && x - 1 <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x - 1][y + 1] == UserSee) {
159 if (board_record[x - 1][y + 1] == '0') {
160 show[x - 1][y + 1] = '0';
161 get_mine_count_recursion(board_record, show, x - 1, y + 1);
162 }
163 else {
164 show[x - 1][y + 1] = board_record[x - 1][y + 1];
165 }
166 }
167
168 }
169 }
170
171 // 为了判断游戏是否结束,需要统计show中的UserSee的数量,当show中的UserSee的数量等于雷数时,就说明游戏结束,玩家赢
172 int if_win(char show[ROWS][COLS], int row, int col) {
173 int i = 0;
174 int j = 0;
175 int count = 0;
176 for (i = 1;i <= row;i++) {
177 for (j = 1;j <= col;j++) {
178 if (show[i][j] == UserSee) {
179 count++;
180 }
181 }
182 }
183 return count;
184 }
185
186 // 玩家寻找雷
187 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
188 int i = 0;
189 int j = 0;
190 // 先记录下每个在正常范围内的棋子周围含有的雷数
191 char board_record[ROWS][COLS] = { '0' };
192 for (i = 1;i <= row;i++) {
193 for (j = 1;j <= col;j++) {
194 if (mine[i][j] != '1') {
195 board_record[i][j] = get_mine_count(mine, i, j) + '0'; // 数字变成字符
196 }
197 else {
198 board_record[i][j] = UserSee;
199 }
200 }
201 }
202 DisplayBoard(board_record, row, col);
203 int x = 0;
204 int y = 0;
205 int count = EASY_COUNT + 1;
206 while (count != EASY_COUNT) {
207 printf("请输入排查雷的坐标: > ");
208 scanf("%d%d", &x, &y);
209 if (x >= 1 && x <= row && y >= 1 && y <= col) {
210 // 坐标合法
211 // 1. 踩雷
212 if (mine[x][y] == '1') {
213 printf("很遗憾,你被炸死了\n");
214 DisplayBoard(mine, row, col);
215 break;
216 }
217 else // 不是雷
218 {
219 // 递归计算x,y坐标周围有几个雷
220 get_mine_count_recursion(board_record, show, x, y);
221 DisplayBoard(show, row, col);
222 }
223 // 判断玩家是否赢,或游戏是否结束
224 count = if_win(show, ROW, COL);
225 printf("\n count=%d \n", count);
226 }
227 else {
228 printf("输入坐标非法,请重新输入!\n");
229 }
230 }
231 // 如果UserSee的数量等于雷数,那么就说明玩家赢了,游戏结束
232 if (count == EASY_COUNT) {
233 printf("恭喜你,排雷成功 \n");
234 DisplayBoard(mine, row, col);
235 }
236 }
由以上三个文件就可以实现以下效果:
若有任何问题,欢迎批评和指正~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?