C语言实现扫雷(递归展开)

C语言实现扫雷(递归展开),第1张

C语言实现扫雷(递归展开)

前情提要:递归解释分析过程已加粗。

1、由于扫雷面板给用户排雷时需要把雷隐藏起来,又需要记录雷的信息进行排雷判断,所以一个二维数组难以一起做到这些功能,所以创建了两个二维数组进行使用。

2、由于创建了两个不同数组需要不同的初始化方式,所以初始化函数多加了一个参数,对不同的数组进行初始化。

3、埋雷借助rand以及srand函数配合使用产生随机的坐标进行埋雷,确保坐标具有随机性。

4、对排雷时附近没有雷的坐标进行递归展开,分析得出递归需要满足这三个条件:

(1)、该坐标是不是雷;

(2)、该坐标附近8个坐标是不是都没有雷;

(3)、该坐标是否被排查过,这点需要特别注意,如果未进行判断标记,会使递归进入死递归,从而导致栈溢出,程序挂掉。

5、对扫雷面板行,列,雷的数量用#define的标识符常量进行定义,会方便整个程序的修改,测试。

详见以下代码:

#pragma once
#include 
#include 
#include 

//面板显示行列数
#define ROW 9
#define COL 9
//实际创建数组的行数和列数,方便展开判断
#define ROWS ROW+2
#define COLS COL+2
//雷数量设置
#define MINE_COUNT 10

//初始化
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);
//打印面板
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
//埋雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

//初始化 标志雷和隐藏雷的字符不一样所以需要两个相同的二位数组进行记录
//由于标志的不同所以形参需要增加一个所需初始化成的字符
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	//遍历
	//行
	for (int i = 0; i < rows; i++)
	{
		//列
		for (int j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}
//打印面板,遍历数字,把行号和列号打印出来,方便观察
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	printf("***********  扫雷游戏  ************n");
	//打印列好
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("n");
	for (int i = 1; i <= row; i++)
	{
		//行号
		printf("%d ", i);
		//遍历
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("n");
	}
	printf("***********  扫雷游戏  ************n");
}
//埋雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//埋雷的数量由宏定义的标识符给定,便于修改
	int count = MINE_COUNT;
	while (count)
	{
		//随机获取雷的坐标(%行 列 + 1 得到在面板范围的坐标)
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//避免重复埋雷
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}
//一个坐标附近8个坐标的雷的情况
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	//直接读取字符减掉8个'0,即可正确得到周围雷的个数
	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';
}
//判断面板上还未被排除的坐标个数来判断是否排雷完成
int IsWin(char show[ROWS][COLS], int row, int col)
{
	int count = 0;
	//遍历
	for (int i = 1; i <= row; i++)
	{
		for (int j = 1; j <= col; j++)
		{
			if (show[i][j] == '*')
				count++;
		}
	}
	return count;
}
//扫雷展开附近8个坐标都没有雷的递归函数
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	//获取排雷坐标附近雷的数量
	int count = GetMineCount(mine, x, y);
	//没有雷才进行展开
	if (count == 0)
	{
		//没有雷标志空格
		show[x][y] = ' ';
		//注意一定要在坐标还未进行过排雷和坐标不会越界的情况下进行展开,否则会进入越界和死递归
		//造成程序崩溃
		if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x - 1, y - 1);
		if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x - 1, y);
		if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x - 1, y + 1);
		if (show[x][y - 1] == '*' && x > 0 && x < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x , y - 1);
		if (show[x][y + 1] == '*' && x > 0 && x < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x, y + 1);
		if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x + 1, y - 1);
		if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x + 1, y);
		if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x + 1, y + 1);
	}
	else
	{
		//坐标附近有雷,正确显示雷的数量
		show[x][y] = count + '0';
	}
}
//排雷过程判断
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		//输入排雷坐标
		printf("请输入排雷坐标:>");
		scanf("%d %d", &x, &y);
		//判断输入坐标是否在面板范围内
		if (x > 0 && x <= row && y > 0 && y <= col)
		{
			//如果是雷,游戏结束
			if (mine[x][y] == '1')
			{
				printf("你被炸死了!!!!!!n");
				break;
			}
			else 
			{
				//不是雷,判断附近雷的个数以及是否展开
				Expand(mine, show, x, y);
				//获取还未被排除的坐标个数
				int ret = IsWin(show, row, col);
				//判断是否已经排完
				if (ret == MINE_COUNT)
				{
					printf("恭喜你,排雷成功!!!!!!n");
					break;
				}
				//排一个坐标,打印新的面板信息
				DisplayBoard(show, row, col);

			}
		}
		//提示重新输入坐标
		else
		{
			printf("坐标输入错误,请重新输入!n");
		}
	}
	//游戏结束,显示此局雷的分布
	printf("此局雷的分布如下图所示n");
	DisplayBoard(mine, row, col);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//菜单
void menu()
{
	printf("**************************n");
	printf("********* 1.Play *********n");
	printf("********* 0.Exit *********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);
	//测试时显示雷的分布情况
	//isplayBoard(mine, ROW, COL);
	//排雷过程
	FindMine(mine, show, ROW, COL);
}
int main()
{
	//随机数发生器配合rand函数使用
	srand((unsigned int)time(NULL));
	int input = 0;
	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;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5711440.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存