c语言实现扫雷(详细讲解)

简介: c语言实现扫雷(详细讲解)

前言:


游戏规则:


我们随便点一个格子,方格即被打开并显示出方格中的数字,方格中数字则表示其周围的8个方格隐藏雷的数目.根据数字,排查出所有的雷即为游戏成功,当点击到有雷的格子时,会被炸死,游戏失败.


一、游戏设计思路介绍:


  1. 设置游戏的菜单(自由设计):


  1. 游戏函数的创建:


  1. 创建雷盘


  1. 初始化雷盘


  1. 打印雷盘


  1. 模式选择:(用于确定雷的个数)


  1. 布置雷


  1. 排查雷


  1. 自动递归循环排雷


  1. 判断输赢


效果展示



二、游戏的分步讲解


2.1、主函数测试区(test.c)基本构成


主函数测试区的作用是.设计菜单,和game函数的调用.


菜单可自由设计,牛牛就不过多介绍了.


主要介绍一下,game函数的实现:


通过调用各函数来实现游戏的总体结构,具体函数的实现放在game.c文件中.主要作用是完成游戏的总体框架.合理的调用相应的函数.


void game()
{
  //创建雷盘
  char secret[ROWS][COLS] = { 0 };
  char show[ROWS][COLS] = { 0 };
  //初始化雷盘
  initboard(secret, ROWS, COLS,'0');//初始化答案的雷盘
  initboard(show, ROWS, COLS,'*');//初始化玩家的雷盘
  //打印雷盘
  //printboard(secret, ROW, COL);//打印给自己看的答案雷盘
  printboard(show, ROW, COL);//打印给玩家的雷盘
  //布置雷
  int num = c_pattern();//模式选择函数
  setmine(secret, ROW, COL,num);//
  //printboard(secret, ROW, COL);//打印给自己看的答案雷盘
  //排查雷
  findmine(secret, show, ROW, COL,num);//排查雷
}


2.2、游戏中函数实现区(game.c) (重点)


2.21、雷盘的创建与初始化函数


如果只有一个雷盘,那么该雷盘既要保存雷的信息,又不能显示给玩家看雷的位置.这边不能很好的进行初始化雷盘.所以我们需要创建两个雷盘:


1.“秘密雷盘”:布置雷的雷盘(只给牛牛自己看的)


2.“展示雷盘”:玩家所看到的雷盘


问题:1


了解扫雷规则的小伙伴知道,当我们输入一个坐标的时候,该坐标就会显示出统计的周围八个坐标雷的个数.所以在创建雷盘的时候会遇到一个问题,玩家在排查雷盘的边角坐标时,周围八个坐标的位置很有可能会越界.


解决方法:


我们可以创建一个更大的数组,比如,当我们需要9×9的数组时,我们创建一个11×11的数组.这样就可以防止越界访问,


越界情况: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _解决方法:



雷盘的创建:


ROWS是一个宏定义的值,在函数声明区中定义.暂时可以理解为数值11.


  //创建雷盘
  char secret[ROWS][COLS] = { 0 };//秘密雷盘
  char show[ROWS][COLS] = { 0 };//展示雷盘


雷盘的初始化:


雷盘创建好之后,我们怎样为棋盘进行合理的初始化呢?


"秘密雷盘"的初始化:


我们用’字符1’表示雷的坐标


字符’0’表示不是雷的坐标.


至于为什么用字符’0’和字符’1’,后面会妙用.


"‘展示雷盘"的初始化:


为了有神秘感,又不能让玩家看见雷的坐标,我们可以全部初始化为’ * '(字符星号).


  initboard(secret, ROWS, COLS,'0');//初始化答案的雷盘
  initboard(show, ROWS, COLS,'*');//初始化玩家的雷盘


