高质量C语言实现扫雷(递归实现大规模扫雷)

上一次我们用C语言实现了三子棋,这一次我们来实现扫雷。

三子棋链接:高质量C语言实现三子棋


扫雷的游戏规则:“扫雷的规则我们随便点一个格子,方格即被打开并显示出方格中的数字,方格中数字则表示其周围的8个方格隐藏了几颗雷,点开的数字是几,则说明该数字旁边的8个位置中有几个雷,如果挖开的是地则会输掉游戏。”


读者可以参照下面的游戏图例感受。

 下面我将创建一个一个函数来拼凑出扫雷游戏。


目录

 一.打印菜单

二.游戏菜单互动功能 

三.游戏函数 

1. 初始化棋盘

 2.布置雷

3.打印棋盘

4.扫雷

四.拓展


 一.打印菜单

#include<stdio.h>
void menu()
{
	printf("*********************************\n");
	printf("******    1.五子棋      *********\n");
	printf("******    0.退出游戏    *********\n");
	printf("*********************************\n");
}
int main()
{
	menu();
	return 0;
}

函数:menu()

功能:打印游戏菜单


这里先写一个函数打印菜单,下一步我们来完善互动功能。


二.游戏菜单互动功能 

int main()
{
	int input = 0;
	do 
	{
	menu();
	printf("请选择->\n");
	scanf("%d", &input);
	switch (input)
		{
		case 1:
			//game();游戏函数,暂时没写
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);//当输入0退出游戏时,input为0,循环停止
	return 0;
}

 下一步就是写游戏函数了。


三.游戏函数 

游戏思路:(这次实现的是初级难度的扫雷,棋盘9X9,10个雷)

我们先创建两个二维数组一个数组布置雷,方便我们统计雷的数量,另一个数组打印出来给用户看,棋盘大小为11X11。用户输入一个合法坐标,我们判断该坐标是不是雷,如果是就游戏结束,不是就遍历该坐标周围8个位置,通过布置雷的那个棋盘统计雷的数量,比如说是3个,就在给用户看的数组上面把对应的坐标替换成字符‘3’。

11X11的棋盘是为了方便统计棋盘边缘的雷的数量,这样就不会越界访问了,用户进行扫雷的棋盘是9X9


如果有疑问也不要紧,沿着我的思路慢慢看下去就会豁然开朗了。


1. 初始化棋盘

#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
void game()
{
	char board[11][11] = { 0 };//放置雷的棋盘
	char show[11][11] = { 0 };//呈现给用户看的棋盘
	InitBoard(show, ROW, COL, '*');//初始化棋盘的函数,下面会讲
	InitBoard(board, ROW, COL, '0');
}

我们先创建两个11X11的char型的二维数组。

ROW和COL是我们对两个数组操作时需要用到的,ROWS和COLS是我们传递数组给函数时告诉函数这是11X11的数组。

void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{//上面的ROWS和COLS是告诉函数我们传的是一个11X11的数组
	int i = 1;
	int j = 1;
	if (ch == '0')//这里判断初始化的数组是不是布置雷的
	{
		for (i = 0; i <= 10; i++)
		{
			for (j = 0; j <= 10; j++)
			{
				board[i][j] = ch;//将整个数组都初始化为字符'0'
			}
		}
		return;//初始化完成,退出函数
	}
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			board[i][j] = ch;//将呈现给用户看的棋盘都初始化为字符'*',代表这个坐标未被扫描
		}
	}
}

函数:InitBoard(char ,int ,int )

功能:初始化两个棋盘。


 2.布置雷

void game()
{
	char board[11][11] = { 0 };//放置雷的棋盘
	char show[11][11] = { 0 };//呈现给用户看的棋盘
	InitBoard(show, ROW, COL, '*');//初始化棋盘的函数,下面会讲
	InitBoard(board, ROW, COL, '0');
	SetBoard(board, ROW, COL);//布置雷
}

上面是游戏函数的代码,下面是布置雷函数的代码。

