扫雷小游戏(健全版)

简介: 扫雷小游戏(健全版)

游戏介绍。

        💡首先给我们一个雷盘,刚开始所有点都是不可见的,然后玩家猜测,如果周围八格没有雷就显示空白,然后周围坐标也没有雷的话展开至有雷的坐标,通过周围八格有几个雷就显示数字几,通过数字来判断周围雷的位置,还可以设置标记,如果排除所有的雷就算游戏成功。


越界访问问题引出。

📜说明:为了防止在后边出现问题,我们在前边就提出这个问题,如果玩家输入的是期盼边缘的位置,那么遍历周围八个位置那就会有越界的情况,例如

       💭如何解决呢?可以扩展一下数组,例如要用到三乘三的数组,我们可以创建一个四乘四的数组,只用到里面的三乘三的部分。在初始化时要传入四乘四的部分,这样就不用很麻烦特殊位置特殊处理,还不用担心越界的问题。

我们在game.h里定义一下,需要用到哪个就传哪个

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

总体思路。

       ⛳️我们仍然用分文件的方式编写,棋盘的话,利用数组来模拟实现,假设字符数组刚开始全部都为*,玩家选中该位置后,排查该位置周围的8个位置,如果有雷则改为相应雷的数量,然而我们一个字符数组既要是*,又要改变为雷的数量,那么雷放在哪里呢,再设立一个字符数组,命名为mine数组,里面装着雷,如果该位置为雷,就设立为字符1,在玩家输入坐标后,我们判断该位置是否为雷,不是雷就遍历周围的八个位置,得到雷的数量,在第一个字符数组中打印出来,第一个字符命名为show数组。


游戏的主体。

int main()
{
  srand((unsigned int)time(NULL));
  int key = 0;
  do
  {
    menu();
    printf("请选择;>");
    scanf("%d", &key);
    switch (key)
    {
    case 1:
      printf("游戏开始\n");
      game();
      break;
    case 0:break;
    default:
      printf("选择错误,重新选择\n");
    }
  } while(key);
  return 0;
}

       📘do while循环先打印菜单,菜单我们就省略了,在这里我们用输入的key来判断是否进入游戏循环(srand函数是因为后边要随机生成雷的位置。)

接下来要进行game函数的实现

      ⭐ 首先来创建两个数组,一个命名为show,一个命名为mine

初始化函数的编写。

       💭在game.c中实现,在game.h中声明。(下边实现某些功能的函数都是如此)

代码如下

void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
  int i = 0;
  for (i = 0; i < rows; i++)
  {
    int j = 0;
    for (j = 0; j < cols; j++)
    {
      board[i][j] = set;
    }
  }
}

🐞注意:我们要初始化的数组有两个,因为要初始化的内容不同,如果在函数里写死的话,要用两个初始化函数才能初始化完成,我们可以多传一个参数,传过来不同的数组初始化为不同的值。还要注意的是传参行和列要传ROWS和COLS,初始化大的数组。

实现打印功能。

void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
  printf("--------扫雷游戏-------\n");
  int i = 0;
  for (i = 1; i < row+1; i++)
  {
    int j = 0;
    for (j = 1; j < col+1; j++)
    {
      printf("%c ",board[i][j]);
    }
    printf("\n");
  }
}

       🌱注意:传入相应的数组,还有行和列,这个时候我们就不需要看外围的了,直接传ROW和COL即可。

       然而想让玩家很直观的看到每个位置的行和列,而不是一个一个位置数,可以逐行逐列打印行号和列号。优化代码如下。

void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
  printf("--------扫雷游戏-------\n");
  int i = 0;
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", i);
  }
    printf("\n");//打印列号
  for (i = 1; i < row+1; i++)
  {
    int j = 0;
    printf("%d ", i );//打印行号
    for (j = 1; j < col+1; j++)
    {
      printf("%c ",board[i][j]);
    }
    printf("\n");
  }
}

初步工作完成

在game函数中创建数组:

   char mine[ROWS][COLS] = {0};

   char show[ROWS][COLS] = {0};