//初始化雷盘函数的实现
void initboard(char board[ROWS][COLS], int rows, int cols, char ret)
{
  //ret表示全部初始化的字符,由调用该函数时传递,方便实现代码的复用
  int i = 0,  j = 0;
  for (i = 0; i < rows; i++)
  { 
    for (j = 0; j < cols; j++)
    {
      board[i][j] = ret;
    }
  }
}


2.22、雷盘的打印函数


以9×9大小的雷盘为例子.


重点在于,函数接收的数组大小为11×11,但是我们只需要使用其中中间的9×9雷盘,所以在打印雷盘时,打印坐标的起始值为1而并非0,刚好又符号玩家的坐标需要(非程序员认为是从1开始).


简易雷盘的打印:


void printboard(char board[ROWS][COLS], int row, int col)
{
  printf("------扫雷游戏------\n");
  int i = 0, j = 0;
  for (i = 0; i <= row;i++)//打印列标
  {
    printf("%2d", i);
  }
  printf("\n");
  for (i = 1; i <= row; i++)
  {
    printf("%2d", i);
    for (j = 1; j <= col; j++)
    {
      printf("%2c",board[i][j]);
    }
    printf("\n");//打印一行后换行
  }
  printf("------扫雷游戏------\n");
  printf("\n");
}


效果图:



美观雷盘的打印:


与前面三子棋打印方法类似.


牛牛都留好注释了,没看懂备注的,可以点这里,有分步骤教学讲解:


三子棋棋盘打印


