目录
扫雷(下图选自微软扫雷)
是款大家十分熟悉的游戏
那么今天我们就来创建一个属于自己扫雷游戏
建议搭配代码食用,味道更佳:扫雷
(如想拷贝,建议从代码仓库拷贝)
我们这里将全部代码分成了三个部分,分别是
text.c(存放main函数和game函数(游戏主体框架))
game.h(自定义库,包含了对game.c的引用)
game.c(game函数的各个部分的具体实现)
全体代码
话不多说,我们上代码:
主函数
void menu() { printf("********************\n"); printf("*******1.play*******\n"); printf("*******0.exit*******\n"); printf("********************\n"); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择:->"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏!"); break; default: printf("输入有误,请重新输入!"); break; } } while (input); return 0; }
game()函数
void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; Initboard(mine,ROW,COL,'0');//初始化mine棋盘 Initboard(show,ROW,COL,'*');//初始化show棋盘 Display_board(show,ROW,COL);//打印show棋盘 Set_mine(mine, ROW, COL);//放置炸弹 int flag = 0; while(Find_mine(mine, show, ROW, COL))//判断是否踩雷 { Display_board(show, ROW, COL);//打印show棋盘 if (Check(show, ROW, COL))//判断游戏是否结束 { flag = 1; break; } } if (flag == 1) printf("你赢了!\n"); else if (flag == 0) printf("你输了!\n"); }
初始化棋盘函数
void Initboard(char board[ROWS][COLS], int row, int col, char ret) { int i = 0; for(i=0;i < ROWS;i++) { int j = 0; for (j = 0; j < COLS; j++) { board[i][j] = ret; } } }
打印棋盘函数
void Display_board(char board[ROWS][COLS], int row, int col) { int i = 0; for (i = 0; i <= row; i++) { printf("%d ", i); } printf("\n"); for(i=1;i<=row;i++) { printf("%d ", i); int j = 0; for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("---------扫雷--------\n"); }
初始化地雷放置函数
void Set_mine(char mine[ROWS][COLS], int row, int col) { int count = 0; while (count < Mine_num) { int x = rand() % ROW + 1; int y = rand() % COL + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count++; } } }
选坐标及判断是否踩雷函数
int Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入坐标:->"); scanf("%d %d", &x, &y); if (mine[x][y] == '1') { return 0; } else { show[x][y] = Find_Mine_Number(mine, x, y) + '0'; Expand_Find_Mine(mine, show, x, y); break; } } return 1; }
判断选点周围雷的个数函数
int Find_Mine_Number(char mine[ROWS][COLS], int x, int y) { if (x >= 1 && x <= ROW && y >= 1 && y <= COL) { int i = 0; int j = 0; int sum = 0; for (i = x - 1; i <= x + 1; i++) for (j = y - 1; j <= y + 1; j++) { sum += mine[i][j] - '0'; } return sum; } else return 0; }
递归实现大面积排雷函数
void Expand_Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) { if(x >= 1 && x <= ROW && y >= 1 && y <= COL) { if (show[x][y] == '0') { show[x][y] = ' '; int i = 0; for (i = x - 1; i <= x + 1; i++) { int j = 0; for (j = y - 1; j <= y + 1; j++) { if (show[i][j] != ' ') show[i][j] = Find_Mine_Number(mine, i, j) + '0'; if (show[i][j] == '0') Expand_Find_Mine(mine, show, i, j); } } } } }
判断是否结束函数
int Check(char show[ROWS][COLS], int row, int col) { int count = 0; int i = 0; for(i=1;i<=row;i++) { int j = 0; for (j = 1; j <= col; j++) { if (show[i][j] == '*') count++; } } if (count <= 10) return 1; else return 0; }
代码详解
这里不对main函数做详解,里面的srand语句会在后续进行解释
game()
void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; Initboard(mine,ROW,COL,'0');//初始化mine棋盘 Initboard(show,ROW,COL,'*');//初始化show棋盘 Display_board(show,ROW,COL);//打印show棋盘 Set_mine(mine, ROW, COL);//放置炸弹 int flag = 0; while(Find_mine(mine, show, ROW, COL))//判断是否踩雷 { Display_board(show, ROW, COL);//打印show棋盘 if (Check(show, ROW, COL))//判断游戏是否结束 { flag = 1; break; } } if (flag == 1) printf("你赢了!\n"); else if (flag == 0) printf("你输了!\n"); }
在敲代码前,我们要先屡清思路
打印棋盘在后文就不做解释,就是传统的打印二维数组的方法即可打印出来(作者只不过加了个横纵坐标,读者可以看着代码轻松掌握,这里作者采用 ‘ * ’ 来作为未知格子的标志。代码里面所有的打印都要打印show数组,详情见下)
有了思路后,我们就来一步一步实现。
1.设置游戏棋盘
这里要注意,我们要设置两个游戏棋盘:
一个棋盘用来存放雷,我们从头到尾都不对它进行修改
一个棋盘用来展示给玩家看(打印时打印这个棋盘,上面的内容包括未知的格子,点击后的格子周围的雷数)
void Initboard(char board[ROWS][COLS], int row, int col, char ret) { int i = 0; for(i=0;i < ROWS;i++) { int j = 0; for (j = 0; j < COLS; j++) { board[i][j] = ret; } } }
这里我们定义ROWS和COLS为我们要玩的棋盘大小+2,至于为什么要这样设置
1.可以方便后续排雷
(红色为实际棋盘大小,即我们玩游戏时能看到的棋盘大小)
(外圈黑色为我们初始化的棋盘大小)
可见:
若排雷点的周围8个地点全在红圈内的话(如点b),可以判断出雷的个数;
若排雷点的周围有n个地点不在红圈内的话(如点a,点c),因为我们后面会将红圈以外的位置全都默认为没有雷存在,所以实际判断出的雷的个数就等于红圈内的个数。
2.方便我们选择坐标
因为我们平时的习惯于直角坐标系的形式,所以我们对于c点习惯于看成(1.1)
(红色为实际棋盘大小)
综上我们初始化棋盘大小为COLS=COL+2,ROWS=ROW+2(这里的COL,COLS和ROW,ROWS均在头文件中全局定义,详情见开头仓库)
void Initboard(char board[ROWS][COLS], int row, int col, char ret)
char board[ROWS][COLS]:初始化棋盘的大小,用二维数组来表示
char ret:要将棋盘初始化的内容。因为有两个棋盘,要分别对他们初始化不同的值
int row, int col:可以不用写,有没有都一样
2.初始化地雷(这里的地雷我们放到mine数组里面)
这里要用到一个函数:rand函数
void Set_mine(char mine[ROWS][COLS], int row, int col) { int count = 0; while (count < Mine_num) { int x = rand() % ROW + 1; int y = rand() % COL + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count++; } } }
这里采用字符 ‘ 0 ’ 和 ‘ 1 ’来表示无雷和有雷(方便后面计算雷的个数,当然读者也可以根据自己的喜好用别的方式表示雷,只不过后面计算雷的个数时可能比较繁琐)。
int x = rand() % ROW + 1;
int y = rand() % COL + 1;来在规定坐标范围内随机生成雷
3.选择坐标以及判断是否踩雷(在mine数组里面进行判断是否踩雷)
int Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入坐标:->"); scanf("%d %d", &x, &y); if (mine[x][y] == '1') { return 0; } else { show[x][y] = Find_Mine_Number(mine, x, y) + '0'; Expand_Find_Mine(mine, show, x, y); break; } } return 1; }
如图
这里有一个有趣的函数,也是这个扫雷函数的好玩之处,即可以一次排查出大范围的区域
4.递归实现大面积排雷
Expand_Find_Mine(mine, show, x, y);
这个代码难度不大,但是思路十分重要,因为他是用递归的方式求解的,而一般人在递归的时候总是会出现栈溢出的情况,让我们看看它是如何解决的
void Expand_Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) { if(x >= 1 && x <= ROW && y >= 1 && y <= COL) { if (show[x][y] == '0') { show[x][y] = ' '; int i = 0; for (i = x - 1; i <= x + 1; i++) { int j = 0; for (j = y - 1; j <= y + 1; j++) { if (show[i][j] != ' ') show[i][j] = Find_Mine_Number(mine, i, j) + '0'; if (show[i][j] == '0') Expand_Find_Mine(mine, show, i, j); } } } } }
首先就是递归函数最重要的部分:结束条件
这里 if(x >= 1 && x <= ROW && y >= 1 && y <= COL) 就是他的结束条件
我们从d点开始判断:
1.判断d周围的雷数,若为0则开始大面积排雷,并且将d化为空格字符 ’ ‘ ;
若不为0则不满足大面积排雷要求,不对d进行改动
2.然后对周围八个点依次判断其周围雷的个数
if ( show[ i ][ j ] != ' ' )
show[ i ][ j ] = Find_Mine_Number( mine, i, j ) + ' 0 ';3.判断完后,对周围一个雷没有的点,继续对他进行d的操作 (这里我们用点(1.1)来举例)
发现周围的雷数为0,那么我们就从该点开始继续递归
if ( show[ i ][ j ] == ' 0 ' )
Expand_Find_Mine( mine, show, i, j);最终递归完成的结果如图:
这就是我们点了一个点后所展开的范围,所有雷数为0的全被换成了空格字符 ’ ‘。
5.判断踩雷个数(在mine数组中判断周围雷的个数)
int Find_Mine_Number(char mine[ROWS][COLS], int x, int y) { if (x >= 1 && x <= ROW && y >= 1 && y <= COL) { int i = 0; int j = 0; int sum = 0; for (i = x - 1; i <= x + 1; i++) for (j = y - 1; j <= y + 1; j++) { sum += mine[i][j] - '0'; } return sum; } else return 0; }
sum += mine[ i ][ j ] - ' 0 ';
因为前面我们设置了雷用字符 ’ 1 ’ 表示,所以这里可以直接用周围八个格子的相加的和 - ‘ 0 ’ 就得出了该点周围雷的个数
6.判断游戏是否结束(在show数组里面判断是否结束)
int Check(char show[ROWS][COLS], int row, int col) { int count = 0; int i = 0; for(i=1;i<=row;i++) { int j = 0; for (j = 1; j <= col; j++) { if (show[i][j] == '*') count++; } } if (count <= 10) return 1; else return 0; }
- 若踩雷则:在while进行判断时,因为函数返回值为0,会直接跳出,而flag==0,则输掉游戏
void game() { ... ... while(Find_mine(mine, show, ROW, COL))//判断是否踩雷 { Display_board(show, ROW, COL);//打印show棋盘 if (Check(show, ROW, COL))//判断游戏是否结束 { flag = 1; break; } } if (flag == 1) printf("你赢了!\n"); else if (flag == 0) printf("你输了!\n"); }
- 若排查玩全部雷,则只需要遍历未知格子的个数,少于等于安置雷的个数时,游戏胜利
以上就是扫雷的全部内容,如果对您有帮助的话,请留下您宝贵的赞,让更多人能学到有趣的扫雷小游戏!!