飞道的博客

室友说你怎么才学到数组,然后我用C语言做了个扫雷(递归展开+难度选择+优化打印)

306人阅读  评论(0)

扫雷

创作不易,还请观众老爷耐心看完儿! 

目录

扫雷

1.扫雷框架

2.初始化棋盘

3.打印棋盘

格式1:

格式2:

4.布置雷

5.排查雷

5.1排查雷的功能

5.2递归展开功能bool_mine

5.3计算该位置的雷的个数

6.扫雷的全部代码

7.作者试玩环节



1.扫雷框架

2.初始化棋盘

        这是用户看到的棋盘,9*9大小,需要自己去排查雷

为了程序员更好地去控制扫雷,我们需要两个数组

  1. 程序员看的雷区数组 -- 里面存放着雷的位置 -- 11*11大小
  2. 用户看的展示数组 -- 全是*号,需要用户去排查 -- 11*11大小

使这两个数组一样大的原因:是为了使我们后面设计的接口函数更加兼容,即使展示的数组本质是11*11大小,但是我们打印的时候打印9*9就可以了

        为了可以提高代码可维护性和兼容性,我们将数组的行数和列数,使用来替换


  
  1. #define ROWS 11
  2. #define COLS 11
  3. #define ROW 9
  4. #define COL 9

  
  1. void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
  2. {
  3. for ( int i = 0; i < rows; ++i)
  4. {
  5. for ( int j = 0; j < cols; ++j)
  6. {
  7. board[i][j] = set;
  8. }
  9. }
  10. }

        我们将如果初始化的内容以字符的形式作为参数传进去,上述代码就可以很好地解决了兼容性,这一个函数可以解决雷区棋盘和展示用户的棋盘的初始化


3.打印棋盘

         打印棋盘大家可以充分发挥自己的艺术细胞,根据自己的喜好去设置棋盘的打印格式。下面博主提供两种。

格式1:


  
  1. void print_mineline(int row)//打印分割线
  2. {
  3. for ( int i = 0; i < row; ++i)
  4. {
  5. if (i == row / 2)
  6. {
  7. printf( "扫雷");
  8. }
  9. printf( "==");
  10. }
  11. printf( "\n");
  12. }
  13. void DisplayBoard(char board[ROWS][COLS], int row, int col)
  14. {
  15. print_mineline(row); //先打印一行分割线
  16. //打印列号
  17. for ( int j = 0; j <= col; ++j)
  18. {
  19. if ( 0 == j) //将列对齐
  20. {
  21. printf( " ");
  22. continue;
  23. }
  24. printf( "%d ", j);
  25. }
  26. printf( "\n");
  27. for ( int i = 1; i <= row; ++i)
  28. {
  29. printf( "%d ", i);
  30. for ( int j = 1; j <= col; ++j)
  31. {
  32. printf( "%c ", board[i][j]);
  33. }
  34. printf( "\n");
  35. }
  36. print_mineline(row); //最后再打印一行分割线
  37. printf( "\n");
  38. }

效果:

格式2:


  
  1. void DisplayBoard(char board[ROWS][COLS], int row, int col)
  2. {
  3. //打印列号
  4. for ( int j = 0; j <= col; ++j)
  5. {
  6. if (j == 0)
  7. {
  8. printf( " ");
  9. continue;
  10. }
  11. printf( " %d ", j);
  12. }
  13. printf( "\n\n");
  14. for ( int i = 1; i <= row; ++i)
  15. {
  16. //打印列号
  17. //1.打印第一部分
  18. printf( " %d ", i);
  19. for ( int j = 1; j <= col; ++j)
  20. {
  21. printf ( " %c ", board[i][j]);
  22. if (j <= col -1)
  23. printf( "|");
  24. }
  25. printf( "\n");
  26. //2.打印第二部分
  27. if (i <= row - 1)
  28. {
  29. printf( " ");
  30. for ( int j = 1; j <= col; ++j)
  31. {
  32. printf( "---");
  33. if (j <= col - 1)
  34. printf( "+");
  35. }
  36. }
  37. printf( "\n");
  38. }
  39. }

效果:

         但是格式2的缺点是如果棋盘是9*9以上大小,那么存在一些对齐问题,当然厉害的同学可以改善一下。这是提供的两个思路


4.布置雷

根据雷的个数n,随机布置n个雷,雷的个数可以根据用户的选择来定,例如

1.简单 -- 10个雷

2.普通 -- 20个雷

3.困难 -- 40个雷

