【C语言】扫雷(保姆级教程+内含源码)

简介: 本篇文章详细的,有体系的介绍了扫雷的结构,方便我们小白的更加理解C语言的数组。内含源码。

C系列文章目录


前言

一,模块化编程

二,游戏思路与逻辑

三,实现游戏步骤/过程

1,菜单界面(menu)

2,实现多行多列扫雷

       3, 实现多个雷

4,棋盘初始化

5,棋盘的打印

6,布置雷的信息

7,玩家输入雷实现步骤

8,统计输入坐标周围有多少雷

四,结果运行

五,模块化代码实现

1、test.c

2、game.c

3、game.h


前言

通过本章我们可以学习到使用C语言写的扫雷小游戏,该教程堪称保姆级,小白都可学习


一,模块化编程

    1. 可维护性:模块化编程将代码划分为独立的模块,每个模块负责特定的任务或功能。这样,在需要修改或调试某个功能时,只需关注相关的模块,而不需要涉及整个程序。这大大简化了维护和调试的工作,使得代码更易于理解和修改。
    2. 重用性:模块化编程鼓励开发人员将一些常用的功能封装成模块,然后在不同的项目中重复使用。这样可以避免重复编写相同的代码,减少了开发工作量,提高了开发效率。同时,通过不断重用经过测试和验证的模块,可以提高代码的可靠性和稳定性。
    3. 可扩展性:当需要添加新的功能或修改现有功能时,模块化编程能够提供更好的可扩展性。由于模块之间的依赖关系明确定义和管理,可以单独修改或替换某个模块,而不会影响到其他模块。这种灵活性使得系统更容易适应变化和演化。
    4. 并行开发:模块化编程允许多个开发人员并行工作,每个人负责开发和测试不同的模块。这样可以提高开发效率,缩短项目的开发周期。同时,模块化编程也方便团队协作和沟通,降低了开发过程中的冲突和合并的风险。
    5. 可测试性:模块化编程使得单元测试更容易进行。每个模块都是相对独立的,可以单独测试其功能和性能。这样可以更容易地发现和修复问题,提高软件的质量和稳定性。

    总之,模块化编程的优势包括可维护性、重用性、可扩展性、并行开发和可测试性。这些优势使得代码更易于理解、修改和维护,提高了开发效率和软件质量。

    在学习扫雷前,我们先了解模块化编程,模块化编程的思想可以让我们更好的进行编程,让我们更好的理解接下来的扫雷过程。

    二,游戏思路与逻辑

    1.创建菜单函数提醒玩家选择是否玩游戏。

    2.创建一个main()函数,让我们选择是否游戏

    3.当我们选择玩游戏,就进入一个扫雷的游戏函数

    4.我们在游戏函数中,创建两个二维数组,一个让人不知道那个是雷,一个放的雷信息

    5.开始对游戏函数进行功能填充

      1. 首先,进行雷的初始化棋盘。
      2. 然后,再打印出雷的初始化棋盘。注意:一定是要先进行 初始化 然后再 打印棋盘。
      3. 接着,就可以布置雷的信息了。
      4. 最后,输入排查雷的坐标。

      6.检查出的坐标是不是雷,布置雷存放的是字符(1) 没有放置的是字符(0)
      7.输入坐标的时候一共有④种情况:《很遗憾,你被炸死了》、《非法坐标了,请重新输入》、《该坐标被占用,请重新输入》、《恭喜你,排雷成功》
      8.然后,再回到步骤①,是否选择 进入游戏 以及 退出游戏

      三,实现游戏步骤/过程

      1,菜单界面(menu)

      菜单界面函数实际上就像是我们的一个界面,就好比是游戏的界面目录,餐馆当中的菜单。一样的道理。这个是库函数就有的我们只需要直接引用下即可。示例代码如下

      void menu()
      {
        printf("*******-------扫雷----------********\n");
        printf("*******      1.play         ********\n");
        printf("*******      0.exit         ********\n");
        printf("*******-------扫雷----------********\n");
      }

      image.gif

      2,实现多行多列扫雷

      #define ROW 9
      #define COL 9//现实棋盘的行数
      #define ROWS ROW+2//初始化棋盘的行数,是为了后面的统计雷的方便
      #define COLS COL+2

      image.gif

      #define 宏定义在这里的好处:

      1,方便以后程序的修改,不用以后对程序进行修改时,每个相同的变量都改变,只需对宏定义上进行修改。
      2,提高程序的运行效率,更加方便模块化。

      3, 实现多个雷

      #define EASY_COUNT 10//存放10个雷

      image.gif

      我们只需要改一下EASY_COUNT后面的数字,就可以让我们更改有存放多少雷。

      4,棋盘初始化

      打印棋盘,本质上是打印数组的内容。如下所示

      //初始化棋盘
      void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
      {
        int i = 0;
        int j = 0;
        for (i = 0; i < rows; i++)
        {
          for (j = 0; j < cols; j++)
          {
            board[i][j] = set; //将接受到的set字符,初始化数组
          }
        }
      }

      image.gif

      char set 是实参传递到形参的字符。

      实参数组名 行 可以进行省略,但是 列 不能进行省略。

      5,棋盘的打印

      打印棋盘,本质上是打印数组的内容。

      //打印棋盘
      void DisplayBoard(char board[ROWS][COLS], int row, int col)
      {
        int i = 0;
        int j = 0;
        printf("----------扫雷-----------\n");
        for (i = 0; i <= row; i++)
        {
          printf("%d ", i);//打印提醒行
        }
        for (i = 1; i <= row; i++)
        {
          printf("\n");
          printf("%d ", i);//打印提醒列
          for (j = 1; j <= col; j++)
          {
            printf("%c ", board[i][j]);//传参的字符打印
          }
        }
        printf("\n----------扫雷-----------\n");
      }

      image.gif

      效果图如下:

      image.gif编辑

      6,布置雷的信息

      在存放雷的数组内布置雷。

      //设置雷
      void SetMine(char board[ROWS][COLS], int row, int col)
      {
        int count = Lie;
        while(count)
        {
          int x = rand() % row + 1;//横坐标1~9
          int y = rand() % col + 1;//纵坐标1~9
          if (board[x][y] == '0')
          {
            board[x][y] = '1';//存放雷
            count--;
          }
        }
      }

      image.gif

      这里还用到了一个知识点【随机数】

      在实际开发中,我们往往需要一定范围内的随机数,过大或者过小都不符合要求,那么,如何产生一定范围的随机数呢?我们可以利用取模的方法:

      int a = rand() % 10;    //产生0~9的随机数,注意10会被整除
      如果要规定上下限:
      int a = rand() % 30 + 11;    //产生11~41的随机数
      分析:取模即取余,rand()%30+11 我们可以看成两部分:rand()%30 是产生 0~30 的随机数,后面+11保证 a 最小只能是 11,最大就是 30+11=41

      使用 <time.h> 头文件中的 time() 函数即可得到当前的时间(精确到秒),就像下面这样:

      srand((unsigned)time(NULL));

      7,玩家输入雷实现步骤

      这里的玩家输入坐标,在玩家输入下棋的时候,定义了个静态局部变量,在执行代码的时候。玩游戏的时候会提醒一次, 输入第一个坐标记得空一格!每次进入游戏只有一次,这里主要就是用到了 静态局部变量 就可以保证上一次的值不会被销毁。

      检查坐标处是不是雷,布置存放的是字符'1',没有放置雷存放的是字符'0'。

      判断坐标输入合法性几种情况:

      1.很遗憾,你被炸死了!
      2.该坐标非法了,请重新输入。
      3.该坐标被占用,请重新输入。
      4.恭喜你,排雷成功

      //排查雷
      void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
      {
        int x = 0;
        int y = 0;
        int win = 0;
        while (win < row * col - Lie)
        {
          printf("请输入坐标:>");
          scanf("%d %d", &x,&y);
          if (x >= 1 && x <= row && y >= 1 && y <= col)
          {
            if (show[x][y] != '*')
            {
              printf("该坐标被占用,请重新输入:>");
            }
            else
            {
              if (mine[x][y] == '1')
              {
                printf("很抱歉,你被炸死了");
                DisplayBoard(mine, ROW, COL);
                break;
              }
              else
              {
                int count = get_mine(mine, x, y);
                show[x][y] = count + '0';
                DisplayBoard(show, ROW, COL);
                win++;
              }
            }
          }
          else
          {
            printf("该坐标非法,请重新输入:>");
          }
        }
        if (win == row * col - Lie)
        {
          printf("恭喜你,扫雷成功\n");
          DisplayBoard(mine, ROW, COL);
        }
      }

      image.gif

      8,统计输入坐标周围有多少雷

      //统计雷
      int get_mine(char board[ROWS][COLS], int x, int y)
      {
        return (board[x - 1][y - 1] +
          board[x - 1][y] +
          board[x - 1][y + 1] +
          board[x + 1][y + 1] +
          board[x + 1][y] +
          board[x + 1][y - 1] +
          board[x][y - 1] +
          board[x][y + 1] - 8 * '0');//统计坐标周围多少雷
      }

      image.gif

      四,结果运行

      我们将存放80个雷,方便我们测试功能。

      image.gif编辑

      五,模块化代码实现

      1、test.c

      测试游戏的逻辑。

      #define _CRT_SECURE_NO_WARNINGS
      #include "game.h"
      mnue()
      {
        printf("*******-------扫雷----------********\n");
        printf("*******      1.play         ********\n");
        printf("*******      0.exit         ********\n");
        printf("*******-------扫雷----------********\n");
      }
      void game()
      {
        char mine[ROWS][COLS] = { 0 };
        char show[ROWS][COLS] = { 0 };
        //初始化棋盘
        InitBoard(mine, ROWS, COLS, '0');
        InitBoard(show, ROWS, COLS, '*');
        //设置雷
        SetMine(mine, ROW, COL);
        //打印棋盘
        DisplayBoard(show, ROW, COL);
        //排查雷
        FindMine(show, mine, ROW, COL);
      } 
      int main()
      {
        int input = 0;
        do
        {
          mnue();
          printf("请选择:>");
          scanf("%d", &input);
          switch (input)
          {
          case 1:
            game();
            break;
          case 0:
            printf("退出游戏\n");
            break;
          default:
            printf("请重新选择:");
            break;
          }
        } while (input);
        return 0;
      }

      image.gif

      2、game.c

      游戏和相关函数实现。

      #define _CRT_SECURE_NO_WARNINGS
      #include "game.h" 
      //初始化棋盘
      void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
      {
        int i = 0;
        int j = 0;
        for (i = 0; i < rows; i++)
        {
          for (j = 0; j < cols; j++)
          {
            board[i][j] = set; //将接受到的set字符,初始化数组
          }
        }
      }
      //打印棋盘
      void DisplayBoard(char board[ROWS][COLS], int row, int col)
      {
        int i = 0;
        int j = 0;
        printf("----------扫雷-----------\n");
        for (i = 0; i <= row; i++)
        {
          printf("%d ", i);//打印提醒行
        }
        for (i = 1; i <= row; i++)
        {
          printf("\n");
          printf("%d ", i);//打印提醒列
          for (j = 1; j <= col; j++)
          {
            printf("%c ", board[i][j]);//传参的字符打印
          }
        }
        printf("\n----------扫雷-----------\n");
      }
      //设置雷
      void SetMine(char board[ROWS][COLS], int row, int col)
      {
        int count = Lie;
        while(count)
        {
          int x = rand() % row + 1;//横坐标1~9
          int y = rand() % col + 1;//纵坐标1~9
          if (board[x][y] == '0')
          {
            board[x][y] = '1';//存放雷
            count--;
          }
        }
      }
      //统计雷
      int get_mine(char board[ROWS][COLS], int x, int y)
      {
        return (board[x - 1][y - 1] +
          board[x - 1][y] +
          board[x - 1][y + 1] +
          board[x + 1][y + 1] +
          board[x + 1][y] +
          board[x + 1][y - 1] +
          board[x][y - 1] +
          board[x][y + 1] - 8 * '0');//统计坐标周围多少雷
      }
      //排查雷
      void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
      {
        int x = 0;
        int y = 0;
        int win = 0;
        while (win < row * col - Lie)
        {
          printf("请输入坐标:>");
          scanf("%d %d", &x,&y);
          if (x >= 1 && x <= row && y >= 1 && y <= col)
          {
            if (show[x][y] != '*')
            {
              printf("该坐标被占用,请重新输入:>");
            }
            else
            {
              if (mine[x][y] == '1')
              {
                printf("很抱歉,你被炸死了");
                DisplayBoard(mine, ROW, COL);
                break;
              }
              else
              {
                int count = get_mine(mine, x, y);
                show[x][y] = count + '0';
                DisplayBoard(show, ROW, COL);
                win++;
              }
            }
          }
          else
          {
            printf("该坐标非法,请重新输入:>");
          }
        }
        if (win == row * col - Lie)
        {
          printf("恭喜你,扫雷成功\n");
          DisplayBoard(mine, ROW, COL);
        }
      }

      image.gif

      3、game.h

      关于游戏包含的函数声明,符号声明头文件的包含以及宏定义。

      #pragma once
      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      #define ROW 9
      #define COL 9
      #define ROWS ROW+2
      #define COLS COL+2
      #define Lie 80
      //初始化棋盘
      void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
      //打印棋盘
      void DisplayBoard(char board[ROWS][COLS], int row, int col);
      //设置雷
      void SetMine(char board[ROWS][COLS], int row, int col);
      //排查雷
      void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);

      image.gif


      总结

      本篇文章详细的,有体系的介绍了扫雷的结构,方便我们小白的更加理解C语言的数组。

      相关文章
      |
      1月前
      |
      C语言
      【数据结构】栈和队列(c语言实现)(附源码)
      本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
      137 9
      |
      2月前
      |
      C语言
      扫雷游戏(用C语言实现)
      扫雷游戏(用C语言实现)
      105 0
      |
      1月前
      |
      存储 搜索推荐 算法
      【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
      本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
      67 16
      |
      28天前
      |
      搜索推荐 算法 C语言
      【排序算法】八大排序(上)(c语言实现)(附源码)
      本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
      76 8
      |
      28天前
      |
      搜索推荐 算法 C语言
      【排序算法】八大排序(下)(c语言实现)(附源码)
      本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
      73 7
      |
      26天前
      |
      C语言 Windows
      C语言课设项目之2048游戏源码
      C语言课设项目之2048游戏源码,可作为课程设计项目参考,代码有详细的注释,另外编译可运行文件也已经打包,windows电脑双击即可运行效果
      32 1
      |
      1月前
      |
      C语言
      【数据结构】二叉树(c语言)(附源码)
      本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
      107 8
      |
      1月前
      |
      存储 C语言
      【数据结构】手把手教你单链表(c语言)(附源码)
      本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
      61 4
      |
      1月前
      |
      存储 C语言
      【数据结构】顺序表(c语言实现)(附源码)
      本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
      65 3
      |
      1月前
      |
      C语言
      【数据结构】双向带头循环链表(c语言)(附源码)
      本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
      43 0