前言:
继三子棋小游戏,我们继续来实现一个扫雷游戏。即使学校里有许多作业没完成,也依旧阻挡不了博主总结写博客的态势!
1.认识扫雷
扫雷游戏简单来说就是在一个棋盘上,玩家选择未知的小格子,这个格子如果是雷,扫雷失败游戏结束,如果不是雷,就在这个格子里显示基于这个小格子一周未显示的格子存在的雷的个数。
随着我们一直排雷,最后把棋盘上所有不是雷的格子选出来,游戏就胜利了。
1.1游戏构思
这个游戏需要用什么去实现呢?我们先在网上看一下扫雷游戏的是长啥样的,好确定实现需要的元素。
(存储和初始化)要有这个9*9的棋盘,我们可以使用一个9行9列的二维数组,然后为这个数组做一个初始化的接口函数。(埋雷)初始化完后,我们自己决定用什么符号表示雷(比如可以使用数字1来表示是雷),然后往棋盘里随机布置雷的存储位置,也就是把数组里的对应元素改成表示雷的符号。
(显示棋盘)还有一个重要的点是:显示棋盘,在选择玩游戏之后,第一事是先把棋盘打印出来给玩家看。由于我们现在没有使用界面化的工具(没有像上图那样有那些蓝色方格子),为了让棋盘没有被排查过的格子有些神秘感,最开始我们将棋盘初始化为字符'*'(星)。
这时候读者有没有发现如果仅使用一个二维数组会有些冲突的地方呢?我们请看到埋雷那一部分,我们说假设用数字1来表示埋的雷,现在在显示棋盘的时候又说要用*来表示未排查过的格子,那么未被排查过的又是雷格子该怎么表示?使用一些其它的符号吗?那玩家玩上一局发现规律后就完全通关了。那既然这样,我们就使用两个一模一样的二维数组吧,一个用来显示,一个用来表示雷的信息。因为我们使用*字符来初始化显示棋盘,那干脆用字符数组吧,埋雷的棋盘用字符'1'表示雷。
总结:创建两个9行9列的字符数组,一个是用来显示的,一个使用来放雷的信息的。显示的我们将它初始化为'*',在经过排查后,'*'变成表示该格子周围一圈8个格子潜藏的雷的个数(字符3和数字3虽然不是同一个东西,但我们能用来表示是3个的意思);埋藏雷的信息的棋盘我们不需要打印出来(相当于是一个后台,玩家通过对界面显示的棋盘进行操作,前台的操作对应后台,形成对接这种感觉~),我们就把埋雷的棋盘叫雷盘吧,雷盘最初我们就假设初始化字符'0',埋雷的时候,把该是雷的位置里的'0'改成'1'就可以了。
(排雷)那么还有一个问题就是,如何联系雷盘与显示棋盘,也就是说,在排雷的时候,我们根据显示的棋盘输入一个未排查的坐标后,应该找到对应的雷盘上的坐标,计算出周围雷的个数后,返回来覆盖掉显示棋盘位置上的'*'。
我们看下面这种情况:
但是我们的数组只有9行9列,黄色部分超出了数组范围,如果对其进行访问就造成了非法访问内存,程序会挂掉,那该怎么做?我们不妨将数组扩大一圈,变成成11行11列(因为横向最顶部,最底部多了一行,相当于加了两行,加了最左列和最右列,相当于加了两列)。雷盘和显示盘在初始化的时候,11行11列都初始化,虽然说显示棋盘只用的部分是9*9,但我们也选择初始化11行11列(因为这样就不会在初始化两个棋盘的时候,传参的行列不一致)。在显示显示棋盘的时候依旧是9*9的部分。
1.2碎碎念
首先,如果读者能读到这里,那么先为你叫好,毕竟前面那么多的构思文字都看得下来,说明你是一个很有耐心的读者。哈哈,那么接下来是实现扫雷的接口函数部分啦:
2.扫雷接口实现
2.1菜单打印
和三子棋一样,博主就直接写出来了:
void menu() { printf("*****************\n"); printf("*** 1. play ****\n"); printf("*** 0. exit ****\n"); printf("*****************\n"); } void game() { printf("扫雷游戏\n"); } int main() { int input = 0; do { menu(); printf("这是一个扫雷游戏,请选择>:"); scanf("%d", &input); switch (input) { case 1: game();//接着就是实现game函数 break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入\n"); } } while (input); return 0; }
2.2创建标识符常量和初始化数组
game.h
ROW和COL是给棋盘真正有用的部分(9*9)设的, 当我们在排查雷或打印棋盘,不需要用(11*11)
test.c
game.c
2.3打印棋盘
打印的是显示棋盘9*9的部分:
void Display(char board[ROWS][COLS], int row, int col) { int i = 0; printf("-----扫雷游戏-----\n"); //打印一行列数 for (i = 0; i <= col; 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]);//第1行到第9行是棋盘的有效部分 } //第0行和第10行是外面一圈 printf("\n"); } }
这里需要补充一点的是:我们打印出来的辅助玩家输入的坐标行和数组的元素是不是对应的关系。定义两个都是11行11列的数组的,行的下标和列的下标都是从0-10。我们看下面的图:
打印完棋盘后,现在就是在雷盘上埋雷啦~,得先有雷才能排雷嘛。
2.4随机埋雷
void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT;//埋雷是一个循环,用一个未埋的雷数来控制循环 while (count) { //随机生成1-9的x,y坐标 int x = rand() % row + 1; int y = rand() % col + 1; //判断是否是可以埋雷的位置 if (board[x][y] == '0') { board[x][y] = '1'; count--;//成功埋一颗雷,剩余雷数减一 } } }
既然雷已经埋完了,现在我们再来把排雷的接口函数实现一下吧。
2.5排查雷
因为显示用的棋盘和存储雷的信息的棋盘它们是一模一样的,所以它们的坐标是相对应的。
//获取雷的个数 int Get_MineCount(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + 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; int win = 0; while (win < row*col-EASY_COUNT)//win是排掉不是雷的个数等于棋盘减去雷的个数时游戏胜利,不用再继续输入坐标排雷了 { printf("请输入排查坐标:>"); scanf("%d%d", &x, &y); //判断坐标合法性 if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9)) { if (mine[x][y] == '1') { printf("很遗憾,排雷失败,你被炸死了\n"); Display(mine, row, col); break; } else { int count = Get_MineCount(mine, x, y);//获取附近雷的个数 show[x][y] = count + '0'; Display(show, row, col); win++; } } else { printf("输入坐标不合法,请重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); Display(mine, row, col); } }
将排查的位置周围一圈都加起来,减去8个字符'0'的ASCII码值,等于字符1的个数。字符1的ASCII码值是49,字符0的ASCII码值是48,比如现在排查的位置周围有3颗雷,那么一圈加下来就是8个字符0加上3,我们减去8个字符0,让Get_MineCount返回数字3。那么show数组对应的坐标要赋值成3+'\0'是为什么呢?因为这是字符为元素的数组,加上字符0使其成为相应的数字字符,打印出来也可以表示雷的个数。
好啦,这样就实现我们的简单版的扫雷游戏啦,还有一些功能我们没有实现的就是,当我们排查一个坐标的时候,展开一大片,这是需要使用递归实现的;还有标记的功能,图形化界面等等,我们还是一样,博主学有余力的时候再出一篇博客。
由于没有递归展开一片的功能,博主玩胜利一把需要输入71个坐标,哈哈~
3.源码
3.1头文件和函数原型声明game.h
game.h #define _CRT_SECURE_NO_WARNINGS 1 #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 InitBoard(char board[ROWS][COLS], int rows, int cols, char set); void Display(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);
3.2游戏函数实现game.c
game.c #include "game.h" void InitBoard(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 Display(char board[ROWS][COLS], int row, int col) { int i = 0; printf("-----扫雷游戏-----\n"); //从零开始可以使其对齐 for (i = 0; i <= col; 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]);//第1行到第9行是棋盘的有效部分 } //第0行和第10行是外面一圈 printf("\n"); } } void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT;//埋雷是一个循环,用一个未埋雷数来控制循环 while (count) { //随机生成1-9的x,y坐标 int x = rand() % row + 1; int y = rand() % col + 1; //判断是不是可以埋雷的位置,不能重复埋雷 if (board[x][y] == '0') { board[x][y] = '1'; count--;//成功埋一颗雷,剩余雷数减一 } } } int Get_MineCount(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + 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; int win = 0; while (win < row*col-EASY_COUNT) { printf("请输入排查坐标:>"); scanf("%d%d", &x, &y); if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9)) { if (mine[x][y] == '1') { printf("很遗憾,排雷失败,你被炸死了\n"); Display(mine, row, col); break; } else { int count = Get_MineCount(mine, x, y);//获取附近雷的个数 show[x][y] = count + '0'; Display(show, row, col); win++; } } else { printf("输入坐标不合法,请重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); Display(mine, row, col); } }
3.3测试代码文件test.c
test.c #define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void menu() { printf("*****************\n"); printf("*** 1. play ****\n"); printf("*** 0. exit ****\n"); printf("*****************\n"); } void game() { char mine[ROWS][COLS];//雷盘 char show[ROWS][COLS];//显示盘 InitBoard(mine, ROWS, COLS, '0');//将11行11列的雷盘都初始化成字符0 InitBoard(show, ROWS, COLS, '*'); Display(show, ROW, COL); SetMine(mine, ROW, COL); FindMine(mine, show, ROW, COL); } int main() { int input = 0; //使用rand()生成随机数必须要调用一次srand srand((unsigned int)time(NULL)); do { menu(); printf("这是一个扫雷游戏,请选择>:"); scanf("%d", &input); switch (input) { case 1: game();//接着就是实现game函数 break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入\n"); } } while (input); return 0; }
讲到这里,结束~
结语:希望读者读完有所收获!在学C的路上,祝福我们能越来越C!✔
读者对本文不理解的地方,或是发现文章在内容上有误等,请在下方评论区留言告诉博主哟~,也可以对博主提出一些文章改进的建议,感激不尽!最后的最后!
❤求点赞,求关注,你的点赞是我更新的动力,一起努力进步吧。