写点什么

“纯 C”实现——扫雷游戏(递归实现展开功能)

作者:一介凡夫
  • 2022 年 8 月 11 日
    广东
  • 本文字数:7171 字

    阅读完需:约 24 分钟

“纯C”实现——扫雷游戏(递归实现展开功能)

文章目录

📺游戏动画演示


  • 🚀扫雷实现思路

  • 🚀棋盘实现

  • 🚀布置雷实现

  • 🚀玩家扫雷实现

  • 🚀带展开功能的扫雷

  • 🚀小结语


🚗text.c 文件

🚗game.h 文件

🚗game.c 文件



📺游戏动画演示

游戏实现完成的模样:


🚀扫雷实现思路

实现扫雷游戏的前提是要知道:>扫雷游戏的玩法(会玩的跳过这步)


扫雷游戏也就是排雷,让玩家点一个坐标,然后显示该坐标周围八个格子有多少个雷


如图所示:



如果踩到雷了就游戏结束,如图



那如果把全部非雷的位置找了出来那么游戏就胜利!




那么知道了游戏的规则,我们就可以大概知道要实现什么功能了!


还是老规矩,玩之前是不是得要把一个游戏画面打印出来给玩家看着进行排雷,那么游戏没有开始的时候我们的游戏画面该是怎么样的呢,你可以要一些字符来填充,比如“#”,“+”等等,我这就用字符“ * ”表示还没被点的坐标



那打印字符前是需要一个数组存放数据,这样子的话二维数组的选择是无可厚非的了!


我们用“ * ”来初始化的数组是用来展示给玩家看的,那么我们是不是还需要一个二维数组,用来存放地雷数据呀!


因为开两个二维数组,这样子才不会混乱,一个用来展示给玩家看,可以理解成前端,一个是用来控制雷的信息,可以理解成后端。


<font color=#DC44 size=4 face=“黑体”>那么下一步,就是怎样去布置雷呢?</font>


记不记得我们之前实现的三子棋游戏,让电脑下棋的时候是不是用随机值进行下棋的,那我们布置雷不也是随机放的吗!


那简直不要太简单,直接让随机值去完成即可


那么最后一步,就是玩家一直输入坐标,然后显示该坐标的周围八个格子有多少个雷,就一直判断玩家是否踩雷了,直至游戏胜利了或者失败!



游戏的大概思路知道了!我们就开始层层解剖啦!


🚀棋盘实现

实现棋盘我们是不是说过要用二维数组来存放“ * ”表示没有被排过雷的初始状态。


那么二维数组的行列该怎么确定呢?


其实我们也不知道具体要多少,是根据玩家来确定的嘛,玩家想玩 20*20,就打印 20*20,玩家想玩简单一点的 9*9,我们就打印 9*9,所以是不是需要一个宏定义来设定行列的大小呢,这样的方便了后续想修改多少行就多少行,多少列就多少列,灵活性就更高了!


#define ROW 10  //实际游戏的行#define COL 10  //实际游戏的列
#define EASY_COUNT 30 //要布置多少个雷的设置(游戏难度)
//为什么要这样定义,下面排雷的时候会讲,别急#define ROWS ROW+2 //方便后台处理的行 #define COLS COL+2//放后台处理的行
复制代码


那行列确定好了,是不是就需要初始化数组了,那需要把二维数组传给函数,比如我这里的 show 数组是展示给玩家看的数组,那是不是需要初始化成“ * ”,这个 mine 就是存放雷信息的,为了避免和 show 数组重复我们可以先初始化成字符'0'


  //模拟一下调用函数的过程  char mine[ROWS][COLS] = { 0 };  char show[ROWS][COLS] = { 0 };  //初始化雷的棋盘  BoardInit(mine, ROWS, COLS, '0');  //初始化展现的棋盘  BoardInit(show, ROWS, COLS, '*');//**************************************************//void BoardInit(char board[ROWS][COLS], int rows, int cols, char set){  int i, j;  for (i = 0; i < rows; i++)  {    for (j = 0; j < cols;j++)    {      board[i][j] = set;    }  }}
复制代码


初始化完成后就需要把它打印出来给玩家看,所以还需要一个打印数组的函数,打印的方式可以任意,你喜欢以什么样的方式打印就用什么方式(怎么好看怎么来),我这里就用我上面演示的那样!



==注意==:我们那里 mine 数组的内容是不用打印的,打印的话就可以知道雷的信息(作弊专用!)


void BoardPrint(char board[ROWS][COLS], int row, int col){  int i, j;  printf("********扫雷游戏*********\n");  for (i = 0; i <= col; i++)  {    printf("%d ", i);  }  printf("\n");  for (i = 0; i <= col; i++)  {    printf("--");  }  printf("\n");
for (i = 1; i <= row; i++) { if (i < 10) printf("%d |", i); else printf("%d|", i); for (j = 1; j <= col; j++) { printf("%c ", board[i][j]);
} printf("\n"); } printf("********扫雷游戏*********\n");
}
复制代码

🚀布置雷实现

