C语言实现扫雷游戏(含展开、含标记)---完整源码与分析!!!

        在学习C语言的基础内容后,今天我们来写一个扫雷游戏进行练手。大家跟着我的思路,很快就能掌握相关技巧与逻辑。

目录

一、游戏介绍

二、C语言窗口实现

 三、分模块实现游戏功能

1、游戏主界面

2、设置棋盘及初始化       

3、设置雷的位置

4、打印棋盘

5、排雷

5.1标记

5.2排雷

5.3统计周围个数(含0展开)

四、C语言源码

1.game.c文件

2.game.h文件

3.text.c文件


        扫雷是一款经典的游戏,相信大家都玩过,如果没有玩过的朋友也不用太担心,我给大家先进行游戏介绍。

一、游戏介绍

        游戏主界面如下图所示(标准化游戏): 

规则:在一个布满方格的界面,如上左图,里面存放若干雷(以10 颗举例),当你点开一个位置时,如若有雷,会爆炸也就是意味游戏失败,如上中图,当点开的位置没有雷,会计算点开位置周围9宫格内部的含雷数量。如下图:

         显示1,表明周围8个位置的含雷数目为1个(即周围8个位置中有一个位置是雷)。如果周围8个位置没有,并且自己也不是雷,点开后,则显示空白。

        等到把没有雷的位置全部点开,只剩下十个有雷的位置,则证明游戏成功。

二、C语言窗口实现

        我们用C语言实现游戏时,不能像上面那种前端界面做的很好,而是利用C语言输出窗口进行判断,输入一个坐标进行判断,确定是否有雷,周围雷的数目,以及游戏的输赢。如下图所示:

        如上图,左图为初始界面,中图为游戏中,有图为游戏输界面,其中1表示雷的位置。    

            在用C语言实现的时候 ,由于代码的功能太过于繁琐,不能将其写在一个文件中,故创建三个文件进行编写。

1、game.c文件,用于书写游戏的主函数,包括游戏的整个逻辑框架;

2、game.h文件,用于书写所用到的头文件以及函数的声明;

3、 text.c文件,用于书写各个功能模块函数;

游戏的整个框架为:

第一步:游戏主界面

第二步:设置游戏

第三步:扫雷

 三、分模块实现游戏功能

1、游戏主界面

        此处为开始,选择是否进入游戏,输入1进入,输入0退出游戏,输入其他数字,输出-选择错误,请重新输入。

        等到游戏结束后,此界面还需要再次出现供玩家选择,因此,此处需要使用一个do……while循环,先执行一次循环,在进行判断。

代码为:

//text。c文件
int main()
{
     int input=0;   
    do
    {
        menu();//打印菜单
    printf("请输入你的选择:\n");
     scanf("%d",& input);
        switch( input)//根据输入的数字选择情况
        {
          case 1:
            game();//进入游戏函数
            break;
         case 0:
            printf("退出游戏\n");
            break;
         default:
            printf("选择错误,请重新输入:\n");
            break;
        }
     }
    while( input);
    return 0;
}

 其中菜单函数的代码为:

//text.c文件
void menu()
{
    printf("****************************************\n");
    printf("***************1.play*******************\n");
    printf("***************0.exit*******************\n");
    printf("****************************************\n");
}

其代码效果为:

2、设置棋盘及初始化       

        在进入游戏后,首先要进行棋盘的创建:

棋盘规划:

1、底层棋盘(mine)--用于布置雷的位置(不会显示出来),并且用来判断输赢

2、显示棋盘(show)--用于玩家游戏的棋盘(给玩家显示),便于玩家输入。

棋盘的大小:

        在此,构建两个二维数组进行棋牌的构建,二维数组的大小应该是棋牌的行数/列数+2,比如:如果是9×9的棋盘,应该构建11×11的数组。因为需要判断棋盘每一个位置周围的雷的数目,如果二维数组的大小和棋盘的大小相同,后面判断最外层棋盘位置的雷的数目时,会进行数组越界访问。

构建棋盘代码:

//game.h文件
#define ROW 9//定义棋盘的行数
#define COL 9//定义棋盘的列数

