比特扫雷,实现了利用递归来展开(有详细注释)

仍有许多需要改进的地方,但是如果不在乎效率,这个代码已经可以正确运行了。

在递归展开的部分,我使用了一个笨方法。

在理想情况下,递归应该在不能够继续展开(空格的周围已经没有未检查过的空格可以继续递下去了,空格的周围都是检查过的空格和数字字符)的情况下“归”。

但是呢,这里的递归次数是我自己定死的,可以在程序运行前修改,但是在程序运行起来之后就不能修改了。毫无疑问,这种方法的效率是很低的——即使我点到的位置,它周围的八个格子全部都是地雷,递归仍然会执行设定次数。

在game.c的144行,可以调整递归的次数。我所设定的递归次数是比较合适的次数,90%的情况都可以满足了。但是也发生过没能把所有应该展开的地方全部展开的情况(当路径弯弯绕绕的时候就会出现这种情况),当然我可以增大递归执行的次数,但是这也会使程序的效率进一步变低。

另外,在程序中,无论一个坐标之前是否被检查过,都会再次检查,这也是程序效率变低。

有一些思路:

  • 如果运气不好,在第一次输入坐标时就会引爆地雷,导致游戏失败。这个是容易避免的(虽然我程序中并没有避免),就是在输入了一个坐标之后再设置地雷,只要避开被检查了的那个坐标即可,当然,获胜的条件(次数)也要修改一下
  • 如果想提高程序的效率,避免重复检查同一个坐标,可以创建一个数组Mark,用于记录哪个坐标被检查过了。
  • 标记功能也可以用数组Mark来实现。检查模式和标记模式的切换可以利用输入的坐标中的x来实现。例如:先输入x,判断x是否等于100,如果不等于,继续输入另一个坐标y,如果等于100,切换为标记模式。同样可以通过这种方式从标记模式切换回检查模式
  • 如果想要实现递归在不能够继续展开(空格的周围已经没有未检查过的空格可以继续递下去了,空格的周围都是检查过的空格和数字字符)的情况下“归”,那首先要处理的,是数组ShowMineNumber的外围(第一行和最后一行,第一列和最后一列),让它的外围在一开始就是型号‘*’和空格‘ ’以外的字符(我在程序中限制了递归的中心坐标不能超出中间的9*9范围)

由于时间关系,就不继续改了。下面就是具体的程序,带有详细的注释。

main.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "game.h"
#include <stdlib.h>
#include <time.h>


int main()
{
	srand((unsigned int)time(NULL));			//使用时间戳作为随机数生成的起点
	int selection = 0;
	do
	{
		Menu();			//打印菜单
		printf("请输入你的选择:》");
		scanf("%d", &selection);

		switch (selection)
		{
		case 1:
			printf("玩游戏——扫雷\n");
			PlayGame();
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (selection != 0);
	return 0;
}

game.h

#pragma once

#define		ROW		9
#define		COLUMN	9

#define		ROW_EXTEND		(ROW+2)
#define		COLUMN_EXTEND	(COLUMN+2)

#define		MINE_NUMBER_EASY		10
#define		MINE_NUMBER_MIDDLE		20
#define		MINE_NUMBER_HARD		30

/*
* 简介:打印菜单
* 参数:none
* 返回值:none
*/
void Menu();

// 简介:二维数组初始化,根据传进来的symbol进行初始化
// 参数:board,11*11二维数组,想要将其初始化的数组
// 参数:row,行
// 参数:column,列
// 参数:symbol,初始化的目标符号,如:‘*’
// 返回值:none
void BoardInit(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column, char symbol);

// 简介:在屏幕上打印出数组的中间部分,11*11的数组打印出中间的9*9,另外,给行、列加标号
// 参数:board,11*11二维数组,想要打印的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void DisplayBoard(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column);


// 简介:设置地雷的位置,传进来11*11的数组,雷可能出现的位置在中间的9*9
// 参数:board,11*11二维数组,想要设置雷的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void SetMine(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column);

// 简介:输入一个坐标,统计这个坐标周围有多少个地雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:counter,这个坐标周围地雷的个数
int CountMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], int x, int y);

// 简介:输入一个坐标和这个坐标周围的地雷个数counter
// 简介:如果counter == 0,展开,将坐标周围的‘*’变成‘ ’
// 简介:如果counter! = 0,不展开,修改ShowMineNumber中该坐标处的‘数字字符’
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:counter,这个坐标周围地雷的个数
// 返回值:none
void Spread(char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int counter);