//美观雷盘:
void printboard(char board[ROWS][COLS], int row, int col)//打印棋盘//建议参照棋盘的外观查看代码
{
  printf("-----------------扫雷游戏-------------\n");
  int i = 0, j = 0;
  printf(" ");//打印空格是为了对齐(因为下面的行号占用位置)
  //打印显示在第一行的列标
  for (i = 1; i <= row; i++)
  {
    printf("%3d ", i);//因为一个数据行的格子占3个位置,所以我们这里用%3d占用三个位置的空间
  }
  printf("\n");//打印列标后换行。
  printf("  +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格,因为分割行前面没有行号占用位置,只能补空格。
  for (i = 0; i < row; i++)//打印第一行的分割线
  {
    printf("---+");
  }
  printf("\n");//每次打印一行就要换行
  //开始打印数据行
  for (i = 1; i <= row; i++)
  {
    printf("%2d", i );//打印数据行前面的行号,%2d是因为当行数>9的时候,两位数会占用两个位置,影响对齐。(细节)
    printf("|");//和上面一样,先打印一个 | ,可以观察棋盘外观,将数据行拆分打印。
    for (j = 1; j <= col; j++)//打印一行中间的棋子和其它分割线
    {
      printf(" %c |", board[i][j]);//这里打印的是“空格”“棋子”“空格”“|”
    }
    printf("\n");//每次打印一行就要换行
    //打印剩余的分割行
    printf("  +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格
    for (j = 0; j < col; j++)//打印一行外观的分割线
    {
      printf("---+");//每次打印一行就要换行
    }
    printf("\n");
  }
  printf("-----------------扫雷游戏-------------\n");
}


效果图:



2.23、模式选择函数


为了让玩家可以控制难度,牛牛设置了一个难度选择函数,根据玩家的选择来设置相应的雷的数量.


此函数重点在于,要使用getchar()函数将缓存区的清除,否则影响下面的难度选择的输入.(牛牛当时疏忽了,找了好久才找到原因,缓存区有一个换行符被直接读取给了scanf(“%c”, &pattern);😭😭😭)


//模式选择函数的实现(返回设置雷的个数):
int c_pattern()
{
  int num = 0;//表示布置雷的数量
again://玩家选择模式错误时返回到此处
  printf("欢迎玩家进入游戏:\n");
  printf("请新选择难度:(num代表雷的数量)\n");
  printf("A.简单模式:num=5  B.中等模式:num=15 C.困难模式:num=30 D.自定义难度(自由输入雷的个数)\n ");
  char pattern = 0;
  getchar();//清楚缓存区
  scanf("%c", &pattern);//玩家模式选择
  switch (pattern)
  {
  case 'A':
  case 'a':
    printf("简单模式:num=5\n");
    num = 5;
    return num;
  case 'B':
  case 'b':
    printf("中等模式:num=10\n");
    num = 15;
    return num;
  case 'C':
  case 'c':
    printf("困难模式:num=15\n");
    num = 30;
    return num;
  case 'D':
  case 'd':
    printf("自定义难度:");
    printf("请输入布置雷的个数:\n");
    getchar();//清楚缓存区
    int intput = 0;
    scanf("%d", &intput);//用户自定义的雷的个数
    num = intput;
    return num;
  default:
    printf("不好意思,牛牛还没有开发此模式,请重新选择:\n\n");
    goto again;//让玩家重新选择
    break;
  }
}


2.24、布置雷函数


布置雷的逻辑与三子棋的电脑落子逻辑上是一样的.


通过生成两个随机数,将其作为坐标,修改(秘密棋盘)该坐标的值为’1’(表示雷).


//布置雷盘函数实现
void setmine(char board[ROWS][COLS], int row, int col,int num)
{
  int x = 0, y = 0;
  int count = 0;
  for (count = 0; count < num; )
  {
    x = 1 + rand() % row;
    y = 1 + rand() % col;
    if (board[x][y] == '0')
    {
      board[x][y] = '1';
      count++;//每次布置好一个雷之后,才会计数
    }
  }
}


2.25、排查雷函数


让玩家输入要排查雷的坐标,先判断坐标的合法性,是否越界.


如果坐标合法,统计该坐标周围八个坐标有多少个雷.


如果该坐标周围没有雷,就将该坐标设置为空格,并递归排查周围八个坐标的值.


如果该坐标是雷,则游戏结束.


每次排查一个坐标后,判断玩家是否取得胜利.


//排查雷函数的实现
void findmine(char secret[ROWS][COLS], char show[ROWS][COLS], int row, int col,int num)
{
  int x = 0, y = 0;
  int win = 0;//表示被排查的雷的个数
  while (win < (row * col -num))
  {
    printf("请输入排查雷的坐标:\n格式为:行号 列标\n");
    scanf("%d%d", &x, &y);
    if (show[x][y] != '*')//被排查过的坐标不是*
    {
      printf("该坐标已经被排查过了.请重新输入:");
      continue;
    }
    if (x >= 1 && x <= row && y >= 1 && y <= col)
    {
      if (secret[x][y] == '1')//如果是1就代表是雷,游戏结束
      {
        printf("很遗憾,你失败了\n");
        printf("请看答案:\n");
        printboard(secret, ROW, COL);//失败后,给玩家看答案雷盘
        printf("很遗憾,你失败了\n上面是答案:\n");
        break;
      }
      else//此坐标不是雷
      {
        digui(secret, show, ROW, COL,x,y,&win);//自动递归排雷函数
        win=is_win(show, ROW, COL);
        printboard(show, ROW, COL);//打印给玩家的雷盘
      }
    }
    else
    {
      printf("坐标非法,请重新输入:\n");
    }
  }
  if (win == (row * col - num))//行号*列标表示总共的坐标数-已经被排查的坐标数
  {
    printf("恭喜你排雷成功\n");
    printf("牛牛为你点赞!!!\n");
  }
}


2.26、统计坐标周围雷的个数函数



由于是存放的都是字符,所以计算结果-8×’0’,得到数值.


//统计坐标周围雷的数量
int countmine(char secret[ROWS][COLS], int x, int y)
{
  int ret = secret[x - 1][y - 1]+ secret[x - 1][y] + secret[x - 1][y + 1] 
    + secret[x][y - 1] + secret[x][y + 1]
    + secret[x + 1][y - 1] + secret[x + 1][y] + secret[x + 1][y + 1] - 8 * '0';
  return ret;
}


2.27、自动递归排雷函数


如果一次只能排查一个坐标,那这游戏是不是太难了?


我们可以通过递归的方式,从这个坐标的周围八个坐标展开,进行排雷.


当然使用递归的时候一定要记住,要有限制条件,否则就会死循环调用,直到栈空间耗尽.


这里,我们执行递归的条件是:


1.周围没有雷,否则显示雷的个数,不进入递归.


2.此坐标并没有被排查过(状态是:’ * ').


//自动递归排雷函数
digui(char secret[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
  if (x >= 1 && x <= row && y >= 1 && y <= col)//防止递归的时候坐标越界
  {
    int count = countmine(secret, x, y);//计算该坐标周围有几个雷
    if (count == 0)//如果周围八个坐标没有雷
    {
      show[x][y] = ' ';//周围没有雷的坐标变为空格
      int i = 0, j = 0;
      for (i = x - 1; i <= x +1; i++)//得到周围八个坐标
      {
        for (j = y - 1; j <= y+1 ; j++)
        {
          if (show[i][j] == '*' && (i != x || j != y))//防止重新递归show[x][y]坐标
          {
            digui(secret, show, ROW, COL, i, j);
          }
        }
      }
    }
    else//如果周围有雷
    {
      show[x][y] = count + '0';
    }
  }
}


2.28、判断输赢


玩家每次输入一个坐标,进行递归排雷之后,统计目前有多少坐标已经被排查了.


计算个数后,返回值.


if (win == (row * col - num))//行号*列标表示总共的坐标数-已经被排查的坐标数=雷的数量

则玩家胜利通关.


//计算已经被排查过的位置
int is_win(char show[ROWS][COLS],int row,int col)
{
  int count1 = 0;//已经被排查的坐标个数
  int i = 0, j = 0;
  for (i = 1; i <= row; i++)
  {
    for (j = 1; j <= col; j++)
    {
      if (show[i][j] != '*')//只要不是*,表示该坐标已经被排查了.
      {
        count1++;
      }
    }
  }
  return count1;
}
目录
相关文章
|
2月前
|
C语言
C语言 扫雷详解
C语言 扫雷详解
|
2月前
|
C语言
C语言-------扫雷游戏的代码实现
C语言-------扫雷游戏的代码实现
28 0
|
2月前
|
算法 C语言 C++
【C语言-扫雷游戏全功能详解】
【C语言-扫雷游戏全功能详解】
39 1
|
4月前
|
C语言
C语言之详解数组【附三子棋和扫雷游戏实战】(二)
C语言之详解数组【附三子棋和扫雷游戏实战】(二)
|
20天前
|
C语言
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
41 1
|
1月前
|
C语言
爱上C语言:扫雷小游戏,展开一片功能实现
爱上C语言:扫雷小游戏,展开一片功能实现
爱上C语言:扫雷小游戏,展开一片功能实现
|
2月前
|
编译器 定位技术 C语言
【C语言实战项目】扫雷游戏
【C语言实战项目】扫雷游戏
28 0
|
2月前
|
存储 Serverless C语言
C语言第十二弹--扫雷
C语言第十二弹--扫雷
|
2月前
|
存储 定位技术 C语言
基于C语言实现扫雷小游戏
本文介绍了使用C语言实现扫雷小游戏的过程。扫雷是一款经典的单机游戏,玩家需要通过点击方格来揭示数字或地雷,最终清除所有非地雷方格。实现过程中,首先定义了游戏所需的数据结构,如游戏地图、玩家信息等。然后,实现了游戏的初始化、渲染、输入处理等核心功能。在游戏逻辑方面,处理了点击事件、数字计算和胜负判断等。通过不断优化和完善,最终完成了基于C语言的扫雷小游戏实现,为玩家提供了一种简单有趣的游戏体验。
26 0
|
2月前
|
机器学习/深度学习 小程序 C语言
C语言初学者:原来我也可以实现扫雷小游戏(简易版)!
C语言初学者:原来我也可以实现扫雷小游戏(简易版)!