#define ROWS ROW+2//定义数组的行数
#define COLS COL+2//定义数组的列数
//game.c文件
void game()//定义游戏函数
{
    char mine[ROWS][COLS]={0};//定义底层雷的二维数组
    char show[ROWS][COLS]={0};//定义显示层的二维数组
}

 初始化棋盘:

        将构建的二维数组中,底层数组所有位置设置为‘0’(还未设置雷的位置,雷用‘1’设置)。显示层数组的所有位置为‘*’(还未测试,测试后用别的字符代替);

        定义一个打印函数,在game函数中调用两次,分别打印底层界面与显示层界面。在打印函数中,利用for循环进行遍历初始化。

代码为:

//game.c文件
void game()
{
    char mine[ROWS][COLS]={0};//定义底层雷的二维数组
    char show[ROWS][COLS]={0};//定义显示层的二维数组
    //初始化数组的内容为指定的内容
    //mine 数组在没有布置雷的时候 都是'0'
    //show 数组在没有排查雷的时候 都是'*'
    Initboard(mine,ROWS,COLS,'0');//调用打印函数,用于打印底层数组
    Initboard(show,ROWS,COLS,'*');//调用打印函数,用于打印显示层数组
}
//text.c文件
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;//根据不同打印的参数,打印不同的数组
        }
    }
}

3、设置雷的位置

         首先定义一下雷的数量,并且要方便以后的修改。

        SetMine函数进行雷的生成,通过库里面的函数rand()产生随机数,并且判断产生的随机数是否符合棋盘的位置(此处为棋盘的位置并不是之前二维数组的位置),注意位置大小。使用rand()函数时,必须在主函数中进行修饰: srand((unsigned int )time (NULL));,不然,每次生成的随机数都是相同的。

代码为:

//game.h文件
#define EASY_COUNT 10

//game.c文件
void game()
{
    char mine[ROWS][COLS]={0};//定义底层雷的二维数组
    char show[ROWS][COLS]={0};//定义显示层的二维数组
    //初始化数组的内容为指定的内容
    //mine 数组在没有布置雷的时候 都是'0'
    //show 数组在没有排查雷的时候 都是'*'
    Initboard(mine,ROWS,COLS,'0');
    Initboard(show,ROWS,COLS,'*');

      SetMine(mine,ROW,COL);//雷的位置在底层数组中,其范围为棋盘的范围,注意传参的大小
}

// game.c文件主函数中
//设置随机数的生成起点
     srand((unsigned int )time (NULL));
//text.c文件
void SetMine(char board[ROWS][COLS],int row,int col)
{
    int count = EASY_COUNT;
  //横坐标范围1-9,纵坐标范围1-9
    while (count)
    {
        int x=rand()%row+1;//产生的随机数%行数+1,便使得其位于棋盘横坐标之中
        int y=rand()%col+1;//产生的随机数%列数+1,便使得其位于棋盘纵坐标之中
        if(board[x][y]=='0')
        {
            board[x][y]='1';
            count--;//产生一个雷,数量减少
        }
    }
}

4、打印棋盘

        打印棋盘,只需要打印显示数组即可,利用for循环进行遍历,为了便于玩家输入,在打印时每一行,每一列都标有行号与列号。

代码如下:

//game.h 文件中 game()函数内容 
DisplayBoard(show,ROW,COL);
//text.c文件
void DisplayBoard(char board[ROWS][COLS],int row,int col)
{
    int i, j=1;//定义循环变量
   printf("--------扫雷游戏----------\n");//便于划分
  for(j=0;j<=col;j++)//打印列号
    {     
     printf("%d ",j);
    }
     printf("\n  ");//便于划分
   for(j=1;j<=col;j++)//便于划分
  {
      printf("--");
  }
  printf("\n");
    for(i=1;i<=row;i++)//打印数组元素
    {
        printf("%d|",i);
        for(j=1;j<=col;j++)
        printf("%c ",board[i][j]);
        printf("|");
        printf("\n");
    }
    printf("  ");
      for(j=1;j<=col;j++)
          {
                printf("--");
          }
            printf("\n");
           printf("--------扫雷游戏----------\n");
}