4.疯狂 -- 80个雷

当然以上的布置雷都是限制在了9*9的棋盘中,大家也可以根据难度设计棋盘的大小,尺寸

为了代码的可维护性以及可读性,我们将不同难度下的雷的个数也使用宏来替换,以及可以使用枚举来帮助我们实现难度选择


  
  1. //雷的个数
  2. #define COUNT_EASY 10
  3. #define COUNT_ORD 20
  4. #define COUNT_DIF 40
  5. #define COUNT_FRE 80
  6. //难度等级
  7. enum degree
  8. {
  9. EASY = 1, //简单
  10. ORD, //普通
  11. DIF, //困难
  12. FRE //疯狂
  13. };

  
  1. void menu_degree()//难度选择菜单 -- 跟我们的枚举常量值一致
  2. {
  3. printf( "==========1.简单==========\n");
  4. printf( "==========2.普通==========\n");
  5. printf( "==========3.困难==========\n");
  6. printf( "==========4.疯狂==========\n");
  7. }
  8. int SetMine(char board[ROWS][COLS], int row, int col)
  9. {
  10. int count = 0; //雷的个数
  11. system( "cls"); //我们游戏开始部分肯定会要菜单,所以这里使用一个清屏功能
  12. int input = 0;
  13. do
  14. {
  15. menu_degree();
  16. printf( "请选择扫雷难度\n");
  17. scanf( "%d", &input);
  18. switch (input)
  19. {
  20. case EASY:
  21. count = COUNT_EASY;
  22. break;
  23. case ORD:
  24. count = COUNT_ORD;
  25. break;
  26. case DIF:
  27. count = COUNT_DIF;
  28. break;
  29. case FRE:
  30. count = COUNT_FRE;
  31. break;
  32. default:
  33. printf( "输入错误,请重新输入");
  34. break;
  35. }
  36. } while (input != EASY && input != ORD &&
  37. input != DIF && input != FRE);
  38. int _count = count; //保存count的值
  39. while (count)
  40. {
  41. //随机生成十个雷,放在中间的9*9中
  42. int x = rand() % row + 1;
  43. int y = rand() % col + 1;
  44. //判断该位置是否已经布置了雷
  45. if (board[x][y] == '0') //没布置
  46. {
  47. board[x][y] = '1';
  48. --count;
  49. }
  50. }
  51. return _count; //这里我们将布置雷的个数返回了,之后要用
  52. }

解释:布置雷只需要在9*9中布置,所以咱们传参传的是row,col而不是rows,cols


5.排查雷

游戏的核心部分:

1.玩家输入需要排查雷的坐标

2.判断是否有效

3.根据雷区判断,如果是雷,游戏结束

4.如果不是雷,则在显示区棋盘中赋上雷的个数

5.如果雷的个数是0,还是考虑将周围8个位置进行递归展开

5.1排查雷的功能

在排查雷的函数中,mine -- 雷区棋盘,show -- 展示区棋盘,是需要配合使用的。例如判断一个位置是否是雷,我们要去雷区棋盘找,而即使修正展示区的位置内容,我们需要去show棋盘中修改,并且即使打印出来给用户观看。

判断输赢:

  •  如果输入的坐标位置是雷,则输掉游戏
  •  如果不是雷的位置都被排查完了,那么玩家赢得游戏胜利

  
  1. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
  2. {
  3. //雷的个数通过SetMine的返回值可以得到,然后我们通过参数的形式,传给FineMine
  4. //因为我们的版本雷的个数不固定
  5. int chance = row*col - count; //记录不是雷的个数
  6. while (chance)
  7. {
  8. system( "cls");
  9. DisplayBoard(show, row, col); //展示show棋盘
  10. printf( "请输入需要排查雷的坐标\n");
  11. int x = 0, y = 0;
  12. scanf( "%d %d", &x, &y);
  13. //检查坐标的合法性
  14. if (x >= 1 && x <= row && y >= 1 && y <= col)
  15. {
  16. //不是雷--显示雷的个数
  17. if (mine[x][y] != '1') //没点到雷
  18. {
  19. //递归展开该位置
  20. boom_mine(mine, show, x, y, &chance); //将该位置周围的展开,传坐标
  21. //展开雷chance是会减少的,使用址传递,在函数里面修改chance
  22. }
  23. //如何是雷--爆炸
  24. else
  25. {
  26. printf( "很遗憾,你被炸死了\n");
  27. DisplayBoard(mine, row, col);
  28. break;
  29. }
  30. }
  31. else
  32. {
  33. printf( "坐标不合法\n");
  34. }
  35. }
  36. //判断是否赢了-- 将所有不是雷的地方都排查出来了
  37. if (chance == 0)
  38. {
  39. printf( "排雷成功,游戏胜利\n");
  40. }
  41. Sleep( 2000); //停顿两秒看下结果
  42. }