// 简介:输入一个坐标,统计坐标附近是否有空格,如果有空格,统计空格周围有多少个雷
// 简介:根据counter,以该空格为中心来展开ShowMineNumber中的内容,并利用递归重复这个过程
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:CallTime,这个函数可以被递归的次数
// 返回值:none
void SpreadSpace(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int CallTime);


// 简介:统计一个坐标周围有多少个雷,并展开一片,展开的位置用空格表示
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:none
void CountSpreadMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y);


// 简介:找地雷的位置,在9*9的范围内排雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:row,行
// 参数:column,列
void FindMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int row, int column);


// 简介:玩游戏
// 参数:none
// 返回值:none
void PlayGame();

game.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "game.h"



// 简介:打印菜单
// 参数:none
// 返回值:none
void Menu()
{
	printf("******************************\n");
	printf("********** 1. play ***********\n");
	printf("********** 0. exit ***********\n");
	printf("******************************\n");
}

// 简介:二维数组初始化,根据传进来的symbol进行初始化
// 参数:board,11*11二维数组,想要将其初始化的数组
// 参数:row,行
// 参数:column,列
// 参数:symbol,初始化的目标符号,如:‘*’
// 返回值:none
void BoardInit(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column, char symbol)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < column; j++)
		{
			board[i][j] = symbol;	//把二维数组的每一个元素都变成symbol
		}
	}
}

// 简介:在屏幕上打印出数组的中间部分,11*11的数组打印出中间的9*9,另外,给行、列加标号
// 参数:board,11*11二维数组,想要打印的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void DisplayBoard(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
	printf("------Mine Sweeper------\n");	//分割行
	for (int j = 0; j <= column; j++)
	{
		printf("%d ", j);					//在第一行打印列标号
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);					//在每一行的第一个(第一列)打印行标号
		for (int j = 1; j <= column; j++)
		{
			printf("%c ", board[i][j]);		//打印数组中间部分的内容
		}
		printf("\n");
	}
	printf("------Mine Sweeper------\n");	//分割行
}

// 简介:设置地雷的位置,传进来11*11的数组,雷可能出现的位置在中间的9*9
// 参数:board,11*11二维数组,想要设置雷的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void SetMine(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
	int Counter = MINE_NUMBER_EASY;
	while (Counter > 0)
	{
		int x = (rand() % row) + 1;			//产生1-9的随机数
		int y = (rand() % column) + 1;		//产生1-9的随机数
		if (board[x][y] == '0')				//如果随机坐标处没有雷,就在随机坐标处放一个雷
		{
			board[x][y] = '1';
			Counter--;
		}
	}
}


// 简介:输入一个坐标,统计这个坐标周围有多少个地雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:counter,这个坐标周围地雷的个数
int CountMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], int x, int y)
{
	int counter = 0;
	for (int i = (x - 1); i <= (x + 1); i++)	//row circulation
	{
		for (int j = (y - 1); j <= (y + 1); j++)	//column circulation
		{
			if (MinePosition[i][j] == '1')	//如果这个位置是地雷
			{
				counter++;
			}
		}
	}
	return counter;
}


// 简介:输入一个坐标和这个坐标周围的地雷个数counter
// 简介:如果counter == 0,展开,将坐标周围的‘*’变成‘ ’
// 简介:如果counter! = 0,不展开,修改ShowMineNumber中该坐标处的‘数字字符’
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:counter,这个坐标周围地雷的个数
// 返回值:none
void Spread(char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int counter)
{
	if (counter == 0)		//如果周围的地雷数目为0
	{
		for (int i = (x - 1); i <= (x + 1); i++)	//row circulation
		{
			for (int j = (y - 1); j <= (y + 1); j++)	//column circulation
			{
				if (ShowMineNumber[i][j] == '*')		//ShowMineNumber[i][j]的位置还没有被排查(*代表还没有被排查)
				{
					ShowMineNumber[i][j] = ' ';
				}
			}
		}
	}
	else					//如果周围的地雷数目不为0
	{
		ShowMineNumber[x][y] = counter + '0';				//修改ShowMineNumber中对应坐标存储的‘字符’
	}
}

