C语言扫雷(主要讲解扫雷递归展开)

    前言:

        在博客上面已经有好多博主撰写过扫雷相关的游戏代码,而且写的也很全面,在这我就不再详细写出扫雷的代码。

        但是对于扫雷中的一个难点就是递归展开,在这我可能会把重点放在递归展开。

        完整代码放在结尾了,需要的自拿!!

扫雷棋盘设计和菜单打印:

  菜单打印:

        游戏菜单的打印在之前讲三子棋的时候已经讲过,在这里将代码给出:

void menu()
{
	printf("****************************\n");
	printf("*********** 1.play *********\n");
	printf("*********** 0.exit *********\n");
	printf("****************************\n");
}
int main()
{
	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;
}

  写game()函数:棋盘的设计和打印

        这里需要注意我是将用户的棋盘和开发人员的棋盘分开创建。

分别命名为:用户棋盘:player[][] = {};

                        开发者棋盘:board[][] = {};

void game()
{
	char board[ROWS][COLS] = { 0 };
	char player[ROWS][COLS] = { 0 };
	//设计开发人员棋盘
	Creat_Board(board,ROWS,COLS);
	//设计玩家所见棋盘
	Creat_board(player, ROWS, COLS);
	//打印开发人员棋盘
	Print_Board(board,ROW,COL);
	//打印玩家棋盘
	Print_Board(player, ROW, COL);
	//设置雷并打印开发棋盘

创建一个game.h的头文件。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#define COLS 11
#define ROWS 11
#define Mine 10

代码如下:

void game()
{
	char board[ROWS][COLS] = { 0 };
	char player[ROWS][COLS] = { 0 };
	//设计开发人员棋盘
	Creat_Board(board,ROWS,COLS);
	//设计玩家所见棋盘
	Creat_board(player, ROWS, COLS);
	//打印开发人员棋盘
	Print_Board(board,ROW,COL);
	//打印玩家棋盘
	Print_Board(player, ROW, COL);
}

        这里我将打印用户和开发者的棋盘都打印出来,方便布置雷的时候可以看到雷的位置,也方便后期的测试。大家可以后期可以隐藏掉开发者棋盘,只保留用户所棋盘。

开发者棋盘:初始化全‘0’(字符0)

用户棋盘:初始化‘*’

        在game.c的源文件写创建棋盘的函数和打印棋盘的函数,后期如果需要就直接调用即可,当然需要注意将这些函数的头文件放在game.h中。

设置雷的位置:

      我们在开发者的棋盘中设计雷的位置。

Set_mine(board, ROW, COL);

          

        

实现Set_mine(board, ROW, COL)函数:

        这里设置10个雷,用字符‘1’代表雷的位置这里用字符‘1’在后期计算雷的数量的时候是有用的

void Set_mine(char board[ROWS][COLS], int row, int col)
{
	int a = 0;
	while (a != Mine)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			a++;
		}
	}
}

玩家扫雷并显示周围雷的数量:

        当雷布置完成后,玩家需要输入坐标

1、如果没有踩中雷,就需要显示它的周围8个位置的雷的数量。

2、如果踩中雷,就需要打印被炸死游戏结束。

3、如果越界输入坐标,就需要显示非法输入,请重新输入坐标。

void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
//玩家输入坐标
	int x = 0;
	int y = 0;
	int mine = row*col-Mine;
	//scanf("%d %d", &x, &y);
	while (mine)
	{
		printf("请输入坐标<<");
		scanf("%d %d", &x, &y);
		if (board[x][y] == '1')
		{
			printf("Oh,No你被炸死了\n");
			Print_Board(board, ROW, COL);
			break;
		}
		else if(x>=1&&x<=9&&y>=1&&y<=9)
		{
			player[x][y] = Num_mine(board, x, y)+'0';
			Print_Board(player, ROW, COL);
			mine--;
		}
		else
		{
			printf("非法输入,请重新输入\n");
		}
	}
}

        每次排完一个坐标的mine就减1,一共是9*9个格子,雷的数量是10个,也就是我们需要排71个格子(亲自点开71个格子)才能取得胜利,这样做和根本不想玩这个游戏了,有些不必要的格子没有必要再点。

 设计自动展开递归函数:

        如果我们写一个函数 能帮我们把一些不要的区域自动展开,就像这样:自动展开一片

      

