7

【C语言】 扫雷游戏(保姆级的实现过程)_3月月更_泽En_InfoQ写作平台

 2 years ago
source link: https://xie.infoq.cn/article/95c76c08ba78072c52ae44b01
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

​​​​​​​Hello,大家好我是泽 En,一起共同学习,多多指教(●'◡'●)

扫雷游戏,想必大家都有玩过吧。没完过的话也可以试着玩一玩,这样对写扫雷游戏这个小游戏的化是会有一个很好的思路的。那么本片博客就来介绍如何实现扫雷游戏的具体步骤。

扫雷游戏链接👉 扫雷游戏网页版 - Minesweeper


Ⅱ 模块化编程

再说实现三子棋逻辑思路前,我们来说说什么是 模块化编程 吧🤔

传统方式编程:所有的函数均放在 main.c 里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路。

模块化编程:把各个模块的代码放在不同的.c 文件里,在.h 文件里提供外部可调用函数的声明,其它.c 文件想使用其中的代码时,只需要 #include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。

传统方式编程:所有的函数均放在 main.c 里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路。

模块化编程:把各个模块的代码放在不同的.c 文件里,在.h 文件里提供外部可调用函数的声明,其它.c 文件想使用其中的代码时,只需要 #include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等!

总的来说就是:当你代码比较多的时候,就可以采用模块化编程来完成这个程序😜


Ⅲ 游戏思路与逻辑

  1. 创建菜单界面函数选择退出游戏或者是进入游戏。

  2. 存放布置好雷的新星以及存放排查出雷的信息。

  3. 首先,进行雷的初始化棋盘。

  4. 然后,再打印出雷的初始化棋盘。注意:一定是要先进行 初始化 然后再 打印棋盘。

  5. 接着,就可以布置雷的信息了。

  6. 最后,输入排查雷的坐标。

  7. 检查出的坐标是不是雷,布置雷存放的是字符(1) 没有放置的是字符(0)

  8. 输入坐标的时候一共有④种情况:《很遗憾,你被炸死了!》、《宁已经在这里输入过坐标了,请重新输入!》、《宁输入的坐标范围错误!重新输入》、《恭喜你,排雷成功!太优秀了!》

  9. 然后,再回到步骤①,是否选择 进入游戏 以及 退出游戏。


Ⅳ 实现游戏步骤/过程


① 创建颜色函数

🖊创建颜色函数 color()

前景色颜色的对应值↓

0=黑色                8=灰色  1=蓝色                9=淡蓝色        十六进制                                  2=绿色                10=淡绿色        A          3=湖蓝色              11=淡浅绿色      B 4=红色                12=淡红色        C  5=紫色                13=淡紫色        D          6=黄色                14=淡黄色        E         7=白色                15=亮白色        F

color()创建颜色函数如下↓

void color(short x)  {  if (x >= 0 && x <= 15)    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);     else    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);}

使用颜色函数的好处实际上无非就是让程序运行看上去更加美观、鲜明,实际上没什么实际作用,这点是我们要知道的。

这里的 STD_OUTPUT_HANDLE 需要引头文件 #include<Windows.h> ,才可以进行使用。


② 菜单界面(menu)

菜单界面函数实际上就像是我们的一个界面,就好比是游戏的界面目录,餐馆当中的菜单。一样的道理。这个是库函数就有的我们只需要直接引用下即可。示例代码如下↓

