目录
前言
一、扫雷是什么?
二、代码详解(模块化)
1.创建文件
2.test.c
3.game.c
4.game.h
总结
test.c
game.c
game.h
前言
相信大家过年玩游戏玩王者都玩腻了吧,就算没有玩腻,也被各种队友坑得再也没有玩游戏的欲望了吧。真要玩游戏,还得看单机游戏。
扫雷才是永远的神!
一、扫雷是什么?
在写程序之前,我们必须先要了解扫雷游戏的原理:我们随便打开一个扫雷游戏:
我们可以看到:许多小正方形组成了一个大的正方形平面,我们点击其中一个正方形,如果他不是雷,就会显示他周围的八个小正方形中有几个雷:
如图,我们翻开箭头所指的小正方形,该小方块中的数字为1,即表示他的周围8个小方块中有一个是雷。
而当我们点到雷时,游戏就结束了
注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序还能够标记小红旗(即你认为哪个地方有雷,就在哪个地方右键,插一朵小红旗),但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码
注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序会在你点击一个雷以后,自动为你翻开一大片,但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码
注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序会有一个难度的选择,涉及到版面大小的变化和雷的数量的变化,但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码
以上完整版的代码,将在进阶篇中再写出来。
在这里,我们写一个9*9,无拓展,无红旗标记功能的扫雷程序!
二、代码详解(模块化)
1.创建文件
我们这里尝试使用模块化的方式进行代码的书写,即创建三个文件,分别是:
test.c:用来测试整个代码的逻辑,存放主函数的地方
game.c:用来书写test.c中需要使用到的自定义函数
game.h:由于test.c和game.c不在同一个文件中,所以需要函数声明后才能使用,我们就把声明放在这个文件,以后需要用到game.c中的函数的时候,我们只需要引用该头文件即可(也可以叫做函数的封装)
2.test.c
首先:我们参照之前的三子棋的开局:->点这里
(这里我们的srand函数是种下随机数种子,我们先不管,但是肯定会用到)
这是我们制作大多数游戏都需要写的一段代码:
int main(void)
{
int input = 0;
srand((unsigned int)time(NULL));//随机值种子
do
{
menu();
printf("请输入:>");
scanf("%d",&input );
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("请重新输入\n");
break;
}
} while (input);
return 0;
}
接着,我们可以把menu函数写了: 这个基本上没有什么难度的,就是根据我们上面的逻辑写一个菜单:
按1,即进入游戏主体
按0,即退出游戏
void menu()
{
printf("**********************\n");
printf("********1.play********\n");
printf("********0.exit********\n");
printf("**********************\n");
}
重头戏:game()函数的主体(逻辑)部分
首先,我们想一下,如果我们后面写函数来计算某个坐标周围八个方块有多少个是雷时,有一点特殊情况,那就是,当我们要“扫雷”的那个坐标在边角的时候,他周围可能没有那么多数组元素,到时候再写代码就会出现错误
那么有什么方法可以规避呢?
加入我们的棋盘是9*9,那我们可以再创建一个数组(11*11)来装雷,这样就完美解决了边角问题:
如图,这样我们就把我们真正看到的“边角”放在了中间。
1、于是,我们创建两个数组:mine 和show数组,其中mine数组拿来装雷,大小为11*11,show数组拿来显示雷的个数,大小为9*9
创建好以后,我们要初始化我们的数组,将mine数组中的每个元素初始化为 0 ,来表示没有雷(类似的,将雷设为 1 ,但是一开始是没有雷的,需要我们后期去放入)
然后我们将show数组中的每一个元素都初始化为 *
即达到这样的效果:2、初始化好了以后,我们还要打印棋盘
3、布置雷
4、排查雷
//1.布置雷
//2.扫雷:输入坐标,是雷就炸死,不是雷就告诉你这个坐标周围八个坐标上总共有多少个雷,直到把所有非雷的位置全部都找出来,游戏结束,扫雷成功
//拓展:标记雷;炸开一片:1.该坐标不是雷 2.该坐标周围八个坐标也不是雷 3.该坐标未被排查过,这个时候就会展开一片
void game()//游戏的主体
{
char mine[ROWS][COLS] = { 0 };//最开始最好都是'0',用来存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//最开始是'*',用来存放排查出的雷的信息
//初始化棋盘
init_board(mine, ROWS, COLS,'0');
init_board(show, ROWS, COLS,'*');
//打印棋盘
//show_board(mine, ROW, COL);
//布置雷
set_mine(mine,ROW,COL);
show_board(show,ROW,COL);
//排查雷
find_mine(mine,show,ROW,COL);
}
总体上的游戏布局就是这样。
3.game.c
注意:
我们开始写game()中的代码:
首先,我们为了方便后期棋盘大小的改动,我们可以定义几个数:
#define ROW 9//即9*9的棋盘,show[][]数组
#define COL 9
#define ROWS ROW+2//即11*11的棋盘。用于mine[][]数组
#define COLS COL+2
#define EASY_COUNT 10 //简单模式中,雷的个数,我们暂且定为10个
定义以后,我们就统统用我们的定义来代替相应的数字,如果后期我们想要更改游戏的难度,也只需要更改这里的数字即可
一、void init_board(char arr[ROWS][COLS], int rows, int cols,char set)
这是我们的初始化棋盘函数,前面三个参数肯定是需要的,我们要做的是将两个数组中的每个元素设为我们设置的“set”,简单的两个for循环即可轻松实现
void init_board(char arr[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
二、void show_board(char arr[ROWS][COLS], int row, int col)
这是我们的展示棋盘(不是展示mine数组,而是show数组,即展示的是 * 和数字,而不是0和1)的函数,需要的三个参数如上,分别是数组名,数组的长度,注意,我们在test.c文件中,传入的参数是ROW和COL,为9,故此时的形式参数row和col也为9,但这并不妨碍我们将形式数组设置为11(其实我们创建的两个数组的大小都是11*11的,只不过有一个数组没有将所有元素都打印出来而已)的大小,因为我们最后压根没有使用他的所有元素
我们可以看出,如果我们直接这样打印,玩家可能会很麻烦,因为每次输入坐标都需要一个个数,所以我们最好给玩家一张坐标纸,以及一条分割线
效果如图。
如果我们想要实现这样的表格,我们就需要先把第一排打印出来:
int i = 0; printf("-----------扫雷--------------\n"); for (i = 0; i <= col; i++)//标识符 { printf("%d ",i); } printf("\n" );
这样我们就得到了0-9的数字排列
之后看竖排:不难发现,首先是要打印该行的行序号,之后打印相应坐标数组中的元素(*)
而且用户看到的坐标也应该是从1开始,而不是从0开始
for (i = 1; i <= row; i++)
{
printf("%d ",i );
for (j = 1; j <= col; j++)
{
printf("%c ",arr[i][j] );
}
printf("\n");
}
printf("-----------扫雷--------------\n");
合起来便是:
void show_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("-----------扫雷--------------\n");
for (i = 0; i <= col; i++)//标识符
{
printf("%d ",i);
}
printf("\n" );
for (i = 1; i <= row; i++)
{
printf("%d ",i );
for (j = 1; j <= col; j++)
{
printf("%c ",arr[i][j] );
}
printf("\n");
}
printf("-----------扫雷--------------\n");
}
三、void set_mine(char mine[ROWS][COLS], int row, int col)
这是用来布置雷的,很显然,我们又需要得到随机数了->随机数,其中有详细地解释随机数的写法。
我们需要在mine数组中,布置下10(EASY_COUNT)个雷,坐标x,y首先要满足大小在1~9之间,那么参照三子棋中的方法,只需要用一个随即是除9取余(0~8)加1(1~9),即可得到符合要求的x、y坐标,当然,每次的坐标不能重叠,所以还需要一步判断,看该坐标是否已经被占用,即该数组元素仍为0,我们才能“放置雷”
void set_mine(char mine[ROWS][COLS], int row, int col) { int count = EASY_COUNT; int x = 0; int y = 0; while (count != 0) { x = rand() % row + 1;//布置雷的坐标范围是1~9,为了出现1而不是0,就必须要最后+1,而不是%(row+1) y = rand() % row + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count--; } } }
四、int get_mine_count(char mine[ROWS][COLS], int x, int y)
这是我们用来数某个数组元素周围八个中有几个雷的。
我们回忆一下,我们在mine函数中是使用的0表示没有雷,1表示有雷,那该如何计算周围有几个雷呢?我们可以利用ASCll码的特点,即字符1减去字符0,结果是数字1,因此,我们可以周围八个元素加起来,再减去八个字符0,最后得到的数字,不就是我们想要的地雷的个数了吗?我们只需要返回该值即可
但是考虑到我们的show_board函数中,使用的是%c来替换arr[ i ][ j ]的,所以最后还需要我们将之转换为字符——即加一个字符‘0’(这一步我们可以放在后面进行)
int get_mine_count(char mine[ROWS][COLS], int x, int y) { //'0' - '0' = 0 //'1' - '0' = 1 //字符相减其实是ASCLL码 //这也是为什么使用0 和 1来表示非雷与雷!!!! return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]) - 8 * '0'; }
五、void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
这是我们最核心的函数了,即开始排查雷了,由于我们放雷是用0 和1 表示的,所以我们使用if语句,如果是雷,就输入相应的文字提醒,如果不是雷,就调用上面写的 get_mine_count函数,注意,这个时候需要将数字转换为字符哈!
上述语句需要使用一个循环,只有当所有的雷都排查完了或者是排查到雷了,才会跳出这个循环
void find_mine(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("请输入要排查的坐标:>\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1')//是雷,炸死了 { printf("很遗憾,被炸死了\n"); show_board(show, ROW, COL); break; } else { int count = get_mine_count(mine, x, y);//统计雷的个数 show[x][y] = count + '0';//放的是字符,不是数字!.加'0'可以将数字转化成对应的字符 show_board(show, ROW, COL); win++; } } else { printf("非法输入,请重新输入:>\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功\n" ); show_board(show, ROW, COL); } }
4.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 init_board(char arr[ROWS][COLS], int rows, int cols,char set); //打印 void show_board(char arr[ROWS][COLS], int row, int col); //布置雷 void set_mine(char mine[ROWS][COLS], int row, int col); //排查雷 void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
总结
模块化的写作是非常好用的,大家在练习编程时可以多多使用该方法,养成一个良好的习惯
完整代码:
目录
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");
}
//1.布置雷
//2.扫雷:输入坐标,是雷就炸死,不是雷就告诉你这个坐标周围八个坐标上总共有多少个雷,直到把所有非雷的位置全部都找出来,游戏结束,扫雷成功
//拓展:标记雷;炸开一片:1.该坐标不是雷 2.该坐标周围八个坐标也不是雷 3.该坐标未被排查过,这个时候就会展开一片
void game()//游戏的主体
{
char mine[ROWS][COLS] = { 0 };//最开始最好都是'0',用来存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//最开始是'*',用来存放排查出的雷的信息
//初始化棋盘
init_board(mine, ROWS, COLS,'0');
init_board(show, ROWS, COLS,'*');
//打印棋盘
//show_board(mine, ROW, COL);
//布置雷
set_mine(mine,ROW,COL);
show_board(show,ROW,COL);
//排查雷
find_mine(mine,show,ROW,COL);
}
int main(void)
{
int input = 0;
srand((unsigned int)time(NULL));//随机值种子
do
{
menu();
printf("请输入:>");
scanf("%d",&input );
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("请重新输入\n");
break;
}
} while (input);
return 0;
}
game.c
//game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void init_board(char arr[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
void show_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("-----------扫雷--------------\n");
for (i = 0; i <= col; i++)//标识符
{
printf("%d ",i);
}
printf("\n" );
for (i = 1; i <= row; i++)
{
printf("%d ",i );
for (j = 1; j <= col; j++)
{
printf("%c ",arr[i][j] );
}
printf("\n");
}
printf("-----------扫雷--------------\n");
}
//布置雷
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count != 0)
{
x = rand() % row + 1;//布置雷的坐标范围是1~9,为了出现1而不是0,就必须要最后+1,而不是%(row+1)
y = rand() % row + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
//'0' - '0' = 0
//'1' - '0' = 1
//字符相减其实是ASCLL码
//这也是为什么使用0 和 1来表示非雷与雷!!!!
return (mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1])
- 8 * '0';
}
void find_mine(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("请输入要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//是雷,炸死了
{
printf("很遗憾,被炸死了\n");
show_board(show, ROW, COL);
break;
}
else
{
int count = get_mine_count(mine, x, y);//统计雷的个数
show[x][y] = count + '0';//放的是字符,不是数字!.加'0'可以将数字转化成对应的字符
show_board(show, ROW, COL);
win++;
}
}
else
{
printf("非法输入,请重新输入:>\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n" );
show_board(show, ROW, COL);
}
}
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 init_board(char arr[ROWS][COLS], int rows, int cols,char set);
//打印
void show_board(char arr[ROWS][COLS], int row, int col);
//布置雷
void set_mine(char mine[ROWS][COLS], int row, int col);
//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);