void SetBoard(char board[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int num = 0;
	while (num < 10)//统计放置的雷有没有达到10个
	{
		x = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
		y = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
		if (board[x][y] == '0')//判断随机生成的坐标是不是雷
		{
			board[x][y] = '1';//不是雷就在这放雷
			num++;
		}
	}
}
函数:SetBoard(char ,int ,int)
功能:在board[11][11]棋盘上布置雷

这个随机坐标函数是rand()函数,使用之前我们需要先引入<stdlib.h>头文件,在主函数的开头设置随机数生成器,以时间戳为变化的参数,时间戳需要引入<time.h>头文件。


#include<stdlib.h>
#include<time.h>
int main()
{
	srand((unsigned int)time(NULL));//设置随机数生成器
	//srand()和time(),time函数的参数设置为空指针,强制转换为无符号整形(unsigned int)
	return 0;
}

3.打印棋盘

void game()
{
	char board[11][11] = { 0 };//放置雷的棋盘
	char show[11][11] = { 0 };//呈现给用户看的棋盘
	InitBoard(show, ROW, COL, '*');//初始化棋盘的函数
	InitBoard(board, ROW, COL, '0');
	SetBoard(board, ROW, COL);//布置雷
	PrintBoard(show, ROW, COL);//实际游戏只会打印show棋盘
	PrintBoard(board, ROW, COL);//这里为了检查是否有布置好雷,所以打印布置雷的棋盘
}

上面是游戏函数,下面是打印棋盘的函数

void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 1;
	int j = 1;
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);//第一列打印对应行的函数,是棋盘的坐标
		for (j = 1; j <= col; j++)
		{
			if (!i)//判断是不是第一行
			{
				printf("%d ", j);//是的话就打印1-9,是棋盘的坐标
				continue;
			}
			printf("%c ", board[i][j]);//打印9X9棋盘内容
		}
		printf("\n");
	}
	printf("-------------------\n");
}

函数:PrintBoard(char ,int ,int)

功能:打印棋盘

打印棋盘的时候打印了坐标,方便用户输入坐标


效果图如下(上面的棋盘是显示给用户看的,下面的棋盘是布置雷的,1代表雷)

 


4.扫雷

int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
	static int step = 0;//设置静态变量统计玩家走的步数
	int sum = 0;
	if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
	{
		printf("恭喜你扫雷成功!\n");
		step = 0;//游戏结束重置步数
		return 0;
	}
	int x = 0;
	int y = 0;
	printf("请选择排雷的坐标\n");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
		{
			printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
			continue;//回到输入坐标的地方
		}
		if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
		{
			printf("你被炸死了,游戏结束\n");
			PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
			step = 0;//游戏结束重置步数
			return 0;//跳出函数
		}
		else
			break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
	}
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			sum += board[i][j];//这里是把字符的ASCLL码值加起来
		}
	}
	show[x][y] = sum - 8 * 48;//字符0的ASCLL码值是48,上面把坐标周围以及坐标自身的ASCLL码值加起来,最后再减8个0的ACSLL码值得到的就是周围的雷的数量的ASCLL码值(读者可自行验算!!!)
	step++;//扫了一次雷,步数加一
	return 1;//返回1,游戏函数的while循环继续
}

函数:Play(char ,char ,int ,int )

功能:首先判断玩家是否扫雷成功,然后判断给出的坐标是否有雷并且是否合法,再进行扫雷


效果图:(上面的1-9和左边的1-9是坐标,方便玩家快速查找到对应位置的坐标,棋盘中间的数字代表周围8个位置有几个雷,0代表没有雷。)


四.拓展

如上图,我们不难发现,坐标为5 5的位置是字符0,说明周围8个位置没有雷,那么我们能不能让他把周围8个格子替换成空格然后重新遍历周围8个位置每一个位置再遍历自己周围没有被遍历过的位置,把周围没有雷的替换成空格,直到遇到周围8个位置有雷的,就像下面这样。

 这样就可以避免自己一个一个手动输入坐标。

这个可以看成一个连锁反应,周围8格没有雷,就把周围8格的每一格的    周围8格中    没有被遍历过的    遍历一次,一直这样,直到遇到边界或者周围有雷的位置。


很显然,这可以用到递归,我们来专门写一个遍历周围8个位置的函数,用来替换上面讲过的Play函数最后面的判断代码(也就是两个for循环组成的那个代码)。

