这个小游戏你肯定玩过,但是如果你能用C语言自己写出来,那是不是体验感更好呢?看完我这篇文章,我保证你能写出来

简介: 这个小游戏你肯定玩过,但是如果你能用C语言自己写出来,那是不是体验感更好呢?看完我这篇文章,我保证你能写出来

  这个小游戏就是扫雷,相信大家对这个游戏都不陌生吧,这是电脑自带的仅有的几个小游戏之一,以前在学校上电脑课的时候关掉老师的控制偷偷玩的游戏扫雷算一个,我们以前都是玩别人做好的游戏,那如果我们自己写一个扫雷游戏出来让别人玩是不是成就感满满哒。


      那我们就开始实现这个扫雷吧。


一、初阶版本


       我们需要先创建一个测试模块test.c和游戏模块game.c、game.h


       我们先来看一下最终的效果图。



我们要先理清这个游戏的每一步是需要做什么的,需要写在测试模块里。


测试模块的代码如下,每一行都有对应的解释:


声明:下面的ROWS比ROW多2,COLS比COL多2,因为我们扫雷的游戏的规则是排查一个坐标,如果这个位置不是雷的话,我们就以这个位置为中心,检查统计周围的8个位置中又多少个雷,然后把周围雷的数目标在排查雷的位置上提醒玩家。如果我们是9*9的扫雷数组的话,那么当我们排查边上的位置的时候,如果访问边上外面的位置的话,就越界访问了,但是如果每次访问边上的位置的时候加一个判断条件的话,又显得很麻烦,所以,我们干脆把数组设置成11*11,最边上那些位置不设置雷,这样即不影响显示雷的信息,也不会导致数组的越界访问,这样设置的话是恰到好处的。


void menu(void)
{
  printf("**********************\n");
  printf("******  扫雷游戏 *****\n");
  printf("******* 1.play *******\n");
  printf("******* 0.exit *******\n");
  printf("**********************\n");
}
void game(void)
{
  char mine[ROWS][COLS] = { 0 };//创建一个设置雷的数组
  char show[ROWS][COLS] = { 0 };//创建一个呈现给玩家看的字符数组
  init_board(mine, ROWS, COLS,'0');//初始化数组全为字符‘0’
  init_board(show, ROWS, COLS, '*');//初始化数组全为字符‘*’
  set_mine(mine, ROW,COL,EASY_COUNT);//随机设置雷
  print(mine, ROW, COL);//打印有雷的数组观察雷的位置,真正玩的时候不需要打印这个
  printf("\n");
  print(show, ROW, COL);//呈现在玩家屏幕的字符数组
  printf("开始扫雷\n");
  while (1)//循环排查雷
  {
    int ret = sweep_mine(mine,show, ROW, COL);//输入坐标,排查雷,赢了或者炸死了返回1
    if (ret == 1)
    {
      break;//当全部雷排查完了或者踩到雷之后跳出循环
    }
    print(show, ROW, COL);//打印排查一次雷之后的信息
  }
  print(show, ROW, COL);
  printf("\n");
  print(mine, ROW, COL);
}
void test(void)
{
  srand((unsigned int)time(NULL));//为了后续随机生成雷
  int input = 0;
  do
  {
    menu();//打印菜单
    printf("请选择:>\n");
    scanf("%d", &input);
    switch (input)//判断是否玩游戏,1为开始游戏,0为退出游戏,其他数字为选择错误
    {
    case 1:
    {
      game();
      break;
    }
    case 0:
    {
      printf("退出游戏\n");
    }
    default:
    {
      printf("选择错误,请重新选择!\n");
      break;
    }
    }
  } while (input);
}
int main()
{
  test();
  return 0;
}


     可以看到,我们首先需要创建两个二维字符数组,一个是设置雷的数组char mine[ROWS][COLS],一个是呈现在玩家游戏界面的数组    char show[ROWS][COLS] 。


      接下来我们需要写一个函数init_board初始化两个字符数组。


1.初始化数组


void init_board(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;//把两个数组里面的每一个位置都置为字符‘set’
                              //'set'由参数传过来
                              //我们应该先把设置雷的数组mine初始化为‘0’
                              //把show数组初始化为‘*’
    }
  }
}