效果图:

5、排雷

5.1标记

        我们在进行排雷的时候,会根据已有的信息进行计算或猜测雷的位置,如果将这些位置进行标记,可以大大提高我们的排雷效率这样。

        因此,我们在每一次检测某一位置前,进行判断是否需要标记,如果需要标记,输入标记的坐标,如果坐标合理,不超出棋盘范围,就进行标记,标号为'#',标记在显示层界面,如果不需要标记我们再进行检测。

代码如下:

//text.c文件 FindMine()函数中   
 Biaoji(show,ROW,COL);


//text.c文件
void Biaoji(char show[ROWS][COLS],int row,int col)//定义标记函数
{
    int z;
    int x=0;
    int y=0;
        while(1)
        {
              printf("您是否需要标记雷的位置,如果是,请输入1,如果否,请输入0:\n");
              scanf("%d",&z);
              if(z==1)//需要标记
              {
                      printf("请输入要标记的坐标:\n");
                      scanf("%d%d",&x,&y);
                           if(x>=1&&x<=row && y>=1&&y<=col)
                               {
                                   show[x][y]='#';
                                DisplayBoard(show,ROW,COL);
                               }
                           else
                              {
                             printf("输入坐标非法,请重新输入。\n");
                              }

              }

              else if(z==0)//不需要标记
                break;
               
             else//输入错误
             printf("输入错误请重新输入:\n");
        }
}

结果:

5.2排雷

        进行检测,先判断输入坐标是否满足棋盘坐标合法,如果不满,重新输入;如果满足进入检测,检测时判断是否检测过,如果检测过,重新输入。如果没被检测过,进行检测,判断其是否为雷,如果为雷,则为失败,如果不是雷,进入判断周围雷数量的函数,统计周围个数。最后判断检测次数是否为0,若为0,则成功,若不为0,继续检测。

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
    int x,y;
    int win=WIN_COUNT ;
    //测试棋盘非雷的次数(当测试的次数等于棋盘总位置-雷的数量,且没被炸死即为获胜)
do
    {
        Biaoji(show,ROW,COL);//调用是否需要标记函数
        printf("请输入要排查的坐标:\n");//输入需要检测的坐标
        scanf("%d%d",&x,&y);
        if(x>=1&&x<=row && y>=1&&y<=col)//如果坐标合法
          {
            if(show[x][y]=='*'||show[x][y]=='#')//判断是否进行检测过
            {
                   if(mine[x][y]=='1')//如果没检测过,检测结果为1就是雷
                     {
                      printf("很遗憾,您被炸死!\n");//被炸死
                      DisplayBoard(mine,ROW,COL);
                      break;
                     }
                  else//如果没被炸死,进行判断周围有几个雷
                     {
                       int *p;
                       p=&win;//将检测次数传至判断周围雷个数的函数中
                       get_mine_count(mine,show,x,y,p);//统计mine数组中x,y坐标周围有几个雷
                       DisplayBoard(show,ROW,COL);//打印显示棋盘
                      }
            }
       
            else//如果坐标已经被查就重新选择
             printf("该坐标已被排查,请重新选择!\n");
           }


       else//如果坐标非法,就重新输入
        {
            printf("输入坐标非法,请重新输入。\n");
             continue;
        }
    
    }
    while(win!=0)//测试的次数还未减为0,进行测试

if(win==0)//判断检测次数是否为0,若为0,则成功
    {
     printf("恭喜您,排雷成功!\n");
    DisplayBoard(mine,ROW,COL);//打印雷棋盘
    }
}

部分结果显示:

 

5.3统计周围个数(含0展开)

        在进行某一位置进行检测时,如果检测的位置不是雷,此时应该计算周围八个坐标的雷数,如下所示:

        在中间的位置中,显示的为4,则周围的8个地方的雷数之和就是四。

        我们判断完不是雷后,进入 get_mine_count函数进行计算周围位置的雷数之和。首先判断需要统计的位置是否满足要求:

