C语言:扫雷(含标记与展开一片功能)

目录

扫雷

整体框架

game.h对要用到的函数进行声明

test.c游戏运行测试框架

 game.c游戏逻辑的实现

 ---存放棋盘

---初始化棋盘 

---显示棋盘

 ---布置雷

---排查雷

---判断游戏是否结束

 思考

1.实现展开一片

2.实现标记功能:

 总结:


代码连接:https://gitee.com/zhy156/c-code-practice-warehouse.git

我还是将扫雷游戏分为两个模块1.整体框架2.游戏逻辑实现

并将它们分别写在了3个文件当中

game.h :对要用到的函数进行声明

game.c :对游戏逻辑的实现

test.c    :用来测试游戏的运行

扫雷

整体框架

--显示菜单--玩家选择--游戏进行/退出  

基本和上一篇三子棋游戏一样,这里不再赘述

接下来给出游戏进行的整体框架:

1.给出数组存放棋盘(这里我们创建两个棋盘:布置雷,排查雷)

2.初始化棋盘 (’1‘:雷,’0‘:非雷, ' * '::未操作)

3.布置雷
      ---随机生成10个雷的坐标
      ---判断坐标的合法性(这里直接限制坐标生成的范围免去判断)
         --判断此位置是否被占用(是,重新生成坐标)
         --将棋盘元素替换为雷的元素(将布置雷棋盘中的’0‘换为’1‘)

4.排查雷

       ---玩家输入坐标

       ---判断坐标的合法性(非法,提醒玩家重新输入)

        --判断该位置是否被占用(是,提醒玩家重新输入)

        --判断该位置是否为雷(是,游戏结束)

           --不是雷,给出这个雷周围8个元素雷的个数(排查)

5.判断游戏是否结束

我们引如一个计数器:win,每排查一个坐标,win++;直到win==显示棋盘大小-布置雷的个数

循环结束,

---判断win==显示棋盘大小-布置雷的个数

   --相等:打印排雷成功

C语言:扫雷(含标记与展开一片功能)_第1张图片

game.h对要用到的函数进行声明

C语言:扫雷(含标记与展开一片功能)_第2张图片

test.c游戏运行测试框架

C语言:扫雷(含标记与展开一片功能)_第3张图片

 game.c游戏逻辑的实现

 ---存放棋盘

我们创建两个数组来存放棋盘,一个布置雷,一个给玩家排查

--这两个数组大小对应

--我们声明的数组会比棋盘多2行和2列

            原因:对雷排查的时候,若该坐标不是雷,需要我们返回它周围8个元素雷的个数

当排查的坐标是边界的时候会访问越界

为了防止数组越界的问题:将其拓宽

C语言:扫雷(含标记与展开一片功能)_第4张图片

 因此创建的时候:

C语言:扫雷(含标记与展开一片功能)_第5张图片

代码如下:

C语言:扫雷(含标记与展开一片功能)_第6张图片

---初始化棋盘 

要求:

将mine数组全部初始化为’  0   ‘

将show数组全部初始化为’  *   ‘

实现:

通过多重循环遍历数组里所有元素,将其元素替换

我们设置一个函数,将要初始化的数组,初始化的字符传递给它

代码如下:

//初始化棋盘
void Init_board(char board[ROWS][COLS], int rows, int cols, char m)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = m;
		}
	}
}

---显示棋盘

使用多重循环遍历显示棋盘的所有元素并打印(注意要显示的是9*9的)

优化棋盘:打印出棋盘行数与列数

代码:

void Display_board(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	for (i = 0; i < 10; 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");
	}
	printf("___________扫雷_________\n");

}

C语言:扫雷(含标记与展开一片功能)_第7张图片

 ---布置雷

1.随机生成10个坐标:

--通过使用时间戳来设置随机起点---srand((unsigned int)time(NULL))

--调用rand()函数来生成随机数

--将生成的随机数放入坐标X,Y中

注意生成坐标的合法性和避免重复放雷

--X=rand()%row+1(产生在棋盘范围内的坐标)

假设显示棋盘是9*9,row=9,生成的随机数%9会产生0-8的数字,+1就变(0,9)

Y同理

2.使用while循环再引入一个计数器:count:

--每放置完一个雷,count--;

--直到所有雷被放置完,循环结束

代码:

//放置雷
void Set_mine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;//生成坐标在显示棋盘范围内
		int y = rand() % col + 1;

		if (board[x][y] == '0')//如果元素为’0‘才能被替换,也避免了重复放雷
		{
			board[x][y] = '1';
			count--;
		}

	}

}

C语言:扫雷(含标记与展开一片功能)_第8张图片

---排查雷

1.玩家排查雷:

----玩家输入坐标     创建x,y来接收坐标

2.判断坐标的合法性:使用条件语句与操作符if((x>.=1&&x<=row)&&(y>=1&&y<=col))

3.判断坐标是否为雷:if(mine[x][y]==' 1 ')                 是游戏结束

