《在C语言中布雷:一场逻辑与运气的较量》---全部源码

1:扫雷游戏的介绍:

hello,hello,小伙伴们来晚了,想必大家都玩过扫雷,下面我整理用c语言写扫雷的过程详解与思路:

扫雷(Minesweeper)是一款经典的单人益智游戏,起源于1980年代,最初在个人电脑上流行。它的目标是在没有触碰到地雷的情况下,揭开尽可能多的方格。以下是扫雷游戏的一些基本介绍:

### 游戏规则
1. **棋盘**: 游戏通常在一个由多个方格组成的网格上进行,网格的大小和地雷的数量可以根据难度进行设置(例如:9*9,8*8、16*16等)。
2. **地雷**: 在网格中随机分布着一定数量的地雷(例如10个地雷)。
3. **揭开方格**: 玩家点击一个方格进行揭开。如果点击的是地雷,游戏结束;如果点击的不是地雷,则显示该方格周围地雷的数量(0到8)。
4. **数字提示**: 每个方格上的数字表示该方格周围8个方格中地雷的数量。玩家可以根据这些数字推测周围地雷的位置。
5. **标记地雷**: 玩家可以右键点击某个方格来标记他们认为是地雷的方格。标记的方格会显示一个小旗子。
6. **胜利条件**: 玩家需要揭开所有非地雷的方格,或者恰当标记所有地雷,从而完成游戏。

### 策略与技巧
- **逻辑推理**: 玩扫雷不仅需要运气,还需要通过数字进行逻辑推理,判断地雷的可能位置。
- **安全区**: 当一个方格显示“0”时,可以自动揭开其周围所有未揭开的方格,因为这些方格中不可能有地雷。
- **标记与排除法**: 当几个方格组合在一起时,可能会出现一些特定的模式,通过这些模式可以推断出地雷的位置。

2:扫雷的实现过程

首先,我把扫雷这个项目分成三个文件,分别是test.c:用于代码的主逻辑;game.c:用于游戏的实现;game.h:我将文件的声明和函数的声明和定义放到三个文件,这样便于我们写代码,调试起来也更灵活方便,代码修改起来也更方便,也适合我们合作。

如下图:

接下来,我们就要实现代码的主逻辑了,为了增加趣味性和说明我可以先打印菜单和提供玩家选择,为了美观,我把菜单封装成一个函数。玩家既可以选择玩游戏,也可以选择退出游戏,如果选择错误,也会提醒玩家,所以我可以使用switch条件分语句供玩家选择,选择1:扫雷

选择0:退出游戏;选择其他:default报错。当然,不管玩家玩不玩,我都会打印菜单,供玩家选择,那么do,while循环很合适,我把玩家选择放为循环判断条件,以保证玩家玩一把不过瘾能够再玩。

具体代码如下:

#include <stdio.h>
void menu()
{
	printf("***********************\n");
	printf("********Play .1********\n");
	printf("********Exit .0********\n");
	printf("***********************\n");

}

int main()
{
	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("扫雷\n");//玩游戏的主题逻辑
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新输入:\n");
		}
	} while(input);
	return 0;
}

 为了好测试,我将玩游戏这里先简单代替,进行测试,写一步测试一步,方便后面的代码实现。测试结果如下:

现在我们的核心任务就是考虑扫雷这个游戏到底是如何实现的:

  首先,我们考虑第一个事情,假如我想统计 (2,3)这个坐标周围的八个格子,那么很好统计,那么如果我想统计(0.0)或者(8,1)附近的雷呢,如果还是一如既往的返回周围的八个格子,很显然,数组越界了,那么,为了防止数组越界,我们给数组每一行一列增加一,即11*11的二维数组,这样无论怎么访问,怎么统计雷的个数,都不会越界了。

其次:扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息,所以我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放 信息。 假设我们下一个9*9的扫雷,那么我们就需要有这个棋盘,我们就可以用二维数组来实现棋盘,那我们在考虑,在扫雷游戏,我们需要生成雷,我们还需要扫完这个格子,周围8个格子的信息。假设我将雷初始化为1,而没有雷的地方初始化为0,扫完这个格子,判断周围有几个雷在显示;那么就会产生矛盾,这个格子到底是雷呢,还是我们扫完后排雷的信息。因此,我们可以考虑,用两个二维数组来实现,一个来存放雷(mine数组),一个来显示排雷的信息(show数组);这样,如果我们排完一个格子,就会在排雷信息这个数组来显示。同时为了保持神秘,show数组开始时初始化为字符'*',为了保持两个数组的类型⼀致,可以使⽤同⼀ 套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。于是我们对这两个二维数组进行初始化:

函数实现如下:

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}
void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初始化棋盘
	InitBoard(mine, ROWS, COLS,'0');//存放布置雷
	InitBoard(show, ROWS, COLS, '*');//存放已经排雷的信息
	
}

我分别在game()函数中再封装初始化棋盘的函数,然后在game.h对齐声明,game.c文件实现,以下函数的实现都是如此,这里不过多说明。