1、该坐标是否超限;

2、该坐标是否已被计算过;

        满足要求的坐标进行统计,每统计一次,需要检测的次数减一(判断最终取胜的条件),判断完后,需要在底层坐标中将其改为'-'(用于后面判断该位置是否进行过统计),在显示层坐标中,显示统计的结果(用于玩家判断周围雷的位置)。

        采用for循环进行遍历的方式,如果位置有雷 z++,将最终的值进行显示;

        如果经过统计,该位置周围的8个位置都没有0,此时显示层该位置处应该显示为空,并且进一步扩大展开,判断周围这8个位置的雷数,并显示出来。如下图所示:

        当中间蓝色位置的周围灰色8个位置的雷数为零时,这时候我们就可以大胆的点这八个位置,游戏为了简便,便给我们一起就点开了,当然开始计算灰色位置它们自己周围8个雷数的综合并显示出来,此时便用到了递归,统计周围8个位置的雷数,每一次统计,检测次数win都要减一(用于判断输赢的条件),此处需要注意的时,如果上右图的蓝色圈位置周围雷数又为0.则需要进一步展开,这样右图5的位置就进行了重复,陷入死循环,因为它既是0位置的周围8个位置之一,也是蓝色圈位置的周围8个之一。但是我们提前有规定,在进入统计的时候,会对坐标有统计条件,统计过的位置不会再重复统计,这也就是说一个位置只会统计一次,这一点非常重要,不就会在递归调用的时候陷入死循环。还请读者好好理解。 

void get_mine_count(char mine[ROWS][COLS],char show[ROWS][COLS], int x,int y,int *win)
{
    if(x>=1&&x<=ROW && y>=1&&y<=COL&& mine[x][y]!='-')//判断输入坐标合法,并且未被统计过
    {

         int i,j;
         int z=0;
           mine[x][y]='-';//进行统计,统计完后令其为-
            (*win)--;//统计一次,检测次数减一
       for(i=-1;i<=1;i++)//进行周围雷的数量统计
        {
          for(j=-1;j<=1;j++)
          {
              if( mine[x+i][y+j]=='1')
                z++;
          }
        }
    show[x][y]=z+'0';//在显示界面层显示雷的数量
    if(z==0)//如果周围雷的数量为0,则进一步展开统计
    {
      show[x][y]=' ';若周围雷的数量为0,则显示空
      for(i=-1;i<=1;i++)
          {
              for(j=-1;j<=1;j++)
              get_mine_count( mine, show,x+i,y+j,win);递归周围位置,检测周围位置的数量。
          }

    }

     }
}

         最后判断检测次数是否为0,若为0,则成功,若不为0,继续检测。至此,各个模块的功能与代码介绍完毕。

四、C语言源码

1.game.c文件

#include "game.h"

void menu()
{
    printf("****************************************\n");
    printf("***************1.play*******************\n");
    printf("***************0.exit*******************\n");
    printf("****************************************\n");
}
void game()
{
    char mine[ROWS][COLS]={0};
    char show[ROWS][COLS]={0};
    //初始化数组的内容为指定的内容
    //mine 数组在没有布置雷的时候 都是'0'
    //show 数组在没有排查雷的时候 都是'*'
    Initboard(mine,ROWS,COLS,'0');
    Initboard(show,ROWS,COLS,'*');
  
    //设置雷
      SetMine(mine,ROW,COL);
     
      DisplayBoard(show,ROW,COL);
   
     //排查雷
      FindMine(mine,show,ROW,COL);
}

int main()
{
     int input=0;
     //设置随机数的生成起点
     srand((unsigned int )time (NULL));
    do
    {
    menu();
    printf("请输入你的选择:\n");
    scanf("%d",& input);
   
    switch( input)
    {
      case 1:
            game();
            break;
     case 0:
            printf("退出游戏\n");
            break;
     default:
            printf("选择错误,请重新输入:\n");
            break;
    }

        }
    while( input);
    return 0;
}

2.game.h文件

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 1
#define WIN_COUNT  ROW*COL-EASY_COUNT


