当前位置:   article > 正文

【C语言】简易版扫雷游戏(数组、函数的练习)

【C语言】简易版扫雷游戏(数组、函数的练习)

目录

一、分析和设计

1.1、扫雷游戏的功能分析

1.2、文件结构设计(多文件的练习)

1.3、数据结构的设计

二、代码

三、效果展示

三、优化


一、分析和设计

1.1、扫雷游戏的功能分析

        以在线版的扫雷游戏为参考,分析它的功能:扫雷游戏网页版 - Minesweeper

  • 使用控制台的输入控制游戏,控制台的输出打印游戏。
  • 简单版:9 x 9 尺寸的棋盘,10个雷。
  • 排雷规则:
  1. 1)如果不是雷,则显示周围(8个位置)的雷个数。
  2. 2)如果是雷,游戏失败。
  3. 3)如果把雷之外的所有非雷位置找出,游戏成功。

1.2、文件结构设计(多文件的练习)

  1. test.c:游戏的主逻辑,可用于测试。
  2. game.c:实现游戏的各种函数。
  3. game.c:实现游戏的各种函数的声明、数据的声明。

1.3、数据结构的设计

       存储棋盘形状的数据,使用二维数组再好不过了,易理解易操作。需要存储的数据:① 哪些位置是雷(用0和1表示)。② 打印在控制台的扫雷画面(未排雷的位置显示 ' * ' ,已排雷且非雷的位置显示周围雷的个数)。

        因为有字符' * ',所以存周围雷个数的数组的元素用char类型;虽然存雷位置的数组都是数字0和1,但是统一起见,元素的类型都定义为char,这样写代码的时候不用区分char还是int,不宜弄错。

        虽然说这两种数据可以放到一起,比如非雷位置放9,雷位置放10;排雷时非雷位置(9)可改成周围雷个数(≤8);打印时,等于9或10的位置打印' * ',小于等于8的位置打印周围雷个数。但是这样在打印时会多一些判断,读代码的人理解起来也没有分成两个数组容易。

        数据结构如下:

  1. char mine[ROWS][COLS] = {0};
  2. char show[ROWS][COLS] = {0};

        排雷时,如果找到非雷,则显示周围雷的个数。当这个非雷是二维数组的边缘部分时,统计周围雷的个数会发生越界:

        为了解决这个问题,需要把数组的大小增大一圈(两个数组统一增大,免得写代码判断的时候范围不统一,记混了):

二、代码

        game.c

  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void print_menu(){
  4. printf("******************************\n");
  5. printf("********* 1.play ********\n");
  6. printf("********* 2.exit ********\n");
  7. printf("******************************\n");
  8. printf("请选择:");
  9. }
  10. void init_mine(char mine[][EASY_COLS]) {
  11. for (int i = 0; i < EASY_ROWS; i++)
  12. for (int j = 0; j < EASY_COLS; j++)
  13. mine[i][j] = '0';
  14. }
  15. void init_show(char show[][EASY_COLS]) {
  16. for (int i = 0; i < EASY_ROWS; i++)
  17. for (int j = 0; j < EASY_COLS; j++)
  18. show[i][j] = '*';
  19. }
  20. void set_mine(char mine[][EASY_COLS]) {
  21. int r = 0;
  22. int c = 0;
  23. for (int i = 0; i < EASY_COUNT; i++) {
  24. r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
  25. c = rand() % EASY_COL + 1;
  26. if ('0' == mine[r][c]) // 这个位置不是雷,放雷
  27. mine[r][c] = '1';
  28. else
  29. i--; // 这个位置已经是雷了,这轮不算
  30. }
  31. }
  32. void print_mine(char mine[][EASY_COLS]) {
  33. for (int i = 0; i <= EASY_ROW; i++)
  34. printf("%d ", i);
  35. printf("\n");
  36. for (int i = 1; i <= EASY_ROW; i++) {
  37. printf("%d ", i);
  38. for (int j = 1; j <= EASY_COL; j++)
  39. printf("%c ", mine[i][j]);
  40. printf("\n");
  41. }
  42. }
  43. void print_show(char show[][EASY_COLS]) {
  44. for (int i = 0; i <= EASY_ROW; i++)
  45. printf("%d ", i);
  46. printf("\n");
  47. for (int i = 1; i <= EASY_ROW; i++) {
  48. printf("%d ", i);
  49. for (int j = 1; j <= EASY_COL; j++)
  50. printf("%c ", show[i][j]);
  51. printf("\n");
  52. }
  53. printf("输入坐标:");
  54. }
  55. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]) {
  56. int x = 0; // 输入的坐标
  57. int y = 0;
  58. int count = 0; // 已找到的的非雷
  59. while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
  60. print_show(show); // 打印棋盘
  61. scanf("%d %d", &x, &y); // 输入坐标
  62. system("cls");
  63. if (mine[x][y] == '1') {
  64. printf("炸弹!游戏失败!\n");
  65. break;
  66. }
  67. else if (mine[x][y] == '0') {
  68. char c = count_mine(mine, x, y); // 计算周围雷的个数
  69. show[x][y] = c;
  70. count++; //找到一个非雷
  71. }
  72. }
  73. if (count == EASY_ROW * EASY_COL - EASY_COUNT)
  74. printf("排雷成功!\n");
  75. }
  76. char count_mine(char mine[][EASY_COLS], int x, int y) {
  77. return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
  78. + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
  79. }

        game.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<time.h>
  5. #include<Windows.h>
  6. #define EASY_ROW 9
  7. #define EASY_COL 9
  8. #define EASY_COUNT 10
  9. #define EASY_ROWS EASY_ROW+2
  10. #define EASY_COLS EASY_COL+2
  11. void print_menu();
  12. void init_mine(char mine[][EASY_COLS]);
  13. void init_show(char show[][EASY_COLS]);
  14. void set_mine(char mine[][EASY_COLS]);
  15. void print_mine(char mine[][EASY_COLS]);
  16. void print_show(char show[][EASY_COLS]);
  17. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]);
  18. char count_mine(char mine[][EASY_COLS], int x, int y);

        test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void game() {
  4. char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
  5. char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数
  6. init_mine(mine); // 初始化,全放'0'
  7. init_show(show); // 初始化棋盘,全为'*'
  8. set_mine(mine); // 随机放雷
  9. find_mine(mine, show);// 扫雷
  10. }
  11. int main() {
  12. int choose = 0;
  13. srand((unsigned int)time(NULL)); // 设置随机种子
  14. do{
  15. print_menu(); // 打印主菜单
  16. scanf("%d", &choose); // 输入选择
  17. system("cls"); // 清屏
  18. switch (choose) {
  19. case 1: // 开始游戏
  20. game();
  21. break;
  22. case 2: // 退出
  23. break;
  24. default:
  25. printf("输入错误,请重新输入。\n");
  26. }
  27. } while (choose != 2);
  28. return 0;
  29. }

