【C语言】小游戏-扫雷(清屏+递归展开+标记)

简介: 【C语言】小游戏-扫雷(清屏+递归展开+标记)

一、游戏介绍

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

排查雷的规则:

1.如果这个位置不是雷,就计算这个位置周围8个坐标有几个雷,并显示雷的个数

2.如果这个位置是雷,就炸死了,表示游戏结束

3.如果把不是雷的位置都找出来了,那就通过了


二、文件分装

实现这个扫雷游戏,我创建了三个文件

源文件:

test.c:整个游戏相关的测试

game.c:实现游戏相关的函数

头文件:

game.h:相关函数的声明,整个代码要引用的头文件以及宏定义


三、代码实现步骤

1.制作简易游戏菜单

这个简易菜单和上一篇文章三字棋一样,就不做过多的解释了

test.c

void menu()//打印简易菜单
{
  printf("************************************\n");
  printf("************   1.进入游戏  **********\n");
  printf("************   0.退出游戏  **********\n");
  printf("************************************\n");
}
int main()
{
  int input = 0;
  srand((unsigned int)time(NULL));//空指针NULL
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("输入错误,请重新输入\n");
      break;
    }
  } while (input);//注意这里要加上;
  return 0;
}

2. 初始化棋盘(11*11)

(1)定义两个数组为11*11:

定义一个数组(mine数组)表示雷的信息(1表示这个坐标是雷,0表示不是雷),另一个数组(show数组)表示排查雷的信息


假设我们要实现一个9*9的棋盘,如果我们只定义一个9*9的二维数组,计算周围8个坐标会越界,那么我们就需要将数组扩大成11*11的二维数组(上下左右分别多一排)


(2)为了简洁,传一个参数char set表示初始化的字符


初始化棋盘(9*9的外部也要初始化),因为mine和show数组不同,再传一个参数set即要初始化的字符内容


(3)宏定义ROWS,COLS,ROW,COL,EASY_COUNT


game.h

#define ROW 9//可以进行扫雷的行列
#define COL 9
#define ROWS ROW+2//进行判断的行列
#define COLS COL+2
#define EASY_COUNT 10 //雷的个数
//初始化棋盘(9*9的外部也要初始化)
void InitBoard(char board[ROWS][COLS],char ch);

game.c

//初始化棋盘(9*9的外部也要初始化),为了mine和show数组不同,再传一个参数set即要初始化的内容
void InitBoard(char board[ROWS][COLS],char ch)
{
  for (int i = 0; i < ROWS; i++)
  {
    for (int j = 0; j < COLS; j++)
    {
      board[i][j] = ch;//自己的棋盘初始化为0,展示的数组初始化为*
    }
  }
}

3.打印棋盘(9*9)

(1)打印出来的棋盘(show)得是9*9的,但是由于定义的数组就是11*11的,所以函数传参还得传11*11定义的数组


(2)因为我们这个游戏是自己输入需要排雷的位置,所以最好在棋盘的旁边打印出对应的数字,方便输入坐标

game.h

//打印棋盘(打印9*9的部分就可以了)
void DisplayBoard(char board[ROWS][COLS]);

 game.c

//打印棋盘(打印9*9的部分就可以了)
void DisplayBoard(char board[ROWS][COLS])//定义的数组是不变的,一直都是11*11
{
  printf("------扫雷------\n");//用来隔开数组
  //打印列号
  for (int i = 0; i <= COL; i++)
  {
    printf("%d ", i);
  }
  printf("\n");
  for (int i = 1; i <= ROW; i++)//传的是11*11,打印是9*9,也就是从数组下标1开始打印到下标为9
  {
    printf("%d ", i);//打印行号
    for (int j = 1; j <= COL; j++)
    {
      printf("%c ", board[i][j]);//打印字符用%c
    }
    printf("\n");
  }
  printf("------扫雷------\n");
}

4.布置雷

(1)布置雷是在mine数组上布置,定义mine数组上1代表有雷,0代表没雷(这样方便后面计算周围8个坐标的和)


(2)布置雷需要随机布置,那就得随机生成横纵坐标,跟上篇三字棋生成随机数一样rand函数


,但是注意取模结束后要+1,因为取模的结果是0-8,我们需要生成的坐标是1-9


(3)这里要注意一点:如果一个位置已经布置了雷,那么这个位置就不需要再布置了,加一条判断语句就可以解决这个问题

game.h

//布置雷
void SetMine(char mine[ROWS][COLS]);

  game.c