void menu(){  color(0);       //Black 黑色  system("cls");  //清屏.  color(10);  printf("|-----------|扫雷游戏|-----------|\n");  printf("|********************************|\n");  printf("|★★★★★★★★★★★★★★★★|\n");  printf("|★★   1.开始  0.退出       ★★|\n");  printf("|★★★★★★★★★★★★★★★★|\n");  printf("|0 = 不是雷 ---------- 1 = 它是雷|\n");  printf("|--------------------------------|\n");}

注→在这里用到了一个 system("cls"); 达到了一个清屏的效果,只有加了这个,你才可以让 cmd 中的界面全部为黑色。因为我们还在这个清屏指令上+color(0); 这个代表的是,黑色。


③ 实现多行多列扫雷

#define ROW 9#define COL 9

使用 #define 宏定义在这里的好处:

  1. 方便程序的修改,不用对整个程序进行修改,只需对宏定义上进行修改。

  2. 提高程序的运行效率,更加方便模块化。

  3. 在三子棋基础上,只需改变宏定义的值,就可以实现多子棋的效果。

在程序当中,是 9 行 9 列的,如果想修改成 10 行 10 列的只需要把 #define 改成 10 即可!

10 行 10 列的扫雷棋盘如下 👇

但是十行十列就是会出现这个的情况,当然这个问题也是非常的好解决的。大家可以看下怎么去解决这个问题。☆⌒(*^-゜)v THX!!


④ 实现多个雷

#define Thunder 10

这里 #define 在上面提到过,就不提了。

修改雷的个数也只需要把这上面的数字(10)修改变的数字修改,当然修改请理性修改,你不可能 9*9 的棋盘,给很多雷吧,那还怎么玩哈哈哈😤。

比如修改成 40 个雷,看看效果如下所示 👇

⑤ 棋盘初始化

打印棋盘,本质上是打印数组的内容。如下所示👇

void Initialization(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;    }  }}

char set 是实参传递到形参的字符。

实参数组名 可以进行省略,但是 不能进行省略。


⑥ 棋盘的打印

打印棋盘,本质上是打印数组的内容,这里数组的内容是字符 '0' 。如下所示👇

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

打印棋盘的效果图,如下所示👇


⑦ 布置雷的信息

打印完棋盘之后,就开始布置雷。

