初阶扫雷的保姆级教程,有这一篇就够了(万字讲解 适合初学者)

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C语言初阶
⭐代码仓库:C Advanced
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

前言

1.规则和玩法

扫雷是一款大众类的益智小游戏,目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。其胜利条件不是将所有地雷插上旗子,而是点开所有不是地雷的格子。

2.对读者说的话

利用C语言制作扫雷对于初学者是比较复杂的,但利用所学的初阶C语言可以制作一个可以玩的很开心的扫雷游戏,如果大家对于扫雷代码感到有点吃不消,下面我放一个我之前自己创作的三子棋的入门教程,能帮助大家更好地理解C语言扫雷制作的方法。
三子棋教程

ps:这篇博客用的是9*9的棋盘和10颗雷,如果想改变棋盘和雷的数量都可以在宏定义上更改!

一、基本思路分析

1.创建一个菜单(menu),供玩家选择玩还是退出
2.制作一个switch语句,控制游戏的开始与退出
3.进入游戏,创建两个二维数组,一个存放布置好的雷的信息,另一个存放排查出的雷的信息(边界要加2)
4.开始游戏
5.初始化棋盘
6.打印棋盘
7.电脑随机布置雷
8.电脑排查周围的雷并打印在棋盘上(简易版)
9.递归实现排开一片区域
10.判断是否排雷成功

二、实现步骤(详细解释)

1.创建文件

如图:大家需要创建一个头文件(game.h)和两个源文件(game.c)、(test.c),这样分工程的制作一个项目,能够更加清晰明了,也能够及时发现错误的地方。头文件game.h内存放的是该项目中的头文件,在另外两个文件中可以输入#include“game.h”,这样更加方便又清晰;源文件test.c中放的是游戏开始或者退出的代码;源文件内game.c实现的是让游戏运行的代码,是整个工程当中最核心的部分也是最重要的部分,经过这么分析,大家应该更加明白设计这么多个文件的重要性了吧!
在这里插入图片描述
如下两图:使用#include"game.h",使得代码更加简洁明了。
在这里插入图片描述
在这里插入图片描述

2.创建菜单

在菜单创建这里比较灵活,大家可以根据自己的习惯创立一个菜单,我用的菜单比较简单,代码如下:

test.c

void menu() {   //打印菜单
	printf("****************************\n");
	printf("***冒险者,请选择是否冒险***\n");
	printf("**********1.游戏开始********\n");
	printf("**********0.退出游戏********\n");
}

3.控制游戏开始或退出

先上代码再解释:

test.c

void test() {
    int input = 0;
	//srand((unsigned int)time(NULL)); //随机生成的种子
	printf("扫雷游戏\n");
	menu();
	do {
		printf("请输入:>"); 
		scanf("%d", &input);
		switch (input) {
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);   //input为非零则为真,在程序里面执行;若为零则为假,跳出程序
}

利用一个switch语句,用户输入1或者0来控制扫雷游戏的进行或者退出,大家可能会想了,为什么我把一个随机生成的种子给注释了?大家不要着急,这个随机种子的设置是为了后续电脑随机布置雷所使用的。

4.开始游戏

test.c

void game() {
	printf("开始游戏\n");
	//1.需要准备两个二维数组,一个数组用来存放布置好的雷的信息,另一个数组用来存放排查出雷的信息
	//2.很奇怪的点,如果排查在边界怎么办,很简单,为了防止越界,那就行增加两行,列增加两列
	char mine[ROWS][COLS] = { 0 };  //存放布置好的雷的信息
	char show[ROWS][COLS] = { 0 };  //存放排查出的雷的信息
}

大家可能看不懂ROWS和COLS,大家往下继续看,接下来我会跟大家解释这两个“诡异”的字母。

5.初始化键盘

上面的程序都是在源文件test.c与头文件game.h中写的,那最核心的game.c程序呢?加下来我们将花费大多数时间来写源文件game.c内的代码。

这里需要所有地方的代码:game.h 、 test.c 和 game.c

game.h

#define ROW 9    //行的行数
#define COL 9    //列的列数
#define ROWS ROW+2   //行数+2,防止越界
#define COLS COL+2   //列数+2,防止越界
//#define MineCount 10  //雷的个数
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

利用宏定义定义一个9行9列的棋盘,为了防止排查雷的时候越界,故行和列都增加2。如下图:以99的棋盘为例,如果雷在99的边界,排查雷的时候是排查雷的周围8个地方,发现会越界唉,所以需要行和列增加两个单位来防止越界。
在这里插入图片描述

game.c

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) {//传入‘0’或者‘*’表示对棋盘的初始化
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			board[i][j] = set;   //定义board[ROWS][COLS]是为了不跟show和mine搞混
		}
	}
}

test.c

