【C语言游戏】超详解扫雷游戏完整版,细节满满!!

目录

 

扫雷

扫雷游戏规则介绍

如何将扫雷游戏实现代码

基本思路

分步代码实现

创建和打印游戏菜单

初始化棋盘

打印棋盘

布置雷

排查雷

游戏主体——game()函数

总代码实现

game.h

test.c

game.c

总结


扫雷

实现扫雷的算法有很多种,我在这里给大家最详细的代码介绍以及思考方法,细节满满哦!!


扫雷游戏规则介绍

每个格子有两种状态,有地雷或者没有地雷。玩家点到地雷游戏结束,玩家标记出所有地雷游戏胜利。

每个没有地雷的格子点开后显示相邻8个格子里面存在地雷的数目,周边没有地雷则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。

如何将扫雷游戏实现代码

与上次三子棋游戏模块一致,分类创建:

game.h:相关游戏函数的声明,变量的宏定义等;

game.c:游戏相关函数的功能实现;

test.c:游戏的测试,游戏的主题体;


基本思路

1.创建和打印游戏菜单

2.创建两个棋盘数组,一个是布置雷的棋盘数组,一个是排查雷的棋盘数组

3.初始化两个棋盘,为了防止后期统计排查雷的个数出现矛盾,所以我这里把布置雷的那个棋盘全部初始为'0',把排查雷的棋盘全部初始化为'*'

4.打印棋盘

5.布置雷,由电脑自主完成随机布置雷的个数,个数可以自己在头文件中定义

6.排查雷,在布置雷的数组里排查,如果是雷则打印被炸死,并退出游戏,打印排查雷的棋盘;如果不是雷,则统计雷的个数,是0则展开空白,不是0则将雷的个数传给排查雷的那个数组

7.判断输赢,如果空格的总的个数于行和列的乘积减去布雷的个数,则表示排雷成功


分步代码实现

创建和打印游戏菜单

void menu()
{
	printf("**********************\n");
	printf("******* 1.play *******\n");
	printf("******* 0.exit *******\n");
	printf("**********************\n");
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//用于随机函数rand的调用
	do
	{
		menu();
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			printf("扫雷游戏开始\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新选择!\n");

		}

	} while (input);
	return 0;
}

初始化棋盘

	//两个数组初始化数组的实现,char board[ROWS][COLS]接收mine数组和show数组
	//这里设置一个字符set接收mine和show数组传参过来的‘0’和‘*’
void Initboard(char board[ROWS][COLS], int rows, int cols,char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

这里的初始化函数调用两次,分别初始化布雷数组和排雷数组,字符set分别接收'0'和'*';

打印棋盘

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

打印棋盘结果如下:

这样打印出来的棋盘好看也方便用户写入坐标

布置雷

void Setmine(char mine[ROWS][COLS], int row, int col)
{
	int count = SETCOUNT;	//为了灵活变通,可以自己设置布置雷的个数,在头文件中自定义个数
	
     while (count)           //直到count为0才退出循环,并且每次都是随机坐标布置雷,count是几,
    {                          x和y就要随机几次
                       
		
		int x = rand() % row + 1;//用户输入的坐标的范围就是行数列数的范围,应该是row+1才是正确 
                                   的坐标范围
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;//每布置一个,雷的个数就减一;
		}
	}
}

排查雷

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 - SETCOUNT)
	{
		int count_blank = 0;//空白个数,每次进入循环都要重置,如果放在循环外面依旧是上一次的值会重复叠加空白;
		printf("请输入排查雷的坐标:\n");

		scanf_s("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)		//判断坐标合法性
		{
			if (mine[x][y] == '1')		//遍历到了雷
			{
				printf("很遗憾,你被炸死了!\n");
				Displayboard(mine, row, col);
				break;

			}
			else
			{
				int count_mine = get_mine_count(mine, x, y);
				
				if (count_mine == 0 && show[x][y]=='*')//周围雷的个数为0,且没有被遍历
				{
					count_blank=get_showblank(mine, show, x, y);//递归展开空白,返回空白个数
					win += count_blank;//空白的个数加到不是雷个数的总数中
				}
				else if(show[x][y]=='*')//这里用else不太合适,这里控制的条件是当周围个数不是0,但是没有被遍历
				{
                    show[x][y] = count_mine + '0';//不是雷,则统计周围有几个雷,放入show数组对应坐标
					win++;//他统计了周围雷的个数,但本身不是雷,加1
					
				}
				
				Displayboard(show, row, col);
				
			}
		}
		else
		{
			printf("坐标不合法,请重新输入!\n");
		}
	}
	if (win== row * col - SETCOUNT)
	{
		printf("恭喜你,排雷成功!\n");
		Displayboard(show, row, col);
	}

		
}

当排查雷时,(x,y)处不是雷且周围雷的个数不为0则需要统计周围雷的个数:

static int  get_mine_count(char mine[ROWS][COLS], int x,int y)  //static修饰函数,那这个函数 
                                                                  就只能在当前源文件里使用了

{   //统计这些坐标周围有几个雷
	return mine[x][y - 1] +
		mine[x - 1][y - 1] +
		mine[x + 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y + 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] - 8 * '0';  //这里返回的是数字,num+'0'='num';即一个数字加上字符0等于数字代表的字符

}

当(x,y)周围雷的个数为0时展开为空格,去递归遍历周围四个坐标雷的个数,并且统计空格的个数:

int get_showblank(char mine[ROWS][COLS], char show[ROWS][COLS], int x , int y)//当雷的个数为0时计算展开的空白个数
{
	
	int count_mine = get_mine_count(mine, x,y);
    int count_blank = 0;//初始化空白的个数
	if(count_mine == 0)
	{
		show[x][y] = ' ';
		count_blank++;//只要是空白就加1;下次递归也一样

	    //判断周围四个坐标合法性,并且要满足没有被遍历,依旧是*号,以防重复遍历
		if (x - 1 >= 1 && x - 1 <= ROW && y >= 1 && y <= COL && show[x - 1][y] == '*')
		{
			count_blank+=get_showblank(mine, show, x - 1, y);//每递归一次,空白个数要累加,包括了x,y的空白和周围四个坐标的空白;
			

		}
		if (x >= 1 && x <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x][y - 1] == '*')
		{
			count_blank += get_showblank(mine, show, x, y - 1);
			
		}
		if (x + 1 >= 1 && x + 1 <= ROW && y >= 1 && y <= COL && show[x + 1][y] == '*')
		{
			count_blank += get_showblank(mine, show, x + 1, y);
			
		}
		if (x >= 1 && x <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x][y + 1] == '*')
		{
			count_blank += get_showblank(mine, show, x, y + 1);
		}
			
	}
	return count_blank;//返回空白个数
   
	



	
}

游戏主体——game()函数

void game()
{
	char mine[ROWS][COLS] = { 0 };	//创建存放布置雷的数组
	char show[ROWS][COLS] = { 0 };	//创建存放排查雷的数组
	Initboard(mine, ROWS, COLS,'0');	//初始化布置雷的棋盘,mine表示布置雷数组的首地址
	Initboard(show, ROWS, COLS, '*');	//初始化排查雷的数组,show表示排查雷数组的首地址
	//Displayboard(mine, ROW, COL);	//打印布置雷的棋盘,这里不用打印扩展的两行两列,只需在中间的棋盘布雷,扫雷也同样
	Displayboard(show, ROW, COL);
	Setmine(mine, ROW, COL);	//布置雷
	//Displayboard(mine, ROW, COL);
	Findmine(mine,show, ROW, COL);	//排查雷
}

扫雷游戏主体函数则相比三子棋简单,只需要调用游戏相关函数即可,这里需要注意的是在调用这些函数进行数组传参时,要注意参数的顺序,这里创建mine和show数组都是用的ROWS和COLS,所以在实现相关函数的功能时用的依然是ROWS和COLS。


总代码实现

game.h

#pragma once
#define ROW 9
#define COL 9	
#define SETCOUNT 10

#define ROWS ROW+2		//为了能成功遍历边界的棋盘,所以需要创建两个扩展的数组将布雷和扫雷一一对应,初始化时也一样要扩展
#define COLS COL+2		
void Initboard(char board[ROWS][COLS], int rows, int cols,char set);
void Displayboard(char board[ROWS][COLS], int row, int col);		//打印棋盘只打印中间的棋盘,不需要打印扩列的
void Setmine(char mine[ROWS][COLS], int row, int col);
void Findmine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

test.c

#include<stdio.h>
#include"game.h"
#include<stdlib.h>
#include<time.h>
void game()
{
	char mine[ROWS][COLS] = { 0 };	//创建存放布置雷的数组
	char show[ROWS][COLS] = { 0 };	//创建存放排查雷的数组
	Initboard(mine, ROWS, COLS,'0');	//初始化布置雷的棋盘,mine表示布置雷数组的首地址
	Initboard(show, ROWS, COLS, '*');	//初始化排查雷的数组,show表示排查雷数组的首地址
	//Displayboard(mine, ROW, COL);	//打印布置雷的棋盘,这里不用打印扩展的两行两列,只需在中间的棋盘布雷,扫雷也同样
	Displayboard(show, ROW, COL);
	Setmine(mine, ROW, COL);	//布置雷
	//Displayboard(mine, ROW, COL);
	Findmine(mine,show, ROW, COL);	//排查雷
}
void menu()
{
	printf("**********************\n");
	printf("******* 1.play *******\n");
	printf("******* 0.exit *******\n");
	printf("**********************\n");
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//用于随机函数rand的调用
	do
	{
		menu();
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			printf("扫雷游戏开始\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新选择!\n");

		}

	} while (input);
	return 0;
}

