大家好,我是浪雨,今天给大家分享用C语言编写扫雷的具体思路和详细的代码,编写这个程序对知识点的要求不多,但很考验我们对知识的运用能力。
主要用到的知识是二维数组,函数,循环
在编写之前,我也看过很多博主写了关于这方面的内容,也给了详细的代码,但评论区仍有一些疑惑,所以我想以一个初学者的视角去审视,将一些问题刨析,给大家讲讲这么做的原因,以及这样做对游戏的作用,故这篇文章以回答问题的方式,讲实现游戏的思路,文章最后会附上源代码。
1.游戏为什么要用两个棋盘?
在解释这个问题之前,我们需要了解扫雷的游戏规则,在扫雷的棋盘中,你点击的那个方块为中心(假设你点击的这个方块不为0),其周围的8个方块有几个雷,那你点击的那个方块就是数字几,如下图所示
红色圈圈是我们输入的坐标,星星表示雷,其周围八个方块有两个雷,所以该坐标在显示后应该显示2,以提醒玩家,这个方块周围有2个雷,那我们在编写程序的时候,应该写一个函数来实现检测输入坐标周围雷的数目,并把这个数字放到这个坐标中。
要实现这个函数,我们需要把雷用字符1表示,非雷用字符0表示,这是因为在ASCII表中,字符1换成十进制数比字符0大一,看下图
红圈是我们选择的坐标,我们将周围8个坐标字符加在一起即'1'+'0'+'0'+"1"+'0'+'0'+'0'+'1',然后减去8*'0',得到的结果就是3,而我们红圈周围的雷的数目就是3,而我们将3+'0'在ASCII表中就对应字符3,然后将这个字符放到红圈坐标中,表示其周围有3个雷。
如果我们只用一个棋盘,那么如下图
这个字符3就会放到红圈的空间上,那我们下一步走到红圈右边的一个方格,然后调用雷的计算函数,计算其周围的雷即'0'+'0'+'0'+'3'+'0'+'0'+'1'+'0'-8*'0',这时候我们会发现,红圈上的字符3会算到雷的计算中,得出的结果是4,即该方格周围有4个雷,但这是错误的,因为这个方格周围只有一个雷,如果我们只用一个棋盘,那会导致计算雷数目的函数无法执行出我们想要的效果,那该怎么解决呢?这个时候就要用到另一个棋盘了,如下图
在计算雷的数目的函数计算完毕后,我们将其返回值放到另一个棋盘相对应的位置上,这样红圈的位置上仍然是字符0,就不会导致误会,在程序的正常运行中,雷盘是不打印给玩家看的,只把显示盘给玩家看,这样我们把雷盘上得来的各种数据转移到显示盘上即可。
2.两个棋盘容易搞混,怎么更好的区分呢?
实际上显示盘只是一个承接体,我们输入一个坐标,会有判断函数判断这个坐标对应的是否是雷,而判断时就是查看雷盘对应坐标的字符,如果是字符1,那个踩雷了,游戏结束,如果是0,那么它周围又有几个雷呢,这个时候计算雷的函数开始计算,然后将得到的结果加上字符0(根据ASCII表的值,转换成相应的数字字符)传给显示盘,然后打印出显示盘即可,所以一般只有在接收数据和打印的时候用到显示盘,其他都是在雷盘上操作的。
3.如何实现当周围一片无雷时,能够快速展开?
当雷盘上有一片区域没有雷,那我们可以将这片无雷的区域展开,不然一个9*9的棋盘,减去10个雷的数目,需要我们输入71次,哈哈,这不像是游戏,更像是折磨了。所以写一个展开函数还是很有必要的,那么该如何实现呢?这里,我提供一些思路,看下图,黑圈是我们走的坐标,其周围一片有很多的空白区域,我们需要编写一个展开函数,将黑圈周围的空白无雷区展开,首先,我们需要调用一下计算雷的个数的函数,如果返回了0,说明周围无雷,可以展开,接下来,我们依次展开黑圈左上方,上方,右上方,左方,右方,左下方,下方,右下方
展开后,我们发现,只展开了黑圈周围的8个方格,没有达到我们想要的效果,我们想要它自动扩展,怎么实现呢?我们刚才写的程序会到周围的每一个方格中,那我在程序走到每一个方格的时候再调用展开函数不就行了吗?如下图,黑圈是我们输入的坐标,经过调用和判断,来到了蓝圈的位置,再经过判断与调用来到了绿圈的位置,再次调用判断,发现不符合递归条件,回到上一层蓝圈的位置,然后程序来到蓝圈上方即粉红圈的位置。。。持续下去,至把无雷区展开,这就是展开的思路,后面会放上代码。
void open(char mineboard[ROWS][COLS], char showboard[ROWS][COLS],int x,int y,int *p)//无雷区域扩展实现 { int i, j; int counter=get_mine(mineboard,x,y); showboard[x][y] = get_mine(mineboard, x, y) + '0'; (*p)++; if (counter == 0) { for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if ( showboard[i][j]=='*' && i >0 && i < 10 && j > 0 && j < 10 && get_mine(mineboard, i, j)==0) { open(mineboard, showboard, i, j,p); } else if(showboard[i][j] == '*' && i > 0 && i < 10 && j > 0 && j < 10) { showboard[i][j] = get_mine(mineboard, i, j) + '0'; (*p)++; } else showboard[i][j] = get_mine(mineboard, i, j) + '0'; } } } }
以上是展开函数的实现部分,这个版本的代码稍微长一点,但很好理解(显示棋盘在初始化时,统一初始化为'*')这里的好理解,是指你能够自己画图,才能理解这样写的目的,不理解为什么传指针的可以看下面一个问题
4.如何判断游戏胜利了呢?
在没有写展开函数的时候,输赢很好判断,我们把棋盘总方块数减去10个雷的数目,然后走一步减一次,直到减为0,且没踩雷,那就算赢了。但加了展开功能之后,胜利条件就要重写,因为一次展开很多,我们要详细记录到底展开了多少个格子,这也是为什么上面的展开函数要传一个指针过去,这个指针指向的就是记录已展开的格子的变量,展开一个加一个,最后方格总数减去雷数和展开的,等于0,游戏就结束了。
下面,我放上游戏所有的源代码
这是主体部分
#include"function_name.h" //扫雷小游戏,编写结束时间2022.3.12 历时1天 void game() { char mainboard[ROWS][COLS] = { 0 };//布雷盘 char showboard[ROWS][COLS] ={0};//显示盘 initboard(mainboard,ROWS,COLS,'0');//初始化 initboard(showboard, ROWS, COLS,'*');//初始化 // priboard(mainboard, ROW, COL);//打印显示盘 priboard(showboard, ROW, COL); putmine(mainboard, ROW, COL); priboard(mainboard, ROW, COL); findmine(mainboard, showboard, ROW, COL); exit(0); } void test() { int input=0;//此处必须给input赋值0,否则输入英文字母后程序会失控 do //具体原因不明,猜测为scanf的缺陷,在default之后加上exit(0)也可解决 { //但无法再次输出 menu(); printf("输入1开始,输入0退出\n"); printf("\n"); printf("Tip:请不要输入英文字母,否则程序将强行退出\n"); scanf_s("%d", &input); srand((unsigned int)time(NULL)); switch (input) { case 1: game(); break; case 0: printf("游戏已退出\n"); break; default: printf("输入错误,请重新输入\n"); break; } } while (input); } int main() { test(); return 520; }
这是头文件部分,里面存着各种函数的声明
#include<stdio.h> #include<stdlib.h> #include<time.h> #define DEGREE 10//雷的数目即游戏难度 #define ROW 9 //打印行 #define COL 9//打印列 #define ROWS ROW+2//实际行 #define COLS COL+2//实际列 void menu(); void initboard(char board[ROWS][COLS], int x,int y,char set);//初始化棋盘 void priboard(char prinboard[ROWS][COLS], int x, int y);//打印棋盘 void putmine(char mine[ROWS][COLS],int row,int col);//埋雷 void findmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS], int row, int col);//排雷
这是函数功能的实现部分
#include"function_name.h" void menu()//界面菜单函数 { printf("*************************\n"); printf("*******扫雷小游戏*******\n"); printf("**1.开始*********0.退出**\n"); printf("*************************\n"); } void initboard(char board[ROWS][COLS], int x, int y,char set)//初始化棋盘; { int i, j; for (i = 0; i < ROWS; i++) { for (j = 0; j < COLS; j++) { board[i][j] = set; } } } void priboard(char prinboard[ROWS][COLS], int x, int y)//打印棋盘 { int i, j; for (i = 0; i <= x; i++) { printf(" %d ", i); } printf("\n"); for (i = 1; i <= x; i++) { printf(" %d ", i); for (j = 1; j <= y; j++) { printf(" %c ", prinboard[i][j]); } printf("\n"); } } void putmine(char mine[ROWS][COLS], int row, int col)//实现埋雷的函数 { int counter = DEGREE; while (counter) { int x = rand() % ROW + 1; int y = rand() % COL + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; counter--; } } } int get_mine(char mineboard[ROWS][COLS],int m,int n)//返回空格中心雷的数目 { return mineboard[m - 1][n - 1] + mineboard[m - 1][n] + mineboard[m - 1][n + 1] + mineboard[m][n - 1] + mineboard[m][n + 1] + mineboard[m + 1][n - 1] + mineboard[m + 1][n] + mineboard[m + 1][n + 1] - 8 * '0'; } void open(char mineboard[ROWS][COLS], char showboard[ROWS][COLS],int x,int y,int *p)//无雷区域扩展实现 { int i, j; int counter=get_mine(mineboard,x,y); showboard[x][y] = get_mine(mineboard, x, y) + '0'; (*p)++; if (counter == 0) { for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if ( showboard[i][j]=='*' && i >0 && i < 10 && j > 0 && j < 10 && get_mine(mineboard, i, j)==0) { open(mineboard, showboard, i, j,p); } else if(showboard[i][j] == '*' && i > 0 && i < 10 && j > 0 && j < 10) { showboard[i][j] = get_mine(mineboard, i, j) + '0'; (*p)++; } else showboard[i][j] = get_mine(mineboard, i, j) + '0'; } } } } void findmine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col)//输入坐标实现排雷 { int x, y; int win = 0; while (ROW*COL-win-DEGREE) { printf("请输入你要走的坐标\n"); scanf_s("%d%d", &x, &y); if (mineboard[x][y] == '1') { printf("你踩到雷了,游戏结束\n"); printf("雷区分布如下,其中'1'为雷\n"); priboard(mineboard, ROW, COL); break; } else { open(mineboard, showboard, x, y, &win); priboard(showboard, ROW, COL); if (ROW * COL - win-DEGREE== 0) { printf("恭喜,你赢了\n"); } } } }