int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
	int sum = 0;
	if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
	{
		printf("恭喜你扫雷成功!\n");
		return 0;
	}
	int x = 0;
	int y = 0;
	printf("请选择排雷的坐标\n");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
		{
			printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
			continue;//回到输入坐标的地方
		}
		if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
		{
			printf("你被炸死了,游戏结束\n");
			PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
			return 0;//跳出函数
		}
		else
			break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
	}
	Judge(show, board, x, y);//整个函数只有这里和开头有变化(静态变量step被设置成全局变量了)
	return 1;//返回1,游戏函数的while循环继续
}

接下来就是用递归实现的大规模扫雷功能的函数了

int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
	int sum = 0;
	if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')//判断坐标是否合法,以及最重要的通过show棋盘来判断该坐标是否已经被扫过了
		return 0;//如果是则退出函数
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			sum += board[i][j];//这里用来统计该坐标周围8个位置的雷的数量
		}
	}
	if ((sum - 8 * 48) == '0')//判断该坐标周围8个位置是否有雷
	{
		show[x][y] = 32;//如果这个坐标周围没有雷则会被替换成空格
		step++;//步数加一
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				Judge(show, board, i, j);//这里就是对该坐标周围8个坐标的  周围  没有被遍历过的坐标遍历一次(统计坐标周围是否有雷),形成递归
			}
		}
	}
	else
	{
		show[x][y] = sum - 8 * 48;//如果有雷则像之前的Play函数一样把统计的雷的数量以字符形式放到show棋盘对应的坐标上
		step++;//步数加一
	}
	return 0;
}

如果读者不太清楚这其中的原理可以自己拿笔和纸动手画一画,一切就会豁然开朗

在这里我把step变成了全局变量,为了方便在递归函数中进行步数统计。而step的初始化我放在了game函数的开头


下面是游戏全部的代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
int step = 0;
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{//上面的ROWS和COLS是告诉函数我们传的是一个11X11的数组
	int i = 1;
	int j = 1;
	if (ch == '0')//这里判断初始化的数组是不是布置雷的
	{
		for (i = 0; i <= 10; i++)
		{
			for (j = 0; j <= 10; j++)
			{
				board[i][j] = ch;//将整个数组都初始化为字符'0'
			}
		}
		return;//初始化完成,退出函数
	}
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			board[i][j] = ch;//将呈现给用户看的棋盘都初始化为字符'*',代表这个坐标未被扫描
		}
	}
}
void SetBoard(char board[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int num = 0;
	while (num < 10)//统计放置的雷有没有达到10个
	{
		x = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
		y = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
		if (board[x][y] == '0')//判断随机生成的坐标是不是雷
		{
			board[x][y] = '1';//不是雷就在这放雷
			num++;
		}
	}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 1;
	int j = 1;
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);//第一列打印对应行的函数,是棋盘的坐标
		for (j = 1; j <= col; j++)
		{
			if (!i)//判断是不是第一行
			{
				printf("%d ", j);//是的话就打印1-9,是棋盘的坐标
				continue;
			}
			printf("%c ", board[i][j]);//打印9X9棋盘内容
		}
		printf("\n");
	}
	printf("-------------------\n");
}
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
	int sum = 0;
	if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')//判断坐标是否合法,以及最重要的通过show棋盘来判断该坐标是否已经被扫过了
		return 0;//如果是则退出函数
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			sum += board[i][j];//这里用来统计该坐标周围8个位置的雷的数量
		}
	}
	if ((sum - 8 * 48) == '0')//判断该坐标周围8个位置是否有雷
	{
		show[x][y] = 32;//如果这个坐标周围没有雷则会被替换成空格
		step++;//步数加一
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				Judge(show, board, i, j);//这里就是对该坐标周围8个坐标的  周围  没有被遍历过的坐标遍历一次(统计坐标周围是否有雷),形成递归
			}
		}
	}
	else
	{
		show[x][y] = sum - 8 * 48;//如果有雷则像之前的Play函数一样把统计的雷的数量以字符形式放到show棋盘对应的坐标上
		step++;//步数加一
	}
	return 0;
}
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
	int sum = 0;
	if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
	{
		printf("恭喜你扫雷成功!\n");
		return 0;
	}
	int x = 0;
	int y = 0;
	printf("请选择排雷的坐标\n");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
		{
			printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
			continue;//回到输入坐标的地方
		}
		if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
		{
			printf("你被炸死了,游戏结束\n");
			PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
			return 0;//跳出函数
		}
		else
			break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
	}
	Judge(show, board, x, y);//整个函数只有这里有变化
	return 1;//返回1,游戏函数的while循环继续
}
void game()
{
	step = 0;//全局变量step,每局游戏开始前重置步数
	char board[11][11] = { 0 };//放置雷的棋盘
	char show[11][11] = { 0 };//呈现给用户看的棋盘
	InitBoard(show, ROW, COL, '*');//初始化棋盘的函数
	InitBoard(board, ROW, COL, '0');
	SetBoard(board, ROW, COL);//布置雷
	do {
		PrintBoard(show, ROW, COL);//循环打印show棋盘
	} while (Play(show, board, ROW, COL));//当游戏结束时Play返回0,结束循坏跳出game函数
}
void menu()
{
	printf("*********************************\n");
	printf("******    1.五子棋      *********\n");
	printf("******    0.退出游戏    *********\n");
	printf("*********************************\n");
}
int main()
{
	srand((unsigned int)time(NULL));//设置随机数生成器
	//srand()和time(),time函数的参数设置为空指针,强制转换为无符号整形(unsigned int)
	int input = 0;
	do 
	{
	menu();
	printf("请选择->\n");
	scanf("%d", &input);
	switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);//当输入0退出游戏时,input为0,循环停止
	return 0;
}