为了后面避免代码的重复,也为了我们对棋盘的修改,比如,我不想玩9*9,所以我将重复的数字进行了定义,如下:

#define ROW 9
#define COL 9
#define ROWS  ROW +2
#define COLS  COL +2
#define EASY_COUNT 10

然后,我们可以打印出来看看,封装了一个DisplayBoard(show, ROW, COL);

函数实现如下:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	printf("----------扫雷----------\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	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");
}

接下来就是设置雷了,封装了一个SetMine(mine, ROW, COL)函数,在设置雷的过程中,需要电脑随机生成雷,我们需要调用rand()函数,不过在使用rand()函数之前,我们需要先设置生成器起点srand((unsigned int)time(NULL)),让time函数返回的时间戳值,这样我们就可以获得不同的种子,生成随机数了,time函数的返回值时time_t,需要我们进行强制类型转换。

设置雷函数具体实现如下:

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//储存雷的个数
	
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % row + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

接下来就是重要的一步:统计输入的坐标周围雷的个数。假设现在玩家输入坐标(x,y),那么我如何统计他周围8个坐标的个数呢?我们可以分别列出它们的坐标,然后如果是雷,那么坐标周围雷的个数就增加一,不是雷,增加0,将这8个坐标对应雷的个数加起来,就是(x,y)这个坐标周围8个格子的雷的个数。但需要注意的是,我们假设雷是字符‘1’,非雷是字符‘0’;那么如果我想得到数字1(即雷的个数)即:1=‘1’-‘0’   ;0 = ‘0’-‘0’;这样对于每一个格子,我只需要将他们都加起来,最后减去 8 * ‘0’即可,得到输入的坐标周围雷数字个数。

然后我们扫雷游戏什么时候结束:即排完所有雷,那么我们就可以进行一个while循环,循环判断条件是row*col-EASY_COUNT >win 在前71次都不跳出循环,第72次跳出,则代表排雷成功。具体代码如下:

int GetMineCount(char board[ROWS][COLS], int x, int y)
{
	return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + board[x][y - 1] +
		board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	printf("请输入坐标:>\n");
	int x = 0;
	int y = 0;
	int win = 0;

	while (row * col - EASY_COUNT > win)
	{
		scanf("%d %d", &x, &y);
		if (show[x][y] == '*')//防止坐标被重复下过
		{
			if (x >= 1 && x <= row && y >= 1 && y <= col)
			{
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你输了\n");
					DisplayBoard(mine, ROW, COL);
					break;
				}
				else
				{
					int ret = GetMineCount(mine, x, y);//获取排这个坐标成功后周围8个格子的雷的个数
					show[x][y] = ret + '0';
					win++;
					DisplayBoard(show, ROW, COL);
				}
			}
			else
				printf("坐标非法,请重新输入:\n");
		}
		else
		{
			printf("这个坐标输过了,换个坐标试试\n");
		}
	}
	if (row * col - EASY_COUNT == win)
	{
		printf("hehe,排雷成功!\n");
	}
}

好了,现在我们的扫雷游戏基本上就可以实现了,但是如果我们还想继续增加几个功能,比如,1.如果玩家推测出这个雷,标记雷的功能;2.如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚的功能;3.计时的功能。那么我们对此进行实现。

我们先考虑标记雷的功能,假设玩家如果知道这个地方是雷,进行标记,那么,是否需要每次下棋前,都提供玩家选择是否标记雷,然后在show数组显示,玩家标记的雷记作 '#',选择(0,0)退出标记,如果标记再打印show数组。当然这也是排雷中的过程,所以加到排雷的过程即可。具体代码如下:

/标记雷
void FlagMine(char board[ROWS][COLS], int row, int col)
{
	printf("请输入要标记雷的坐标:>\n");
	printf("输入(0, 0)退出标记\n");
	int x = 0;
	int y = 0;
	while ( 1)
	{
		scanf("%d %d", &x, &y);
		if (x == 0 && y == 0)
		{
			printf("退出标记\n");
			break;
		}
		
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x][y] == '*')
			{
				board[x][y] = '#';
				break;
			}
			else
				printf("这个坐标不能标记,请重新输入\n");
		}
		else
		{
			printf("坐标非法,请重新输入:\n");
		}
	}
}

接着,我们讨论第二个问题:如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚的功能。

我们先思考,假设还是有个坐标(x,y)我想满足这个条件需要三个条件,1:这个坐标不是雷 2.这个坐标周围没有雷. 3.这个坐标没有被排查过。分析这个问题我们可以通过扫雷游戏 展开一片这个图:

现在我们用这个函数SaveSpace(mine,show,x,y);实现,它周围没有雷,函数递归地展开安全区域。 如果当前位置没有周围的雷,就递归展开它周围的邻接块。然后他的邻居块再继续展开,直到展开到遇到雷为止,并显示雷的信息,如果不是雷,那么递归下去,具体代码实现如下:

void SaveSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	
	if (x<1 || x>ROWS || y<1 || y>COLS || show[x][y] != '*')
		return;
	else
	{
		int minecount = GetMineCount(mine, x, y);
		show[x][y] = minecount + '0';//揭示雷的个数
		
		if (minecount == 0)
		{
			SaveSpace(mine, show, x - 1, y - 1); // 上左  
			SaveSpace(mine, show, x - 1, y);     // 上  
			SaveSpace(mine, show, x - 1, y + 1); // 上右  
			SaveSpace(mine, show, x, y - 1);     // 左  
			SaveSpace(mine, show, x, y + 1);     // 右  
			SaveSpace(mine, show, x + 1, y - 1); // 下左  
			SaveSpace(mine, show, x + 1, y);     // 下  
			SaveSpace(mine, show, x + 1, y + 1); // 下右  
		}
	}
}

 3:当玩家看到棋盘时,开始计时,排雷成功,结束计时即可。

3.扫雷游戏的代码实现

game.h

#define  _CRT_SECURE_NO_WARNINGS
#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 InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

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

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

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

//标记雷
void FlagMine(char board[ROWS][COLS], int row, int col);


 test.c

#define  _CRT_SECURE_NO_WARNINGS
#include "game.h" 
void menu()
{
	printf("***********************\n");
	printf("********Play .1********\n");
	printf("********Exit .0********\n");
	printf("***********************\n");

}

void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初始化棋盘
	InitBoard(mine, ROWS, COLS,'0');//存放布置雷
	InitBoard(show, ROWS, COLS, '*');//存放已经排雷的信息
	//打印棋盘
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	//排雷
	FindMine(mine, show, 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");
		}
	} while(input);
	return 0;
}

game.c

#define  _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	printf("----------扫雷----------\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	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");
	printf("\n");
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//储存雷的个数
	
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % row + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

int GetMineCount(char board[ROWS][COLS],int x,int y)
{
	return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + board[x][y - 1] + 
		board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}

void SaveSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	
	if (x<1 || x>ROWS || y<1 || y>COLS || show[x][y] != '*')
		return;
	else
	{
		int minecount = GetMineCount(mine, x, y);
		show[x][y] = minecount + '0';//揭示雷的个数
		
		if (minecount == 0)
		{
			SaveSpace(mine, show, x - 1, y - 1); // 上左  
			SaveSpace(mine, show, x - 1, y);     // 上  
			SaveSpace(mine, show, x - 1, y + 1); // 上右  
			SaveSpace(mine, show, x, y - 1);     // 左  
			SaveSpace(mine, show, x, y + 1);     // 右  
			SaveSpace(mine, show, x + 1, y - 1); // 下左  
			SaveSpace(mine, show, x + 1, y);     // 下  
			SaveSpace(mine, show, x + 1, y + 1); // 下右  
		}
	}
}

//标记雷
void FlagMine(char board[ROWS][COLS], int row, int col)
{
	printf("请输入要标记雷的坐标:>\n");
	printf("输入(0, 0)退出标记\n");
	int x = 0;
	int y = 0;
	while ( 1)
	{
		scanf("%d %d", &x, &y);
		if (x == 0 && y == 0)
		{
			printf("退出标记\n");
			break;
		}
		
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x][y] == '*')
			{
				board[x][y] = '#';
				break;
			}
			else
				printf("这个坐标不能标记,请重新输入\n");
		}
		else
		{
			printf("坐标非法,请重新输入:\n");
		}
	}
}


//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	time_t start_time = time(NULL);//记录时间开始

	while (row * col - EASY_COUNT > win)
	{
		
		printf("请输入排查雷坐标:>");
		scanf("%d %d", &x, &y);
		if (show[x][y] == '*')//防止坐标被重复下过
		{
			if (x >= 1 && x <= row && y >= 1 && y <= col)
			{
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你输了\n");
					DisplayBoard(mine, ROW, COL);
					break;
				}
				else
				{
					
					int ret = GetMineCount(mine, x, y);//获取排这个坐标成功后周围8个格子的雷的个数
					//1.这个坐标不是雷
					//2.这个坐标周围没有雷
					//3.这个坐标没有被排查过
						
						if (mine[x][y] == '0' && ret == 0 && mine[x][y] != '1')
							SaveSpace(mine,show,x,y);
						else{
							show[x][y] = ret + '0';//不为'0'则显示周围雷数
						}
						win++;
						DisplayBoard(show, ROW, COL);
						//标记雷
						FlagMine(show, ROW, COL);
						DisplayBoard(show, ROW, COL);
						
				}
			}
			else
				printf("坐标非法,请重新输入:\n");
		}
		else
		{
			printf("这个坐标输过了,换个坐标试试\n");
		}
	}
	if (row * col - EASY_COUNT == win)
	{
		printf("hehe,排雷成功!\n");
		time_t end_time = time(NULL);//记录时间结束
		double elapsed_time = difftime(end_time, start_time);
		printf("经过的时间: %.f 秒\n", elapsed_time);
	}
}

这仅是大概功能的实现,谢谢大家指正,感谢看到这里的你!

完! 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值