4.若不为雷,判断该坐标是否被已排查1:if(show[x][y]!=' * ')     是,提醒玩家重新输入

5.以上条件都满足:将对应坐标的元素替换并标上它周围雷的个数:show[x][y]=count + '0'

引入int变量count来统计,该元素周围8个元素雷的个数,count=Get_count();(写一个函数来计算)

Get_count:

---使用多重循环遍历除该坐标的元素

注意:由于我们创建的是字符数组:字符’ 0 ‘ 与数字 0 是不一样的,它们的ASCII码值相差32

我们的目的是在字符数组中显示该元素周围雷的个数(显示的是字符,但也需要统计雷的个数

因此要对它进行转换:

在Get_count函数中我们将mine数组与该点坐标传递过去,遍历该点周围元素并将其减去‘ 0 ’

,在将这个结果都加起来就能得到该点雷的数量

再将这个结果:count +‘ 0 ’ 转换为字符并与show数组中对应的元素替换即可

讲解:一个数字 加上‘ 0 ’能转换为对应的字符数字或者该数字+32也能转换

因为在ASCII码表中数字0与字符' 0 '相差 32 也相差‘ 0 ’

C语言:扫雷(含标记与展开一片功能)_第9张图片

 假设我们选中了圆圈的元素,要统计周围8个元素,使用遍历:假设该点坐标X,Y

该点的上一行:X-1   该点左边的一列:Y-1

该点的下一行:X+1  该点的右边一列: Y+1

遍历的范围就出来了:i:(X-1,X+1)

                                    j:(Y-1,Y+1)

注意当i==X&&j==Y的时候是该点坐标,我们不需要统计,当满足条件的时候使用continue跳过即可

代码:

//得到该元素周围雷的个数
int Get_count(char mine[ROWS][COLS], int x, int y)
{
	int i, j;
	int ret = 0;
	for (i = x - 1; i <= x + 1; i++)//遍历该点周围的元素
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if ((i == x) && (j == y))//遍历到自己就跳过
			{
				continue;
			}

			ret += mine[i][j] - '0';//计算周围雷的个数

		}
	}

	return ret;
}

//排查雷
void Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int win = 0;//统计排雷的次数
	int count = 0;
	int x, y;


	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')
			{
				printf("很遗憾,你被炸死了\n");
				win = 100;
				Display_board(mine, ROW, COL);
				break;//选中雷,退出循环
			}
			else if (show[x][y] != '*')
			{
				printf("该坐标你已选择,请重新选择\n");
			}
			else
			{
				count = Get_count(mine, x, y);//得到周围雷的个数
				show[x][y] = count + '0';//将数字转换为字符数字并在对应的位置替换元素
				Display_board(show, ROW, COL);//显示棋盘
				win++;
			}

		}
		else
		{
			printf("非法输入,请重新输入");
		}

	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
	}

}

C语言:扫雷(含标记与展开一片功能)_第10张图片

---判断游戏是否结束

我们引如一个计数器:win,每排查一个坐标,win++;(与排查雷写在一起构成循环)

直到win==显示棋盘大小-布置雷的个数

循环结束,

---判断win==显示棋盘大小-布置雷的个数

   --相等:打印排雷成功

思考

代码能否优化?

1.我想要实现像网页版的扫雷,选取一个坐标后若周围没有雷就展开一片,怎么实现?

2.我像要实现标记雷的功能,该怎么实现?

1.实现展开一片

---思路:通过递归实现

展开一片 的要求如下:

递归函数:

---1.选取的坐标周围没有雷

---2.没有雷我们show数组中对应的元素替换为‘ ’ 

win++

---3.判断坐标是否合法(因为要递归,将该点周围元素传进来时可能会造成数组越界)

---4.判断该坐标是否已经排查(减小重复计算) 

   ---若满足以上条件,将该坐标的周围元素在传递给函数,再去判断

---5.若选取的坐标周围有雷:我们就将该元素周围雷的数量返回并替换show数组中对应的元素(和初阶排查雷一样) 

win++  

这样:如果选取的元素周围没有雷:执行234,满足条件,将该元素周围坐标传入函数

若满足条件1继续执行234,若又满足上面的限制条件,又将该元素周围坐标传入函数

直到传入的坐标周围有雷:停止递归,返回该雷的个数。

这样就能达到展开一片的效果

这里引入了win,每执行一步替换show数组操作,win++,用来判断是否终止循环

  代码:

int Expand_blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int win)
{
	int count = Get_count(mine, x, y);
	int i, j;

	if ((x >= 1 && x <= ROW) && (y >= 1 && y <= COL)) //限制坐标范围
	{
		if (count == 0) //判断这个点周围是否有雷,没有就将show对应的元素设为’‘,有的话给上数字
		{
			show[x][y] = ' ';
			win++;
			//将这个坐标周围的元素传递给Expand_blank函数,看其周围有没有雷,没有将show中对应的元素变为’‘,有的话给上数字	
			for (i = x - 1; i <= x + 1; i++) //这样就形成了递归,可以达到拓展一片的效果
			{
				for (j = y - 1; j <= y + 1; j++)
				{
					if ((i == x) && (j == y))
					{
						continue;//遍历到自己就跳过
					}
					if (show[i][j] == '*') //排除已经检查过的元素
					{
						Expand_blank(mine, show, i, j, win);
					}
				}
			}
		}
		else
		{
			show[x][y] = count + '0';
			win++;
		}

	}
	return win;

}

