目录
1.扫雷游戏的分析
2.代码实现
1.test.c
2.game.c和game.h
3.完整代码
3.1test.c
3.2 game.c
3.3 game.h
4.结语
1.扫雷游戏的分析
首先制作简单的*x*(简单的9x9为例)扫雷游戏需要一个界面,而对于这个9x9的方格,无毋庸置疑就需要二位数组来表示,如图,
这个空棋盘需要的放入数据,比如“*”、雷、雷的个数和不是雷的地方,如果用一个数组的话,可能一个数组信息比较多,不方便打印、输入、埋雷和计算雷;这里可以试着用2个数组,一个用来展示(就是用来给玩家观看的界面),展示“*”,并且收集并打印周围雷的个数,而另一个数组就用 来表示雷的符号,用来随机埋雷、输入、输出雷的个数和雷的地方,就如图
在这里,设置雷很好想,那就是用rand函数来表示伪随机数,并且用上srand改变种子,这种很好想,这里的关键就是该如何去计算周围的雷,玩过扫雷的人都知道,在3x3内排除中间那个周围的雷个数,但是回头一想,在角落没有3x3,如果按照3x3来搞,可能创建的数组栈会溢出,那怎么办呢,我们发现就算是栈溢出,最多就是左(右)上(下)这两地方溢出一格,那这样子就好办了,我们在原来的数组基础上加2个格子(因为上下或者左右各溢出一格),虽然这个地方只需要用一次浪费了内存,但是方便写代码。
接下来最重要的就是计算雷的个数了,按照3x3内部算几个雷,因为我们这里用1表示雷,0表示不是雷,所以这里也就衍生了另一个简介的方法就是加一起,就是雷的个数,哈哈哈这个想法确实很简单,但是到了实际,因为这个是字符,它会返回ASCLL码,这里0和1是指的字符而不是数字,所以返回时会返回48(‘0’)49(‘1’),不过问题不大,咱们可以减去‘0’得到的数字不就正常了嘛。如图减去8*‘0’就能得到所需要的雷数了。
2.代码实现
为了展现完整的逻辑链,我们可以试着多文件的方式去尝试敲代码,整个逻辑链会更加清晰,而扫雷游戏的代码又是数组与函数的结合,函数的使用可能会突然需要一个,这会导致写几行代码就创建一个函数,代码库会看起来很凌乱,所以我们这边可以用个game.c的cpp文件,test.c写大概,而game.c就写在游戏中需要的函数,当然有了game.c内部函数的引用肯定是需要申明的,如果一个一个声明太麻烦,因为我们可能需要很多函数,引用可能需要很多次,那么如何解决,我们可以再创建一个文件,就是头文件game.h,当然这里引用头文件时,是有格式的不是<game.h>,而是"game.h"。好的正篇开始,按照逻辑给你们一点一点的剥皮。
1.test.c
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include"game.h" void menu() { printf("**********************************\n"); printf("********** 0 exit **************\n"); printf("********** 1 play **************\n"); printf("**********************************\n"); } void game() { char show[ROWS][COLS];//这里创建2个不同数组的原因之前讲了,就是方便一个用来展示雷的个数和打印“*”,而另一个就是描述计算雷个数和随机埋雷。 char mine[ROWS][COLS]; } int main() { int input = 0;//这里用一个变量可以控制2个地方,一个循环一个,switch选择 do//因为这里咱们需要至少循环一次来看玩家是否玩游戏,所以,咱们得用do...while循环 { menu();//菜单 scanf("%d", &input); switch (input) { case 0: printf("退出游戏\n"); break; case 1: printf("开始游戏\n"); game(); break; default: printf("输入错误,请重新输入\n"); } } while (input); return 0; }按这样test.c的大致框架就已经完成了,接下来就是完成game函数里面的内容。接下来就是按照咱们分析的,就是对数组进行初始化了,show需要初始化为“*”,mine初始化为“0”(因为我们还没埋雷,并且用1表示雷,0表示没雷)。
2.game.c和game.h
因为使用函数时需要申明函数,定义,才能使用,这里需要的对show和mine的初始化,可以用同一个函数给初始化但是就需要,分初始化的内容,比如show需要”*“,而mine”0“,为了一个函数表示,咱们就得需要一个用来接收这个初始化的内容,直接上图!!!
这里我总是出一个问题,到现在还在犯的问题就是申明和定义时数组只写了数组名字和数组类型,唯独忘记写长度。要是不写这个长度会出现问题的,而且这个问题还很难注意到。
接下来就是打印出show的初始化数组了,如图:不过我们可以试着吧行和列打印出来,方便读取坐标,然后这里就可以变成
为什么我这里打印行列时,两个地方的输出范围不一样,因为咱们在打印列这个数组时,也需要一列的空间,这时候,上面对齐的列就不平衡了,所以我们这里需要用0来抵消这一差别。这所表现的就是列从1开始打印的结果。
下一步咱们就需要,设置雷了,当然你要是不太放心,mine的初始化是否正确,你可以去顺便把mine的初始化打印一遍,好的咱们开始设置雷,雷的设置需要随机值,为了调整难度,咱们可以在game.c中定义一个字符串为雷的个数,既然需要随机值,那就忘不了srand函数和rand的函数,而他们需要头文件time.h,stdilb.h。而srand函数一般不带入循环内部,他是随着时间而变化的。到现在,基本上都讲的差不多了,直接上代码
一般这样子,问题就不大大了。因为我们之前就说了,用0表示非雷,而用1表示雷,这里我们还要用条件来判断,这个坐标是否被埋过雷没,如果没这个条件,可能雷的个数会少几个。
设置完雷之后,就是排雷,而排雷就需要输入坐标,为了与上面对应,咱们可以把坐标也用x,y表示,排雷的过程中需要用到if来判断是否之前选择过,没选择才进行下一步,接下来就需要show和mine这两个数组了,所以在设置函数时需要设置2个数组,在期间咱们还需要设置一个函数,这个函数只需要为这里面设置这个计算周围雷的函数,后面就是解决最后一部分了,就是结束标志,咱们这里写的c语言的简单版,只能就是把不是雷的地方排完结束了,这里讲的很抽象,直接上图,听不懂的老铁可以,到最后直接看整个代码。
int Get_mine(char mine[ROWS][COLS], int x, int y)//获取周围雷的个数 { return 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] + mine[x - 1][y] - 8 * '0';//这里因为咱们数组的类型是字符要得到返回值为雷的个数的话那就需要减去'0'。 } void Find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int r,int c) { int x = 0; int y = 0; int win = 0; while (win < ROW * COL - MINE_COUNT)//跳出条件 { printf("请输入坐标\n"); scanf("%d %d", &x, &y); if (mine[x][y] == '1')//炸死条件 { printf("扫雷结束,你被炸死了\n"); break; } else if (show[x][y] == '*')//用于判断是否选择过 { show[x][y] = Get_mine(mine,x, y); win++; } else if(x > ROW || y < COL)//判断坐标是否是错误的 { printf("坐标异常,请重新输入\n"); } else { printf("该坐标已经输入了,请重新输入\n"); } } if (win == ROW * COL - MINE_COUNT)//扫雷结束 { printf("扫雷结束,恭喜你扫雷成功\n"); } }这里图片不好展示,直接展示代码的一部分吧,最后读到这里就算走完了,代码能够正常运行,如果不确定结束条件是否正确,你可以把雷的个数变成80个,然后对着雷的扫雷,看看是否有误,因为这里本人,已经运行过了,最后就是全部代码的整合。
3.完整代码
3.1test.c
//test.c #define _CRT_SECURE_NO_WARNINGS #include"game.h" void menu() { printf("**********************************\n"); printf("********** 0 exit **************\n"); printf("********** 1 play **************\n"); printf("**********************************\n"); } void game() { char show[ROWS][COLS]; char mine[ROWS][COLS]; Display_board(show, ROWS, COLS,'*'); Display_board(mine, ROWS, COLS,'0'); Display(show, ROW, COL); Set_mine(mine, ROW, COL); Display(mine, ROW, COL);//一般不打印,就是为了观看是否达到自己的目标. Find_mine(mine, show, ROW, COL); } int main() { int input = 0;//一个变量控制一个循环,一个选择 srand((unsigned int)time(NULL));//改变rand的种子 do//至少运行一次 { menu(); scanf("%d", &input); switch (input) { case 0: printf("退出游戏\n"); break; case 1: printf("开始游戏\n"); game(); break; default: printf("输入错误,请重新输入\n"); } } while (input); return 0; }3.2 game.c
//game.c #define _CRT_SECURE_NO_WARNINGS #include"game.h" void Display_board(char board[ROWS][COLS], int r, int c,int set) { int i = 0; for (i = 0; i < r; i++) { int j = 0; for (j = 0; j < c; j++) { board[i][j] = set;//初始化 } } } void Display(char board[ROWS][COLS], int r, int c) { int i = 0; for (i = 0; i <= c; i++)//表示列,这里表示列从一开始是为了更好的对齐 { printf("%d ", i); } printf("\n"); for (i = 1; i <= r; i++)//这里咱们只需要打印1-9即可 { printf("%d ", i);//表示行 int j = 0; for (j = 1; j <= c; j++) { printf("%c ", board[i][j]); } printf("\n"); } } void Set_mine(char mine[ROWS][COLS], int r, int c) { int count = MINE_COUNT; while (count) { int x = rand() % r + 1;//随机数为1-9即可,加上为了可改变性,这里用了r和c int y = rand() % c + 1; if (mine[x][y] == '0')//这里用我们用条件来判断这个随机值是否需要被埋过雷。 { mine[x][y] = '1'; count--; } } } int Get_mine(char mine[ROWS][COLS], int x, int y)//获取周围雷的个数 { return 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] + mine[x - 1][y] - 8 * '0';//这里因为咱们数组的类型是字符要得到返回值为雷的个数的话那就需要减去'0'。 } void Find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int r,int c) { int x = 0; int y = 0; int win = 0; while (win < ROW * COL - MINE_COUNT)//跳出条件 { printf("请输入坐标\n"); scanf("%d %d", &x, &y); if (mine[x][y] == '1')//炸死条件 { printf("扫雷结束,你被炸死了\n"); break; } else if (show[x][y] == '*')//用于判断是否选择过 { show[x][y] = Get_mine(mine,x, y); win++; } else if(x > ROW || y < COL)//判断坐标是否是错误的 { printf("坐标异常,请重新输入\n"); } else { printf("该坐标已经输入了,请重新输入\n"); } } if (win == ROW * COL - MINE_COUNT)//扫雷结束 { printf("扫雷结束,恭喜你扫雷成功\n"); } }3.3 game.h
pragma once #define ROW 9 #define COL 9 #define ROWS ROW + 2 #define COLS COL + 2 #define MINE_COUNT 80//雷的个数 #include<stdio.h> #include<stdlib.h> #include<time.h> void Display_board(char board[ROWS][COLS], int r, int c, int set);//初始化函数 void Display(char board[ROWS][COLS], int r, int c);//打印函数 void Set_mine(char mine[ROWS][COLS], int r, int c);//设置雷 void Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c);最后,关于我为什么把所有头文件放到game.h中的原因,就是头文件太多了,而且基本上也有重复的,但是game.h是必不可少的,所有可以直接把所有的头文件放在game.h中,而其他文件也反正也需要game.h的,迟早也要用。
4.结语
通过这个扫雷游戏,让我真正明白了数组与函数一起组合的力量,不要小看这些代码,虽然看起来很简单,但是到自己亲直接敲代码时,可能要琢磨很久,但是不要气馁,因为等到打完那一瞬间,你就会感到心旷神怡,当代码运行的过程中,内心的喜悦,以及自我的成长才是代码农的成就感。加油吧少年(忘记说了,如果有错误请告诉俺,俺可能写的有点疲倦,毕竟这个博客写的很久,感谢支持)。