5.2递归展开功能bool_mine

查出该位置周围8个位置的雷的个数

  1. 0个以上的雷  --  将这个show棋盘对应的位置赋值为该数字字符
  2. 0个雷 -- 先这个show棋盘对应的位置赋值为该数字字符,再对该位置周围8个位置进行递归展开

防止出现死递归,我们思考一下递归限制条件:

  1. 如果该位置越界,那么不做任何处理
  2. 如果该位置已经被排查过了就不做任何处理

        如果你的代码出现了错误,调式一看发现有个stack overflow这个报错,原因之一就是你写出死递归了


  
  1. void boom_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pchance)
  2. {
  3. //递归限制条件
  4. //1.如果越界了,直接返回
  5. if (x == 0 || x == ROW+ 1 || y == 0 || y == COL+ 1)
  6. {
  7. return;
  8. }
  9. //2.如果该位置已经展开,直接返回
  10. if (show[x][y] != '*')
  11. {
  12. return;
  13. }
  14. //3.剩下的就是需要展开的情况
  15. int count = get_mine(mine, x, y); //计算该位置的雷的个数
  16. if (count) //如果雷的个数不为0,直接展开
  17. {
  18. show[x][y] = count + '0';
  19. --(*pchance); //不是雷的个数也减一
  20. return; //这里需要返回,不然无限递归下去了
  21. }
  22. else //如果雷的个数是0
  23. {
  24. //对周围八个位置进行展开操作
  25. show[x][y] = count + '0';
  26. --(*pchance);
  27. boom_mine(mine, show, x - 1, y - 1, pchance);
  28. boom_mine(mine, show, x - 1, y, pchance);
  29. boom_mine(mine, show, x - 1, y + 1, pchance);
  30. boom_mine(mine, show, x, y - 1, pchance);
  31. boom_mine(mine, show, x, y + 1, pchance);
  32. boom_mine(mine, show, x + 1, y - 1, pchance);
  33. boom_mine(mine, show, x + 1, y, pchance);
  34. boom_mine(mine, show, x + 1, y + 1, pchance);
  35. }
  36. }

5.3计算该位置的雷的个数

        这里就很好地体现了我们在雷区棋盘中放字符0字符1的好处。

        我们将    周围8个位置的字符加进来   -   8*字符0   =   雷的个数


  
  1. int get_mine(char mine[ROWS][COLS], int x, int y)
  2. {
  3. //周围八个位置的内容 - 8 * '0' == 周围雷的个数
  4. return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
  5. + mine[x][y - 1] + mine[x][y + 1]
  6. + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
  7. }

 


6.扫雷的全部代码

        上述的操作是讲解扫雷的核心函数的实现,下面我们进行分块编程,并且设计一些菜单,将他们整合在一起。

game.h


  
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<time.h>
  6. #include<windows.h>
  7. //操作棋盘的时候用的
  8. #define ROWS 11
  9. #define COLS 11
  10. //用户看到的
  11. #define ROW 9
  12. #define COL 9
  13. //雷的个数
  14. #define COUNT_EASY 10
  15. #define COUNT_ORD 20
  16. #define COUNT_DIF 40
  17. #define COUNT_FRE 80
  18. enum degree
  19. {
  20. EASY = 1, //简单
  21. ORD, //普通
  22. DIF, //困难
  23. FRE //疯狂
  24. };
  25. //初始化棋盘
  26. void InitBoard(char board[ROWS][COLS],int rows,int cols,char set);
  27. //打印棋盘
  28. void DisplayBoard(char board[ROWS][COLS], int row, int col);
  29. //布置雷
  30. int SetMine(char board[ROWS][COLS], int row, int col);
  31. //排查类
  32. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count);