初始化完了之后我们应该设置雷了,雷的数目我们可以随机定,我们就来设置10个雷吧,难度小一点。


2.设置雷


//由于真正有雷的地方是在9*9的数组里面,所以我们传过来的参数应该是ROW和COL就行了
void set_mine(char mine[ROWS][COLS], int row, int col,int count)
{
  while (count)//count为设置雷的数目,可自行设定
  {
    int x = rand() % row + 1;//随机生成1-9之间的x轴坐标
    int y = rand() % col + 1;//随机生成1-9之间的y轴坐标
    if (mine[x][y] == '0')
    {
      mine[x][y] = '1';//雷设置为字符‘1’
      count--;//由于生成的随机坐标可能是重复的,所以应该是每成功设置了一个雷之后,count--
    }
  }
}


3.打印游戏界面数组


//由于真正玩游戏时呈现出来的数组是9*9的,所以只需要把参数ROW和COL传过来即可
void print(char board[ROWS][COLS], int row, int col)
{
  int i = 0;
  int j = 0;
  for (j = 0; j <= col; j++)
  {
    printf("%d ", j);
  }
  printf("\n");
  for (i = 1; i <= row; i++)
  {
    printf("%d ", i);
    for (j = 1; j <= col ; j++)
    {
      printf("%c ", board[i][j]);
    }
    printf("\n");
  }
}


然后就是正式进入游戏环节了。


4.实现一个扫雷的函数


int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
    //由于字符‘0’的ASC||值是48,字符‘1’的ASC||值49,所以8个位置的字符加起来减去8*‘0’就得到了
    //一个整数,数值为8个位置中字符‘1’的数目,返回这个数
  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');
}
int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  printf("请输入要排雷的坐标:>\n");
  int x = 0;
  int y = 0;
  int win = 0;//记录排查出的雷的个数
  while (win<row*col-EASY_COUNT)//循环进去的条件是所有雷被排查出来之前
  {
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标的合法性
    {
            //由于每排查一个位置之后该位置如果不是雷就统计周围8个位置雷的个数放到这个位置上去,
            //所以当这个位置不等于‘*’是证明该坐标被占用了
      if (show[x][y] != '*')
      {
        printf("该位置已经被排查过了,请重新输入:>");
        continue;//该次循环后面的程序就没有必要执行了,直接去到下一次循环
      }
      if (mine[x][y] == '1')//为雷,那就被炸死了
      {
        printf("很遗憾,你被炸死了!\n");
        return 1;//返回1作为一个判断游戏是否结束的判断条件(下一张图会解释)
      }
      else
      {
        int n = get_mine_count(mine, x, y);//统计周围8个位置雷的个数
        show[x][y] = n + '0';//由于show是字符数组,所以整数转为字符应该加上‘0’,
                                     //这个数的数值不变,但含义变了,把这个字符数字放到这个
                                     //位置上作为提示信息
        print(show, ROWS, COLS);
        win++;//成功排查出一个雷,win就+1
        print(show, ROW, COL);
        printf("请继续排雷:>");
      }
    }
    else
    {
      printf("坐标非法,请重新输入:>");
    }
  }
  if (win == row*col - EASY_COUNT)//当所有雷都排查出来之后,就排雷成功
  {
    printf("恭喜你,排雷成功\n");
    return 1;//返回1作为一个判断游戏是否结束的判断条件(下一张图会解释)
  }
}



      可以看到,sweep_mine前面由一个int类型的变量ret接收,后面紧接着是if语句判断,因为从上面的扫雷代码可以知道如果返回值是1的话,要么是扫雷成功了,要么是被炸死了,也就是说游戏结束了。


来到这的话初阶的扫雷游戏就写完了。你学会了吗?


下面是整个游戏的参考代码


(1.test)