void Initboard(char board[ROWS][COLS],int rows ,int cols,char);
void DisplayBoard(char board[ROWS][COLS],int row,int col);
void SetMine(char board[ROWS][COLS],int row,int col);
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);

3.text.c文件

#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;
        }
    }
}


void DisplayBoard(char board[ROWS][COLS],int row,int col)
{
    int i=1;
    int j=1;
    printf("--------扫雷游戏----------\n");
    for(j=0;j<=col;j++)
      {
         printf("%d ",j);
      }
    printf("\n  ");
    for(j=1;j<=col;j++)
     {
         printf("--");
     }
    printf("\n");
    for(i=1;i<=row;i++)
     {
        printf("%d|",i);
        for(j=1;j<=col;j++)
        printf("%c ",board[i][j]);
        printf("|");
        printf("\n");
     }
    printf("  ");
   for(j=1;j<=col;j++)
    {
      printf("--");
    }
    printf("\n");
    printf("--------扫雷游戏----------\n");
}



void get_mine_count(char mine[ROWS][COLS],char show[ROWS][COLS], int x,int y,int *win)
{
    if(x>=1&&x<=ROW && y>=1&&y<=COL&& mine[x][y]!='-')
    {

         int i,j;
         int z=0;
         mine[x][y]='-';
         (*win)--;
         for(i=-1;i<=1;i++)
          {
            for(j=-1;j<=1;j++)
            {
              if( mine[x+i][y+j]=='1')
                z++;
            }
          }
         show[x][y]=z+'0';
         if(z==0)
        {
          show[x][y]=' ';
          for(i=-1;i<=1;i++)
          {
            for(j=-1;j<=1;j++)
            get_mine_count( mine, show,x+i,y+j,win);
          }
        }
}



void SetMine(char board[ROWS][COLS],int row,int col)
{
    int count = EASY_COUNT;
    //横坐标范围1-9,纵坐标范围1-9
    while (count)
    {
        int x=rand()%row+1;
        int y=rand()%col+1;
        if(board[x][y]=='0')
        {
            board[x][y]='1';
            count--;
        }

    }
}



void Biaoji(char show[ROWS][COLS],int row,int col)
{
    int z;
    int x=0;
    int y=0;
            while(1)
            {
              printf("您是否需要标记雷的位置,如果是,请输入1,如果否,请输入0:\n");
              scanf("%d",&z);
              if(z==1)
              {
                  printf("请输入要标记的坐标:\n");
                  scanf("%d%d",&x,&y);
                       if(x>=1&&x<=row && y>=1&&y<=col)
                       {
                           show[x][y]='#';
                        DisplayBoard(show,ROW,COL);
                       }
                      else
                       {
                         printf("输入坐标非法,请重新输入。\n");
                      }
              }
              else if(z==0)
                break;
              else
                printf("输入错误请重新输入:\n");
        }
}


void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
   
    int x=0;
    int y=0;
    int win=WIN_COUNT ;
    //找到非0的次数
do

  {
    Biaoji(show,ROW,COL);
        printf("请输入要排查的坐标:\n");
        scanf("%d%d",&x,&y);
        if(x>=1&&x<=row && y>=1&&y<=col)
            {
                if(show[x][y]=='*'||show[x][y]=='#')
                    {

                       if(mine[x][y]=='1')
                         {
                              printf("很遗憾,您被炸死!\n");
                              DisplayBoard(mine,ROW,COL);
                              break;
                         }
                      else
                         {
                              int *p;
                              p=&win;
                              get_mine_count(mine,show,x,y,p);//统计mine数组中x,y坐标周围有几个雷
                              DisplayBoard(show,ROW,COL);
                          }
                    }
                else
                    {
                       printf("该坐标已被排查,请重新选择!\n");
                    }

            }


        else
           {
            printf("输入坐标非法,请重新输入。\n");
            continue;
           }
   }while(win!=0);
      
       if(win==0)
      {
         printf("恭喜您,排雷成功!\n");
        DisplayBoard(mine,ROW,COL);
      }
}

        完结撒花,创作不易,还请多多点赞支持~~~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值