//排查雷
void Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int win = 0;//统计排雷的次数
	int x = 0;
	int y = 0;
	int count = 0;//存放该坐标周围雷的个数

	while (win < row * col - EASY_COUNT)//排雷次数达到要求退出循环
	{
		count = 0;
		printf("请输入你要排查的坐标:");
		scanf("%d%d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				Display_board(mine, ROW, COL);
				break;//选中雷,退出循环
			}
			else if (show[x][y] != '*')
			{
				printf("该坐标你已选择,请重新选择\n");

			}
			else
			{
				win = Expand_blank(mine, show, x, y, win);
				Display_board(show, ROW, COL);//展开一片后显示棋盘
			}

		}
		else
		{
			printf("非法输入,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
	}

}

 这里我在排查雷里去返回该元素周围雷的个数时,使用递归函数,来达到展开一片的效果

将其与判断是否结束循环写在一起:引入计数器:win,每当show数组中有一个元素被替换,win++,直到win==显示棋盘大小-雷的个数;

因此设置了 int Expand_blank(mine,show,x,y,win)函数,让它返回win,来对循环终止进行判断

效果展示:

C语言:扫雷(含标记与展开一片功能)_第11张图片

2.实现标记功能:

思路:玩家进入游戏,显示棋盘后,给玩家提供选择:选1:排查雷,选2:标记雷

实现:

----在循环中,创建input变量,接收玩家输入的数字,

----使用switch()语句,根据玩家的输入进行不同的选择

           case 1:  排查雷                      break;

           case 2:标记雷                     break;

            defaut:提醒玩家正确输入;break;

C语言:扫雷(含标记与展开一片功能)_第12张图片

这里还需要补充一个标记函数

代码:

//标记雷
void sign_mine(char show[ROWS][COLS], int row, int col)
{
	int x, y;
	printf("请输入你要标记雷的坐标:");
	while (1)
	{
		scanf("%d%d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
		{
			show[x][y] = 'M';
			Display_board(show, ROW, COL);
			break;
		}
		else
		{
			printf("非法输入,请重新输入:");
		}
	}
}

 接下来就要思考如将它放在循环里,并在选择排查雷的时候若选到雷了,游戏直接结束

---同样进行”判断游戏是否结束“:引入计数器win,当win==显示棋盘大小-雷数量的时候,游戏胜利,

---这里标记雷是不影响win的加减的,因为只是标记,并没有去检查show数组中的对应元素

---这里注意:当在排查雷函数里选中雷过后,我们直接将win赋为>显示棋盘大小-雷个数,直接终止循环,并打印”很遗憾,你踩到地雷了“

接下类进行判断:如果win==显示棋盘大小-雷数量才打印游戏胜利

这里需要修改以下排查雷函数:让其为int类型,并返回win的大小来判断是否结束循环

//找到雷
int get_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int win)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入你要排查的坐标:");
		scanf("%d%d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				win = 100;
				Display_board(mine, ROW, COL);
				break;//选中雷,退出循环
			}
			else if (show[x][y] != '*')
			{
				printf("该坐标你已选择,请重新选择\n");
			}
			else
			{
				win = Expand_blank(mine, show, x, y, win);
				Display_board(show, ROW, COL);//展开一片后显示棋盘
				break;
			}

		}
		else
		{
			printf("非法输入,请重新输入\n");
		}
	}
	return win;

}

整体框架:

C语言:扫雷(含标记与展开一片功能)_第13张图片

 执行效果展示:

C语言:扫雷(含标记与展开一片功能)_第14张图片

 总结:

C语言简易实现扫雷所用到的知识:

----数组:用来存放棋盘        

----循环:用来遍历数组中的元素并显示        

----条件语句和操作符:  用来增加限制条件        

----递归 ;用来将大问题拆分为类似的小问题并解决  (实现展开一片的功能)

##所用知识罗列:

1.数组:

----二维数组

----#define 定义常量来规定数组大小

2.循环

----for():用来遍历数组里的所有元素并展示

----while()  :   搭配限制条件来实现埋雷,结束循环

----do_while():让程序至少运行一次,给玩家提供选择

3.条件语句与操作符

----与循环搭配使用起到:排除已筛查元素,判断坐标的合法性,判断坐标是否被占用,判断输入是否合法

----选择语句switch搭配循环:根据玩家的输入,进行不同结果反映(排查雷或者标记雷)

4.递归:搭配循环语句,条件语句来起到重复调用自己,并给上递归结束的限制条件,来实现递归

你可能感兴趣的:(C语言实战,c语言,笔记)