// 简介:输入一个坐标,统计坐标附近是否有空格,如果有空格,统计空格周围有多少个雷
// 简介:根据counter,以该空格为中心来展开ShowMineNumber中的内容,并利用递归重复这个过程
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:CallTime,这个函数可以被递归的次数,调用时,默认给 0
// 返回值:none
void SpreadSpace(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int CallTime)
{
	if (CallTime >= ROW)		//当递的次数达到最大值,就开始归
	{
		return;
	}
	for (int i = (x - 1); i <= (x + 1); i++)	//row circulation
	{
		for (int j = (y - 1); j <= (y + 1); j++)	//column circulation
		{
			if ((1 <= i) && (i <= ROW) && (1 <= j) && (j <= COLUMN))			//判断坐标是否越界了
			{
				if (ShowMineNumber[i][j] == ' ')		//遍历3*3,9个格子,看他们是不是空格,如果这个地方是空格
				{
					int counter = 0;
					counter = CountMine(MinePosition, i, j);			//统计空格坐标i,j周围有多少个雷
					Spread(ShowMineNumber, i, j, counter);				//根据counter,以该空格为中心来展开ShowMineNumber中的内容
					SpreadSpace(MinePosition, ShowMineNumber, i, j, CallTime+1);//递归,注意,CallTime+1
				}
			}
		}
	}
}

// 简介:统计一个坐标周围有多少个雷,并展开一片,展开的位置用空格表示
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:none
void CountSpreadMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y)
{
	int counter = 0;
	counter = CountMine(MinePosition, x, y);			//统计xy周围有多少个雷
	ShowMineNumber[x][y] = counter + '0';				//修改ShowMineNumber相应坐标的内容(数字字符),只有第一个坐标xy需要这样做
	Spread(ShowMineNumber, x, y, counter);				//根据counter来展开ShowMineNumber中的内容

	SpreadSpace(MinePosition, ShowMineNumber, x, y, 0);		//递归
	DisplayBoard(ShowMineNumber, ROW, COLUMN);			//在屏幕上打印(刷新)二维数组ShowMineNumber,只需要刷新一次即可
}


// 简介:找地雷的位置,在9*9的范围内排雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:row,行
// 参数:column,列
void FindMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
	int x = 0;		//row coordinate 
	int y = 0;		//column coordinate
	int WinFlag = 0;	//每次找到一个不是地雷的坐标,WinFlag就++
	while (1)
	{
		printf("Please Input A Coordinate( (1 1) is the top left corner):>");
		scanf("%d %d", &x, &y);
		if ((1 <= x) && (x <= row) && (1 <= y) && (y <= column))//判断合法性, 输入的坐标已经是在9*9的范围内了,不需进行偏移
		{
			if (ShowMineNumber[x][y] != '*')		//如果重复输入同一个坐标(输入的坐标不是未查看的),跳过一轮循环
			{
				printf("Repeat the coordinate, please input again\n");
				continue;
			}
			if (MinePosition[x][y] == '1')		//如果点到的是地雷
			{
				printf("I am sorry to say that you failed\n");
				DisplayBoard(MinePosition, ROW, COLUMN);		//将地雷的位置打印出来给玩家看看
				break;
			}
			else							//如果点到的不是地雷
			{
				WinFlag++;		
				CountSpreadMine(MinePosition, ShowMineNumber, x, y);	//统计周围的地雷的个数,并打印刷新,并自动展开
			}
		}
		else
		{
			printf("The coordinate that you input is illegal, please input again\n");
		}
		if (WinFlag == ((row * column) - MINE_NUMBER_EASY))			//赢了,如果WinFlag到达某个数值,此时已经把炸弹全部找出来了
		{
			printf("Congratulation! Congratulation!\n");
			DisplayBoard(MinePosition, ROW, COLUMN);		//将地雷的位置打印出来给玩家看看
			break;
		}
	}
}


// 简介:玩游戏
// 参数:none
// 返回值:none
void PlayGame()
{
	char MinePosition[ROW_EXTEND][COLUMN_EXTEND] = { 0 };	//存放布置好的地雷的位置,‘0’-没有雷,‘1’-有雷
	char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND] = { 0 };	//存放排查出的地雷的信息,‘*’-还没有排查

	BoardInit(MinePosition, ROW_EXTEND, COLUMN_EXTEND, '0');		//初始化数组
	BoardInit(ShowMineNumber, ROW_EXTEND, COLUMN_EXTEND, '*');		//初始化数组

	SetMine(MinePosition, ROW, COLUMN);		//设置地雷的位置

	DisplayBoard(MinePosition, ROW, COLUMN);					//展示数组,地雷的位置/调试用
	DisplayBoard(ShowMineNumber, ROW, COLUMN);					//展示数组(棋盘)
	FindMine(MinePosition, ShowMineNumber, ROW, COLUMN);		//找地雷的位置,在9*9的范围内排雷
}

猜你喜欢

转载自blog.csdn.net/weixin_63096487/article/details/133551040