自动展开一片的要求:

        1、保证该坐标周围8个坐标没有雷

        2、如果选择的坐标周围8个坐标没有雷,就将用户棋盘上相对应的点置为‘ ’,然后向上下左右移一个坐标。

        例如选择坐标(x,y)没有雷,就将player[x][y] = ' ' ,之后这个坐标应该变成(x-1,y)、(x+1,y)、(x,y+1)、(x,y-1)

        3、移动后的坐标如果周围8个格子还是没有雷那么还是进行2中的操作,不过这里需要注意的是,假设第一个排查的是(x,y)周围8个没有雷,第二个排查的是(x-1,y),第二个周围8个也没有雷,之后坐标要向上下左右移动,但是如果向移动坐标又会变成原先的(x,y),造成了死递归的情况。

        4、递归展开不能越界,所以x,y的范围只能是棋盘的范围。

        5、当展到的某个坐标周围8个坐标有雷时就不再展了,并且将该格子上面显示周围雷的数量。

图示分析 :       

如图所示:

        图一为开发者所见棋盘,图二是玩家棋盘

   第一步:选定(x,y),并判断周围8个坐标是不是有雷,有的话不递归直接上面显示雷的数量

没有的话玩家棋盘该坐标置为空格,并向四周展开。

图一

图二

第二步,判断展开的x,y有没有越界,如果越界了,就不用展开了,发现不能向下展开,但是向上左右可以展开。之后判断上左右三个点的坐标周围8个坐标有没有雷。

        发现左右两个坐标周围没有雷,所以置为空格,但是上面的一个坐标周围有一个雷,所以置为1(这里我用红色代替)

  第三步:以刚刚展开的三个坐标各自为中心,然后继续向4个方向展开。

以下三种情况不需要展开:

1、如果是之前展过的坐标就不需要展开。

2、如果越界了就不需要展开。

3、如果自身周围有雷了就不需要再展开。

所以只需要展左右两个坐标,以左右两个坐标为中心,发现:

        左边的坐标只能向上展,向左,向下展越界了,向右展发现已将展过了,所以只能向上展。

        右边的坐标能向上、右展。

        

        

接下来的几步都是重复前面三步,直到没有坐标可以展为止。

最终效果如图所示:

红色表示周围有一个雷;

蓝色表示周围有两个雷:

函数实现:

    计算周围雷数量的函数:

int Num_mine(char board[ROWS][COLS],int x,int y)//排查一个坐标周围雷的数量
{
	return board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1] +
		board[x][y - 1] + board[x][y + 1] + board[x + 1][y] + board[x + 1][y - 1]
		+ board[x + 1][y + 1]-8*'0';
}

    需要注意的是这里返回值我设置成整型,也就是之前为什么把棋盘都初始化为‘0’,雷设计成‘1’的原因,字符‘1’如何变成整型‘1’,只需要减去字符‘0’。

   递归函数的设计:     

        1、保证该坐标周围8个坐标没有雷

        2、如果选择的坐标周围8个坐标没有雷,就将用户棋盘上相对应的点置为‘ ’,然后向上下左右移一个坐标。

        例如选择坐标(x,y)没有雷,就将player[x][y] = ' ' ,之后这个坐标应该变成(x-1,y)、(x+1,y)、(x,y+1)、(x,y-1)

        3、移动后的坐标如果周围8个格子还是没有雷那么还是进行2中的操作,不过这里需要注意的是,假设第一个排查的是(x,y)周围8个没有雷,第二个排查的是(x-1,y),第二个周围8个也没有雷,之后坐标要向上下左右移动,但是如果向移动坐标又会变成原先的(x,y),造成了死递归的情况。

        4、递归展开不能越界,所以x,y的范围只能是棋盘的范围。

        5、当展到的某个坐标周围8个坐标有雷时就不再展了,并且将该格子上面显示周围雷的数量。

        

void _mine(char board[ROWS][COLS] ,char player[ROWS][COLS], int x, int y)//展开一片区域
{
	int ret = Num_mine(board, x, y);
	if (ret == 0 && x > 0&&x<=9 && y > 0&&y<=9 && player[x][y] != ' ')
	{
		player[x][y] = ' ';
		_mine(board,player, x - 1, y);
		_mine(board, player, x + 1, y);
		_mine(board, player, x, y - 1);
		_mine(board, player, x, y + 1);
	}
	else if (ret != 0)
	{
		player[x][y] = ret + '0';
	}

}

大家可以作为参考,自行设计类似的递归函数。

判断输赢:

        有了递归函数,可以自行展开一片,就不需要刚刚的必须要亲自点开71个格子了。

这里们可以想想,我们一直排雷,什么时候算结束?

也就是当棋盘上的81个位置只剩下10个格子没有点开。

所以这里我才用遍历的方式,设计一个函数,如果棋盘上有‘*’就++,直到这个数量等于10的时候就赢了。

        当满足条件了就返回1,不满足返回0;