game.c

#include<stdio.h>
#include"game.h"
	//两个数组初始化数组的实现,char board[ROWS][COLS]接收mine数组和show数组
	//这里设置一个字符set接收mine和show数组传参过来的‘0’和‘*’
void Initboard(char board[ROWS][COLS], int rows, int cols,char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}
void Displayboard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("————扫雷游戏 ————\n");
	for (i = 0; i <= col; 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");
}
void Setmine(char mine[ROWS][COLS], int row, int col)
{
	int count = SETCOUNT;	//为了灵活变通,可以自己设置布置雷的个数,在头文件中自定义个数
	while (count)
	{	//直到count为0才退出循环,并且每次都是随机坐标布置雷,count是几,x和y就要随机几次
		int x = rand() % row + 1;//用户输入的坐标的范围就是行数列数的范围,应该是row+1才是正确的坐标范围
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;//每布置一个,雷的个数就减一;
		}
	}
}
static int  get_mine_count(char mine[ROWS][COLS], int x,int y)  //static修饰函数,那这个函数就只能在当前源文件里使用了

{   //统计这些坐标周围有几个雷
	return mine[x][y - 1] +
		mine[x - 1][y - 1] +
		mine[x + 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y + 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] - 8 * '0';  //这里返回的是数字,num+'0'='num';即一个数字加上字符0等于数字代表的字符

}
int get_showblank(char mine[ROWS][COLS], char show[ROWS][COLS], int x , int y)//当雷的个数为0时计算展开的空白个数
{
	
	int count_mine = get_mine_count(mine, x,y);
    int count_blank = 0;//初始化空白的个数
	if(count_mine == 0)
	{
		show[x][y] = ' ';
		count_blank++;//只要是空白就加1;下次递归也一样

	    //判断周围四个坐标合法性,并且要满足没有被遍历,依旧是*号,以防重复遍历
		if (x - 1 >= 1 && x - 1 <= ROW && y >= 1 && y <= COL && show[x - 1][y] == '*')
		{
			count_blank+=get_showblank(mine, show, x - 1, y);//每递归一次,空白个数要累加,包括了x,y的空白和周围四个坐标的空白;
			

		}
		if (x >= 1 && x <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x][y - 1] == '*')
		{
			count_blank += get_showblank(mine, show, x, y - 1);
			
		}
		if (x + 1 >= 1 && x + 1 <= ROW && y >= 1 && y <= COL && show[x + 1][y] == '*')
		{
			count_blank += get_showblank(mine, show, x + 1, y);
			
		}
		if (x >= 1 && x <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x][y + 1] == '*')
		{
			count_blank += get_showblank(mine, show, x, y + 1);
		}
			
	}
	return count_blank;//返回空白个数
   
	



	
}
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 - SETCOUNT)
	{
		int count_blank = 0;//空白个数,每次进入循环都要重置,如果放在循环外面依旧是上一次的值会重复叠加空白;
		printf("请输入排查雷的坐标:\n");

		scanf_s("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)		//判断坐标合法性
		{
			if (mine[x][y] == '1')		//遍历到了雷
			{
				printf("很遗憾,你被炸死了!\n");
				Displayboard(mine, row, col);
				break;

			}
			else
			{
				int count_mine = get_mine_count(mine, x, y);
				
				if (count_mine == 0 && show[x][y]=='*')//周围雷的个数为0,且没有被遍历
				{
					count_blank=get_showblank(mine, show, x, y);//递归展开空白,返回空白个数
					win += count_blank;//空白的个数加到不是雷个数的总数中
				}
				else if(show[x][y]=='*')//这里用else不太合适,这里控制的条件是当周围个数不是0,但是没有被遍历
				{
                    show[x][y] = count_mine + '0';//不是雷,则统计周围有几个雷,放入show数组对应坐标
					win++;//他统计了周围雷的个数,但本身不是雷,加1
					
				}
				
				Displayboard(show, row, col);
				
			}
		}
		else
		{
			printf("坐标不合法,请重新输入!\n");
		}
	}
	if (win== row * col - SETCOUNT)
	{
		printf("恭喜你,排雷成功!\n");
		Displayboard(show, row, col);
	}

		
}

总结

这是扫雷优化也叫完整版的扫雷游戏,博主花了一天时间思考和修改代码,整理博客,实属不易。这让我感觉到每一个游戏的实现离不开每一个细节的把控,如果忽略一个细节,整个游戏就无法完成。扫雷游戏细节颇多,对于我们的思考能力和代码实现能力有一定考验,彻底掌握则需要我们反复去琢磨,去动手实现,只有这样我们才能变得更强。如果这篇文章能帮助到你,请给我一键三连,有了大家的鼓励和指教我才能更进一步!!谢谢大家

 

 

 

 

 

 

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页