#define _CRT_SECURE_NO_WARNINGS 1
//test.c
#include "game.h"
void menu(void)
{
  printf("**********************\n");
  printf("******  扫雷游戏 *****\n");
  printf("******* 1.play *******\n");
  printf("******* 0.exit *******\n");
  printf("**********************\n");
}
void game(void)
{
  char mine[ROWS][COLS] = { 0 };
  char show[ROWS][COLS] = { 0 };
  //初始化
  init_board(mine, ROWS, COLS,'0');
  init_board(show, ROWS, COLS, '*');
  set_mine(mine, ROW,COL,EASY_COUNT);
  print(mine, ROW, COL);
  printf("\n");
  print(show, ROW, COL);
  printf("开始扫雷\n");
  while (1)
  {
    int ret = sweep_mine(mine,show, ROW, COL);
    if (ret == 1)
    {
      break;
    }
    print(show, ROW, COL);
  }
  print(show, ROW, COL);
  printf("\n");
  print(mine, ROW, COL);
}
void test(void)
{
  srand((unsigned int)time(NULL));
  int input = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
    {
      game();
      break;
    }
    case 0:
    {
      printf("退出游戏\n");
    }
    default:
    {
      printf("选择错误,请重新选择!\n");
      break;
    }
    }
  } while (input);
}
int main()
{
  test();
  return 0;
}


(2.)game.c


#define _CRT_SECURE_NO_WARNINGS 1
//game.c
#include "game.h"
void init_board(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 print(char board[ROWS][COLS], int row, int col)
{
  int i = 0;
  int j = 0;
  for (j = 0; j <= col; j++)
  {
    printf("%d ", j);
  }
  printf("\n");
  for (i = 1; i <= row; i++)
  {
    printf("%d ", i);
    for (j = 1; j <= col ; j++)
    {
      printf("%c ", board[i][j]);
    }
    printf("\n");
  }
}
void set_mine(char mine[ROWS][COLS], int row, int col,int count)
{
  while (count)
  {
    int x = rand() % row + 1;
    int y = rand() % col + 1;
    if (mine[x][y] == '0')
    {
      mine[x][y] = '1';
      count--;
    }
  }
}
int get_mine_count(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');
}
int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  printf("请输入要排雷的坐标:>\n");
  int x = 0;
  int y = 0;
  int win = 0;
  while (win<row*col-EASY_COUNT)
  {
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)
    {
      if (show[x][y] != '*')
      {
        printf("该位置已经被排查过了,请重新输入:>");
        continue;
      }
      if (mine[x][y] == '1')
      {
        printf("很遗憾,你被炸死了!\n");
        return 1;
      }
      else
      {
        int n = get_mine_count(mine, x, y);
        show[x][y] = n + '0';
        win++;
        /*expendboard(mine, show, x, y);*/
        print(show, ROW, COL);
        printf("请继续排雷:>");
      }
    }
    else
    {
      printf("坐标非法,请重新输入:>");
    }
  }
  if (win == row*col - EASY_COUNT)
  {
    printf("恭喜你,排雷成功\n");
    return 1;
  }
}


(3)game.h


#pragma once
//game.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
//声明函数
void init_board(char board[ROWS][COLS], int rows, int cols,char set);
void print(char board[ROWS][COLS], int rows, int cols);
void set_mine(char board[ROWS][COLS], int row, int col,int count);
int sweep_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);


 细心的小伙伴已经发现了,真正的扫雷游戏的排雷规则不是这样的呀,这样一个一个地排查需要排到什么时候才能赢呀?确实没错,真正的扫雷游戏是并没有那么简单,所以我才说上面写的这个是的,那我们到底需要怎么改造一下上面的代码使这个游戏更完美呢?接下来我们就来写一下进阶版本的。


二、进阶版扫雷


      我们知道扫雷游戏的规则就是当你排查一个位置时,如果这个位置的周围8个位置中一个雷都没有,那它就会再分别以这8个坐标为中心,检查这个中心周围的8个位置是否有雷,如果有,则统计雷的数目并把对应的字符赋值给show数组相应的位置,如果没有,则继续以这个位置为中心排查,直到周围有雷才停止。这个扫雷的代码我们可以以递归的形式实现。