int Iswin(char player[ROWS][COLS], int row, int col)
{
	int sum = 0;
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (player[i][j] == '*')
				sum++;
		}
	}
	if (sum == 10)
	{
		return 1;
	}
	return 0;
}

在Player_mine函数中加入判断语句即可。

	 if (Iswin(player,ROW,COL))
			{
				printf("恭喜你,扫雷成功\n");
				break;
			}

main.c源文件

#define  _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void game()
{
	char board[ROWS][COLS] = { 0 };
	char player[ROWS][COLS] = { 0 };
	//设计开发人员棋盘
	Creat_Board(board,ROWS,COLS);
	//设计玩家所见棋盘
	Creat_board(player, ROWS, COLS);
	//打印开发人员棋盘
	//Print_Board(board,ROW,COL);
	//打印玩家棋盘
	Print_Board(player, ROW, COL);
	//设置雷并打印开发棋盘
	Set_mine(board, ROW, COL);
	//Print_Board(board, ROW, COL);
	//玩家扫雷
	Player_mine(board,player,ROW,COL);
}
void menu()
{
	printf("****************************\n");
	printf("*********** 1.play *********\n");
	printf("*********** 0.exit *********\n");
	printf("****************************\n");
}
int main()
{
	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);
	//char board[ROWS][COLS] = {0};
	//char player[ROWS][COLS] = {0};
	//设计棋盘
	return 0;
}

game.c源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void Creat_Board(char board[ROWS][COLS], int rows, int cols)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = '0';
		}
	}

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

}
void Print_Board(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("————————————扫雷——————————————\n");
	for (i = 0; i <= col; 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");
	}
}
//void Print_Board(char board[ROWS][COLS], int rows, int cols)
//{
//	int i = 0;
//	printf("————————————扫雷——————————————\n");
//	printf("\n");
//	for (i = 0; i < rows; i++)
//	{
//		int j = 0;
//		for (j = 0; j < cols; j++)
//		{
//			printf("%c", board[i][j]);
//		}
//		printf("\n");
//	}
//}
void Set_mine(char board[ROWS][COLS], int row, int col)
{
	int a = 0;
	while (a != Mine)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			a++;
		}
	}
}
int Num_mine(char board[ROWS][COLS],int x,int y)//排查一个坐标周围雷的数量
{
	return board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1] +
		board[x][y - 1] + board[x][y + 1] + board[x + 1][y] + board[x + 1][y - 1]
		+ board[x + 1][y + 1]-8*'0';
}
void _mine(char board[ROWS][COLS] ,char player[ROWS][COLS], int x, int y)//展开一片区域
{
	int ret = Num_mine(board, x, y);
	if (ret == 0 && x > 0&&x<=9 && y > 0&&y<=9 && player[x][y] != ' ')
	{
		player[x][y] = ' ';
		_mine(board,player, x - 1, y);
		_mine(board, player, x + 1, y);
		_mine(board, player, x, y - 1);
		_mine(board, player, x, y + 1);
	}
	else if (ret != 0)
	{
		player[x][y] = ret + '0';
	}

}
int Iswin(char player[ROWS][COLS], int row, int col)
{
	int sum = 0;
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (player[i][j] == '*')
				sum++;
		}
	}
	if (sum == 10)
	{
		return 1;
	}
	return 0;
}

void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
//玩家输入坐标
	int x = 0;
	int y = 0;
	//scanf("%d %d", &x, &y);
	while (1)
	{
		printf("请输入坐标<<");
		scanf("%d %d", &x, &y);
		if (board[x][y] == '1')
		{
			printf("Oh,No你被炸死了\n");
			Print_Board(board, ROW, COL);
			break;
		}
		else if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			/*player[x][y] = Num_mine(board, x, y)+'0';
			Print_Board(player, ROW, COL);*/
			_mine(board, player, x, y);
			 Print_Board(player, ROW, COL); 
			 if (Iswin(player,ROW,COL))
			{
				printf("恭喜你,扫雷成功\n");
				break;
			}
		}
		else
		{
			printf("非法输入,请重新输入\n");
		}
	}
}

game.h头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define COLS 11
#define ROWS 11
#define Mine 10
#define COL COLS-2
#define ROW ROWS-2
void Creat_Board(char board[ROWS][COLS], int rows, int cols);
void Creat_board(char board[ROWS][COLS], int rows, int cols);
void Print_Board(char board[ROWS][COLS], int row, int col);
void Set_mine(char board[ROWS][COLS], int row, int col);
//void Print_Board(char board[ROWS][COLS], int row, int col);
void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值