C项目(扫雷)

简介: C项目(扫雷)

大家好,我是浪雨,今天给大家分享用C语言编写扫雷的具体思路和详细的代码,编写这个程序对知识点的要求不多,但很考验我们对知识的运用能力。

主要用到的知识是二维数组,函数,循环

在编写之前,我也看过很多博主写了关于这方面的内容,也给了详细的代码,但评论区仍有一些疑惑,所以我想以一个初学者的视角去审视,将一些问题刨析,给大家讲讲这么做的原因,以及这样做对游戏的作用,故这篇文章以回答问题的方式,讲实现游戏的思路,文章最后会附上源代码。


1.游戏为什么要用两个棋盘?

在解释这个问题之前,我们需要了解扫雷的游戏规则,在扫雷的棋盘中,你点击的那个方块为中心(假设你点击的这个方块不为0),其周围的8个方块有几个雷,那你点击的那个方块就是数字几,如下图所示

红色圈圈是我们输入的坐标,星星表示雷,其周围八个方块有两个雷,所以该坐标在显示后应该显示2,以提醒玩家,这个方块周围有2个雷,那我们在编写程序的时候,应该写一个函数来实现检测输入坐标周围雷的数目,并把这个数字放到这个坐标中。

要实现这个函数,我们需要把雷用字符1表示,非雷用字符0表示,这是因为在ASCII表中,字符1换成十进制数比字符0大一,看下图

红圈是我们选择的坐标,我们将周围8个坐标字符加在一起即'1'+'0'+'0'+"1"+'0'+'0'+'0'+'1',然后减去8*'0',得到的结果就是3,而我们红圈周围的雷的数目就是3,而我们将3+'0'在ASCII表中就对应字符3,然后将这个字符放到红圈坐标中,表示其周围有3个雷。


如果我们只用一个棋盘,那么如下图

这个字符3就会放到红圈的空间上,那我们下一步走到红圈右边的一个方格,然后调用雷的计算函数,计算其周围的雷即'0'+'0'+'0'+'3'+'0'+'0'+'1'+'0'-8*'0',这时候我们会发现,红圈上的字符3会算到雷的计算中,得出的结果是4,即该方格周围有4个雷,但这是错误的,因为这个方格周围只有一个雷,如果我们只用一个棋盘,那会导致计算雷数目的函数无法执行出我们想要的效果,那该怎么解决呢?这个时候就要用到另一个棋盘了,如下图  

在计算雷的数目的函数计算完毕后,我们将其返回值放到另一个棋盘相对应的位置上,这样红圈的位置上仍然是字符0,就不会导致误会,在程序的正常运行中,雷盘是不打印给玩家看的,只把显示盘给玩家看,这样我们把雷盘上得来的各种数据转移到显示盘上即可。


2.两个棋盘容易搞混,怎么更好的区分呢?

实际上显示盘只是一个承接体,我们输入一个坐标,会有判断函数判断这个坐标对应的是否是雷,而判断时就是查看雷盘对应坐标的字符,如果是字符1,那个踩雷了,游戏结束,如果是0,那么它周围又有几个雷呢,这个时候计算雷的函数开始计算,然后将得到的结果加上字符0(根据ASCII表的值,转换成相应的数字字符)传给显示盘,然后打印出显示盘即可,所以一般只有在接收数据和打印的时候用到显示盘,其他都是在雷盘上操作的。


3.如何实现当周围一片无雷时,能够快速展开?

当雷盘上有一片区域没有雷,那我们可以将这片无雷的区域展开,不然一个9*9的棋盘,减去10个雷的数目,需要我们输入71次,哈哈,这不像是游戏,更像是折磨了。所以写一个展开函数还是很有必要的,那么该如何实现呢?这里,我提供一些思路,看下图,黑圈是我们走的坐标,其周围一片有很多的空白区域,我们需要编写一个展开函数,将黑圈周围的空白无雷区展开,首先,我们需要调用一下计算雷的个数的函数,如果返回了0,说明周围无雷,可以展开,接下来,我们依次展开黑圈左上方,上方,右上方,左方,右方,左下方,下方,右下方  