在game()中初始化函数如下

   initboard(mine, ROWS, COLS, '0');

   initboard(show, ROWS, COLS, '*');

在game()函数中,我们将两个字符数组都打印出来

   displayboard(mine, ROW, COL);

   displayboard(show, ROW, COL);

📑运行后如图

布置雷。

在mine数组中,随机更改部分0变为1,需要使用到srand函数产生随机值,假设有十个雷。

//随机生成雷
void PutMine(char board[ROWS][COLS], int row, int col)
{
  int count = EASY_COUNT;
  while (count)
  {
    int x = (rand() % row) + 1;
    int y = (rand() % col) + 1;
    if (board[x][y] == '0')
    {
      board[x][y] = '1';
      count--;
    }
  }
}

👉这里使用了rand,前边我们已经在主函数中添加了srand((unsigned int)time(NULL));这里写成了一个死循环,只有当生成10个雷之后,才能跳出循环,注意的是,生成雷的坐标在ROW和COL空间内。雷的个数count被定义为EASY_COUNT,这里是我们在game.h中定义的雷的个数,方便我们后续的改写。


查找雷的环节

        👉当放置雷之后,接下来就到了,玩家输入坐标,然后判断该位置是否为雷,如果为雷,就提示玩家被炸死了,且打印雷的位置给玩家看,如果该位置不是雷,那就返回周围八个坐标雷的数量,在show数组打印出来。