//布置雷
void SetMine(char mine[ROWS][COLS])
{
  int count = EASY_COUNT;
  while (count)//注意这个循环进行的次数可能大于10次,可能有的位置已经是1,或者是多次生成的坐标相同
  {
    int x = rand() % ROW + 1;//%ROW得到的范围是0-8,结果+1的范围就算1-9
    int y = rand() % COL + 1;
    if (mine[x][y] != '1')
    {
      mine[x][y] = '1';
      count--;
    }
  }
}

5.计算(x,y)周围8个坐标的和

注意求和的时候需要将字符和数字进行转换,加和的时候数组中的是字符0,而加和是数字0,字符=数字-'0' eg:'1'=1-'0'

  game.c

//计算(x,y)周围8个坐标的和
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
  return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
    mine[x][y - 1] +mine[x][y + 1] +
    mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]-8*'0';//记住要减去8个'0'变成数字
}

6.排查雷

排查雷的规则:

1.如果这个位置不是雷,就计算这个位置周围8个坐标有几个雷,并显示雷的个数

2.如果这个位置是雷,就炸死了,表示游戏结束

3.如果把不是雷的位置都找出来了,那就通过了

(1)传参需要传两个数组,mine数组用来判断是否踩中了雷,show数组打印出来看


(2)排雷需要多次进行,while语句控制结束,当棋盘剩下的格子只剩下10个雷的时间就结束,


或者踩到了雷,直接break跳出结束