game.c

        打印的两个方式都放在里面了 


  
  1. #include"game.h"
  2. //初始化棋盘
  3. void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
  4. {
  5. for ( int i = 0; i < rows; ++i)
  6. {
  7. for ( int j = 0; j < cols; ++j)
  8. {
  9. board[i][j] = set;
  10. }
  11. }
  12. }
  13. //打印棋盘--将行列号也打印出来
  14. //void print_mineline(int row)
  15. //{
  16. // for (int i = 0; i < row; ++i)
  17. // {
  18. // if (i == row / 2)
  19. // {
  20. // printf("扫雷");
  21. // }
  22. // printf("==");
  23. // }
  24. // printf("\n");
  25. //}
  26. //void DisplayBoard(char board[ROWS][COLS], int row, int col)
  27. //{
  28. // print_mineline(row);
  29. // //打印列号
  30. // for (int j = 0; j <= col; ++j)
  31. // {
  32. // if (0 == j)//将列对齐
  33. // {
  34. // printf(" ");
  35. // continue;
  36. // }
  37. // printf("%d ", j);
  38. // }
  39. // printf("\n");
  40. //
  41. //
  42. // for (int i = 1; i <= row; ++i)
  43. // {
  44. // printf("%d ", i);
  45. // for (int j = 1; j <= col; ++j)
  46. // {
  47. // printf("%c ", board[i][j]);
  48. // }
  49. // printf("\n");
  50. // }
  51. // print_mineline(row);
  52. // printf("\n");
  53. //}
  54. void DisplayBoard(char board[ROWS][COLS], int row, int col)
  55. {
  56. //打印列号
  57. for ( int j = 0; j <= col; ++j)
  58. {
  59. if (j == 0)
  60. {
  61. printf( " ");
  62. continue;
  63. }
  64. printf( " %d ", j);
  65. }
  66. printf( "\n\n");
  67. for ( int i = 1; i <= row; ++i)
  68. {
  69. //打印列号
  70. //1.打印第一部分
  71. printf( " %d ", i);
  72. for ( int j = 1; j <= col; ++j)
  73. {
  74. printf ( " %c ", board[i][j]);
  75. if (j <= col -1)
  76. printf( "|");
  77. }
  78. printf( "\n");
  79. //2.打印第二部分
  80. if (i <= row - 1)
  81. {
  82. printf( " ");
  83. for ( int j = 1; j <= col; ++j)
  84. {
  85. printf( "---");
  86. if (j <= col - 1)
  87. printf( "+");
  88. }
  89. }
  90. printf( "\n");
  91. }
  92. }
  93. void menu_degree()
  94. {
  95. printf( "==========1.简单==========\n");
  96. printf( "==========2.普通==========\n");
  97. printf( "==========3.困难==========\n");
  98. printf( "==========4.疯狂==========\n");
  99. }
  100. //布置雷
  101. //mine中,是雷就布置1,不是雷就是0
  102. //布置雷的函数返回值 -- 返回雷的个数
  103. int SetMine(char board[ROWS][COLS], int row, int col)
  104. {
  105. int count = 0; //雷的个数
  106. system( "cls");
  107. int input = 0;
  108. do
  109. {
  110. menu_degree();
  111. printf( "请选择扫雷难度\n");
  112. scanf( "%d", &input);
  113. switch (input)
  114. {
  115. case EASY:
  116. count = COUNT_EASY;
  117. break;
  118. case ORD:
  119. count = COUNT_ORD;
  120. break;
  121. case DIF:
  122. count = COUNT_DIF;
  123. break;
  124. case FRE:
  125. count = COUNT_FRE;
  126. break;
  127. default:
  128. printf( "输入错误,请重新输入");
  129. break;
  130. }
  131. } while (input != EASY && input != ORD &&
  132. input != DIF && input != FRE);
  133. int _count = count; //保存count的值
  134. while (count)
  135. {
  136. //随机生成十个雷,放在中间的9*9中
  137. int x = rand() % row + 1;
  138. int y = rand() % col + 1;
  139. //判断该位置是否已经布置了雷
  140. if (board[x][y] == '0') //没布置
  141. {
  142. board[x][y] = '1';
  143. --count;
  144. }
  145. //布置了
  146. }
  147. return _count;
  148. }
  149. //设计排查雷 -- 展开功能 -- 返回展开的雷的个数
  150. //排查雷
  151. int get_mine(char mine[ROWS][COLS], int x, int y)
  152. {
  153. //周围八个位置的内容 - 8 * '0' == 周围雷的个数
  154. return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
  155. + mine[x][y - 1] + mine[x][y + 1]
  156. + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
  157. }
  158. void boom_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pchance)
  159. {
  160. //递归限制条件
  161. //1.如果越界了,直接返回
  162. if (x == 0 || x == ROW+ 1 || y == 0 || y == COL+ 1)
  163. {
  164. return;
  165. }
  166. //2.如果该位置已经展开,直接返回
  167. if (show[x][y] != '*')
  168. {
  169. return;
  170. }
  171. //3.剩下的就是需要展开的情况
  172. int count = get_mine(mine, x, y); //计算该位置的雷的个数
  173. if (count) //如果雷的个数不为0,直接展开
  174. {
  175. show[x][y] = count + '0';
  176. --(*pchance); //不是雷的个数也减一
  177. return; //这里需要返回,不然无限递归下去了
  178. }
  179. else //如果雷的个数是0
  180. {
  181. //对周围八个位置进行展开操作
  182. show[x][y] = count + '0';
  183. --(*pchance);
  184. boom_mine(mine, show, x - 1, y - 1, pchance);
  185. boom_mine(mine, show, x - 1, y, pchance);
  186. boom_mine(mine, show, x - 1, y + 1, pchance);
  187. boom_mine(mine, show, x, y - 1, pchance);
  188. boom_mine(mine, show, x, y + 1, pchance);
  189. boom_mine(mine, show, x + 1, y - 1, pchance);
  190. boom_mine(mine, show, x + 1, y, pchance);
  191. boom_mine(mine, show, x + 1, y + 1, pchance);
  192. }
  193. }
  194. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
  195. {
  196. int chance = row*col - count; //记录不是雷的个数
  197. while (chance)
  198. {
  199. system( "cls");
  200. DisplayBoard(mine, row, col);
  201. DisplayBoard(show, row, col);
  202. printf( "请输入需要排查雷的坐标\n");
  203. int x = 0, y = 0;
  204. scanf( "%d %d", &x, &y);
  205. //检查坐标的合法性
  206. if (x >= 1 && x <= row && y >= 1 && y <= col)
  207. {
  208. //如何是雷--爆炸
  209. //不是雷--显示雷的个数
  210. if (mine[x][y] != '1') //没点到雷
  211. {
  212. boom_mine(mine, show, x, y, &chance); //将该位置周围的展开,传坐标
  213. }
  214. else
  215. {
  216. printf( "很遗憾,你被炸死了\n");
  217. DisplayBoard(mine, row, col);
  218. break;
  219. }
  220. }
  221. else
  222. {
  223. printf( "坐标不合法\n");
  224. }
  225. }
  226. //判断是否赢了-- 将所有不是雷的地方都排查出来了
  227. if (chance == 0)
  228. {
  229. printf( "排雷成功,游戏胜利\n");
  230. }
  231. Sleep( 2000);
  232. }