void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  int x=0, y = 0;
  int flag = 0;
  while(flag<row*col- EASY_COUNT)
  {
    printf("请输入猜测坐标\n");
    scanf("%d %d", &x, &y);
    if (x>1&&x<=row&&y>1,y<=col)
    {
      if (mine[x][y] == '1')
      {
        printf("很遗憾,您被炸死了\n");
        displayboard(mine, ROW, COL);
        break;
      }
      else
      {
        if (show[x][y] != '*')
        {
          printf("该坐标已被查找,请重新输入");
        }
        //不是雷且不重复,就统计雷的个数.
        else
        {
                    system("cls");//清空屏幕
          int count = GetMineCounter(mine, x, y);
          show[x][y] = count + '0';
          displayboard(show, ROW, COL);
          flag++;
        }
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
  if (flag == row*col-EASY_COUNT)
  {
    printf("恭喜您,排雷成功\n");
    displayboard(mine, ROW,COL);
  }
}

判断循环是否结束,就是判断是否查找出所有的雷

①如果玩家输入的位置是雷,就提示游戏结束,同时打印出雷的数组,让玩家看具体那个位置是雷

②如果输入已经输入过的坐标,那就提示玩家该坐标已经被查找过。

如果输入的坐标不正确,就提示玩家,不找出所有雷或者不被炸死,循环就不会结束,跳出循环后。

③如果是被炸死,那么就没有找出所有的雷。如果没被炸死,flag一直加加,知道等于总数减去雷的数量,这是就输出排雷成功。

💡system("cls")清空屏幕,让游戏更加美观。

⭐计算某位置周围雷数量

想要知道该坐标周围雷的数量,利用GetMineCounter函数来解决,在game.h中声明,看代码,原理很简单。

GetMineCounter(char mine[ROWS][COLS], int x, int y)
{
  int i = 0;
  int j = 0;
  int count = 0;
  for (i = x - 1; i <= x + 1; i++)
  {
    for (j = y - 1; j <= y + 1; j++)
    {
      if (mine[i][j] == '1')
      {
        count++;
      }
    }
  }
  return count;
}

返回的是雷的数量,为int型的,可以根据ASCLL,在findmine函数里返回值加上字符‘0’,就将其转化为字符类型。


👑打开网页版排雷小游戏,上面有标记雷的功能,而且选中一个不是雷的位置会展开很大一片

接下来实现这两个功能。

⭐标记功能

在查找一个位置后,直接弹出选项,是否需要标记某一位置,我们在findmine函数中没有踩到雷时加入以下几行代码。

void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  int x=0, y = 0;
  int flag = 0;
  while(flag<row*col- EASY_COUNT)
  {
    printf("请输入猜测坐标\n");
    scanf("%d %d", &x, &y);
    if (x>1&&x<=row&&y>1,y<=col)
    {
      if (mine[x][y] == '1')
      {
        printf("很遗憾,您被炸死了\n");
        displayboard(mine, ROW, COL);
        break;
      }
      else
      {
        if (show[x][y] != '*')
        {
          printf("该坐标已被查找,请重新输入");
        }
        //不是雷且不重复,就统计雷的个数.
        else
        {
          int count = GetMineCounter(mine, x, y);
          show[x][y] = count + '0';
          displayboard(show, ROW, COL);
          flag++;
          printf("是否需要标记某位置:(1/0)");
          int map = 0;
          scanf("%d", &map);
          if (map == 1)
          {
            int a = 0, b = 0;
            scanf("%d %d", &a, &b);
            show[a][b] = '#';
          }
          system("cls");
          displayboard(show, ROW, COL);
        }
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    } 
  }
  if (flag == row*col-EASY_COUNT)
  {
    printf("恭喜您,排雷成功\n");
    displayboard(mine, ROW,COL);
  }
}

🔥释放一大片。

void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p)
{
  int num = GetMineCounter(mine, x, y);
  if (num == 0)
  {
    (*p)++;//++操作符的优先级比取地址符号高,所以要加括号。
    show[x][y] = '@';
    int i = 0;
    int j = 0;
    for (i = x - 1; i <= x + 1; i++)
    {
      for (j = y - 1; j <= y + 1; j++)
      {
        if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归
        {
          ExplosionSpread(mine, show, row, col, i, j, p);
        }
      }
    }
  }
  else
  {
    (*p)++;
    show[x][y] = num + '0';
  }
}

       我们将周围没有雷的坐标改为@这样清晰明了一点(棋盘太简单了,设置为空格不好看),for循环的控制部分和遍历查找雷的数量相同,找该位置周围八个坐标,如果有满足条件的就进入函数,然后不断递归,就达到了想要的效果。如果这个位置周围有雷,那就在show数组里改变这个坐标,显示出这个坐标周围雷的个数。

小技巧:将雷的数量EASY_COUNT改为1,方便调试,在game函数里,提前把show数组提前打印出来,可以在输入猜测坐标前提前知道雷的位置。

运行后效果


游戏完整代码

game.h

#pragma once
//#pragma pack(1)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 1
#define ROW 9
#define COL 9
#define COLS COL+2
#define ROWS ROW+2
void initboard(char board[ROWS][COLS], int rows,int cols,char set );
void displayboard(char board[ROWS][COLS], int row, int col);
//放置雷
void PutMine(char board[ROWS][COLS], int row, int col);
//查找雷
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
int GetMineCounter(char mine[ROWS][COLS], int row, int col);
//大面积展开
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p);

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;
  for (i = 0; i < rows; i++)
  {
    int j = 0;
    for (j = 0; j < cols; j++)
    {
      board[i][j] = set;
    }
  }
}
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
  printf("--------扫雷游戏-------\n");
  int i = 0;
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", i);
  }
  printf("\n");
  for (i = 1; i < row+1; i++)
  {
    int j = 0;
    printf("%d ", i );
    for (j = 1; j < col+1; j++)
    {
      printf("%c ",board[i][j]);
    }
    printf("\n");
  }
}
//随机生成雷
void PutMine(char board[ROWS][COLS], int row, int col)
{
  int count = EASY_COUNT;
  while (count)
  {
    int x = (rand() % row) + 1;
    int y = (rand() % col) + 1;
    if (board[x][y] == '0')
    {
      board[x][y] = '1';
      count--;
    }
  }
}
//查找雷
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  int x=0, y = 0;
  int flag = 0;
  int* p = &flag;
  while(flag<row*col- EASY_COUNT)
  {
    printf("请输入猜测坐标\n");
    scanf("%d %d", &x, &y);
    if (x>1&&x<=row&&y>1,y<=col)
    {
      if (mine[x][y] == '1')
      {
        printf("很遗憾,您被炸死了\n");
        displayboard(mine, ROW, COL);
        break;
      }
      else
      {
        if (show[x][y] != '*')
        {
          printf("该坐标已被查找,请重新输入");
        }
        //不是雷且不重复,就统计雷的个数.
        else
        {
          ExplosionSpread(mine, show, row, col, x, y, p);
          system("cls");
          displayboard(show, ROW, COL);
          flag++;
          printf("是否需要标记某位置:(1/0)");
          int map = 0;
          while ((map = getchar()) != '\n');
          scanf("%d", &map);
          if (map == 1)
          {
            int a = 0, b = 0;
            scanf("%d %d", &a, &b);
            show[a][b] = '#';
          }
          system("cls");
          displayboard(show, ROW, COL);
        }
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    } 
  }
  if (flag == row*col-EASY_COUNT)
  {
    printf("恭喜您,排雷成功\n");
    displayboard(mine, ROW,COL);
  }
}
//GetMineCounter(char mine[ROWS][COLS], int x, int y)
//{
//  return (mine[x - 1][y] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x - 1][y + 1] + mine[x - 1][y - 1] + mine[x][y + 1] + mine[x + 1][y + 1] -8*'0');
//}
GetMineCounter(char mine[ROWS][COLS], int x, int y)
{
  int i = 0;
  int j = 0;
  int count = 0;
  for (i = x - 1; i <= x + 1; i++)
  {
    for (j = y - 1; j <= y + 1; j++)
    {
      if (mine[i][j] == '1')
      {
        count++;
      }
    }
  }
  return count;
}
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p)
{
  int num = GetMineCounter(mine, x, y);
  if (num == 0)
  {
    (*p)++;//++操作符的优先级比取地址符号高,所以要加括号。
    show[x][y] = '@';
    int i = 0;
    int j = 0;
    for (i = x - 1; i <= x + 1; i++)
    {
      for (j = y - 1; j <= y + 1; j++)
      {
        if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归
        {
          ExplosionSpread(mine, show, row, col, i, j, p);
        }
      }
    }
  }
  else
  {
    (*p)++;
    show[x][y] = num + '0';
  }
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
  printf("*****************************\n");
  printf("*******   1.play    *********\n");
  printf("*******   2.exit    *********\n");
  printf("*****************************\n");
}
//在game函数里创建游戏的基本运行条件
void game()
{
  //初始化键盘
  char mine[ROWS][COLS] = {0};
  char show[ROWS][COLS] = {0};
  initboard(mine, ROWS, COLS, '0');
  initboard(show, ROWS, COLS, '*');
  displayboard(show, ROW, COL);
  //displayboard(mine, ROW, COL);
  //接下来随机生成雷
  PutMine(mine, ROW, COL);
  //displayboard(mine, ROWS, COLS);
  // 可以观察布置雷怎么样。
  displayboard(mine, ROW, COL);
  //玩家查找雷
  /*int a, b;
  scanf("%d %d", &a, &b);*/
  findMine(mine,show, ROW, COL);
}
int main()
{
  srand((unsigned int)time(NULL));
  int key = 0;
  do
  {
    menu();
    printf("请选择;>");
    scanf("%d", &key);
    switch (key)
    {
    case 1:
      printf("游戏开始\n");
      game();
      break;
    case 0:break;
    default:
      printf("选择错误,重新选择\n");
    }
  } while(key);
  return 0;
}


目录
相关文章
|
2月前
|
C语言
扫雷游戏
扫雷游戏
22 0
|
2月前
|
存储
扫雷游戏的实现
扫雷游戏的实现
|
7月前
|
C语言
扫雷游戏的实现(上)
扫雷游戏的实现
29 0
|
2月前
扫雷游戏(优化版)
扫雷游戏(优化版)
33 0
扫雷游戏(优化版)
|
11月前
|
小程序
小游戏扫雷实现教学(详解)
小游戏扫雷实现教学(详解)
179 0
小游戏扫雷实现教学(详解)
|
7月前
扫雷游戏的实现(详解)
扫雷游戏的实现(详解)
51 0
|
7月前
|
存储
扫雷小游戏
扫雷小游戏
59 0
|
7月前
|
C语言
扫雷游戏的实现(下)
扫雷游戏的实现(下)
33 0