void Lay_thunder(char Findout[ROWS][COLS], int row, int col){  //布置雷  int count = Thunder;  while (count)  {    int x = rand() % row + 1;    int y = rand() % col + 1;    if (Findout[x][y] = '0')    {      Findout[x][y] = '1';      count--;    }  }}

这里还用到了一个知识点【随机数】

在实际开发中,我们往往需要一定范围内的随机数,过大或者过小都不符合要求,那么,如何产生一定范围的随机数呢?我们可以利用取模的方法:

int a = rand() % 10; //产生0~9的随机数,注意10会被整除

如果要规定上下限:

int a = rand() % 51 + 13;    //产生13~63的随机数

分析:取模即取余,rand()%51+13 我们可以看成两部分:rand()%51 是产生 0~50 的随机数,后面+13保证 a 最小只能是 13,最大就是 50+13=63

使用 <time.h> 头文件中的 time() 函数即可得到当前的时间(精确到秒),就像下面这样:

srand((unsigned)time(NULL)); 

🍁注意:这个在程序当中是只执行一次即可!


⑧ 玩家输入雷实现步骤

这里的玩家输入坐标,在玩家输入下棋的时候,定义了个静态局部变量,在执行代码的时候。玩游戏的时候会提醒一次, 输入第一个坐标记得空一格!每次进入游戏只有一次,这里主要就是用到了 静态局部变量 就可以保证上一次的值不会被销毁。

检查坐标处是不是雷,布置存放的是字符'1',没有放置雷存放的是字符'0'。

判断坐标输入合法性几种情况:

  1. 很遗憾,你被炸死了!

  2. 宁已经在这里输入过坐标了,请重新输入!

  3. 宁输入的坐标范围错误!重新输入。

  4. 恭喜你,排雷成功!太优秀了!

void Check(char Layouts[ROWS][COLS], char Findout[ROWS][COLS], int row, int col){  //1.输入排查雷的坐标  //2.检查坐标处是不是雷,布置雷存放的是字符'1',没有放置雷存放的是字符'0'。  int x, y;  int win = 0;  while (win<row*col - Thunder)  {    static int j = 1;//延长生明周期,    while (j)    {      color(8);      printf("--------------------------\n");      printf("[输入第一个坐标记得空一格!]\n");      printf("--------------------------\n");      j--;      break;    }    color(11);    printf("---------------\n");    printf("请输入坐标>:");    //x与y坐标范围 1~9    scanf("%d %d", &x, &y);    printf("---------------\n");    //判断坐标的合法性    if (x >= 1 && x <= row && y >= 1 && y <= col)    {      if (Layouts[x][y] == '1')      {        printf("|══════════════════|\n");        printf("|很遗憾,你被炸死了!|\n");        printf("|══════════════════|\n");        Print_board(Layouts, ROW, COL);        Sleep(5000);        break;      }      if (Findout[x][y] == '0')      {        color(6);        printf("|═══════════════════════════════════|\n");        printf("|宁已经在这里输入过坐标了,请重新输入!|\n");        printf("|═══════════════════════════════════|\n");      }      if (Findout[x][y] == '1')      {        color(6);        printf("|════════════════════════════════════|\n");        printf("|宁已经在这里输入过坐标了,请重新输入!|\n");        printf("|════════════════════════════════════|\n");      }      else      {        //不是雷情况下,统计x,y周围坐标有几个雷        int Count = Statistics(Layouts, x, y);        Findout[x][y] = Count + '0';        Print_board(Findout, row, col);        win++;      }    }    else    {      printf("|═════════════════════════════|\n");      printf("|宁输入的坐标范围错误!重新输入|\n");      printf("|═════════════════════════════|\n");    }  }  if (win == row*col - Thunder)  {    printf("|═══════════════════════|\n");    printf("|恭喜你,排雷成功!太优秀了!|\n");    printf("|═══════════════════════|\n");    Print_board(Findout, ROW, COL);  }}

⑨ 排查 x,y 周围有多少雷

static int Statistics(char Layouts[ROWS][COLS], int x, int y){  return Layouts[x-1][y-1]+    Layouts[x][y-1] +    Layouts[x+1][y-1]+    Layouts[x-1][y]+    Layouts[x+1][y]+    Layouts[x-1][y+1]+    Layouts[x][y+1]+    Layouts[x+1][y+1] - 8*'0';}

🍁注意:静态局部变量去修饰函数的时候,让这个函数只能在自己所在的源文件内看到,其它的内部当中是看不到的。


Ⅴ 结果演示

被雷"砸死"的结果演示。


Ⅵ 模块化代码实现


(一)、test.c

测试游戏的逻辑。

//扫雷游戏的测试#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>#include "game.h"void menu(){  color(0);       //Black 黑色  system("cls");  //清屏.  color(10);  printf("|-----------|扫雷游戏|-----------|\n");  printf("|********************************|\n");  printf("|★★★★★★★★★★★★★★★★|\n");  printf("|★★   1.开始  0.退出       ★★|\n");  printf("|★★★★★★★★★★★★★★★★|\n");  printf("|0 = 不是雷 ---------- 1 = 它是雷|\n");  printf("|--------------------------------|\n");}void game(){  printf(" ---------\n");  printf("|PLAY GAME|\n");  printf(" ---------\n");  char Layouts[ROWS][COLS] = { 0 };//存放布置好雷的信息  char Findout[ROWS][COLS] = { 0 };//存放排查出雷的信息  //初始化棋盘  Initialization(Layouts, ROWS, COLS, '0');//mine  Initialization(Findout, ROWS, COLS, 'x');//show  //打印棋盘  /*Print_board(Layouts, ROW, COL);*/  Print_board(Findout, ROW, COL);  //布置雷  Lay_thunder(Layouts, ROW, COL);  /*Print_board(Findout, ROW, COL);*/  //排查雷  Check(Layouts,Findout,ROW,COL);}void test(){  int input = 0;  srand((unsigned)time(NULL));  do  {    menu();      color(5);    printf("\n");    printf("|═════════════════════════════════|\n");    printf("|Please enter the interface number|:");    scanf("%d", &input);    switch (input)    {    case 1:      game();      break;    case 0:      printf("|════════|\n");      printf("|退出游戏|\n");      printf("|════════|\n");      break;    default:      printf("\n");      printf("|═════════════════════════════════|\n");      printf("|由于你输入错误罚你5s不能玩(→_→)|\n");      printf("|═════════════════════════════════|\n");      Sleep(5000);    }  } while (input);}int main(void){  test();  return 0;}

(二)、game.h

关于游戏包含的函数声明,符号声明头文件的包含以及宏定义。

#define COL 9#define ROWS ROW+2#define COLS COL+2//颜色函数void color(short x);//初始化函数,初始化11*11,因为 行 & 列 都需要加1void Initialization(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘,最终打印 9*9 棋盘即可void Print_board(char board[ROWS][COLS], int row, int col);//布置雷void Lay_thunder(char Findout[ROWS][COLS], int row, int col);//排查雷void Check(char Layouts[ROWS][COLS], char Findout[ROWS][COLS], int row, int col);

(三)、game.c

游戏和相关函数实现。

#define _CRT_SECURE_NO_WARNINGS 1//游戏的函数的实现#include "game.h"#include<stdio.h>void color(short x){  if (x >= 0 && x <= 15)    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);  else    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);}void Initialization(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 Print_board(char board[ROWS][COLS], int row, int col){  int i = 0;   int j = 0;  color(7);  printf("════════════════════\n");  for (i = 0; i <= row; i++)  {    if (i == 0)    {      printf("%d|", i);    }    else    {      printf("%d|", i);    }  }  printf("\n/|══════════════════");  printf("\n");  for (i = 1; i <= row; i++)  {    printf("%d|", i);    for (j = 1; j <= col; j++)    {      printf("%c|", board[i][j]);    }    printf("\n");  }  color(6);  printf("\n-----扫雷游戏------\n");}void Lay_thunder(char Findout[ROWS][COLS], int row, int col){  //布置雷  int count = Thunder;  while (count)  {    int x = rand() % row + 1;    int y = rand() % col + 1;    if (Findout[x][y] = '0')    {      Findout[x][y] = '1';      count--;    }  }}//静态局部变量去修饰函数的时候,让这个函数只能在自己所在的源文件内看到,其它的内部当中是看不到的。static int Statistics(char Layouts[ROWS][COLS], int x, int y){  return Layouts[x-1][y-1]+    Layouts[x][y-1] +    Layouts[x+1][y-1]+    Layouts[x-1][y]+    Layouts[x+1][y]+    Layouts[x-1][y+1]+    Layouts[x][y+1]+    Layouts[x+1][y+1] - 8*'0';}void Check(char Layouts[ROWS][COLS], char Findout[ROWS][COLS], int row, int col){  //1.输入排查雷的坐标  //2.检查坐标处是不是雷,布置雷存放的是字符'1',没有放置雷存放的是字符'0'。  int x, y;  int win = 0;  while (win<row*col - Thunder)  {    static int j = 1;//延长生明周期,    while (j)    {      color(8);      printf("--------------------------\n");      printf("[输入第一个坐标记得空一格!]\n");      printf("--------------------------\n");      j--;      break;    }    color(11);    printf("---------------\n");    printf("请输入坐标>:");    //x与y坐标范围 1~9    scanf("%d %d", &x, &y);    printf("---------------\n");    //判断坐标的合法性    if (x >= 1 && x <= row && y >= 1 && y <= col)    {      if (Layouts[x][y] == '1')      {        printf("|══════════════════|\n");        printf("|很遗憾,你被炸死了!|\n");        printf("|══════════════════|\n");        Print_board(Layouts, ROW, COL);        system("pause");      }      if (Findout[x][y] == '0')      {        color(6);        printf("|═══════════════════════════════════|\n");        printf("|宁已经在这里输入过坐标了,请重新输入!|\n");        printf("|═══════════════════════════════════|\n");      }      if (Findout[x][y] == '1')      {        color(6);        printf("|════════════════════════════════════|\n");        printf("|宁已经在这里输入过坐标了,请重新输入!|\n");        printf("|════════════════════════════════════|\n");      }      else      {        //不是雷情况下,统计x,y周围坐标有几个雷        int Count = Statistics(Layouts, x, y);        Findout[x][y] = Count + '0';        Print_board(Findout, row, col);        win++;      }    }    else    {      printf("|═════════════════════════════|\n");      printf("|宁输入的坐标范围错误!重新输入|\n");      printf("|═════════════════════════════|\n");    }  }  if (win == row*col - Thunder)  {    printf("|═══════════════════════|\n");    printf("|恭喜你,排雷成功!太优秀了!|\n");    printf("|═══════════════════════|\n");    Print_board(Findout, ROW, COL);  }}

好了,那么这个扫雷游戏就到这里了,不知道你学会了没有。对于初学者这是可以尝试下,对编程的思维和逻辑,以及代码的理解能力帮助都是非常大的( •̀ .̫ •́ )✧

别忘记👍(╹ڡ╹ )


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK