扫雷
创作不易,还请观众老爷耐心看完儿!
目录
扫雷
1.扫雷框架
2.初始化棋盘
3.打印棋盘
格式1:
格式2:
4.布置雷
5.排查雷
5.1排查雷的功能
5.2递归展开功能bool_mine
5.3计算该位置的雷的个数
6.扫雷的全部代码
7.作者试玩环节
1.扫雷框架
2.初始化棋盘
这是用户看到的棋盘,9*9大小,需要自己去排查雷
为了程序员更好地去控制扫雷,我们需要两个数组
- 程序员看的雷区数组 — 里面存放着雷的位置 — 11*11大小
- 用户看的展示数组 — 全是*号,需要用户去排查 — 11*11大小
使这两个数组一样大的原因:是为了使我们后面设计的接口函数更加兼容,即使展示的数组本质是11*11大小,但是我们打印的时候打印9*9就可以了
为了可以提高代码可维护性和兼容性,我们将数组的行数和列数,使用宏来替换
#define ROWS 11#define COLS 11#define ROW 9#define COL 9
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set){for (int i = 0; i < rows; ++i){for (int j = 0; j < cols; ++j){board[i][j] = set;}}}
我们将如果初始化的内容以字符的形式作为参数传进去,上述代码就可以很好地解决了兼容性,这一个函数可以解决雷区棋盘和展示用户的棋盘的初始化。
3.打印棋盘
打印棋盘大家可以充分发挥自己的艺术细胞,根据自己的喜好去设置棋盘的打印格式。下面博主提供两种。
格式1:
void print_mineline(int row)//打印分割线{for (int i = 0; i < row; ++i){if (i == row / 2){printf("扫雷");}printf("==");}printf("\n");}void DisplayBoard(char board[ROWS][COLS], int row, int col){print_mineline(row);//先打印一行分割线//打印列号for (int j = 0; j <= col; ++j){if (0 == j)//将列对齐{printf(" ");continue;}printf("%d ", j);}printf("\n");for (int i = 1; i <= row; ++i){printf("%d ", i);for (int j = 1; j <= col; ++j){printf("%c ", board[i][j]);}printf("\n");}print_mineline(row);//最后再打印一行分割线printf("\n");}
效果:
格式2:
void DisplayBoard(char board[ROWS][COLS], int row, int col){//打印列号for (int j = 0; j <= col; ++j){if (j == 0){printf(" ");continue;}printf(" %d ", j);}printf("\n\n");for (int i = 1; i <= row; ++i){//打印列号//1.打印第一部分printf(" %d ", i);for (int j = 1; j <= col; ++j){printf (" %c ", board[i][j]);if (j <= col -1)printf("|");}printf("\n");//2.打印第二部分if (i <= row - 1){printf(" ");for (int j = 1; j <= col; ++j){printf("---");if (j <= col - 1)printf("+");}}printf("\n");}}
效果:
但是格式2的缺点是如果棋盘是9*9以上大小,那么存在一些对齐问题,当然厉害的同学可以改善一下。这是提供的两个思路
4.布置雷
根据雷的个数n,随机布置n个雷,雷的个数可以根据用户的选择来定,例如
1.简单 — 10个雷
2.普通– 20个雷
3.困难 — 40个雷
4.疯狂 — 80个雷
当然以上的布置雷都是限制在了9*9的棋盘中,大家也可以根据难度设计棋盘的大小,尺寸
为了代码的可维护性以及可读性,我们将不同难度下的雷的个数也使用宏来替换,以及可以使用枚举来帮助我们实现难度选择
//雷的个数#define COUNT_EASY 10#define COUNT_ORD 20#define COUNT_DIF 40#define COUNT_FRE 80//难度等级enum degree{EASY = 1,//简单ORD,//普通DIF,//困难FRE//疯狂};
void menu_degree()//难度选择菜单 -- 跟我们的枚举常量值一致{printf("==========1.简单==========\n");printf("==========2.普通==========\n");printf("==========3.困难==========\n");printf("==========4.疯狂==========\n");}int SetMine(char board[ROWS][COLS], int row, int col){int count = 0;//雷的个数system("cls");//我们游戏开始部分肯定会要菜单,所以这里使用一个清屏功能int input = 0;do{menu_degree();printf("请选择扫雷难度\n");scanf("%d", &input);switch (input){case EASY:count = COUNT_EASY;break;case ORD:count = COUNT_ORD;break;case DIF:count = COUNT_DIF;break;case FRE:count = COUNT_FRE;break;default:printf("输入错误,请重新输入");break;}} while (input != EASY && input != ORD &&input != DIF && input != FRE);int _count = count;//保存count的值while (count){//随机生成十个雷,放在中间的9*9中int x = rand() % row + 1;int y = rand() % col + 1;//判断该位置是否已经布置了雷if (board[x][y] == '0')//没布置{board[x][y] = '1';--count;}}return _count;//这里我们将布置雷的个数返回了,之后要用}
解释:布置雷只需要在9*9中布置,所以咱们传参传的是row,col而不是rows,cols
5.排查雷
游戏的核心部分:
1.玩家输入需要排查雷的坐标
2.判断是否有效
3.根据雷区判断,如果是雷,游戏结束
4.如果不是雷,则在显示区棋盘中赋上雷的个数
5.如果雷的个数是0,还是考虑将周围8个位置进行递归展开
5.1排查雷的功能
在排查雷的函数中,mine — 雷区棋盘,show — 展示区棋盘,是需要配合使用的。例如判断一个位置是否是雷,我们要去雷区棋盘找,而即使修正展示区的位置内容,我们需要去show棋盘中修改,并且即使打印出来给用户观看。
判断输赢:
- 如果输入的坐标位置是雷,则输掉游戏
- 如果不是雷的位置都被排查完了,那么玩家赢得游戏胜利
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count){ //雷的个数通过SetMine的返回值可以得到,然后我们通过参数的形式,传给FineMine //因为我们的版本雷的个数不固定int chance = row*col - count;//记录不是雷的个数while (chance){system("cls");DisplayBoard(show, row, col);//展示show棋盘printf("请输入需要排查雷的坐标\n");int x = 0, y = 0;scanf("%d %d", &x, &y);//检查坐标的合法性if (x >= 1 && x = 1 && y <= col){//不是雷--显示雷的个数if (mine[x][y] != '1')//没点到雷{ //递归展开该位置boom_mine(mine, show, x, y, &chance);//将该位置周围的展开,传坐标 //展开雷chance是会减少的,使用址传递,在函数里面修改chance} //如何是雷--爆炸else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine, row, col);break;}}else{printf("坐标不合法\n");}}//判断是否赢了-- 将所有不是雷的地方都排查出来了if (chance == 0){printf("排雷成功,游戏胜利\n");}Sleep(2000);//停顿两秒看下结果}
5.2递归展开功能bool_mine
查出该位置周围8个位置的雷的个数
- 0个以上的雷 — 将这个show棋盘对应的位置赋值为该数字字符
- 0个雷 — 先这个show棋盘对应的位置赋值为该数字字符,再对该位置周围8个位置进行递归展开
防止出现死递归,我们思考一下递归限制条件:
- 如果该位置越界,那么不做任何处理
- 如果该位置已经被排查过了就不做任何处理
如果你的代码出现了错误,调式一看发现有个stack overflow这个报错,原因之一就是你写出死递归了
void boom_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pchance){//递归限制条件//1.如果越界了,直接返回if (x == 0 || x == ROW+1 || y == 0 || y == COL+1){return;}//2.如果该位置已经展开,直接返回if (show[x][y] != '*'){return;}//3.剩下的就是需要展开的情况int count = get_mine(mine, x, y);//计算该位置的雷的个数if (count)//如果雷的个数不为0,直接展开{show[x][y] = count + '0';--(*pchance);//不是雷的个数也减一return;//这里需要返回,不然无限递归下去了}else//如果雷的个数是0{//对周围八个位置进行展开操作show[x][y] = count + '0';--(*pchance);boom_mine(mine, show, x - 1, y - 1, pchance);boom_mine(mine, show, x - 1, y, pchance);boom_mine(mine, show, x - 1, y + 1, pchance);boom_mine(mine, show, x, y - 1, pchance);boom_mine(mine, show, x, y + 1, pchance);boom_mine(mine, show, x + 1, y - 1, pchance);boom_mine(mine, show, x + 1, y, pchance);boom_mine(mine, show, x + 1, y + 1, pchance);}}
5.3计算该位置的雷的个数
这里就很好地体现了我们在雷区棋盘中放字符0和字符1的好处。
我们将 周围8个位置的字符加进来 – 8*字符0 = 雷的个数
int get_mine(char mine[ROWS][COLS], int x, int y){//周围八个位置的内容 - 8 * '0' == 周围雷的个数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');}
6.扫雷的全部代码
上述的操作是讲解扫雷的核心函数的实现,下面我们进行分块编程,并且设计一些菜单,将他们整合在一起。
game.h
#define _CRT_SECURE_NO_WARNINGS 1#pragma once#include#include#include#include//操作棋盘的时候用的#define ROWS 11#define COLS 11//用户看到的#define ROW 9#define COL 9//雷的个数#define COUNT_EASY 10#define COUNT_ORD 20#define COUNT_DIF 40#define COUNT_FRE 80enum degree{EASY = 1,//简单ORD,//普通DIF,//困难FRE//疯狂};//初始化棋盘void InitBoard(char board[ROWS][COLS],int rows,int cols,char set);//打印棋盘void DisplayBoard(char board[ROWS][COLS], int row, int col);//布置雷int SetMine(char board[ROWS][COLS], int row, int col);//排查类void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count);
game.c
打印的两个方式都放在里面了
#include"game.h"//初始化棋盘void InitBoard(char board[ROWS][COLS], int rows, int cols,char set){for (int i = 0; i < rows; ++i){for (int j = 0; j < cols; ++j){board[i][j] = set;}}}//打印棋盘--将行列号也打印出来//void print_mineline(int row)//{//for (int i = 0; i < row; ++i)//{//if (i == row / 2)//{//printf("扫雷");//}//printf("==");//}//printf("\n");//}//void DisplayBoard(char board[ROWS][COLS], int row, int col)//{//print_mineline(row);////打印列号//for (int j = 0; j <= col; ++j)//{//if (0 == j)//将列对齐//{//printf(" ");//continue;//}//printf("%d ", j);//}//printf("\n");//////for (int i = 1; i <= row; ++i)//{//printf("%d ", i);//for (int j = 1; j <= col; ++j)//{//printf("%c ", board[i][j]);//}//printf("\n");//}//print_mineline(row);//printf("\n");//}void DisplayBoard(char board[ROWS][COLS], int row, int col){//打印列号for (int j = 0; j <= col; ++j){if (j == 0){printf(" ");continue;}printf(" %d ", j);}printf("\n\n");for (int i = 1; i <= row; ++i){//打印列号//1.打印第一部分printf(" %d ", i);for (int j = 1; j <= col; ++j){printf (" %c ", board[i][j]);if (j <= col -1)printf("|");}printf("\n");//2.打印第二部分if (i <= row - 1){printf(" ");for (int j = 1; j <= col; ++j){printf("---");if (j = 1 && x = 1 && y <= col){//如何是雷--爆炸//不是雷--显示雷的个数if (mine[x][y] != '1')//没点到雷{boom_mine(mine, show, x, y, &chance);//将该位置周围的展开,传坐标}else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine, row, col);break;}}else{printf("坐标不合法\n");}}//判断是否赢了-- 将所有不是雷的地方都排查出来了if (chance == 0){printf("排雷成功,游戏胜利\n");}Sleep(2000);}
test.c
#include"game.h"void game()//游戏函数{srand((unsigned int)time(NULL));char mine[ROWS][COLS] = { 0 };//11*11的数组便于操作char show[ROWS][COLS] = { 0 };//show数组与mine数组尺寸类型一样,可以使函数更兼容//1.初始化棋盘InitBoard(mine, ROWS, COLS,'0');//将mine初始化为字符0InitBoard(show, ROWS, COLS, '*');//将show初始化为*//3.布置雷int count = SetMine(mine,ROW,COL);//布置雷是在mine中布置//是在9*9中布置//4.排查雷FindMine(mine, show, ROW, COL,count);}void menu(){printf("=================================\n");printf("====== 1. play ======\n");printf("====== 0. exit ======\n");printf("=================================\n");}void test()//测试函数{int input = 0;do{system("cls");menu();printf("请输入->:");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("游戏退出\n");break;default:printf("错误输出\n");Sleep(1000);break;}} while (input);}int main(){test();return 0;}
7.作者试玩环节
打完一把还是需要点时间的,博主就不玩完了。
当然了,上述代码肯定还存在着很多不足的地方,如果大家有什么好的建议欢迎在评论区留言,我们下期见吧。