• C语言第十一课(上):编写扫雷游戏(综合练习2)


    目录

     

    前言:

    一、文件建立:

            1.头文件game.h:

            2.函数定义文件game.c:

            3.工程测试文件test.c:

    二、编写井字棋游戏: 

            1.程序整体执行思路:

            2.menu菜单:

            3.game游戏函数逻辑:

            4.game函数中各项功能的实现:

            ①.头文件game.h内容:

            ②.棋盘的初始化InitBoard:

            ③.打印棋盘DisplayBoard:

            ④.布置“地雷”SetMine:

            ⑤.统计雷get_mine_count:

            ⑥.排雷FindMine:

    三、完整程序代码:

            1.gam.h:

            2.game.c:

            3.test.c:

    四、总结:


    前言:

            前面两篇文章中,各位小伙伴们写出了自己在C语言写学习中的第一个小游戏井字棋,阿銮在这里恭喜各位小伙伴辣!本文我们将进行更加复杂的阶段编程练习——编写扫雷游戏。

    一、文件建立:

            同样的,我们在编写扫雷游戏时,也使用模块化开发的方式,将整段编程代码划分为game.h、game.c与test.c三个文件进行编写:

            1.头文件game.h:

            该文件用于包含宏与其它头文件,并存放功能实现函数的函数声明:

            2.函数定义文件game.c:

             该文件用于书写所有的程序功能实现的函数定义

            3.工程测试文件test.c:

            该文件用于书写我们的程序主体部分:

    二、编写井字棋游戏: 

            1.程序整体执行思路:

            与之前写过的井字棋游戏整体执行思路相同,首先我们自定义两个函数,menu为菜单函数,负责向玩家打印游戏菜单game为游戏函数,负责实现整个游戏的逻辑实现。当程序开始编译运行后,将会按照顺序,先执行menu函数打印出游戏菜单,接着执行game函数让玩家们进行游戏:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"game.h"
    3. //游戏逻辑:
    4. void game()
    5. {
    6. ...
    7. }
    8. void test()
    9. {
    10. menu();
    11. game();
    12. ...
    13. }
    14. int main()
    15. {
    16. test();
    17. return 0;
    18. }

            同时,与井字棋游戏相同,大多数时候玩家往往会选择多次进行游戏,所以我们通过在数函数部分结合我们在之前介绍过的的循环和分支语句很容易实现该目的:

    1. void test()
    2. {
    3. int input = 0;
    4. do
    5. {
    6. menu();
    7. printf("请选择:");
    8. scanf("%d", &input);
    9. switch (input)
    10. {
    11. case 1:
    12. game();
    13. break;
    14. case 0:
    15. printf("退出游戏...\n");
    16. break;
    17. default:
    18. printf("输入错误,请重新选择!\n");
    19. break;
    20. }
    21. } while (input);
    22. }

            这些步骤与井字棋游戏的整体运行逻完全相同,这里不再进行运行演示。

            2.menu菜单:

            与扫雷游戏相同,仅使用 printf 函数进行屏幕打印即可:

    1. void menu()
    2. {
    3. printf("******************************\n");
    4. printf("******************************\n");
    5. printf("***** 欢迎来到扫雷游戏 *****\n");
    6. printf("***** 1.play *****\n");
    7. printf("***** 0.exit *****\n");
    8. printf("******************************\n");
    9. printf("******************************\n");
    10. }

            且编译运行结果与井字棋相同,不再进行过多阐述。

            3.game游戏函数逻辑:

            (注:此处我们同样只关心实现逻辑,具体功能实现后面会逐一进行研究说明)

            我们同样从扫雷游戏的游戏规则与胜利条件开始研究。扫雷游戏其本质为推理判断游戏,它有9×9标准游戏界面,在游戏开始时,所有方格中的内容将会被隐藏,玩家不清楚每个格子的内容,但在所有格子中,部分格子中被布置下了“地雷”,玩家需要在判断放个安全的情况下进行扫雷,在安全的格子上,若以该方格为中心的九宫格内有危险的“地雷”,该中心方格上将会显示中心九宫格内“地雷”的数量,玩家可以通过选择该位置来进行排雷,若判断失误,选择的位置上为“地雷”,则玩家输掉了该局游戏,直到玩家将全部的地雷全部排查出来,即可获得游戏的胜利。

            梳理了游戏的规则,我们开始尝试编写该游戏。

            在游戏函数中,首先我们进行棋盘的初始化。与井字棋游戏不同的是,在这里我们需要定义并初始化两个棋盘。需要两个棋盘原因是,我们对“安全”与“地雷”的区分将会使用到' 0 '和' 1 '两个字符,其中' 0 表示“安全”而' 1 '则表示“地雷”,如果我们仅仅只定义并初始化一个棋盘,当进行地雷判断时,我们将无法确定' 1 '是表示周围中心九宫格内地雷的数量还是表示该方格内为“地雷,造成我们对游戏情况判断的歧义”:

    1. void game()
    2. {
    3. char mine[ROWS][COLS] = { 0 };
    4. //存放布置的雷的信息
    5. char show[ROWS][COLS] = { 0 };
    6. //存放查出的雷的信息
    7. }

            接着我们对这两个棋盘进行初始化。mine数组中用于存放布置的地雷的信息,故首先将其全部初始化为“安全”,而show数组用于打印向玩家反馈当前扫雷情况,故刚开局时全部位置均未未知,初始化为象征性占位符' * ',并在棋盘初始化完成后将反馈棋盘show进行打印

            (mine数组中存放实际布雷信息,游戏实际为操作mine数组,show数组仅供反馈排雷情况使用)

    1. void game()
    2. {
    3. char mine[ROWS][COLS] = { 0 };
    4. //存放布置的雷的信息
    5. char show[ROWS][COLS] = { 0 };
    6. //存放查出的雷的信息
    7. //初始化棋盘
    8. InitBoard(mine, ROWS, COLS, '0');
    9. InitBoard(show, ROWS, COLS, '*');
    10. //打印棋盘
    11. DisplayBoard(show, ROW, COL);
    12. }

            在棋盘定义并初始化完成后,我们需要在我们定义的全“安全”棋盘中,放入一定数量随机位置的“地雷”

            (注:此处初始化与定义的棋盘,与进行实际操作的行与列不同,后文实现过程将会进行说明)

    1. void game()
    2. {
    3. char mine[ROWS][COLS] = { 0 };
    4. //存放布置的雷的信息
    5. char show[ROWS][COLS] = { 0 };
    6. //存放查出的雷的信息
    7. //初始化棋盘
    8. InitBoard(mine, ROWS, COLS, '0');
    9. InitBoard(show, ROWS, COLS, '*');
    10. //打印棋盘
    11. DisplayBoard(show, ROW, COL);
    12. //布置雷
    13. SetMine(mine, ROW, COL);
    14. }

            在放置好“地雷”后,就可以让玩家开始进行“扫雷”了:

    1. void game()
    2. {
    3. //定义棋盘
    4. char mine[ROWS][COLS] = { 0 };
    5. //存放布置的雷的信息
    6. char show[ROWS][COLS] = { 0 };
    7. //存放查出的雷的信息
    8. //初始化棋盘
    9. InitBoard(mine, ROWS, COLS, '0');
    10. InitBoard(show, ROWS, COLS, '*');
    11. //打印棋盘
    12. DisplayBoard(show, ROW, COL);
    13. //布置雷
    14. SetMine(mine, ROW, COL);
    15. //扫雷
    16. FindMine(mine, show, ROW, COL);
    17. }

            至此,扫雷游戏的游戏整体逻辑我们就理清楚了,而接下来就是各项功能的实现了。

            4.game函数中各项功能的实现:

            ①.头文件game.h内容:

            在我们的头文件中,存放着宏定义、引用头文件与函数的声明

            首先我们来看宏定义

    1. #define ROW 9
    2. #define COL 9
    3. #define ROWS ROW+2
    4. #define COLS ROW+2
    5. #define EASY_COUNT 10

            可以看到,在这个游戏中,我们一共定义了五个宏常量。其中行ROW和列COL定义为9,而行ROWS和列COLS定义为11。那么同样是棋盘的行和列,为什么要定义两组不同的行和列呢?这样定义的原因是为了避免出现越界,同时便于进行操作

            我们向玩家展示的棋盘是9×9的棋盘,但是当我们后面需要统计中心九宫格中地雷的数量时,倘若在操作最上或最下行(第一、九行)与最左或最右(第一、九列)时,若只定义9×9的棋盘,在对一、九行、列方格周围进行“地雷”的计数时,将会造成数组越界的错误,所以我们在定义时应当在第一行之上、第九行之下、第一列之左和第九列之右各添加一行/列,总计定义11×11的棋盘数组。(即实际定义11×11数组,展示中间的9×9数组)

            我们还定义了宏常量EASY_COUNT,用于表示在我们的一局游戏中添加的“地雷”的数量

            接着是头文件的引用

    1. #include
    2. #include
    3. #include

            整个游戏中多次使用了输入scanf 与输出printf 函数,需引用头文件stdio.h;在进行布雷时,我们需要生成随机数作为“地雷”的下标,这时需要使用srand函数来确定随机数的起点,需引用头文件stdlib.h;在确定随机数起点时,我们采用根据时间戳来确定的方式,需引用头文件time.h

            最后就是扫雷游戏所要用到的全部功能函数的函数声明了:

            (此处为本游戏中全部的函数声明,函数定义将在后面逐一研究)

    1. void menu();
    2. void game();
    3. void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
    4. void DisplayBoard(char board[ROWS][COLS], int row, int col);
    5. void SetMine(char mine[ROWS][ROWS], int row, int col);
    6. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
    7. int get_mine_count(char mine[ROWS][COLS], int x, int y);

            ②.棋盘的初始化InitBoard:

            我们前面已经定义好了两个棋盘,这里的初始化也很简单,我们采用循环遍历的方式,将每一个位置上的数据都初始化为相应符号。对于mine数组来说,该数组用于存放地雷信息,故初始化为全' 0 ';而对于show数组来说,该数组用于展示,需要隐藏方格中的信息,故全部初始化为' * '

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

            ③.打印棋盘DisplayBoard:

            我们在将棋盘初始化完成以及每一次排雷,乃至最终反馈游戏结果时,都需要将我们当前的排雷情况进行打印,反馈给玩家。同时,为了界面更加的美观,我们可以在打印棋盘前先打印一行列号,并在每一行打印前先打印一个行号。棋盘界面的打印我们同样通过遍历思想来实现:

    1. void DisplayBoard(char board[ROWS][COLS], int row, int col)
    2. {
    3. printf(" --------扫雷--------\n");
    4. int i = 0;
    5. printf("| \\ 1 2 3 4 5 6 7 8 9 |\n");
    6. //第一列为列号,在列号最上方打印"\"时,一定注意代码应写为\\,否则\将被视为转义字符而不生效
    7. for (i = 1; i <= row; i++)
    8. {
    9. int j = 0;
    10. printf("| %d ", i);
    11. for (j = 1; j <= col; j++)
    12. {
    13. printf("%c ", board[i][j]);
    14. }
    15. printf("|\n");
    16. }
    17. printf(" --------扫雷--------\n");
    18. }

            我们将其编译运行起来,看看棋盘的打印效果:

            可以看到棋盘的打印已经按照我们规定的格式完成了打印操作。

            ④.布置“地雷”SetMine:

            布置地雷也很好理解,在中心的9×9棋盘中,通过使用时间戳来确定随机数的起点取随机数作为雷的下标,并放置在mine数组中

    1. void SetMine(char mine[ROWS][ROWS], int row, int col)
    2. {
    3. int count = EASY_COUNT;
    4. while (count)
    5. {
    6. //生成随机下标:
    7. int x = rand() % row + 1;
    8. int y = rand() % col + 1;
    9. //布置雷:
    10. if (mine[x][y] == '0')
    11. {
    12. mine[x][y] = '1';
    13. count--;
    14. }
    15. }
    16. }

            由于我们需要生成不止一个“地雷”,故需要进行循环,循环的判断条件我们就设置为初值等于宏常量地雷数的自定义变量count,并在每次布雷后执行减一操作,当其不为零时便判断为真不断执行循环布雷,直至最后一次布雷后变为零,判断为假循环停止

            同时,细心的小伙伴们可能会有疑问了,我们定义的棋盘为11×11,那我们怎样去确定“地雷”都被布置在中间的9×9的部分呢?首先我们看到,在我们取随机下标时,是对传入的行数和列数求余后加一求余后保证下标从零开始,再加一即保证了从和数组的第一列(0+1)开始算起,同时在传参时我们传入的是展示的行与列数:

    1. //布置雷
    2. SetMine(mine, ROW, COL);

            传入参数的值为9,则随机下标的上限即被限制为为9(0+9)。这样就实现了从第一行到第九行,从第一列到第九列,即中心9×9棋盘的范围限制。

            ⑤.统计雷get_mine_count:

            我们还需要一个函数来统计我们排雷位置周围的地雷数量:

    1. //统计雷:
    2. int get_mine_count(char mine[ROWS][COLS], int x, int y)
    3. {
    4. return (mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x - 1][y] + mine[x + 1][y] + mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1] - 8 * '0');
    5. }

            其原理很好理解,将该坐标周围八个位置上的数据加在一起即可。但由于我们定义的是字符型数组,其中存放的是字符' 0 '和字符' 1 '!而不是整形1和0!在打印时若直接进行相加计算,则不会得到我们想要的结果!所以我们在计算时,应当根据其ASCII码值进行计算,将周围八个位置上的字符进行相加后,减去8个字符' 0 ',才是我们想要的得到结果,并将结果返回

            ⑥.排雷FindMine:

            在排雷时,我们同样是根据坐标进行排雷,而在输入坐标时,我们需要对坐标的合理性进行判断。我们首先要循环对行坐标进行判断,当输入坐标在中心9×9棋盘中时,我们进行下一步,否则提示玩家“您输入的行号有误,请重新输入”:

    1. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    2. {
    3. printf("请输入您想要排查的坐标:\n");
    4. int x = 0;
    5. int y = 0;
    6. while (1)
    7. {
    8. printf("您想排查哪一行:");
    9. scanf("%d", &x);
    10. if (x >= 1 && x <= row)
    11. {
    12. ;
    13. }
    14. else
    15. {
    16. printf("您输入的行号有误,请重新输入!\n");
    17. }
    18. }
    19. }

            接着我们用同样的方式对列坐标进行判断

    1. while (1)
    2. {
    3. printf("您想排查哪一列:");
    4. scanf("%d", &y);
    5. if (y >= 1 && y <= col)
    6. {
    7. //执行排查
    8. break;
    9. }
    10. else
    11. {
    12. printf("您输入的列数有误,请重新输入!\n");
    13. }
    14. }

            当行与列均判断合理后进行后续操做,操作完成后使用一个break语句跳出当前坐标的操作,并进行下一次判断与操作。

            当确定坐标在范围内后,我们还需要进行一次判断,内容是要去判断此时我们要进行操作的坐标是否在之前已经被操作过,如果操作过则重新选择坐标,没有操作过才进行接下来的排雷操作:

    1. if (show[x][y] != '*')
    2. {
    3. printf("该坐标已被排查过!\n");
    4. DisplayBoard(show, ROW, COL);
    5. break;
    6. }

            在确定了坐标合理且没有进行过操作后,我们就可以对该坐标进行排雷了:

    1. if (mine[x][y] == '1')
    2. {
    3. DisplayBoard(mine, ROW, COL);
    4. printf("很遗憾,您死了!\n");
    5. reak;
    6. }
    7. else
    8. {
    9. int n = get_mine_count(mine, x, y);
    10. show[x][y] = n + '0';
    11. DisplayBoard(show, ROW, COL);
    12. break;
    13. }

            我们进行判断,若该坐标内存放的是' 1 ',则说明该位置放置的为“地雷”,根据游戏规则,此时玩家就输掉了这场游戏,应提示玩家“很遗憾,您死了”并退出游戏;若为' 0 ',则说明此处没有“地雷”,玩家在该位置上的排雷成功

            接下来,应当在本次成功排雷的坐标上显示出以该坐标为中心的九宫格内的“地雷”数量,我们定义一个变量n来接收计数函数返回的“地雷”数量统计结果。这里同样需要注意,我们的n接收到的是经过处理的ASCII码值,我们在进行使用时,应当对该值进行逆处理,即将该结果加上字符' 0 ',才是我们期望中要打印在该坐标处的内容。

            将它们组合在一起就能实现目的了吗?我们来看:

    1. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    2. {
    3. printf("请输入您想要排查的坐标:\n");
    4. int x = 0;
    5. int y = 0;
    6. while (1)
    7. {
    8. printf("您想排查哪一行:");
    9. scanf("%d", &x);
    10. if (x >= 1 && x <= row)
    11. {
    12. while (1)
    13. {
    14. printf("您想排查哪一列:");
    15. scanf("%d", &y);
    16. if (y >= 1 && y <= col)
    17. {
    18. if (show[x][y] != '*')
    19. {
    20. printf("该坐标已被排查过!\n");
    21. DisplayBoard(show, ROW, COL);
    22. break;
    23. }
    24. if (mine[x][y] == '1')
    25. {
    26. DisplayBoard(mine, ROW, COL);
    27. printf("很遗憾,您死了!\n");
    28. break;
    29. }
    30. else
    31. {
    32. int n = get_mine_count(mine, x, y);
    33. show[x][y] = n + '0';
    34. DisplayBoard(show, ROW, COL);
    35. break;
    36. }
    37. }
    38. else
    39. {
    40. printf("您输入的列数有误,请重新输入!\n");
    41. }
    42. }
    43. }
    44. else
    45. {
    46. printf("您输入的行数有误,请重新输入!\n");
    47. }
    48. }
    49. }

            我们运行后发现,程序出现了问题,即使在我们“踩到地雷”后,程序仍没有结束,仍在提示我们输入下一个坐标,这是为什么呢?我们研究我们的代码后发现,我们最内层的break在执行时并没有结束最外层的循环,导致程序无休止的执行下去而不会终结。那我们该如何处理呢?

            我们的思路是,既然它现在影响不到最外层循环,那我们就想办法让它去影响最外层的循环。我们的解决方案是,在最外层循环的控制表达式上下功夫:

    1. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    2. {
    3. printf("请输入您想要排查的坐标:\n");
    4. int x = 0;
    5. int y = 0;
    6. int win = 0;
    7. while (win < (row * col - EASY_COUNT))
    8. {
    9. printf("您想排查哪一行:");
    10. scanf("%d", &x);
    11. if (x >= 1 && x <= row)
    12. {
    13. while (1)
    14. {
    15. printf("您想排查哪一列:");
    16. scanf("%d", &y);
    17. if (y >= 1 && y <= col)
    18. {
    19. if (show[x][y] != '*')
    20. {
    21. printf("该坐标已被排查过!\n");
    22. DisplayBoard(show, ROW, COL);
    23. break;
    24. }
    25. if (mine[x][y] == '1')
    26. {
    27. DisplayBoard(mine, ROW, COL);
    28. printf("很遗憾,您死了!\n");
    29. win = 0;
    30. break;
    31. }
    32. else
    33. {
    34. int n = get_mine_count(mine, x, y);
    35. show[x][y] = n + '0';
    36. DisplayBoard(show, ROW, COL);
    37. win++;
    38. break;
    39. }
    40. }
    41. else
    42. {
    43. printf("您输入的列数有误,请重新输入!\n");
    44. }
    45. }
    46. }
    47. else
    48. {
    49. printf("您输入的行数有误,请重新输入!\n");
    50. }
    51. }
    52. }

            我们定义一个变量win,当win小于空着的格子,即排雷没有全部排完时,就不断进行循环排雷,若期间“踩到地雷”,便修改win = row * col - EASY_COUNT + 1,即破坏循环条件使判断判定为假而跳出循环。若没有“踩到地雷”则将执行替换的同时将变量win加一,直至全部排完游戏胜利退出循环。

            那么为什么我们在“踩到地雷”时不让win = row * col - EASY_COUNT呢?这样不也可以破坏循环条件没?原因是,在所有的安全坐标都排完以后,此时的变量win是等于row * col -  EASY_COUNT的,我们需要根据这个条件来进行胜利条件判断:

    1. if (win == (row * col - EASY_COUNT))
    2. {
    3. printf("恭喜您排雷成功,获得胜利!\n");
    4. DisplayBoard(mine, ROW, COL);
    5. }

            若“踩到地雷”时也执行这样的修改,则会造成程序语句歧义而产生错误的执行结果

            则总结排雷函数FindMine代码应当为:

    1. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    2. {
    3. printf("请输入您想要排查的坐标:\n");
    4. int x = 0;
    5. int y = 0;
    6. int win = 0;
    7. while (win < (row * col - EASY_COUNT))
    8. {
    9. printf("您想排查哪一行:");
    10. scanf("%d", &x);
    11. if (x >= 1 && x <= row)
    12. {
    13. while (1)
    14. {
    15. printf("您想排查哪一列:");
    16. scanf("%d", &y);
    17. if (y >= 1 && y <= col)
    18. {
    19. if (show[x][y] != '*')
    20. {
    21. printf("该坐标已被排查过!\n");
    22. DisplayBoard(show, ROW, COL);
    23. break;
    24. }
    25. if (mine[x][y] == '1')
    26. {
    27. DisplayBoard(mine, ROW, COL);
    28. printf("很遗憾,您死了!\n");
    29. win = row * col - EASY_COUNT + 1;
    30. break;
    31. }
    32. else
    33. {
    34. int n = get_mine_count(mine, x, y);
    35. show[x][y] = n + '0';
    36. DisplayBoard(show, ROW, COL);
    37. win++;
    38. break;
    39. }
    40. }
    41. else
    42. {
    43. printf("您输入的列数有误,请重新输入!\n");
    44. }
    45. }
    46. }
    47. else
    48. {
    49. printf("您输入的行数有误,请重新输入!\n");
    50. }
    51. }
    52. if (win == (row * col - EASY_COUNT))
    53. {
    54. printf("恭喜您排雷成功,获得胜利!\n");
    55. DisplayBoard(mine, ROW, COL);
    56. }
    57. }

            至此,扫雷游戏的最基础游戏功能就得到了实现。

    三、完整程序代码:

            1.gam.h:

    1. #pragma once
    2. #define ROW 9
    3. #define COL 9
    4. #define ROWS ROW+2
    5. #define COLS ROW+2
    6. #define EASY_COUNT 10
    7. #include
    8. #include
    9. #include
    10. void menu();
    11. void game();
    12. void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
    13. void DisplayBoard(char board[ROWS][COLS], int row, int col);
    14. void SetMine(char mine[ROWS][ROWS], int row, int col);
    15. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
    16. int get_mine_count(char mine[ROWS][COLS], int x, int y);

            2.game.c:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"game.h"
    3. //打印菜单:
    4. void menu()
    5. {
    6. printf("******************************\n");
    7. printf("******************************\n");
    8. printf("***** 欢迎来到扫雷游戏 *****\n");
    9. printf("***** 1.play *****\n");
    10. printf("***** 0.exit *****\n");
    11. printf("******************************\n");
    12. printf("******************************\n");
    13. }
    14. //初始化棋盘:
    15. void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
    16. {
    17. int i = 0;
    18. for (i = 0; i < rows; i++)
    19. {
    20. int j = 0;
    21. for (j = 0; j < cols; j++)
    22. {
    23. board[i][j] = set;
    24. }
    25. }
    26. }
    27. //打印棋盘:
    28. void DisplayBoard(char board[ROWS][COLS], int row, int col)
    29. {
    30. printf(" --------扫雷--------\n");
    31. int i = 0;
    32. printf("| \\ 1 2 3 4 5 6 7 8 9 |\n");
    33. for (i = 1; i <= row; i++)
    34. {
    35. int j = 0;
    36. printf("| %d ", i);
    37. for (j = 1; j <= col; j++)
    38. {
    39. printf("%c ", board[i][j]);
    40. }
    41. printf("|\n");
    42. }
    43. printf(" --------扫雷--------\n");
    44. }
    45. //布置雷:
    46. void SetMine(char mine[ROWS][ROWS], int row, int col)
    47. {
    48. int count = EASY_COUNT;
    49. while (count)
    50. {
    51. //生成随机下标:
    52. int x = rand() % row + 1;
    53. int y = rand() % col + 1;
    54. //布置雷:
    55. if (mine[x][y] == '0')
    56. {
    57. mine[x][y] = '1';
    58. count--;
    59. }
    60. }
    61. }
    62. //统计雷:
    63. int get_mine_count(char mine[ROWS][COLS], int x, int y)
    64. {
    65. return (mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x - 1][y] + mine[x + 1][y] + mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1] - 8 * '0');
    66. }
    67. //排雷:
    68. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    69. {
    70. printf("请输入您想要排查的坐标:\n");
    71. int x = 0;
    72. int y = 0;
    73. int win = 0;
    74. while (win < (row * col - EASY_COUNT))
    75. {
    76. printf("您想排查哪一行:");
    77. scanf("%d", &x);
    78. if (x >= 1 && x <= row)
    79. {
    80. while (1)
    81. {
    82. printf("您想排查哪一列:");
    83. scanf("%d", &y);
    84. if (y >= 1 && y <= col)
    85. {
    86. if (show[x][y] != '*')
    87. {
    88. printf("该坐标已被排查过!\n");
    89. DisplayBoard(show, ROW, COL);
    90. break;
    91. }
    92. if (mine[x][y] == '1')
    93. {
    94. DisplayBoard(mine, ROW, COL);
    95. printf("很遗憾,您死了!\n");
    96. win = row * col - EASY_COUNT + 1;
    97. break;
    98. }
    99. else
    100. {
    101. int n = get_mine_count(mine, x, y);
    102. show[x][y] = n + '0';
    103. DisplayBoard(show, ROW, COL);
    104. win++;
    105. break;
    106. }
    107. }
    108. else
    109. {
    110. printf("您输入的列数有误,请重新输入!\n");
    111. }
    112. }
    113. }
    114. else
    115. {
    116. printf("您输入的行数有误,请重新输入!\n");
    117. }
    118. }
    119. if (win == (row * col - EASY_COUNT))
    120. {
    121. printf("恭喜您排雷成功,获得胜利!\n");
    122. DisplayBoard(mine, ROW, COL);
    123. }
    124. }

            3.test.c:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"game.h"
    3. //游戏逻辑:
    4. void game()
    5. {
    6. char mine[ROWS][COLS] = { 0 };
    7. //存放布置的雷的信息
    8. char show[ROWS][COLS] = { 0 };
    9. //存放查出的雷的信息
    10. //初始化棋盘
    11. InitBoard(mine, ROWS, COLS, '0');
    12. InitBoard(show, ROWS, COLS, '*');
    13. //打印棋盘
    14. DisplayBoard(show, ROW, COL);
    15. //布置雷
    16. SetMine(mine, ROW, COL);
    17. //扫雷
    18. FindMine(mine, show, ROW, COL);
    19. }
    20. void test()
    21. {
    22. srand((unsigned int)time(NULL));
    23. int input = 0;
    24. do
    25. {
    26. menu();
    27. printf("请选择:");
    28. scanf("%d", &input);
    29. switch (input)
    30. {
    31. case 1:
    32. game();
    33. break;
    34. case 0:
    35. printf("退出游戏...\n");
    36. break;
    37. default:
    38. printf("输入错误,请重新选择!\n");
    39. break;
    40. }
    41. } while (input);
    42. }
    43. int main()
    44. {
    45. test();
    46. return 0;
    47. }

    四、总结:

            以上就是基础版扫雷游戏的各项功能实现了,但是它也和之前我们写的井字棋游戏一样,存在着很多的缺陷,例如它并不能像我们电脑里的扫雷游戏一样,选择一个坐标会打开一片,还有同样存在着界面不够美观的问题。而诸如此类这些问题我们在下文中将会一一进行处理。

            以上就是今天我为大家介绍的基础版扫雷的知识啦!既然我已经踏上这条道路,那么,任何东西都不应妨碍我沿着这条路走下去!

            新人初来乍到,辛苦各位小伙伴们动动小手,三连走一走 ~ ~ ~  最后,本文仍有许多不足之处,欢迎各位看官老爷随时私信批评指正!

  • 相关阅读:
    Swift学习内容精选(一)
    Web基础与HTTP协议
    FFmpeg介绍
    动态规划进阶——LeetCode53. 最大子数组的和
    spring5(二):IOC容器概述
    git的使用场景
    JVS三方登录配置说明(钉钉扫码登录介绍)
    uniapp跳转方式
    vscode怎么拷贝插件到另一台电脑
    第二周学习报告
  • 原文地址:https://blog.csdn.net/weixin_59392935/article/details/127950075