int get_mine_count(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');
}
void expendboard(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)//进行空白展开
{
  int a = 0;
  int b = 0;
  int count = get_mine_count(mine, i, j);//计算周围雷数
  if (count == 0)//如果没雷,进去
  {
    show[i][j] = ' ';//先把输入坐标的位置赋值一个空格
    for (a = i - 1; a <= i + 1; a++)//排查周围8个位置是否有雷,某个位置没雷,则继续展开
    {
      for (b = j - 1; b <= j + 1; b++)
      {
                //因为只要满足shou[a][b]为‘*’,那么就会进入递归函数
                //而进入递归函数首先又先判断这个坐标的位置是否为雷,不                    
                //是雷的情况下先把它赋值为空格,再判断以这个坐标为中心
                //的8个坐标,这样的话已经排查过的坐标就已经被赋值为空
                //格了,不再是‘*’,也不是数字,这样就不会出现死递归的情况,
          //并且当递归展开到出现数字为止,而非展到雷为止
        if (show[a][b] == '*')
                    {
                        expendboard(mine, show, a, b);//递归:连续展开
                    }
      }
    }
  }
  else
  {
    show[i][j] = count + '0';   //显示雷数
  }
}
int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
  printf("请输入要排雷的坐标:>\n");
  int x = 0;
  int y = 0;
  int win = 0;
  while (win<row*col-EASY_COUNT)
  {
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)
    {
      if (show[x][y] != '*')
      {
        printf("该位置已经被排查过了,请重新输入:>");
        continue;
      }
      if (mine[x][y] == '1')
      {
        printf("很遗憾,你被炸死了!\n");
        return 1;
      }
      else
      {
        /*int n = get_mine_count(mine, x, y);
        show[x][y] = n + '0';
        win++;*/
        expendboard(mine, show, x, y);
        print(show, ROW, COL);
        printf("请继续排雷:>");
      }
    }
    else
    {
      printf("坐标非法,请重新输入:>");
    }
  }
  if (win == row*col - EASY_COUNT)
  {
    printf("恭喜你,排雷成功\n");
    return 1;
  }
}


进阶版扫雷游戏效果图如下



 这样是不是就有真正扫雷游戏的感觉了哈。


      当然,扫雷游戏里面还应该有小红旗标志有雷的位置,这里交给大家作为拓展部分,有兴趣的小伙伴可以自行实现一下呀,记得回来评论区留言你写好的拓展部分的代码哦。

相关文章
|
27天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
54 7
|
1月前
|
C语言 C++
【C语言】编写“猜数字“小游戏
【C语言】编写“猜数字“小游戏
|
2月前
|
定位技术 API C语言
C语言——实现贪吃蛇小游戏
本文介绍了一个基于Windows控制台的贪吃蛇游戏的实现方法。首先,需调整控制台界面以便更好地显示游戏。接着,文章详细描述了如何使用Win32 API函数如`COORD`、`GetStdHandle`、`GetConsoleCursorInfo`等来控制控制台的光标和窗口属性。此外,还介绍了如何利用`GetAsyncKeyState`函数实现键盘监听功能。文中还涉及了`&lt;locale.h&gt;`库的使用,以支持本地化字符显示。
57 1
C语言——实现贪吃蛇小游戏
|
2月前
|
存储 安全 算法
C 语言——实现扫雷小游戏
本文介绍了使用二维数组创建棋盘并实现扫雷游戏的方法。首先,通过初始化数组创建一个9x9的棋盘,并添加行列标识以便操作。接着,利用随机数在棋盘上布置雷。最后,通过判断玩家输入的坐标来实现扫雷功能,包括显示雷的数量和处理游戏胜利或失败的情况。文中提供了完整的代码实现。
43 1
C 语言——实现扫雷小游戏
|
1月前
|
C语言 定位技术 API
【C语言】实践:贪吃蛇小游戏(附源码)(二)
【C语言】实践:贪吃蛇小游戏(附源码)
【C语言】实践:贪吃蛇小游戏(附源码)(二)
|
1月前
|
C语言 开发者
C语言实现猜数字小游戏(详细教程)
C语言实现猜数字小游戏(详细教程)
|
1月前
|
存储 机器学习/深度学习 编译器
一篇文章,把你的C语言拉满绩点
一篇文章,把你的C语言拉满绩点
12 0
|
2月前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
1月前
|
C语言
【C语言】实践:贪吃蛇小游戏(附源码)(三)
【C语言】实践:贪吃蛇小游戏(附源码)
|
1月前
|
存储 API C语言
【C语言】实践:贪吃蛇小游戏(附源码)(一)
【C语言】实践:贪吃蛇小游戏(附源码)