扫雷游戏的C语言实现(递归实现多格展开)

    上一次介绍了C语言实现三子棋的逻辑与思路,这次我们试着用C语言实现另一个小游戏,扫雷。

    扫雷游戏大家都玩过,通过格子中的数字大小判断周围的雷的数量和位置,直到将所有非雷的格子全部翻开,即为游戏胜利;而如果不幸翻开了雷,则游戏失败。

    以上就是扫雷游戏的主要游戏逻辑,我们的程序设计也是以这个逻辑为基础进行设计的。

    菜单界面的实现上一篇文章已经介绍过了,这次我们直接进入游戏的逻辑。

游戏开始:棋盘的初始化、初始雷区的显示和地雷的放置

    与三子棋类似,扫雷游戏测载体也是一个x乘以y的矩形棋盘,我们同样可以使用二维数组进行搭建,但这次不同的是,我们需要建立两个二维数组,其中一个用来存放我们设置的地雷,另一个数组用来显示我们的扫雷过程。由于本次的棋盘比较大,我们在显示棋盘的时候添加了坐标轴的显示,这样除了能更加直观的观察棋盘外,也通过坐标轴将二维数组的第0行和第0列占用,这样我们在编写程序的时候就可以直接使用输入的值,而不用再进行换算。

void print(char board[ROWS][COLS], int row, int col)//显示
{
	int i = 0;
	int j = 0;
	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");
	}
}

    初始化游戏时,我们需要先给数组赋值,这是我们选择将游戏用的显示数组初始化为“*”,将布雷用的数组初始化为“0”。显示用数组的初始化可以选用任意符号,但布雷区数组的初始化建议用“0”,这涉及到下面排雷过程的逻辑运算。

	char setupboard[ROWS][COLS] = { 0 };//布雷区
	char searchboard[ROWS][COLS] = { 0 };//排雷区
	fresh(setupboard, ROWS, COLS, '0');
	fresh(searchboard, ROWS, COLS, '*');
	set(setupboard, ROW, COL, DEF);

void fresh(char board[ROWS][COLS], int row, int col, char set)//区域初始化
{
	int i = 0;
	int j = 0;
	for (i = 0;i < row;i++)
	{
		for (j = 0;j < col;j++)
		{
			board[i][j] = set;
		}
	}
}

    以上代码中,ROW(9)、COL(9)、ROWS(ROW+2)、COLS(COL+2)均为使用#define在头文件中定义的宏,这样的好处是可以直接在头文件中修改游戏界面的大小。DEF用来设置布雷的数量。

    有朋友可能会对上面的+2产生疑问,这里解释一下,我们实际建立的二维数组要比我们在游戏中使用到的二维大上一圈,至于原因我们在下面解释。

    下一步我们要做的就是布雷了,这里我们和三子棋一样,使用随机数对地雷进行设置,将布雷的位置设置为“1”

void set(char board[ROWS][COLS], int row, int col,int set)//布雷
{
	int x = 0;
	int y = 0;
	while (set > 0)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;//随机生成坐标
		if (board[x][y] == '0')//防止在同一位置重复布雷
		{
			board[x][y] = '1';
			set--;
		}	
	}
}

    这里对随机数进行+1操作是因为,我们在实际游戏中,二维数组的第0行和第0列是不会被使用的,+1防止了将地雷设置在错误的位置。

    完成以上代码,我们的游戏开始界面就算是做好了,效果如下图所示。

游戏进行:排雷过程中的显示和单步多格展开的实现

    参考windows自带的扫雷游戏的逻辑,我们在代码中需要实现的第一步就是对被选中的坐标的情况进行数组显示,要显示的内容就是选中位置周围八个格子的地雷总数。这里又回到上面初始化数组和设置地雷时的细节了。我们初始化时将空位设置为‘0’,将地雷设置为‘1’,现在我们在统计地雷数量时,只需要将选中位置周围的八个元素进行相加,这里要注意,由于我们数组内使用的是字符,所以在进行数学运算时,其运算的主体时这个字符对应的ASCII码值,所以我们需要将结果减去8*‘0’,这样就可以得到地雷的数量。在进行显示的时候,我们又需要将结果加上‘0’再次将数字转换成字符,再传送到显示扫雷过程的数组中,以显示地雷的数量。