展开后,我们发现,只展开了黑圈周围的8个方格,没有达到我们想要的效果,我们想要它自动扩展,怎么实现呢?我们刚才写的程序会到周围的每一个方格中,那我在程序走到每一个方格的时候再调用展开函数不就行了吗?如下图,黑圈是我们输入的坐标,经过调用和判断,来到了蓝圈的位置,再经过判断与调用来到了绿圈的位置,再次调用判断,发现不符合递归条件,回到上一层蓝圈的位置,然后程序来到蓝圈上方即粉红圈的位置。。。持续下去,至把无雷区展开,这就是展开的思路,后面会放上代码。

void open(char mineboard[ROWS][COLS], char showboard[ROWS][COLS],int x,int y,int *p)//无雷区域扩展实现
{
  int i, j;
  int counter=get_mine(mineboard,x,y);
  showboard[x][y] = get_mine(mineboard, x, y) + '0';
  (*p)++;
  if (counter == 0)
  {
    for (i = x - 1; i <= x + 1; i++)
    {
      for (j = y - 1; j <= y + 1; j++)
      {
        if ( showboard[i][j]=='*' && i >0 && i < 10 && j > 0 && j < 10 && get_mine(mineboard, i, j)==0)
        {
          open(mineboard, showboard, i, j,p);
        }
        else if(showboard[i][j] == '*' && i > 0 && i < 10 && j > 0 && j < 10)
        {
          showboard[i][j] = get_mine(mineboard, i, j) + '0';
          (*p)++;
        }
        else
        showboard[i][j] = get_mine(mineboard, i, j) + '0';
      }
    }
  } 
}

以上是展开函数的实现部分,这个版本的代码稍微长一点,但很好理解(显示棋盘在初始化时,统一初始化为'*')这里的好理解,是指你能够自己画图,才能理解这样写的目的,不理解为什么传指针的可以看下面一个问题


4.如何判断游戏胜利了呢?

在没有写展开函数的时候,输赢很好判断,我们把棋盘总方块数减去10个雷的数目,然后走一步减一次,直到减为0,且没踩雷,那就算赢了。但加了展开功能之后,胜利条件就要重写,因为一次展开很多,我们要详细记录到底展开了多少个格子,这也是为什么上面的展开函数要传一个指针过去,这个指针指向的就是记录已展开的格子的变量,展开一个加一个,最后方格总数减去雷数和展开的,等于0,游戏就结束了。

下面,我放上游戏所有的源代码

这是主体部分

#include"function_name.h"
//扫雷小游戏,编写结束时间2022.3.12 历时1天
void game()
{
  char mainboard[ROWS][COLS] = { 0 };//布雷盘
  char showboard[ROWS][COLS] ={0};//显示盘
  initboard(mainboard,ROWS,COLS,'0');//初始化
  initboard(showboard, ROWS, COLS,'*');//初始化
   // priboard(mainboard, ROW, COL);//打印显示盘
  priboard(showboard, ROW, COL);
  putmine(mainboard, ROW, COL);
  priboard(mainboard, ROW, COL);
  findmine(mainboard, showboard, ROW, COL);
  exit(0);
}
void test()
{
  int input=0;//此处必须给input赋值0,否则输入英文字母后程序会失控
  do   //具体原因不明,猜测为scanf的缺陷,在default之后加上exit(0)也可解决
  {       //但无法再次输出
    menu();
    printf("输入1开始,输入0退出\n");
    printf("\n");
    printf("Tip:请不要输入英文字母,否则程序将强行退出\n");
    scanf_s("%d", &input);
    srand((unsigned int)time(NULL));
    switch (input)
    {
    case 1:
      game();
      break;
    case 0:
      printf("游戏已退出\n");
      break;
    default:
      printf("输入错误,请重新输入\n");
      break;
    }
  } while (input);
}
int main()
{
  test();
  return 520;
}

这是头文件部分,里面存着各种函数的声明

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define DEGREE 10//雷的数目即游戏难度
#define ROW 9 //打印行
#define COL  9//打印列
#define ROWS ROW+2//实际行
#define COLS  COL+2//实际列
void menu();
void initboard(char board[ROWS][COLS], int x,int y,char set);//初始化棋盘
void priboard(char prinboard[ROWS][COLS], int x, int y);//打印棋盘
void putmine(char mine[ROWS][COLS],int row,int col);//埋雷
void findmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS], int row, int col);//排雷

这是函数功能的实现部分