三、效果展示

三、优化

        代码还有很多需要优化的地方,比如:

  • 如果排查的位置不是雷,它周围也没雷,周围可以进行展开。
  • 可以选择游戏难度:简单(9x9,10个雷)、中等(16x16,40个雷)、困难(16x30,99个雷)、自定义。
  • 可以标记雷。
  • 游戏中,可以显示时间。

(1)增加展开、标记雷功能

        代码如下:

        game.h:

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<time.h>
  5. #include<Windows.h>
  6. #define EASY_ROW 9
  7. #define EASY_COL 9
  8. #define EASY_COUNT 10
  9. #define EASY_ROWS EASY_ROW+2
  10. #define EASY_COLS EASY_COL+2
  11. // 打印字体颜色
  12. #define NONE "\033[m"
  13. #define BLUE "\033[0;32;34m"
  14. #define RED "\033[0;32;31m"
  15. void print_menu();
  16. void init_mine(char mine[][EASY_COLS]);
  17. void init_show(char show[][EASY_COLS]);
  18. void set_mine(char mine[][EASY_COLS]);
  19. void print_mine(char mine[][EASY_COLS]);
  20. void print_show(char show[][EASY_COLS]);
  21. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]);
  22. char count_mine(char mine[][EASY_COLS], int x, int y);
  23. void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt);
  24. void mark_mine(char flag[][EASY_COLS]);

        game.c:

  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void print_menu(){ // 打印主菜单
  4. printf("******************************\n");
  5. printf("********* 1.play ********\n");
  6. printf("********* 2.exit ********\n");
  7. printf("******************************\n");
  8. printf("请选择:");
  9. }
  10. void init_mine(char mine[][EASY_COLS]) { // 初始化雷信息
  11. for (int i = 0; i < EASY_ROWS; i++)
  12. for (int j = 0; j < EASY_COLS; j++)
  13. mine[i][j] = '0';
  14. }
  15. void init_show(char show[][EASY_COLS]) { //初始化棋盘
  16. for (int i = 0; i < EASY_ROWS; i++)
  17. for (int j = 0; j < EASY_COLS; j++)
  18. show[i][j] = '*';
  19. }
  20. void set_mine(char mine[][EASY_COLS]) { // 随即放置雷
  21. int r = 0;
  22. int c = 0;
  23. for (int i = 0; i < EASY_COUNT; i++) {
  24. r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
  25. c = rand() % EASY_COL + 1;
  26. if ('0' == mine[r][c]) // 这个位置不是雷,放雷
  27. mine[r][c] = '1';
  28. else
  29. i--; // 这个位置已经是雷了,这轮不算
  30. }
  31. }
  32. void print_mine(char mine[][EASY_COLS]) { // 打印雷信息
  33. for (int i = 0; i <= EASY_ROW; i++)
  34. printf(BLUE"%d "NONE, i);
  35. printf("\n");
  36. for (int i = 1; i <= EASY_ROW; i++) {
  37. printf(BLUE"%d "NONE, i);
  38. for (int j = 1; j <= EASY_COL; j++)
  39. printf("%c ", mine[i][j]);
  40. printf("\n");
  41. }
  42. }
  43. void print_show(char show[][EASY_COLS], char flag[][EASY_COLS]) { // 打印棋盘
  44. for (int i = 0; i <= EASY_ROW; i++)
  45. printf(BLUE"%d "NONE, i);
  46. printf("\n");
  47. for (int i = 1; i <= EASY_ROW; i++) {
  48. printf(BLUE"%d "NONE, i);
  49. for (int j = 1; j <= EASY_COL; j++) {
  50. if('1' == flag[i][j])
  51. printf(RED"%c "NONE, show[i][j]);
  52. else
  53. printf("%c ", show[i][j]);
  54. }
  55. printf("\n");
  56. }
  57. printf("输入坐标(输入0 0标记雷):");
  58. }
  59. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]) { // 排雷
  60. int x; // 输入的坐标
  61. int y;
  62. int count = 0; // 已找到的的非雷
  63. while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
  64. x = 0;
  65. y = 0;
  66. while (x == 0 && y == 0) {
  67. print_show(show, flag); // 打印棋盘
  68. scanf("%d %d", &x, &y); // 输入坐标
  69. if (0 == x && 0 == y) // 标记雷
  70. mark_mine(flag);
  71. system("cls");
  72. }
  73. if (mine[x][y] == '1') { // 碰到炸弹了
  74. printf("炸弹!游戏失败!\n");
  75. break;
  76. }
  77. else if (mine[x][y] == '0' && show[x][y] == '*') { // (x,y)不是炸弹,并且没有被排查过
  78. find_other_mine(mine, show, x, y, &count);
  79. }
  80. }
  81. if (count == EASY_ROW * EASY_COL - EASY_COUNT) // 所有非雷点找齐了
  82. printf("排雷成功!\n");
  83. }
  84. char count_mine(char mine[][EASY_COLS], int x, int y) { // 计算非雷点(x,y)周围有几个雷
  85. return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
  86. + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
  87. }
  88. void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt) { // 递归,排查的位置不是雷,周围也没有雷,可以展开
  89. char c = count_mine(mine, x, y); // 计算周围雷的个数
  90. if(c == '0')
  91. show[x][y] = ' ';
  92. else
  93. show[x][y] = c;
  94. (*cnt)++; //找到一个非雷
  95. if (show[x][y] != ' ') { // 周围有雷,就不需要扩展了
  96. return;
  97. }
  98. for (int i = -1; i <= 1; i++) {
  99. for (int j = -1; j <= 1; j++) {
  100. int xt = x + i;
  101. int yt = y + j;
  102. if (show[xt][yt] != '*' || mine[xt][yt] == '1') // 已经排查过的,就不需要重复扩展了;本身是炸弹的,也不要扩展
  103. continue;
  104. if(xt >= 1 && yt >= 1 && xt <= EASY_ROW && yt <= EASY_COL) // 拓展的坐标要在棋盘范围内
  105. find_other_mine(mine, show, xt, yt, cnt);
  106. }
  107. }
  108. }
  109. void mark_mine(char flag[][EASY_COLS]) {
  110. int x;
  111. int y;
  112. printf("输入雷的坐标:");
  113. scanf("%d %d", &x, &y); // 输入坐标
  114. flag[x][y] = '1';
  115. }

        test.c:

  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void game() {
  4. char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
  5. char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数
  6. char flag[EASY_ROWS][EASY_COLS]; // 屏幕上,标记的雷
  7. init_mine(mine); // 初始化,全放'0'
  8. init_show(show); // 初始化棋盘,全为'*'
  9. init_mine(flag); // 初始化全'0','0' 表示未标记,‘1’表示已标记
  10. set_mine(mine); // 随机放雷
  11. find_mine(mine, show, flag);// 扫雷
  12. }
  13. int main() {
  14. int choose = 0;
  15. srand((unsigned int)time(NULL)); // 设置随机种子
  16. do{
  17. print_menu(); // 打印主菜单
  18. scanf("%d", &choose); // 输入选择
  19. system("cls"); // 清屏
  20. switch (choose) {
  21. case 1: // 开始游戏
  22. game();
  23. break;
  24. case 2: // 退出
  25. break;
  26. default:
  27. printf("输入错误,请重新输入。\n");
  28. }
  29. } while (choose != 2);
  30. return 0;
  31. }

        效果展示:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/917842
推荐阅读
相关标签
  

闽ICP备14008679号