int numbermine(char board[ROWS][COLS], int i, int j)//标记雷的个数
{
	return board[i - 1][j - 1] + board[i][j - 1] + board[i + 1][j - 1] +
		board[i - 1][j] + board[i + 1][j] +
		board[i - 1][j + 1] + board[i][j + 1] + board[i + 1][j + 1] - 8 * '0';
}

searchboard[x][y] = numbermine(setupboard, x, y) + '0';

    结束对我们选择的坐标的展开后,参考windows自带的扫雷游戏的逻辑,我们需要对周边的位置进行二次展开。在二次展开时,我们需要遵守三个条件:1、被展开位置没有雷;2、被展开位置没有被展开过;3、被展开位置周围八个位置内没有雷。前两个条件是为了保证展开时不会重复张开造成死循环以及展开时不会错误的将地雷展开,第三个条件是为了限制展开范围,当周围有雷时,结束本次展开。

void openmod(char searchboard[ROWS][COLS], char setupboard[ROWS][COLS], int i, int j)//排雷时,对所选位置进行展开,若周围八个位置都没有雷,则对周围八个位置进行展开,循环直到被展开位置周围出现雷
{
	int x = i - 1;
	int y = j - 1;
	if (1 <= i && ROW >= i && 1 <= j && COL >= j)//划定展开范围,数组最外圈不参加展开
		for (x = i - 1;x <= i + 1;x++)
		{
			for (y = j - 1;y <= j + 1;y++)
			{
				if (setupboard[x][y] == '0' && searchboard[x][y] == '*')//只对未展开且非雷位置进行展开
				{
					searchboard[x][y] = numbermine(setupboard, x, y) + '0';
					if (numbermine(setupboard, x, y) == 0)//若周围八个位置有雷,不进行进一步展开
						openmod(searchboard, setupboard, x, y);
				}
			}
		}
}

    以上代码中我们可以看到我们在展开时对展开范围设置了限制,这是由于我们最初设置二维数组时,最外层的一圈(第0行和第0列以及最后一行和最后一列)是不参与展开,只参与地雷数量统计的,如果不对展开范围进行限制,有可能会在递归过程中造成数组越界访问。同时,如果对最外层的数组进行二次展开,就有可能直接把所有非雷位置全部展开,这样就失去了游戏的意义。

游戏结果:胜负的判断

    扫雷游戏的胜负判断很简单:踩雷就输,把雷全部排出来就算胜利。那么怎么才能判断雷是否都排完了?只需要在每次的排雷后对显示的数组进行遍历,统计还没有翻开的位置,即‘*’的数量,如果与最初设置的雷的数量一致,就说明地雷都被排出来了,此时就说明游戏胜利。

int life(char searchboard[ROWS][COLS],int row,int col)//判断棋盘上还有几个地方没有翻开,若未翻开的数量等于雷的数量,说明游戏胜利
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = 1;i <= row;i++)
	{
		for (j = 1;j <= col;j++)
		{
			if (searchboard[i][j] == '*')
				count++;//统计未展开
		}
	}
	return count-DEF;
}

void search(char setupboard[ROWS][COLS], char searchboard[ROWS][COLS])//循环排雷,踩雷即结束循环,或只存在雷时结束循环
{
	int n = 1;
	while (n)
	{
		int i = 0;
		int j = 0;
		scanf("%d %d", &i, &j);
		if (1 <= i && ROW >= i && 1 <= j && COL >= j)
		{
			if (searchboard[i][j] == '*')
			{
				if (setupboard[i][j] == '1')
				{
					printf("你被炸死了!!!游戏结束\n");
					print(setupboard, ROW, COL);
					n = 0;
				}
				else if (setupboard[i][j] == '0')
				{
					openmod(searchboard, setupboard, i, j);
					print(searchboard, ROW, COL);
					n = life(searchboard, ROW, COL);
					if (n == 0)
						printf("排雷成功,游戏胜利\n");
					else
						printf("安全,继续排雷:");
				}
			}
			else
				printf("这里看过了,请重新输入:");
		}
		else
			printf("输入错误,请重新输入:");
	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值