布置雷要用的就是随机数的获取,我们就保证获取的坐标在要展示二维数组的范围内即可,这里就给大家解释一下上面为什么要定义一个比游戏实现行列数多 2 行 2 列的原因,完全是为了我们在检测有多少个雷的时候,防止数组越界



所以我们是需要把雷放在中间要展示的区域,不要把雷放在边界的地方!!


而且我们的 show 数组,也就是展示给玩家看的数组也要定义多两行两列,因为这样就可以跟 mine 数组相对于,把玩家输入的坐标就可以直接进行判断对应位置是否有雷 ==前提是同样是把原来的行列打印出去给玩家看,比如玩家选择的是 9*9 的,我们后台就实现 11*11 的 show,而打印就打印 show 中间 9*9 的格子 ,11*11 只是为了和我们的 mine 数组的雷相对于==


==我们用字符'1'来表示是雷的标志,字符‘ 0 ’表示不是雷==


void setmine(char board[ROWS][COLS], int row, int col){  //count为雷的数量  int count = EASY_COUNT;  while (count)  {        //确定坐标在合法范围内    int x = rand() % row + 1;    //模row加一为了让它不要布置在边界的地方    int y = rand() % col + 1;
if (board[x][y] == '0') { //每布一个雷,就把count-1 count--; board[x][y] = '1'; } }}
复制代码


我们可以把存放雷信息的 mine 数组打印看一下:>


🚀玩家扫雷实现

把上面的工作做完了,就要进行最后一步,也是这个游戏最重要的一步,玩家输入坐标,然后根据坐标判断游戏的情况


不管怎么样,玩家输入坐标后第一件事就是判断坐标是否合法,非法的话提醒玩家重新输入坐标。


合法之后就要判断输入的坐标是否踩雷,为了就直接结算游戏,非雷就把该坐标的周围 8 个格子检查有多少个雷,把雷数赋值给 show 数组,提示给玩家看周围分布有几个雷!


然后最后一步就是判断游戏胜利的条件了,我们可以定义一个变量 win,而且的值就等于要排雷的次数,==比如你是 99 行列的游戏,雷的数量为 20,99-20 就是剩下你要排雷的次数==


达了这个次数就说明已经把全部非雷的区域确定好,游戏就胜利!


不过要注意的是因为我们是采用 char 字符数组,所以我们虽然是把雷初始化成"1",但它是一个字符‘1’,而非雷的格子为字符‘0’,它们的本质是 ascll 码,字符‘0’的 ascll 码为 48,字符‘1’就为 49,所以我们只需要要周围 8 个格子的字符加起来,然后减去 8*字符‘0’就可以了。

比如你如果周围只有一个雷,那就是 7 * 48 +1 * 49- 8 * 48=1


void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){
int x = 0; int y = 0; //win为要胜利排雷的总次数 比如你是9*9行列的游戏,雷的数量为20,9*9-20就是剩下你要排雷的次数 int win = row*col- EASY_COUNT; while (win) { printf("输入坐标:>"); scanf("%d%d", &x, &y); //判断坐标是否合法 if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y]!='*') { printf("坐标已被占用,请重新输入\n"); } else { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); //游戏结束前打印雷的布置位置给玩家看 BoardPrint(mine, ROW, COL);
break; } else { //没踩雷,就把win-1,直至win为0就胜利 win--; //获取周围雷的个数 int count = get_mine_count(mine, x, y);//函数实现在下面 //加字符'0'是为了将数字变为字符表示
show[x][y] = count + '0'; BoardPrint(show, row, col); } } } else { printf("坐标非法,请重新输入\n"); } } if(win==0) printf("恭喜你,游戏胜利!\n");}
复制代码


//获取坐标中心周围雷的个数int get_mine_count(char mine[ROWS][COLS], int x, int y){  //检查坐标周围8个格子有多少个雷  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');}
复制代码

🚀带展开功能的扫雷

什么是带展开功能的扫雷呢,请看动画



就是一点就把所有为周围为零个雷的区域展开


==最要思想==就是下面的 openshow 函数实现的递归,如果对于坐标为零,就把改坐标该为显示‘ 0 ’,并就把周围 8 个坐标在传给 openshow 函数,非零就停止递归


int win = ROW * COL - EASY_COUNT;//展开功能void openshow(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y){  if (x >= 1 && x <= ROW && y >= 1 && y <= COL)  {    win--;    int count = get_mine_count(mine, x, y);    if (count == 0)    {      if (show[x][y] != '0')      {        show[x][y] = '0';        for (int i = -1; i <= 1; i++)        {          for (int j = -1; j <= 1; j++)          {            openshow(mine, show, x + i, y + j);          }        }      }
} else { show[x][y] = count + '0'; return; } }
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){
int x = 0; int y = 0; //win为要胜利排雷的总次数 while (win) { printf("输入坐标:>"); scanf("%d%d", &x, &y); //判断坐标是否合法 if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("坐标已被占用,请重新输入\n");
} else { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); //游戏结束前打印雷的布置位置给玩家看 BoardPrint(mine, ROW, COL);
break; } else { openshow(mine, show, x, y); BoardPrint(show, row, col); } } } else { printf("坐标非法,请重新输入\n"); } } if (win == 0) printf("恭喜你,游戏胜利!\n");}
复制代码