//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');  //'0'表示这个地方是没有雷的
	InitBoard(show, ROWS, COLS, '*');  //‘*’表示埋雷,把所有地方埋掉

初始化这个棋盘,定义‘0’为没有雷,*’表示埋雷,把整个棋盘都用**表示,呈现在大家的控制台中。

6.打印棋盘

game.h

//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);

geme.c

//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col) {
	int i = 0;
	int j = 0;
	printf("~~~~~~~~~~~扫雷~~~~~~~~~~~~~~\n");
	for (j = 0; j <= col; j++) {
		printf("%d ", j);        //给列标号,方便输入坐标
	}
	printf("\n");
	for (i = 1; i <= row; i++) {
		printf("%d ", i);  //给行标号
		for (j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);    //打印出*来作为隐藏草皮去埋雷
		}
		printf("\n");
	}
	printf("~~~~~~~~~~~扫雷~~~~~~~~~~~~~~\n");
}

想要埋雷,先要隐藏雷,在这99的草皮中,光秃秃的地皮肯定不好埋,玩家一看这边被挖过了就知道这边有雷,不会过去,但是,倘若你在这片区域放上草皮,隐藏一下,玩家就不能一眼看出哪里藏雷,就需要根据推理来判断哪里有雷,所以让这块99的区域都打印成”*“。

test.c

//打印棋盘
	/*PrintBoard(mine, ROW, COL);*/
	PrintBoard(show, ROW, COL);

大家可能会好奇,为什么有个注释行?原因其实很简单,我们把注释行放开,发现原来控制台会出现两个9*9的方格,你可以进行比对了呀!检查检查计算机有没有按照你的心意先初始化不赋地方为雷。
在这里插入图片描述

7.随机布置雷

game.h

//布置雷
void MineSet(char mine[ROWS][COLS], int row, int col);//传入mine[ROWS][COLS]方便看出是传入的是雷的信息

game.c

//布置雷
void MineSet(char mine[ROWS][COLS], int row, int col) {
	int count = MineCount;  //十个雷
	while (count) {
		//下标的随机生成
		int x = rand() % row + 1;  //模row(9)生成随机数0到8后再加一为1到9
		int y = rand() % col + 1;  //同上
		//布置雷
		while (mine[x][y] == '0') {
			mine[x][y] = '1';   //雷表示'1',没有雷的地方赋个'1',代表加一个雷
			count--;   //打印一个雷减一个雷,直到减到0
		}
	}
}

test.c

//布置雷(随机布置)
	MineSet(mine, ROW, COL);  //布置雷是布置在9*9的方格当中,所以传参传的是ROW和COL
	/*PrintBoard(mine, ROW, COL);*/

注释行的作用为打印出雷的具体位置,大家可以看一看计算机有没有出错,以及了解一下雷的具体位置。如下图,数一数,果然是十个,计算机没有骗我!
在这里插入图片描述

void test() {
    //int input = 0;
	srand((unsigned int)time(NULL)); //随机生成的种子
	//printf("扫雷游戏\n");
	//menu();
	/*do {
		printf("请输入:>"); 
		scanf("%d", &input);
		switch (input) {
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);*/   //input为非零则为真,在程序里面执行;若为零则为假,跳出程序
}

这里的随机函数种子就存在很大的作用了,电脑随机生成十个雷。

8.排查雷(简易版)

这个只是比较简易版的排雷,也就是只有比较简单的排查周围八个位置有没有雷,而真正的排雷应该是点开一个区域,如果为0,周围一片没有雷的地方都应该展开,所以,在第9个模块我将会详细跟大家解释如果利用递归来实现排一大片的雷!

game.h

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

大家可能对于周围八个区域空间的雷的信息很头大,这怎么做呀?其实不难,大家想想为什么之前我定义了一个’0’和’1’,这边就起到很重要的作用了,大家看,‘1’的ASCII码为49,‘0’的ASCII码为48,要是想找到周围的雷,是不是只需要将这八个加起来并减去8*‘0’就能知道周围有几个雷了。原来那么简单!动起手来!

//排查雷
int get_mine_count(char mine[ROWS][COLS], int x, int y) {
	return(mine[x - 1][y - 1] + mine[x - 1][y] + 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');  //x,y坐标周围八个坐标的代表是字符'0'或者'1',字符0代表的ASCII码数为48,‘1’代表的ASCII码数为49,只要是减去8*‘0’则可以显示出周围有几个雷
}

test.c
下面这个代码其实要和最后一步一起用才可以,所以,现在这里放一下给大家一个预热!

//排查雷
	FindMine(mine, show, ROW, COL);

9.排查雷(扩展版)

正式版的终于来了,御用的思想主要是递归(这里只放上game.c的代码,其余与第8个模块一致)

在这里插入图片描述

先解析一下,如图,判断紫色方框周围八个位置是否存在雷,发现是有的,但是正下方是没有雷的,此时需要递归去判断褐色方框周围存不存在雷,再进行判断,直到排不出附近那一大片位置的雷,也就是这片区域没有雷,形成一个半包的样子。

game.c

//排查雷
/*int get_mine_count(char mine[ROWS][COLS], int x, int y) {
	return(mine[x - 1][y - 1] + mine[x - 1][y] + 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');  //x,y坐标周围八个坐标的代表是字符'0'或者'1',字符0代表的ASCII码数为48,‘1’代表的ASCII码数为49,只要是减去8*‘0’则可以显示出周围有几个雷
}*/

//判断周围有没有雷
void Broadboom(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	//判断坐标是否越界
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
		return 0;
	}
	//判断是否已经被排除
	if (show[x][y] != '*') {
		return 0;
	}
	int count = get_mine_count(mine, x, y);
	if (count > 0) {
		show[x][y] = count + '0';
		return 0;
	}
	//递归实现排雷
	else if (count == 0) {
		show[x][y] = '0';
		Broadboom(mine, show, x - 1, y);
		Broadboom(mine, show, x - 1, y - 1);
		Broadboom(mine, show, x, y - 1);
		Broadboom(mine, show, x + 1, y - 1);
		Broadboom(mine, show, x + 1, y);
		Broadboom(mine, show, x + 1, y + 1);
		Broadboom(mine, show, x, y + 1);
		Broadboom(mine, show, x - 1, y + 1);
	}
}

那肯定是要先判断这个坐标有没有越界和是否已经被排查过了,引进一个count等于上面排查的雷的数量,如果它大于0,那很简单呀,只需要显示这一个位置旁边雷的个数就好了,但如果是0怎么办?其实很简单,运用一个递归就能解决了!往它其他八个位置递归,再判断这个坐标的周围八个位置的再周围的八个位置是不是有雷,大家可能会疑惑,那判断过的地方呢?哈哈,我早有想到,递归进去的还是这个函数,继续判断是否越界和是否已经被排查过,让计算机一直运作,我们就看最终出现在我们眼前的黑窗口就好了!

10.判断输赢

game.h

与第8个模块相同

//判断输赢
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

//判断游戏输赢
void FindMine(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 - MineCount)) {  //81-10=71 排查出71个地方不是雷就可以跳出循环了
		printf("请输入要排查的坐标>");
		scanf("%d %d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) {    //判断坐标是否合法
			//缺少一个讨论的问题,没有做出来已经被排查的雷,如果这个地方被排查过了,就不需要再排查了
			if (show[x][y] != '*') {
				printf("该地方已经被排查过了!\n");
				continue;
			}
			if (mine[x][y] == '1') {    //踩雷成功
				printf("很遗憾,你踩到雷了\n");
				PrintBoard(mine, ROW, COL);
				break;
			}
			else {
				Broadboom(mine, show, x, y);
				/*show[x][y] = get_mine_count(mine, x, y) + '0';*/
				int n = get_mine_count(mine, x, y);   //重新定义一个函数传进去找周围雷的个数
				show[x][y] = n + '0';  //ASCII码值为n+'0',则为周围几个雷
				PrintBoard(show, ROW, COL);
				win++;
			}
		}
		else {
			printf("输入坐标错误,请重新输入\n");
		}
	}
	if (win == (row * col - MineCount)) {
		printf("恭喜你,排雷成功\n");
	}
}

test.c

与第8个模块代码一样

//排查雷
	FindMine(mine, show, ROW, COL);

代码汇总

game.h

#include<stdio.h>
#include<time.h>
#include<stdlib.h>


#define ROW 9    //行的行数
#define COL 9    //列的列数
#define ROWS ROW+2   //行数+2,防止越界
#define COLS COL+2   //列数+2,防止越界
#define MineCount 10  //雷的个数


//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);

//布置雷
void MineSet(char mine[ROWS][COLS], int row, int col);

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

#include"game.h"

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) {
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			board[i][j] = set;
		}
	}
}

//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col) {
	int i = 0;
	int j = 0;
	printf("~~~~~~~~~~~扫雷~~~~~~~~~~~~~~\n");
	for (j = 0; j <= col; j++) {
		printf("%d ", j);        //给列标号
	}
	printf("\n");
	for (i = 1; i <= row; i++) {
		printf("%d ", i);  //给行标号
		for (j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);    //打印出*来作为隐藏草皮去埋雷
		}
		printf("\n");
	}
	printf("~~~~~~~~~~~扫雷~~~~~~~~~~~~~~\n");
}

//布置雷
void MineSet(char mine[ROWS][COLS], int row, int col) {
	int count = MineCount;  //十个雷
	while (count) {
		//下标的随机生成
		int x = rand() % row + 1;  //模row(9)生成随机数0到8后再加一为1到9
		int y = rand() % col + 1;  //同上
		//布置雷
		while (mine[x][y] == '0') {
			mine[x][y] = '1';   //雷表示'1',没有雷的地方赋个'1',代表加一个雷
			count--;   //打印一个雷减一个雷,直到减到0
		}
	}
}