#include"function_name.h"
void menu()//界面菜单函数
{
  printf("*************************\n");
  printf("*******扫雷小游戏*******\n");
  printf("**1.开始*********0.退出**\n");
  printf("*************************\n");
}
void initboard(char board[ROWS][COLS], int x, int y,char set)//初始化棋盘;
{
  int i, j;
  for (i = 0; i < ROWS; i++)
  {
    for (j = 0; j < COLS; j++)
    {
      board[i][j] = set;
    }
  }
}
void priboard(char prinboard[ROWS][COLS], int x, int y)//打印棋盘
{
  int i, j;
  for (i = 0; i <= x; i++)
  {
    printf(" %d ", i);
  }
  printf("\n");
  for (i = 1; i <= x; i++)
  { 
    printf(" %d ", i);
    for (j = 1; j <= y; j++)
    {
      printf(" %c ", prinboard[i][j]);
    }
    printf("\n");
  }
}
void putmine(char mine[ROWS][COLS], int row, int col)//实现埋雷的函数
{
  int counter = DEGREE;
  while (counter)
  {
    int x = rand() % ROW + 1;
    int y = rand() % COL + 1;
    if (mine[x][y] == '0')
    {
      mine[x][y] = '1';
      counter--;
    }
  }
}
int get_mine(char mineboard[ROWS][COLS],int m,int n)//返回空格中心雷的数目
{
  return mineboard[m - 1][n - 1] + mineboard[m - 1][n] + mineboard[m - 1][n + 1]
    + mineboard[m][n - 1] + mineboard[m][n + 1] + mineboard[m + 1][n - 1] +
    mineboard[m + 1][n] + mineboard[m + 1][n + 1] - 8 * '0';
 }
void open(char mineboard[ROWS][COLS], char showboard[ROWS][COLS],int x,int y,int *p)//无雷区域扩展实现
{
  int i, j;
  int counter=get_mine(mineboard,x,y);
  showboard[x][y] = get_mine(mineboard, x, y) + '0';
  (*p)++;
  if (counter == 0)
  {
    for (i = x - 1; i <= x + 1; i++)
    {
      for (j = y - 1; j <= y + 1; j++)
      {
        if ( showboard[i][j]=='*' && i >0 && i < 10 && j > 0 && j < 10 && get_mine(mineboard, i, j)==0)
        {
          open(mineboard, showboard, i, j,p);
        }
        else if(showboard[i][j] == '*' && i > 0 && i < 10 && j > 0 && j < 10)
        {
          showboard[i][j] = get_mine(mineboard, i, j) + '0';
          (*p)++;
        }
        else
        showboard[i][j] = get_mine(mineboard, i, j) + '0';
      }
    }
  } 
}
void findmine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col)//输入坐标实现排雷
{
  int x, y;
  int win = 0;
  while (ROW*COL-win-DEGREE)
  {
    printf("请输入你要走的坐标\n");
    scanf_s("%d%d", &x, &y);
    if (mineboard[x][y] == '1')
    {
      printf("你踩到雷了,游戏结束\n");
      printf("雷区分布如下,其中'1'为雷\n");
      priboard(mineboard, ROW, COL);
      break;
    }
    else
    {   
        open(mineboard, showboard, x, y, &win);
      priboard(showboard, ROW, COL);
      if (ROW * COL - win-DEGREE== 0)
      {
        printf("恭喜,你赢了\n");
      }
    }
  }
}


目录
相关文章
|
6月前
|
C语言
扫雷游戏
扫雷游戏
46 0
|
4月前
|
存储
|
11月前
|
C语言
扫雷游戏的实现(上)
扫雷游戏的实现
55 0
|
C语言
C/关于扫雷小游戏的创建
C/关于扫雷小游戏的创建
|
11月前
|
存储
扫雷小游戏
扫雷小游戏
80 0
|
小程序
扫雷小游戏详解
扫雷小游戏详解
66 0
|
C语言
经典游戏扫雷详解--你也可以写出扫雷和玩好扫雷(上)
经典游戏扫雷详解--你也可以写出扫雷和玩好扫雷(上)
151 0
经典游戏扫雷详解--你也可以写出扫雷和玩好扫雷(下)
经典游戏扫雷详解--你也可以写出扫雷和玩好扫雷(下)
|
C语言
扫雷小游戏 2020-12-29
扫雷小游戏 2020-12-29