game.h

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS]);

  game.c

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS])
{
  int x = 0;
  int y = 0;
  int win = 0;
  char ch = 0;
  while (win<ROW*COL-EASY_COUNT)//当非雷的坐标已经全部排出来了,即只剩下雷了就跳出循环
  {
    printf("请输入要排查雷的坐标:>");
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
    {
      if (mine[x][y] == '1')//踩中雷了
      {
        system("cls");//清空屏幕
        printf("很遗憾,你被炸死了\n");
        DisplayBoard(mine);
        break;
      }
      else//不是雷,就统计(x,y)周围有几个雷:这时候雷定义为1的好处就出来了,我们直接把周围坐标的和加起来放入show坐标的对应位置上即可
      {
        if (show[x][y] == ' ')//为了避免再次输入已经变成空格的坐标
        {
          printf("该坐标已排查\n");
          continue;
        }
        else
        {
        //展开一片,去除和为0,,并打印出和非0的位置(递归实现)
        //条件:1.这个坐标不是雷
        //2.这个坐标周围没有雷,雷的个数是0
        //3.这个坐标没有被排查过
          RemoveZero(mine, show, x, y, &win);
          system("cls");
          DisplayBoard(show);
          do 
          {
            printf("如果需要'$'对雷进行标记,请输入Y;无需标记,请按Enter键继续\n");
            while ((ch = getchar()) != '\n')//清空缓冲区
            {
              ;
            }
            scanf("%c", &ch);
            if (ch == 'Y')
            {
              //标记雷并打印
              Remark(show);
            }
            else
            {
              continue;
            }
          } while (ch != '\n');//进行多次标记
        }
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
  if (win == ROW * COL - EASY_COUNT)
  {
    system("cls");
    printf("恭喜你,排雷成功\n");
  }
}

优化:


<1>清屏后打印棋盘

system("cls");

头文件:#include<windows.h>

清屏后再打印show数组棋盘,可以让屏幕更干净简洁

system("cls");
DisplayBoard(show);
<2>递归展开

展开一片,去除和为0,,并打印出和非0的位置(递归实现)

条件:1.这个坐标不是雷

          2.这个坐标周围没有雷,雷的个数是0

          3.这个坐标没有被排查过

(1)递归展开:当mine数组计算周围8个坐标的和返回的是0,那就把show数组的这个位置变为空格,并且再看自己周围的八个坐标是否也为0,如果mine数组为0且show数组的对应位置为*,那么再对这个坐标的周围8个坐标进行爆炸式展开(show数组对应位置为*是为了防止多次把同一个位置变为空格


(2)传参记得传一个指针来使win++(即统计GetMineCount()的位置,每变一个符号,剩下的棋格数就少一个,对应又成功一步)

  game.c

//展开一片,去除和为0,并打印出和非0的位置(递归实现)
void RemoveZero(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y,int *num)
{
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//小数组(9*9)内部才需要计算
  {
    //注意加和的时候数组中的是字符0,而加和是数字0,字符=数字-'0' eg:'1'=1-'0'
    int ret = GetMineCount(mine, x, y);
    if (ret == 0)
    {
      show[x][y] = ' ';//如果这个周围坐标和是0,那就变为空格
      (*num)++;//变一个空格win也+1
      //看看周围8个坐标是否为0
      for (int i = x - 1; i <= x + 1; i++)
      {
        for (int j = y - 1; j <= y + 1; j++)
        {
          if (mine[i][j]=='0'&&show[i][j] == '*')//show[i][j] == '*'是为了避免重复
            RemoveZero(mine, show, i, j,num);
        }
      }
    }
    else
    {
      show[x][y] = ret + '0';//数字+'0'变成字符
      (*num)++;
    }
  }
}
<3>标记雷

如果show数组上想要标记的位置为*,则可以标记;如果不是*,则不可标记

   game.c

//标记雷
void Remark(char show[ROWS][COLS])
{
  int x = 0;
  int y = 0;
  while (1)
  {
    printf("请输入要标记的坐标>:\n");
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
    {
      if (show[x][y] == '*')
      {
        show[x][y] = '$';
        system("cls");
        DisplayBoard(show);
        break;
      }
      else
      {
        printf("该位置不能被标记,请重新输入\n");
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
}

四、完整代码

game.h:相关函数的声明,整个代码要引用的头文件以及宏定义

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#define ROW 9//可以进行扫雷的行列
#define COL 9
#define ROWS ROW+2//进行判断的行列
#define COLS COL+2
#define EASY_COUNT 10 //雷的个数
//初始化棋盘(9*9的外部也要初始化)
void InitBoard(char board[ROWS][COLS],char ch);
//打印棋盘(打印9*9的部分就可以了)
void DisplayBoard(char board[ROWS][COLS]);
//布置雷
void SetMine(char mine[ROWS][COLS]);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS]);

game.c:实现游戏相关的函数

#include"game.h"
//实现游戏相关的代码
//9*9的棋盘,上面布置10个雷
//排查雷
//1.如果这个位置不是雷,就计算这个位置周围8个坐标有几个雷,并显示雷的个数
//2.如果这个位置是雷,就炸死了,表示游戏结束
//3.如果把不是雷的位置都找出来了,那就通过了
//一个数组表示雷的信息(1表示这个坐标是雷,0表示不是雷),另一个数组表示排查雷的信息
//当要排查数组最外层的雷时,计算周围8个坐标会越界,这就需要把数组扩充为11*11,当然为了两个数组统一起来,两个数组都定义11*11
//初始化棋盘(9*9的外部也要初始化),为了mine和show数组不同,再传一个参数set即要初始化的内容
void InitBoard(char board[ROWS][COLS],char ch)
{
  for (int i = 0; i < ROWS; i++)
  {
    for (int j = 0; j < COLS; j++)
    {
      board[i][j] = ch;//自己的棋盘初始化为0,展示的数组初始化为*
    }
  }
}
//打印棋盘(打印9*9的部分就可以了)
void DisplayBoard(char board[ROWS][COLS])//定义的数组是不变的,一直都是11*11
{
  printf("------扫雷------\n");//用来隔开数组
  //打印列号
  for (int i = 0; i <= COL; i++)
  {
    printf("%d ", i);
  }
  printf("\n");
  for (int i = 1; i <= ROW; i++)//传的是11*11,打印是9*9,也就是从数组下标1开始打印到下标为9
  {
    printf("%d ", i);//打印行号
    for (int j = 1; j <= COL; j++)
    {
      printf("%c ", board[i][j]);//打印字符用%c
    }
    printf("\n");
  }
  printf("------扫雷------\n");
}
//布置雷
void SetMine(char mine[ROWS][COLS])
{
  int count = EASY_COUNT;
  while (count)//注意这个循环进行的次数可能大于10次,可能有的位置已经是1,或者是多次生成的坐标相同
  {
    int x = rand() % ROW + 1;//%ROW得到的范围是0-8,结果+1的范围就算1-9
    int y = rand() % COL + 1;
    if (mine[x][y] != '1')
    {
      mine[x][y] = '1';
      count--;
    }
  }
}
//计算(x,y)周围8个坐标的和
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
  return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
    mine[x][y - 1] +mine[x][y + 1] +
    mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]-8*'0';//记住要减去8个'0'变成数字
}
//展开一片,去除和为0,并打印出和非0的位置(递归实现)
void RemoveZero(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y,int *num)
{
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//小数组(9*9)内部才需要计算
  {
    //注意加和的时候数组中的是字符0,而加和是数字0,字符=数字-'0' eg:'1'=1-'0'
    int ret = GetMineCount(mine, x, y);
    if (ret == 0)
    {
      show[x][y] = ' ';//如果这个周围坐标和是0,那就变为空格
      (*num)++;//变一个空格win也+1
      //看看周围8个坐标是否为0
      for (int i = x - 1; i <= x + 1; i++)
      {
        for (int j = y - 1; j <= y + 1; j++)
        {
          if (mine[i][j]=='0'&&show[i][j] == '*')//show[i][j] == '*'是为了避免重复
            RemoveZero(mine, show, i, j,num);
        }
      }
    }
    else
    {
      show[x][y] = ret + '0';//数字+'0'变成字符
      (*num)++;
    }
  }
}
//标记雷
void Remark(char show[ROWS][COLS])
{
  int x = 0;
  int y = 0;
  while (1)
  {
    printf("请输入要标记的坐标>:\n");
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
    {
      if (show[x][y] == '*')
      {
        show[x][y] = '$';
        system("cls");
        DisplayBoard(show);
        break;
      }
      else
      {
        printf("该位置不能被标记,请重新输入\n");
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
}
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS])
{
  int x = 0;
  int y = 0;
  int win = 0;
  char ch = 0;
  while (win<ROW*COL-EASY_COUNT)//当非雷的坐标已经全部排出来了,即只剩下雷了就跳出循环
  {
    printf("请输入要排查雷的坐标:>");
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
    {
      if (mine[x][y] == '1')//踩中雷了
      {
        system("cls");//清空屏幕
        printf("很遗憾,你被炸死了\n");
        DisplayBoard(mine);
        break;
      }
      else//不是雷,就统计(x,y)周围有几个雷:这时候雷定义为1的好处就出来了,我们直接把周围坐标的和加起来放入show坐标的对应位置上即可
      {
        if (show[x][y] == ' ')//为了避免再次输入已经变成空格的坐标
        {
          printf("该坐标已排查\n");
          continue;
        }
        else
        {
        //展开一片,去除和为0,,并打印出和非0的位置(递归实现)
        //条件:1.这个坐标不是雷
        //2.这个坐标周围没有雷,雷的个数是0
        //3.这个坐标没有被排查过
          RemoveZero(mine, show, x, y, &win);
          system("cls");
          DisplayBoard(show);
          do 
          {
            printf("如果需要'$'对雷进行标记,请输入Y;无需标记,请按Enter键继续\n");
            while ((ch = getchar()) != '\n')//清空缓冲区
            {
              ;
            }
            scanf("%c", &ch);
            if (ch == 'Y')
            {
              //标记雷并打印
              Remark(show);
            }
            else
            {
              continue;
            }
          } while (ch != '\n');//进行多次标记
        }
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
  if (win == ROW * COL - EASY_COUNT)
  {
    system("cls");
    printf("恭喜你,排雷成功\n");
  }
}

test.c:整个游戏相关的测试

//测试代码
#include"game.h"
void menu()//打印简易菜单
{
  printf("************************************\n");
  printf("************   1.进入游戏  **********\n");
  printf("************   0.退出游戏  **********\n");
  printf("************************************\n");
}
void game()
{
  char mine[ROWS][COLS];
  char show[ROWS][COLS];
  //初始化棋盘(9*9的外部也要初始化)
  InitBoard(mine, '0');
  InitBoard(show, '*');
  //打印棋盘(打印9*9的部分就可以了)
  DisplayBoard(show);
  //布置雷(放在mine数组9*9中即可)
  SetMine(mine);
  //排查雷
  FindMine(mine, show);
}
int main()
{
  int input = 0;
  srand((unsigned int)time(NULL));//空指针NULL
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("输入错误,请重新输入\n");
      break;
    }
  } while (input);//注意这里要加上;
  return 0;
}

五、游戏展示

扫雷小游戏演示


本次C语言小游戏扫雷的内容就到此啦,有什么问题欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 !


目录
相关文章
|
1天前
|
存储 编译器 C语言
爱上C语言:函数递归,青蛙跳台阶图文详解
爱上C语言:函数递归,青蛙跳台阶图文详解
|
1天前
|
C语言
每天一道C语言编程(递归:斐波那契数,母牛的故事)
每天一道C语言编程(递归:斐波那契数,母牛的故事)
5 0
|
1天前
|
C语言
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
11 0
|
1天前
|
机器学习/深度学习 C语言
函数递归深入解析(C语言)
函数递归深入解析(C语言)
|
1天前
|
C语言 索引
c语言的函数与递归
c语言的函数与递归
16 1
|
1天前
|
C语言
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
48 1
|
1天前
|
搜索推荐 C语言 C++
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
|
API C语言
C语言编程之经典小游戏--贪吃蛇
C语言编程之经典小游戏--贪吃蛇
288 0
C语言编程之经典小游戏--贪吃蛇
|
机器学习/深度学习 安全 定位技术
C语言小游戏------贪吃蛇
C语言写的贪吃蛇小游戏
229 0
|
1天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)