• C语言实现《扫雷》


    目录

    前言:

    (一)菜单的创建和使用

    1.编写菜单

    2.创建变量以供选择菜单:

    (二)编写game()函数

    1.初始化棋盘

    2.打印棋盘

    3.布置雷

    4.排查雷

    总结


    前言:

    《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。

    1992年4月6日,扫雷和纸牌空当接龙等小游戏搭载在Windows 3.1系统中与用户见面,主要目的是让用户训练使用鼠标。这个游戏的玩法很简单,有初级、中级、高级和自定义等模式,雷区中随机布置一定数量的地雷,玩家需要尽快找出所有不是地雷的方块,但不许踩到地雷。

    扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。不久,“方块”被改写成了游戏“Rlogic”。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。这款游戏的玩法是在一个9*9(初级),16*16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。

    玩法:

    以windows XP自带扫雷winmine.exe为例(其它版本的扫雷游戏与之大同小异)。游戏区包括雷区、地雷计数器(位于左上角,记录剩余地雷数)和计时器(位于右上角,记录游戏时间),确定大小的矩形雷区中随机布置一定数量的地雷(初级为9*9个方块10个雷,中级为16*16个方块40个雷,高级为16*30个方块99个雷,自定义级别可以自己设定雷区大小和雷数,但是雷区大小不能超过24*30),玩家需要尽快找出雷区中的所有不是地雷的方块,而不许踩到地雷。

    游戏的基本操作包括左键单击(Left Click)、右键单击(Right Click)、双击(Chording)三种。其中左键用于打开安全的格子,推进游戏进度;右键用于标记地雷,以辅助判断,或为接下来的双击做准备;双击在一个数字周围的地雷标记完时,相当于对数字周围未打开的方块均进行一次左键单击操作:

    左键单击:在判断出不是雷的方块上按下左键,可以打开该方块。如果方块上出现数字,则该数字表示其周围3×3区域中的地雷数(一般为8个格子,对于边块为5个格子,对于角块为3个格子。所以扫雷中最大的数字为8);如果方块上为空(相当于0),则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。

    如果当前电脑没有扫雷,可以到该网址前去游玩:扫雷 - Minesweeper Online

    (一)菜单的创建和使用

    1.编写菜单

    1. void menu()
    2. {
    3.     printf("*******************\n");
    4.     printf("*******1.Play******\n");
    5.     printf("*******0.Exit******\n");
    6.     printf("*******************\n");
    7. }
    8.  
    9. int main()
    10. {
    11.     menu();
    12.     return 0;
    13. }


    运用以上代码可以实现简易菜单的编写以及打印。

    2.创建变量以供选择菜单:
     

    在这里我们有两个选择,选1就进入游戏,选0就直接退出游戏。那么我们不妨定义一个变量input。这样当input == 1时开始游戏,当input == 2时自动退出游戏。综上,我们可以考虑使用do....while循环和switch分支选择语句来实现选择的底层的逻辑代码。

    代码如下:

    1. void menu()
    2. {
    3. printf("*******************\n");
    4. printf("*******1.Play******\n");
    5. printf("*******0.Exit******\n");
    6. printf("*******************\n");
    7. }
    8. int main()
    9. {
    10. menu();
    11. int input = 0;
    12. do
    13. {
    14. printf("请选择:");
    15. scanf("%d", &input);
    16. switch (input)
    17. {
    18. case 0:
    19. printf("正在退出游戏...\n");
    20. break;
    21. case 1:
    22. printf("开始游戏\n");
    23. break;
    24. default:
    25. printf("请重新输入\n");
    26. }
    27. } while (input);
    28. return 0;
    29. }

    具体实现如下:

    (二)编写game()函数

    1.初始化棋盘

    对于扫雷游戏,我们可以创建两个二维数组,分别为

    布置雷的Mine数组和显示雷给玩家看的Show数组

    在这里要注意的是,我们想要实现棋盘的打印,就要定义行和列,即长与宽。

    在简单的扫雷难度里,棋盘的大小是 9 * 9的棋盘大小,但是如果未来想要增加难度可以方便的进行修改,所以我们可以实现以下代码

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

    在这里为什么我们要在原有长度ROW和COL上面+2呢,这是因为在以后我们会对这个坐标的其它周围的八个坐标进行排查,如果没有定义这两个位置,那么在之后排查统计的时候就会传进来随机值,这样就会出现BUG。因此我们对ROWS和COLS初始化。

    Mine数组里面初始化为字符‘0’,雷初始化成字符‘1’,为什么要将雷初始化为‘1’在之后就会知道了。

    Show数组里面全部初始化为字符‘*’。

    具体的操作就是运用for循环,遍历二维数组然后初始化:

    1. void InitBoard(char board[ROWS][COLS], int row, int col, char set)
    2. {
    3. int i = 0;
    4. int j = 0;
    5. for (i = 0; i <= row; i++)
    6. {
    7. for (j = 0; j <= col; j++)
    8. {
    9. board[i][j] = set;
    10. }
    11. }
    12. }
    1. InitBoard(mine, ROW, COL, '0');
    2. InitBoard(show, ROW, COL, '*');

    以上就可以实现基本的初始化操作

    2.打印棋盘

    对于棋盘的打印处理,我们首要任务是使玩家可以更直观的使用和观察棋盘的坐标,所以为了方便玩家使用,我们不妨采取一下图例:

    当我想要输入坐标时,就可以通过观察顶部和侧边的数字来查找坐标位置。

    具体操作代码如下:

    1. void DisplayBoard(char board[ROWS][COLS], int row, int col)
    2. {
    3. int i = 0;
    4. int j = 0;
    5. for (int i = 0; i <= row; i++)
    6. {
    7. printf("%d ", i);
    8. }
    9. printf("\n");
    10. for (int i = 1; i <= row;i++)
    11. {
    12. printf("%d ", i);
    13. for (int j = 1; j <= col; j++)
    14. {
    15. printf("%c ", board[i][j]);
    16. }
    17. printf("\n");
    18. }
    19. }

    如此一来便可以实现棋盘的打印

    不过需要注意的是数组不要越界访问!

    如果你的代码又出现报错,那基本就是数组的越界访问了。

    对于这种情况,你应该去查询初始化和打印棋盘阶段的for循环里对于限制条件是否出错,然后不断调试和尝试就可以排除和改正错误。

    3.布置雷
     

    一开始我们在初始化的棋盘中,定义的是棋盘是9 * 9的简单模式,所以对于简单模式,我们布置雷的数量也应当少量处理,布置为10颗最合适,但如果在未来我们想要增加雷,

    我们就可以实现以下代码:

    #define EASY_COUNT 10

    这里我们还要知道的一点,我们布置的雷应当随机布置!所以这里我们不得不使用rand和srand函数。

    在使用之前还应到包含相对于的头文件,这点尤为重要,不可忘记。

    具体代码操作如下:

    1. void SetMine(char mine[ROWS][COLS], int row, int col)
    2. {
    3. int x = 0;
    4. int y = 0;
    5. int count = 0;
    6. count = EASY_COUNT;
    7. while (count)
    8. {
    9. x = rand() % row + 1;
    10. y = rand() % col + 1;
    11. if (x >= 1 && x <= row&&y >= 1 && y <= col)
    12. {
    13. if (mine[x][y] == '0')
    14. {
    15. mine[x][y] = '1';
    16. count--;
    17. }
    18. }
    19. }
    20. }

    如此,雷的位置如图所示:

    字符‘1’的个数刚好为10,即雷的个数为10。

    注意不应当忘记在main函数里加上如下代码:
     

    srand((unsigned int) time(NULL));

    4.排查雷

    在排查雷中,我们应当创建一个循环,因为你排查雷后还要继续排查,不可能说排查一次后游戏结束。所以我们要满足一个条件,如果棋盘上的不含雷的坐标全被排查,则结束循环,打印“排雷成功!”,如果踩雷了则打印“踩雷了!”,对于不是雷的坐标,我们应该统计周围雷的数量然后将show数组里的元素进行替换,替换成周围雷的个数。

    排查雷为《扫雷游戏》的重难点,这部分应当多次练习。

    具体操作的代码如下:
     

    1. static int GetMineCount(char mine[ROWS][COLS], int x, int y);
    2. void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    3. {
    4. int x = 0;
    5. int y = 0;
    6. int win = 0;
    7. int c = 0;
    8. while (win != (row * col) - EASY_COUNT)
    9. {
    10. printf("请输入坐标:");
    11. scanf("%d %d", &x, &y);
    12. if (x >= 1 && x <= row&&y >= 1 && y <= col)
    13. {
    14. if (mine[x][y] == '1')
    15. {
    16. printf("踩雷了!\n");
    17. DisplayBoard(mine, ROW, COL);
    18. }
    19. else if (show[x][y] != '*')
    20. {
    21. printf("该坐标已经被占用!\n");
    22. }
    23. else
    24. {
    25. c = GetMineCount(mine, x, y);
    26. show[x][y] = c + '0';
    27. DisplayBoard(show, ROW, COL);
    28. win++;
    29. }
    30. }
    31. else printf("坐标非法\n");
    32. }
    33. }
    34. static int GetMineCount(char mine[ROWS][COLS], int x, int y)
    35. {
    36. return (mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1]
    37. + mine[x - 1][y] + mine[x + 1][y]
    38. + mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1])
    39. + 8 * '0';
    40. }

    这里要注意的是,在我们统计雷个数的GetMineCount()函数和show[x][y] = c+ '0'里,我们都有一个操作,就是+‘0’。

    为什么我们要这么做?

    首先对于GetMineCount()函数里,我们统计的是数字0或者1或者2等等,这些都是数字,而我们的show数组存放的是char类型的,因此我们需要将这些数字改为字符。

     
    

    // '0' -- 48

    // '1' -- 49

    // '2' -- 50

    // '2' - '0' = 50 - 48 = 2

    以上就是改为字符的合理解释。

    如此扫雷游戏结束。

    代码演示:

    总结

    以上就是《扫雷》游戏的全过程,我们在这里只是实现了最基础的代码操作。

    我们在玩这个游戏的时候会发现,当周围有一大片区域没有雷的时候,需要一点一点的输入坐标。

    这样的内容十分繁琐,很容易就让玩家产生厌烦的心情。

    因此我们在未来会利用递归的方法,实现较为完善的《扫雷》游戏。

    以上代码是自己编写测试许多遍,建议大家学习完后下来可以自主练习,动手敲一敲代码,这样不仅仅对于代码风格还是对循环和函数的理解,都会有一个较大的提升。

    要记住:“Actions speak louder than words”

                     坐而言不如起而行!

    以上各项代码文件均在我的Gitee里:

    Game with C: 这一仓库全部以c语言编写的小游戏 - Gitee.com

  • 相关阅读:
    mapbox的使用
    330. 按要求补齐数组
    Linux系统使用iftop查看带宽占用情况
    t检验(连续变量)和卡方检验(分类变量)
    个人数学建模算法库之图的创建与可视化
    (十一)VBA常用基础知识:worksheet的各种操作之sheet删除
    利用IoDriverObjectType控制内核驱动加载
    数据提取1
    Jenkins集成maven
    Mindspore多机多卡AI分布式训练RuntimeError 1456
  • 原文地址:https://blog.csdn.net/weixin_72917087/article/details/132792806