Lesson 7 从零开始实现扫雷游戏

扫雷实现

文章从思路开始,再到代码的具体实现。个人认为,思路远比代码实现重要。有思路,就可以想方设法用代码实现。碰到的问题也可以通过思考解决。开始的时候不必照顾到所有细节,等实现的时候如果碰到问题,再去调整也是可以的。

大体框架

扫雷游戏比较简单,就是在一个格子上点击,这个格子如果不是雷,那么它会显示它周围的八个格子里有多少雷,如果是雷,那么就结束了。
游戏开始,可以用之前实现猜数字的方式进行,打印menu然后依据用户选择进行游戏或者退出。那么依据这个,就可以先写一个大的框架出来了:

int main()
{
	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;

大框架中使用do...while循环,打印menu,根据用户选择使用switch语句跳转。这里由于0是退出游戏,正好可以作为do...while循环的循环条件(非0继续,为0退出。)game();作为游戏本体,目前是没有实现的,可以先放在这里。下一个小节会详细解析。
补充一下menu

void menu()
{
	printf("******************\n");
	printf("******1.play******\n");
	printf("******0.exit******\n");
	printf("******************\n");
}

game的实现

这里game可能会比较复杂,为了方便管理,给game本体创建一个新的源文件和头文件,并且在main函数所在的文件包含就可以了。

棋盘创建

扫雷的棋盘是一个m*n的方块,那么就可以想到使用二维数组来作为扫雷的棋盘。扫雷的游戏过程就是在对这个二维数组进行操作。需要创建这个二维数组,那么就得知道其行数和列数。这里就有新的问题了,扫雷的难度是根据行数、列数、雷的数量来决定的,那么为了方便后期修改,在创建二维数组的时候最好不要写死。另外,游戏在进行的过程中会统计某个位置周围8个位置的雷的数量。为了方便统计,肯定是想将这个数组初始化为全0的,然后在里面随机的加入1,最后只要统计某个格子周围1的数量就可以将统计值写到这个格子里。但这样就带来了一个问题。如果在棋盘上使用数字,统计的时候也是数字,那么其它格子在统计的时候就会再次统计已经计算过的数字,这样就非常的麻烦。为了解决这个问题,可以创建字符型的二维数组作为棋盘。同时,区分存储雷的二维数组和显示出来的二维数组。另外,如果按照棋盘大小创建数组的话,对于边缘位置的统计会比较麻烦,因此可以让棋盘大一点。总结下来:

  1. 使用字符型二维数组作为棋盘;
  2. 区分存储雷的二维数组和显示雷的二维数组;
  3. 使用变量来控制二维数组的大小和雷的数量,用于不同的难度;
  4. 数组的大小比棋盘大小大一圈;
#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 10
void game()
{
	//数组的创建
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
}

初始化棋盘

走到这里,下一步就是初始化棋盘。mine这个棋盘作为埋雷的棋盘,没有雷的位置规定为字符0,有雷的位置规定为字符1。show这个棋盘作为打印棋盘,均初始化为*即可。为了使用1个函数来初始化所有的棋盘,可以这样设计初始化函数:

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

这个函数有4个参数,分别是需要初始化的棋盘char board[ROWS][COLS],棋盘的大小(int rows, int cols),初始化使用的字符set。初始化之后,就需要打印一下棋盘,这样就会在屏幕上显示出来棋盘的界面了。那么game函数就可以写成:

void game()
{
	//数组的创建
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine,ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS,'*');
	//棋盘的打印
	Displayboard(mine, ROW, COL);
	Displayboard(show, ROW, COL);
}

这里其实不需要打印mine,因为这个就是地雷的棋盘,只有游戏过关或者被炸死的时候才去打印。
这个初始化和打印应该如何实现呢?其实就是遍历数组。废话不说直接上代码:

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void Displayboard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("------扫雷游戏-----\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		int j = 0;
		for (j=1;j<=col;j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("------扫雷游戏-----\n");
}

棋盘初始化之后,就可以开始布置雷了。

布置雷

布置雷就是在mine数组里随机的填上 EASY_COUNT数量的地雷。函数设计为:

void Setmine(char mine[ROWS][COLS], int row, int col);

这个函数是需要随机的在mine数组里写0。这里就需要用到rand函数。所以同样涉及到srandtime。具体可以参考: 随机数生成这篇文章。这个函数在实现的时候还需要注意,已经被埋过雷的地方就不要再埋雷了,同时注意埋雷的范围。
具体实现如下:

void Setmine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count != 0)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

同时,修改game函数:

void game()
{
	//数组的创建
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine,ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS,'*');
	//棋盘的打印
	//Displayboard(mine, ROW, COL);
	Displayboard(show, ROW, COL);
	//布置雷
	Setmine(mine,ROW,COL);
}

布置完成之后,就可以考虑开始找雷了。

排查雷

找雷的时候,根据输入的坐标,分为以下情况:

  1. 坐标不是雷,则需要显示坐标周围8个方格内拥有雷的数量,同时打印show数组;
  2. 所有的雷都找到了,游戏结束,同时打印mine数组;
  3. 坐标是雷,炸死,游戏结束,同时打印mine数组;
    这里是需要使用到mine、show 这两个数组的。因此,函数设计为:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
数雷

对于第一种情况,需要统计,这里也封装一个函数:

int GetMineCount(char mine[ROWS][COLS], int x, int y)

用于统计坐标为(x,y)处,周围8个方格内的雷的数量。具体实现如下:

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

}

注意:

  1. mine数组时字符型的,需要转成数字;
  2. 坐标(x,y)周围的8个方块的坐标分别是什么(想不明白画个图就好了)。
获胜条件

扫雷获胜的条件是除过被埋雷的地方,其余方块都被打开了。这里我们可以设计一个变量win,用于统计已经被打开方块的数量。当win == 棋盘大小-已经埋雷数量时,游戏获胜。
另外,在输入坐标的时候,也需要判断坐标是否合法。

代码实现
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while(win<row*col-EASY_COUNT)//统计已经打开格子的数量
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标是否合法
		{
			if (mine[x][y] == '1')//mine数组里,字符1是地雷
			{
				printf("很遗憾,你被炸死了\n");
				Displayboard(mine, ROW, COL);
				break;
			}
			else//else说明不是雷且坐标合法
			{
				int count = GetMineCount(mine,x,y);
				show[x][y] = count + '0';//这里是将统计出来的雷数量转换成字符,放在show数组里
				Displayboard(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("坐标非法,重新输入:>");
		}
	}
	if (win == row * col - EASY_COUNT)//跳出循环的时候,要么被炸死,要么获胜。按照获胜条件写的判断一下即可。
	{
		printf("恭喜你,排雷成功\n");
		Displayboard(mine, ROW, COL);
	}

}

完成这个代码之后,扫雷游戏基本就实现了。当然,也需要更新一下game函数

void game()
{
	//数组的创建
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine,ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS,'*');
	//棋盘的打印
	Displayboard(show, ROW, COL);
	//布置雷
	Setmine(mine,ROW,COL);
	//排查雷
	FindMine(mine,show,ROW,COL);
}

运行效果

扫雷运行效果

进阶

这个只是初始的游戏。有以下功能待实现:

  1. 标记雷;
  2. 如果点到的位置周围8个位置都没有雷,应该可以自动扩展出去;

这些功能会在学习完递归之后实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值