目录

前言

一、扫雷是什么?

二、代码详解(模块化)

1.创建文件

2.test.c

3.game.c

4.game.h

总结

  test.c

          game.c  

          game.h 



前言

相信大家过年玩游戏玩王者都玩腻了吧,就算没有玩腻,也被各种队友坑得再也没有玩游戏的欲望了吧。真要玩游戏,还得看单机游戏。

扫雷才是永远的神!


一、扫雷是什么?

在写程序之前,我们必须先要了解扫雷游戏的原理:我们随便打开一个扫雷游戏:

android studio 扫雷自定义 自定义扫雷app_python

 我们可以看到:许多小正方形组成了一个大的正方形平面,我们点击其中一个正方形,如果他不是雷,就会显示他周围的八个小正方形中有几个雷:

android studio 扫雷自定义 自定义扫雷app_c语言_02

如图,我们翻开箭头所指的小正方形,该小方块中的数字为1,即表示他的周围8个小方块中有一个是雷。

android studio 扫雷自定义 自定义扫雷app_c语言_03

而当我们点到雷时,游戏就结束了

注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序还能够标记小红旗(即你认为哪个地方有雷,就在哪个地方右键,插一朵小红旗),但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码

注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序会在你点击一个雷以后,自动为你翻开一大片,但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码

注意:熟悉扫雷的兄弟们可能知道,有的扫雷程序会有一个难度的选择,涉及到版面大小的变化和雷的数量的变化,但是我们这里实现的扫雷程序只是一个初级版本,故我们不写这部分代码

以上完整版的代码,将在进阶篇中再写出来。

在这里,我们写一个9*9,无拓展,无红旗标记功能的扫雷程序!

二、代码详解(模块化)

1.创建文件

我们这里尝试使用模块化的方式进行代码的书写,即创建三个文件,分别是:

test.c:用来测试整个代码的逻辑,存放主函数的地方

game.c:用来书写test.c中需要使用到的自定义函数

game.h:由于test.c和game.c不在同一个文件中,所以需要函数声明后才能使用,我们就把声明放在这个文件,以后需要用到game.c中的函数的时候,我们只需要引用该头文件即可(也可以叫做函数的封装)

android studio 扫雷自定义 自定义扫雷app_c语言_04


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)来装雷,这样就完美解决了边角问题:

android studio 扫雷自定义 自定义扫雷app_数组_05

如图,这样我们就把我们真正看到的“边角”放在了中间。

1、于是,我们创建两个数组:mine 和show数组,其中mine数组拿来装雷,大小为11*11,show数组拿来显示雷的个数,大小为9*9

创建好以后,我们要初始化我们的数组,将mine数组中的每个元素初始化为 0 ,来表示没有雷(类似的,将雷设为 1 ,但是一开始是没有雷的,需要我们后期去放入)

然后我们将show数组中的每一个元素都初始化为 * 

即达到这样的效果:

android studio 扫雷自定义 自定义扫雷app_数组_06

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的,只不过有一个数组没有将所有元素都打印出来而已)的大小,因为我们最后压根没有使用他的所有元素

android studio 扫雷自定义 自定义扫雷app_数组_06

我们可以看出,如果我们直接这样打印,玩家可能会很麻烦,因为每次输入坐标都需要一个个数,所以我们最好给玩家一张坐标纸,以及一条分割线

android studio 扫雷自定义 自定义扫雷app_后端_08

效果如图。

如果我们想要实现这样的表格,我们就需要先把第一排打印出来:

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);