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 }

由以上三个文件就可以实现以下效果:

若有任何问题,欢迎批评和指正~

posted @ 2022-03-23 16:28  ttweixiao9999  阅读(213)  评论(0编辑  收藏  举报