当然这样看起来比较臃肿,可以参考我三子棋的代码优化,可以优化成下面这样子。

高质量C语言实现三子棋

game.h(这是自己创建的头文件,放的是所有函数的声明和引用的库函数的头文件)

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
void menu(); 
void InitBoard(char board[ROWS][COLS], int row, int col, char ch);
void SetBoard(char board[ROWS][COLS], int row, int col);
void PrintBoard(char board[ROWS][COLS], int row, int col);
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y);
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col);

test.c

#include"game.h"
int step = 0;
void game()
{
	step= 0;
	char board[11][11] = { 0 };
	char show[11][11] = { 0 };
	InitBoard(show,ROW,COL,'*');
	InitBoard(board, ROW, COL,'0');
	SetBoard(board,ROW,COL);
	do {
		PrintBoard(show, ROW, COL);
	} while (Play(show, board, ROW, COL));
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do {
		menu();
		printf("请选择->\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

game.c

#include"game.h"
extern int step;
void menu()
{
	printf("*****************************\n");
	printf("******     1.play      ******\n");
	printf("******     0.exit      ******\n");
	printf("*****************************\n");
}
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
	int i = 1;
	int j = 1;
	if (ch == '0')
	{
		for (i = 0; i <= 10; i++)
		{
			for (j = 0; j <= 10; j++)
			{
				board[i][j] = ch;
			}
		}
		return;
	}
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			board[i][j] = ch;
		}
	}
}
void SetBoard(char board[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int num = 0;
	while (num < 10)
	{
		x = rand() % 9 + 1;
		y = rand() % 9 + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			num++;
		}
	}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 1;
	int j = 1;
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			if (!i)
			{
				printf("%d ", j);
				continue;
			}
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------------------------------------\n");
}
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
	int sum = 0;
	if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')
		return 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			sum += board[i][j];
		}
	}
	if ((sum - 8 * 48) == '0')
	{
		show[x][y] = 32;
		step++;
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				Judge(show, board, i, j);
			}
		}
	}
	else
	{
		show[x][y] = sum - 8 * 48;
		step++;
	}
	return 0;
}
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
	if (step == 71)
	{
		printf("恭喜你扫雷成功!\n");
		return 0;
	}
	int x = 0;
	int y = 0;
	printf("请选择排雷的坐标\n");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')
		{
			printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
			continue;
		}
		if (board[x][y] == '1')
		{
			printf("你被炸死了,游戏结束\n");
			PrintBoard(board, ROW, COL);
			return 0;
		}
		else
			break;
	}
	Judge(show, board, x, y);
	return 1;
}

本次扫雷的小游戏到这里就结束了,非常感谢各位读者能看到这里,希望这篇文章也对你们有一点帮助,如果有什么问题或者意见都可以在评论区告诉我,我也会认真解释和采纳的。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值