//排查雷
int get_mine_count(char mine[ROWS][COLS], int x, int y) {
	return(mine[x - 1][y - 1] + mine[x - 1][y] + 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');  //x,y坐标周围八个坐标的代表是字符'0'或者'1',字符0代表的ASCII码数为48,‘1’代表的ASCII码数为49,只要是减去8*‘0’则可以显示出周围有几个雷
}

//判断周围有没有雷
void Broadboom(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	//判断坐标是否越界
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
		return 0;
	}
	//判断是否已经被排除
	if (show[x][y] != '*') {
		return 0;
	}
	int count = get_mine_count(mine, x, y);
	if (count > 0) {
		show[x][y] = count + '0';
		return 0;
	}
	//递归实现排雷
	else if (count == 0) {
		show[x][y] = '0';
		Broadboom(mine, show, x - 1, y);
		Broadboom(mine, show, x - 1, y - 1);
		Broadboom(mine, show, x, y - 1);
		Broadboom(mine, show, x + 1, y - 1);
		Broadboom(mine, show, x + 1, y);
		Broadboom(mine, show, x + 1, y + 1);
		Broadboom(mine, show, x, y + 1);
		Broadboom(mine, show, x - 1, y + 1);
	}
}

//判断游戏输赢
void FindMine(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 - MineCount)) {  //81-10=71 排查出71个地方不是雷就可以跳出循环了
		printf("请输入要排查的坐标>");
		scanf("%d %d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) {    //判断坐标是否合法
			//缺少一个讨论的问题,没有做出来已经被排查的雷,如果这个地方被排查过了,就不需要再排查了
			if (show[x][y] != '*') {
				printf("该地方已经被排查过了!\n");
				continue;
			}
			if (mine[x][y] == '1') {    //踩雷成功
				printf("很遗憾,你踩到雷了\n");
				PrintBoard(mine, ROW, COL);
				break;
			}
			else {
				Broadboom(mine, show, x, y);
				/*show[x][y] = get_mine_count(mine, x, y) + '0';*/
				int n = get_mine_count(mine, x, y);   //重新定义一个函数传进去找周围雷的个数
				show[x][y] = n + '0';  //ASCII码值为n+'0',则为周围几个雷
				PrintBoard(show, ROW, COL);
				win++;
			}
		}
		else {
			printf("输入坐标错误,请重新输入\n");
		}
	}
	if (win == (row * col - MineCount)) {
		printf("恭喜你,排雷成功\n");
	}
}

test.c

#include"game.h"

void game() {
	printf("开始游戏\n");
	//1.需要准备两个二维数组,一个数组用来存放布置好的雷的信息,另一个数组用来存放排查出雷的信息
	//2.很奇怪的点,如果排查在边界怎么办,很简单,为了防止越界,那就行增加两行,列增加两列
	char mine[ROWS][COLS] = { 0 };  //存放布置好的雷的信息
	char show[ROWS][COLS] = { 0 };  //存放排查出的雷的信息
	
	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');  //'0'表示这个地方是没有雷的
	InitBoard(show, ROWS, COLS, '*');  //‘*’表示埋雷,把所有地方埋掉
	
	//打印棋盘
	/*PrintBoard(mine, ROW, COL);*/
	PrintBoard(show, ROW, COL);
	
	//布置雷(随机布置)
	MineSet(mine, ROW, COL);  //布置雷是布置在9*9的方格当中,所以传参传的是ROW和COL
	/*PrintBoard(mine, ROW, COL);*/

	//排查雷
	FindMine(mine, show, ROW, COL);
}

void menu() {   //打印菜单
	printf("****************************\n");
	printf("***冒险者,请选择是否冒险***\n");
	printf("**********1.游戏开始********\n");
	printf("**********0.退出游戏********\n");
}

void test() {
    int input = 0;
	srand((unsigned int)time(NULL)); //随机生成的种子
	printf("扫雷游戏\n");
	menu();
	do {
		printf("请输入:>"); 
		scanf("%d", &input);
		switch (input) {
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);   //input为非零则为真,在程序里面执行;若为零则为假,跳出程序
}

int main() {
	test();
	return 0;
}

总结

用初阶C语言写扫雷小游戏这个童年回忆小游戏确实有点难,但大家要吃的下这份苦来,一步一步来,按照这篇博客慢慢来终能学会写这个程序。扫雷小游戏运用了很多函数、数组和一部分递归来解决,只要基础够扎实就能写出这个程序,加油!!!

客官,都读到这里了,赏脸给个三连呗~~~

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2022horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值