🚀小结语

<table><tr><td bgcolor=yellowgreen>看到这个游戏的基础功能就已经完成了,但有一些比标记雷的位置,这个功能还没实现,感兴趣的小伙伴可以自行实现,不出意外的话,后续会更新</table>


<table><tr><td bgcolor=yellowgreen>这次扫雷和上一期的三子棋游戏其实都是对二维数组的理解,然后加上一些 C 语言的基础语法加以组装的实现,你们有 get 到吗?</table>



下面还是老规矩,分享一下我的源代码给你们玩玩!!

🚗text.c 文件

#define _CRT_SECURE_NO_WARNINGS
#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 }; //初始化雷的棋盘 BoardInit(mine, ROWS, COLS, '0'); //初始化展现的棋盘 BoardInit(show, ROWS, COLS, '*');
BoardPrint(show, ROW, COL);
//布雷 setmine(mine, ROW, COL); //BoardPrint(mine, ROW, COL); //作弊开关 //排查雷 FindMine(mine, show, ROW, COL);

//扫雷开始 //FindMine(mine, show, ROW, COL);
}
int main(){ system("color 2");//改变字体颜色 srand((unsigned int)time(NULL)); int input = 0; 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;}
复制代码

🚗game.h 文件

#pragma once
#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>
#define ROW 10#define COL 10
#define ROWS ROW+2#define COLS COL+2#define EASY_COUNT 30//初始化棋盘void BoardInit(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘void BoardPrint(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);


复制代码

🚗game.c 文件

#define _CRT_SECURE_NO_WARNINGS
#include"game.h"

void BoardInit(char board[ROWS][COLS], int rows, int cols, char set){ int i, j; for (i = 0; i < rows; i++) { for (j = 0; j < cols;j++) { board[i][j] = set; } }}
void BoardPrint(char board[ROWS][COLS], int row, int col){ int i, j; printf("********扫雷游戏*********\n"); printf(" "); for (i = 0; i <= col; i++) { printf("%d ", i); } printf("\n"); for (i = 0; i <= col; i++) { printf("--"); } printf("\n");
for (i = 1; i <= row; i++) { if (i < 10) printf("%d |", i); else printf("%d|", i); for (j = 1; j <= col; j++) { printf("%c ", board[i][j]);
} printf("\n"); } printf("********扫雷游戏*********\n");
}


void setmine(char board[ROWS][COLS], int row, int col){ //count为雷的数量 int count = EASY_COUNT; while (count) { int x = rand() % row + 1; int y = rand() % col + 1;
if (board[x][y] == '0') { //每布一个雷,就把count-1 count--; board[x][y] = '1'; } }}
int get_mine_count(char mine[ROWS][COLS], int x, int y){ //检查坐标周围8个格子有多少个雷 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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//{//// int x = 0;// int y = 0;// //win为要胜利排雷的总次数// int win = row*col- EASY_COUNT;// while (win)// {// printf("输入坐标:>");// scanf("%d%d", &x, &y);// //判断坐标是否合法// if (x >= 1 && x <= row && y >= 1 && y <= col)// {// if (show[x][y]!='*')// {// printf("坐标已被占用,请重新输入\n");// // }// else// {// if (mine[x][y] == '1')// {// printf("很遗憾,你被炸死了\n");// //游戏结束前打印雷的布置位置给玩家看// BoardPrint(mine, ROW, COL);//// break;// }// else// {// //没踩雷,就把win-1,直至win为0就胜利// win--;// //获取周围雷的个数// int count = get_mine_count(mine, x, y);// //加字符'0'是为了将数字变为字符表示//// show[x][y] = count + '0';// BoardPrint(show, row, col);// }// }// }// else// {// printf("坐标非法,请重新输入\n");// }// }// if(win==0)// printf("恭喜你,游戏胜利!\n");//}//
//win为要胜利排雷的总次数int win = ROW * COL - EASY_COUNT;//展开功能void openshow(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y){ if (x >= 1 && x <= ROW && y >= 1 && y <= COL) { win--; int count = get_mine_count(mine, x, y); if (count == 0) { if (show[x][y] != '0') { show[x][y] = '0'; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { openshow(mine, show, x + i, y + j); } } }
} else { show[x][y] = count + '0'; return; } }
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){
int x = 0; int y = 0;
while (win) { printf("输入坐标:>"); scanf("%d%d", &x, &y); //判断坐标是否合法 if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("坐标已被占用,请重新输入\n");
} else { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); //游戏结束前打印雷的布置位置给玩家看 BoardPrint(mine, ROW, COL);
break; } else { openshow(mine, show, x, y); BoardPrint(show, row, col); } } } else { printf("坐标非法,请重新输入\n"); } } if (win == 0) printf("恭喜你,游戏胜利!\n");}


复制代码


用户头像

一介凡夫

关注

还未添加个人签名 2022.08.02 加入

还未添加个人简介

评论

发布
暂无评论
“纯C”实现——扫雷游戏(递归实现展开功能)_c_一介凡夫_InfoQ写作社区