test.c


  
  1. #include"game.h"
  2. void game()//游戏函数
  3. {
  4. srand(( unsigned int) time( NULL));
  5. char mine[ROWS][COLS] = { 0 }; //11*11的数组便于操作
  6. char show[ROWS][COLS] = { 0 }; //show数组与mine数组尺寸类型一样,可以使函数更兼容
  7. //1.初始化棋盘
  8. InitBoard(mine, ROWS, COLS, '0'); //将mine初始化为字符0
  9. InitBoard(show, ROWS, COLS, '*'); //将show初始化为*
  10. //3.布置雷
  11. int count = SetMine(mine,ROW,COL); //布置雷是在mine中布置
  12. //是在9*9中布置
  13. //4.排查雷
  14. FindMine(mine, show, ROW, COL,count);
  15. }
  16. void menu()
  17. {
  18. printf( "=================================\n");
  19. printf( "====== 1. play ======\n");
  20. printf( "====== 0. exit ======\n");
  21. printf( "=================================\n");
  22. }
  23. void test()//测试函数
  24. {
  25. int input = 0;
  26. do
  27. {
  28. system( "cls");
  29. menu();
  30. printf( "请输入->:");
  31. scanf( "%d", &input);
  32. switch (input)
  33. {
  34. case 1:
  35. game();
  36. break;
  37. case 0:
  38. printf( "游戏退出\n");
  39. break;
  40. default:
  41. printf( "错误输出\n");
  42. Sleep( 1000);
  43. break;
  44. }
  45. } while (input);
  46. }
  47. int main()
  48. {
  49. test();
  50. return 0;
  51. }

 


7.作者试玩环节

        打完一把还是需要点时间的,博主就不玩完了。        

 当然了,上述代码肯定还存在着很多不足的地方,如果大家有什么好的建议欢迎在评论区留言,我们下期见吧。


转载:https://blog.csdn.net/AAxiao_bei/article/details/127963624
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场