【C语言游戏】超详解扫雷游戏完整版,细节满满!!
发布日期:2021-05-04 20:14:42 浏览次数:29 分类:技术文章

本文共 10231 字,大约阅读时间需要 34 分钟。

目录

 


扫雷

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


扫雷游戏规则介绍

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

每个没有地雷的格子点开后显示相邻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
#include"game.h"#include
#include
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
#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); } }

总结

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

 

 

 

 

 

 

 

 

上一篇:【C语言】吃透getchar()函数,超详细解析!!!
下一篇:【C语言游戏】跟电脑battle三子棋

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月10日 19时46分46秒