目录
一.引言
不知之前的猜数字小游戏有没有让你汗流浃背呢?还是说你轻松应对了呢!!
第一辑:猜数字
第二辑:抽号器
如果是后者,那不妨挑战一下这个第三辑的难度!!!
在学完函数章节后,有必要做个完整版的总结归纳了,那么各位同志们,不妨挑战一下做个知识点饱满丰富的扫雷小项目,不仅能加固自标识符到函数的有关知识点,而且还能体会模块化方法的精髓!!!
二.简介
2.1感想(如果只是想看代码可以移步2.2或者三)
原谅我一个小项目还得搞个简介,主要是想先跟大家分享一下学完这个知识后的感想,这个代码写完后我的最深想法是模块化方法的优秀,层层嵌套,首先我在写最底层的主函数时我会很轻松的写,因为只要写1进入游戏,2退出游戏两个部分,接着在1进入游戏这里去优化game()函数,在这里我明白了学习英语的重要性,所有先给大家普及一下需要知道的英文吧(不要问为什么,问就是查过他们的英文(详见2.2)),ok,在日志我写到game.c包含初始化数组rearray,数组的打印displaybroad,布置雷setmine,以及模拟寻找雷finemine,而在写game()函数的时候,我也是秉承这个想法的一部分一部分写,在这里我只需要想需要几个参数,大概知道各个函数长什么样子,然后去.h中声明即可,这样子不仅加深了我对项目的整体思路,也另我对模块化的部分有了一定的印象,然后我就可以带着自信与方向去敲代码了,敲代码最重要的就是自信与方向,自信是有底气,方向也得对且清晰,所以我想我确实体会到了模块化方法对个人的便利,由于还没与他人共同使用过模块化方法,这里对团队模块化方法先不提。
2.2一些单词
我应该是少数会在文章中写英语单词意思的作者了吧
mine雷 arrary数组 row 行 column列(可简写成col)
本文自创自己看得懂的英文
rearray数组初始化 displaybroad展示棋盘
...(评论区补充吧,打累了)
三.代码实现
3.1 text.c
首先是我们的总测试函数text.c
text.c
#include"game.h"
void menu()
{
printf("***************************\n");
printf("*******1. play *********\n");
printf("*******2. exit *********\n");
printf("***************************\n");
}
void game()
{
//创建两个字符数组
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化数组
rearray(mine,ROWS,COLS,'0');
rearray(show, ROWS, COLS,'*');
//打印棋盘
displaybroad(show, ROW, COL);
//布置雷
setmine(mine,ROW,COL);
//displaybroad(mine, ROW, COL);测试用
//排查雷
findmine(mine, show, ROW, COL);
}
int main()
{
int a=0;
srand((unsigned int)time(NULL));//以时间为种子的随机函数,强制类型转换为unsigned int;
do {
menu();
printf("欢迎安装游戏,请输出数字代表操作:\n");
scanf("%d", &a);
switch (a)
{
case 1:
printf("欢迎来到扫雷游戏\n");
game();
break;
case 2:
printf("按任意键退出游戏!!!\n");
a = 0;
break;
default:
printf("键入错误,请重新选择!!!\n");
break;
}
} while (a);
return 0;
}
小小扫雷嘛,测试文件小正常,功能也差不多是这样子,首先是 菜单的打印,int a是为了接住1和2,进行游戏还是退出游戏,然后先设一个srand函数,实现生成随机数,在以时间为种子,是每次生成的随机数都不一样,因为在一些编译程序中rand函数生成的随机数是固定的数,所以导致随机函数不随机,而以时间为种子,可以是随机函数随机,接着正常打出输入流,然后switch解决1,2的选择,这里建议使用0和1做区分来更好的实现while函数,不赘述,用do while结构,先打菜单然后选择,然后判断即可完成
然后如果选择进入游戏,void game()在正式的游戏中我们需要创建两个字符数组来分别表示mine雷的个数数组,show数组是给玩家看的数组,全数组以*填充,这里我们考虑到,以1表示雷,以0表示非雷,我们发现如果雷的个数为1易与雷的表示混淆,所以我们使用两个数组,至于为什么用两个字符数组,相信读者在看完完整代码的时候能有一定的体会,这里卖个关子!
game()函数先初始化两个数组,然后对其中的show数组进行展示,然后开始埋雷,用户开始找雷,然后game()结束
3.2 game.h
game.h
对game函数中的函数进行声明,然后包含主要的库函数,这里为什么主要的库函数都要写在这里呢,主要是text.c和game.c中都有include"game.h"(引用自创的库函数(详细见b站讲解)),所以方便起见就全部都放在这里了,不过在这个头文件中还有对一些常数的定义,emm,这里建议读者在阅读后亲自打完代码然后体会一下,其实这样子设计是有便于优化及后期测试的,不用对一个数字一改再改,只需要对其define也就是定义改一下就行,这就是全局观啊!
代码奉上
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#define EASY 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void rearray(char a[ROWS][COLS], int rows, int cols, char set);
void displaybroad(char a[ROWS][COLS], int row, int col);
void setmine(char a[ROWS][COLS], int row, int col);
void findmine(char a[ROWS][COLS], char b[ROWS][COLS] ,int row, int col);
这里浅浅解释一下,#pragma once VS自己加的,不解释
第二句防止报错(本文使用了scanf函数)
必要头文件
对常数的定义
函数的声明
ok,对常数你可以先试着不定义#define EASY 10,然后对比前后测试的困难度
3.3 game.c
主角登场!!!game.c
知道你要,所以先上代码!!!!
#include"game.h"
void rearray(char a[ROWS][COLS],int rows,int cols,char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols;j++)
{
a[i][j] = set;
}
}
}
void displaybroad(char a[ROWS][COLS], int row, int col)
{
printf("************扫雷*************\n");
for (int i = 0; i <=row; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", a[i][j]);
}
printf("\n");
}
printf("************扫雷*************\n");
}
void setmine(char a[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = EASY;
while (count)
{
int x = rand() % row + 1;//1-9(原本0-8)
int y = rand() % col + 1;
if (a[x][y] == '0')//判定是否已经是雷,防止重复set mine
{
a[x][y] = '1';
count--;//满足设雷条件则雷减1
}
}
}
static int getminecount(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 + 1]
+ mine[x][y + 1] + mine[x + 1][y] + mine[x - 1][y + 1] - 8 * '0');
}
void findmine(char a[ROWS][COLS], char b[ROWS][COLS], int row, int col)
{
int win = 0;
while (win < row * col - EASY)
{
int x = 0;
int y = 0;
printf("请输入您要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (b[x][y] != '*')
{
printf("键入重复,请重新键入!!!\n");
continue;
}
else if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (a[x][y] == '1')
{
printf("很遗憾,你被炸死了!!!\n");
displaybroad(a, ROW, COL);
break;
}
else
{
int count= getminecount(a, x, y);//获取雷的个数
b[x][y] = count + '0';//导入雷的个数
displaybroad(b, ROW, COL);//打印
win++;
}
}
else
{
printf("键入错误,坐标非法,请重新输入坐标:>\n");
}
}
if (win == row * col - EASY)
{
printf("恭喜你,排雷成功!!!\n");
displaybroad(b, ROW, COL);//打印
printf("下面为您重新返回菜单\n");
}
}
很好,上了你也得看得懂啊!其实吧,在之前较为复杂的程序中我会每一步进行详细的注释,但是读者可以看到,在game.c中我的注释基本没有,第一就是我的思路清晰,声明就已经等于每个注释了,第二就是这个项目还是在1.0版本,实现逻辑较为简单,代码并非大家看不懂,而且使用的还是较为能理解的一代扫雷代码,没有用循环表示其周围八圈,可谓十分简单!!!
所以为了节省篇幅,现在我们一步一步来讲解
3.3.1 数组初始化
void rearray(char a[ROWS][COLS],int rows,int cols,char set)//这个是我们的数组初始化函数
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols;j++)
{
a[i][j] = set;
}
}
}
这个是我们的数组初始化函数,比较简单,读者可以想一下为什么要用到三个参数,没错,就是为了少一串代码,一个是数值数组,一个是字符数组,怎么样才能把他们联系在一起,然后一起初始化呢?这时候就直接把数值数组变成字符数组,然后减个0不就行了,然后对所有数组普遍初始化那不就是直接再加一个参数嘛,ok,过
3.3.2 棋盘展示
void displaybroad(char a[ROWS][COLS], int row, int col)
{
printf("************扫雷*************\n");
for (int i = 0; i <=row; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", a[i][j]);
}
printf("\n");
}
printf("************扫雷*************\n");
}
这个是我们的棋盘展示数组,就是几个循环,参数也是来个数组,我只要知道行和列就可以打出来的,较为简单,所以过
3.3.3 布置雷
void setmine(char a[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = EASY;
while (count)
{
int x = rand() % row + 1;//1-9(原本0-8)
int y = rand() % col + 1;
if (a[x][y] == '0')//判定是否已经是雷,防止重复set mine
{
a[x][y] = '1';
count--;//满足设雷条件则雷减1
}
}
}
这个是布置雷函数,先定义要布置几个雷,然后值得注意的是while中的count你也可以写成1,没什么区别,随机生成x,y坐标(表示雷的坐标),这里注意的是rand()%9+1是什么呢,这里的%9结果是0-8,加1就是 1-9,而%10结果是0-9,注意这里的区别,我们的坐标不需要0,然后后面的判断是为了防止重复布置雷,所以使用了一个标志!
3.3.4 关键函数寻找雷
static int getminecount(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 + 1]
+ mine[x][y + 1] + mine[x + 1][y] + mine[x - 1][y + 1] - 8 * '0');
}
void findmine(char a[ROWS][COLS], char b[ROWS][COLS], int row, int col)
{
int win = 0;
while (win < row * col - EASY)
{
int x = 0;
int y = 0;
printf("请输入您要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (b[x][y] != '*')
{
printf("键入重复,请重新键入!!!\n");
continue;
}
else if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (a[x][y] == '1')
{
printf("很遗憾,你被炸死了!!!\n");
displaybroad(a, ROW, COL);
break;
}
else
{
int count= getminecount(a, x, y);//获取雷的个数
b[x][y] = count + '0';//导入雷的个数
displaybroad(b, ROW, COL);//打印
win++;
}
}
else
{
printf("键入错误,坐标非法,请重新输入坐标:>\n");
}
}
if (win == row * col - EASY)
{
printf("恭喜你,排雷成功!!!\n");
displaybroad(b, ROW, COL);//打印
printf("下面为您重新返回菜单\n");
}
}
先讲下面的寻找雷函数,第一个函数是寻找雷的指定函数,所以我用了static去定义,主要是我懒的去声明这个函数,寻找雷故名思意就是输入想找的坐标,反馈是否被炸死,炸死就死的界面,没炸死就显示周围雷的个数,这里还需要考虑用户非法输入,用户输入重复等情况,相信这点小事情就是洒洒水啦,这里重点讲解一下扫雷计数getminecount函数的基本原理
如果(2,2)是个雷,周围就是以他坐标为中心,8个坐标的总和
是不是这个图有点晕,中间是表示1个雷,左下角是雷,所以我们使用双视角去看这个问题!!!
随便画了个草图,差不多吧,第一个图是雷图(数组mine),右图是show数组(用户看到的数组)
所以我们差不多就知道1是周围8个空的和!
计算完后展示,然后设置一个成功标志,但用户把所有的雷找出来后,也就是把所有的非雷布部分都访问了一遍之后,我们就可以停止访问 ,宣布游戏胜利!!!
这就是模块化方法制作出来的扫雷程序,至于我们要如何隐藏我们的代码便于售出
四.代码隐藏便于售出
这里的代码隐藏我们简单讲一下吧,后续会专门出一个专辑,思路就是
读者可以注意到
game.c
game.h
是两个独立的文件,那么只要我们将game.c变成一个静态库.lib,那么向购买方我们只需提供一个.lib和一个函数声明的头文件即可!!
emm一个简单的隐藏,这主要是得益于我们将所有的关键代码都写在了game.c中,ok,我们代码隐藏就在这里讲个大概,更加深奥,有效的隐藏往往更加高明,下次再讲,see you next essay!