【C语言】手把手带你实现《完整版扫雷》

请添加图片描述扫雷,大家小的时候应该都玩过,如果没玩过也不要紧,这里有链接,可以试着了解一下,对写代码有帮助扫雷游戏网页版 - Minesweeper ,创作不易,还请各位小伙伴多多关注点赞👍收藏⭐,以后也会更新各种小游戏还有关于c语言的博客。✨✨



前言

这种小游戏主要锻炼我们模块化编程能力,什么是模块化编程呢

模块化编程:模块化编程就是把我们的一整个项目,分成很多模块(比如我们生产汽车,可以分为生产发动机、生产轮胎、生产车架、组装等)而一个程序工程包含多个源文件(.c 文件和 .h 文件),每个 .c 文件可以被称为一个模块,每一个模块都有其各自的功能,而每一个.h文件则是声明该模块,相当于功能说明书 ,大大提高代码可阅读性,不会使代码显得臃肿。

扫雷(游戏规则)

挖到地雷,游戏结束,挖到空方快,游戏继续,挖到数字则表示在其周围的八个方块中有多少个雷,如果怀疑一个地方是雷,可以对其进行标记,标记后的位置需要取消标记才能挖开,以免误操作 🌞

实现效果:

在这里插入图片描述

🎉🎉首先开始执行程序,打印一张菜单,可以选择0或者1,0退出,1进入游戏

在这里插入图片描述

🌈🌈如果选择1的话,开始游戏,进行排雷,如果一个格子的周围八个格子都没有雷,此格子会炸开直到遇到雷为止,踩到雷失败,找出所有不是雷的格子获胜。

程序实现思路

  1. 进入程序,首先出现在我们眼前的是菜单,所有首先我们要打印一个菜单
  2. 进入游戏后,屏幕上会出现一个9*9的棋盘,上面全是 ’ * ‘ ,这里我们可以用二维数组来装载
  3. 棋盘定义之后,把他初始化成 ‘ * ’
  4. 打印出棋盘
  5. 开始扫雷,这里我们发现一点问题,如果用一个二维数组装‘ * ‘,那么雷呢??雷我们考虑再定义二维数组来进行装载,这个二位数组我们可以全部初始化为’ 0 ‘,然后随机生成雷改成1,这样1的位置就是雷了
  6. 接下来开始扫雷,如果我们扫的位置上不是雷,那么也需要判断周围八个格子有几颗雷,问题又来了,如果说中间的雷我们可以输入统一的x-1,y-1这种来找到,那么边上的该怎么办,当然我们也可以把每种情况都列出来,不过那不是个好办法,我们可以将数组定义成11*11的这样就完美解决了这个问题
  7. 大家可以参考上面的游戏链接,扫雷的规则是如果我们扫的位置周围一颗雷都没有,那么这块就会爆炸式的寻找周围与他相邻的周围八个格子也没有雷的格子,然后打开他们周围的八个格子,这里又是一个问题
  8. 为了防止数字太多我们眼花缭乱,我们可以把怀疑是雷的格子进行标记,被标记的格子取消先取消标记才可以继续进行操作,这样就不用担心忘记哪个格子自己已经知道是雷了
  9. 如果扫出了所有不是雷的格子,游戏结束,这里可以用一个全局变量,每次扫一个格子就+1,直到这个全局变量==我们真正用到的行和列也就是9*9-雷的数量
  10. 游戏结束后,再次打印菜单进行选择,1进入游戏,0退出游戏,很明显这里做成一个循环,输入0结束循环

代码实现

1.打印菜单

void menu()  //菜单
{
	printf("*********************************************\n");
	printf("**********    1.   进 入 扫 雷   ************\n");
	printf("**********    0.   退       出   ************\n");
	printf("*********************************************\n");
}

无论什么游戏都必须要一张菜单,让用户知道怎么操作

2.main函数

int main()
{
	int input = 0;
	srand((unsigned)time(NULL));   //随机数用于生成雷
	do 
	{
		menu();    //do-while语句进来先打印菜单栏
		printf("请进行选择(0/1)\n");   //这里进行输入有一个小细节就是把退出设置为0,然后把输入数字放在dowhile()里面这样输入0就直接退出循环了
		scanf("%d", &input);   
		switch (input)       //switch语句进行选择,一目了然
		{
		case 1:
			game();  //进入游戏
			break;
		case 0:
			printf("成功退出");
			break;
		default: 
			printf("输入错误,请重新输入");
			break;
		}
	} while (input); //为0中止
}

进入程序,直接一张菜单弹出所以这里选择do。while语句,再用switch语句进行判断,当玩家输入1,进入游戏,当玩家输入0,退出程序

  • 经过上面的思考之后,让我们想想进入游戏后都需要些什么(也就是game()函数都需要什么)❔

    🌈两张棋盘,一张显式出来,一张进行放雷,由于进行判断周围雷的数量的时候会容易产生角标越界,所有我们初始化数组的时候要把行和列都+2

    • 设计好雷的数量和困难程度
    #define ROW 9       	//实际用到的行
    #define COL 9 			//实际用到的列
    #define ROWS  (ROW+2)	//排查周围雷需要的行
    #define COLS  (COL+2)	//排查周围雷需要的列
    #define MINE_COUNT 9	//雷的数量
    
	char mine[ROWS][COLS] = { 0 };   //埋雷用的数组
	char show[ROWS][COLS] = { 0 };	 //显示用的数组

3.初始化棋盘

  • 🌞定义好数组之后要对数组进行初始化,显示用的的数组初始化为 ‘ * ’ 埋雷的数组先初始化为0
void InitBoard(char str[ROWS][COLS], int row, int col,char set) 
//把数组和实际用到的行和列传进来,才能进行初始化,由于两个数组初始化内容不同,再设置一个char类型的set用于传入想传入的值
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			str[i][j] = set;
		}
	}
}

4.打印棋盘

  • 🌞初始化棋盘之后,我们要在屏幕上打印出来,并且为了进行扫雷时页面不那么乱,打印的时候要再进行排版
void PrintBoard(char str[ROWS][COLS], int row, int col)
{
	printf("   ");
	for (int i = 1; i <= col; i++)
	{
		printf("%2d ", i);                    
	}
	printf("\n");
	printf("   ");
	 	for (int i = 1; i <= row; i++)
	{
		printf("---");
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%2d|", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%2c ", str[i][j]);
		}
		printf("\n");
	}
}

效果图

在这里插入图片描述
这里循环打印的时候变量用的是define定义的ROW和COL,如果后续想改变难度,只需要改变define里面的行和列即可

5.设置雷

  • 🌞打印完棋盘之后我们来进行雷的设置
void GenerateMine(char mine[ROWS][COLS], int row, int col)  //接收埋雷的数组,和实际用到的行和列,进行埋雷
{
	int i = 0;
	while (i<MINE_COUNT) 
	{
		int x = rand() % 9 + 1;        //使用rand函数之前要先使用srand函数生成随机值,srand我们不妨main中
		int y = rand() % 9 + 1;
        //生成随机数在坐标1-9的位置,因为坐标为0和坐标为10的位置是为了检查周围雷方便设计的
		if(mine[x][y]=='0')
		{
			mine[x][y] = '1';        //如果等于’ 0’再下雷以免雷的位置重复
			i++;
		}
	}
}

6.开始扫雷

  • 🧐扫雷这里由于情况较多所以比较复杂,我们把所有逻辑都写在这里,如果跳出此函数,即为扫雷结束

    • 进入游戏弹出游戏页面后,我们开始进行扫雷,先输入两个数,为坐标,随后一共分为下面这些情况

      • 如果输入坐标的位置是雷的话,游戏结束
      • 如果输入的坐标超出已有坐标,输出:请输入1—9的坐标值
      • 如果输入的坐标不是雷但是周围八个格子内有雷,此坐标显示为周围八个格子雷的数量
      • 如果输入的坐标不是雷且周围的八个格子也没有雷,此坐标向周围炸开,也就是周围八个格子全部打开,然后判断周围八个格子各自的周围八个格子有没有雷,如果周围也没有雷,则继续向周围炸开,直到周围有雷为止。。。
      • 如果输入-1,-1则选择标记一个格子,再次标记同样的格子为取消标记,取消之前不能进行点开

    知道了大概的情况我们先把扫雷的大体框架写出来,细节的东西我们都包装到函数里,先把函数写在这里

int win = 0;									
//一个全局函数win,每次进行排雷win+1
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{

	int x = 0;
	int y = 0;
	win = 0;									//每次进入游戏初始化一次							
	printf("本局共有%d个雷\n", MINE_COUNT);
	while (win<row*col-MINE_COUNT)          	//所有逻辑在此循环体里,跳出循环体即为游戏结束!
	{
		printf("请输入坐标(?,?),想标记或者取消标记请输入(-1,-1)\n");
		scanf("%d,%d", &x, &y);					//输入坐标
		if (x == -1 && y == -1)
		{
			MarkMine(show, row, col);			//如果输入-1,-1 进行标记,标记我们封装在MarkMine函数里面
			continue;
		}
		if (show[x][y] == '!') 
		{
			printf("此处已被标记,想要操作请先取消标记\n");	//如果此处已经被标记,不能进行操作
		}
		else if (x > 0 && x <=ROW && y>0 && y <=COL)	//如果输入的坐标合法,继续判断
		{
			if (mine[x][y] == '1')						//如果此位置是雷,被炸死,游戏结束
			{
				printf("非常遗憾,你被炸死了。。。\n");
				PrintBoard(mine, ROW, COL);				//这里人性化打印一下雷的分布,让玩家知道怎么挂掉的
				break;
			}
			else if (show[x][y] == '*')					//如果此位置是*
			{
				int count = CheckMine(mine, x, y);		//checkMine函数:查看周围八个格子有几个雷,返回数量
				show[x][y] = count + '0';				//数字+‘ 0’ = ‘数字’给到显示用的数组
				win = win+1;							//点开一个格子win就+1
				if ('0' == show[x][y])					//如果周围八个格子都没有雷,向周围炸开扩散
				{
					CheckAround(mine, show,x,y);		//炸开的代码我们封装在checkAround中
				}
				PrintBoard(show, ROW, COL);				//输出一下新的棋盘
			}
			else
			{
				printf("请勿重复选择\n");
			}
		}
		else
		{
			printf("请输入1-9的坐标值");				
		}
		if (win == ROW * COL - MINE_COUNT)			//当win==row*col-MINE_COUNT(所有不是雷的格子)排雷成功
		{
			printf("排雷成功\n");
		}
	}
}

7.CheckMine函数(检查周围八个格子雷的数量)

  • 🌞这里用于检查周围八个格子雷的数量,因为前面我们把雷设置成‘ 1’ 非雷设置成‘ 0 ’,所有这里把8格子相加再减去8个格子乘‘ 0 ’就能得int类型的的雷的数量
int CheckMine(char mine[ROWS][COLS], int row, int col)
{
	return  mine[row - 1][col - 1] +
		mine[row - 1][col] +
		mine[row - 1][col + 1] +
		mine[row][col - 1] +
		mine[row][col + 1] +
		mine[row + 1][col - 1] +
		mine[row + 1][col] +
		mine[row + 1][col + 1] - 8 * '0';

}

8.MarkMine函数 (对怀疑是雷的格子进行标记)

  • 🌞如果此位置为‘ * ’,进行标记,如果为‘ ! ’,取消标记,如果为已经打开的格子,输出:只能对点开的格子进行操作

void MarkMine(char show[ROWS][COLS], int row, int col)
{
	printf("请输入你想标记的坐标(?,?)\n");
	int x = 0;
	int y = 0;
	scanf("%d,%d", &x, &y);
	if (show[x][y] == '*')
	{
		show[x][y] = '!';
		PrintBoard(show, ROW, COL);
	}
	else if(show[x][y] == '!')
	{
		show[x][y] = '*';
		PrintBoard(show, ROW, COL);
	}
    else
    {
        printf("只能对未点开的格子进行操作\n");
    }    
	
}

9.checkAround函数(爆炸展开函数)

  • 🌞如果周围八个格子没雷,把周围八个格子全部打开,然后再检查周围八个格子的周围有没有雷,没雷继续打开,继续对其周围八个格子进行检查,直到查到雷,这里注意,如果相邻的两个格子互相对其周围八个格子进行检查容易造成死递归,所有我们设置该位置为‘ * ’才进行检查。
void CheckAround(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)
{

	for (int i = x-1; i <= x + 1; i++)					
	{
		for (int j = y-1 ; j <= y + 1; j++)					//对其周围格子进行遍历
		{
			if (show[i][j]=='*'&&i > 0 && i <=ROW && j>0 && j <= COL)	
                //如果没点开过即为‘ *’且不超出我们用到的坐标才继续检查
			{							
						
                char count = CheckMine(mine, i, j) + '0';	//检查周围八个格子雷的数量
				show[i][j] = count;							
				win = win+1;								//上面打开格子下面紧随其后对win进行++
				if (show[i][j] == '0') 						//如果show[i][j]为‘ 0 ’进入递归
				{
					CheckAround(mine, show, i, j);       
				}
			}
		}
	}
}

10.game函数()

  • 🌈🌈 有了上面这些函数,我们把这些函数按步骤写在game里即可
void game()
{
	char mine[ROWS][COLS] = { 0 };          //定义埋雷用的数组
	char show[ROWS][COLS] = { 0 };			//定义显示用的数组
	InitBoard(mine, ROWS, COLS,'0');		//初始化显示用的数组
	InitBoard(show, ROWS, COLS,'*');		//初始化先使用的数组
	PrintBoard(show, ROW, COL);				//输出棋盘
	GenerateMine(mine, ROW, COL);			//埋雷
	//PrintBoard(mine, ROW, COL);     		//这里就是用于测试,开始显示雷在哪,正式进行游戏的时候最好关掉	
	FindMine(mine, show, ROW, COL);			//扫雷函数

}

文件分类

🌞🌞为了使代码更有阅读性,我们不建议把所有函数写在一个文件里,所以这里分成三个文件,模块化管理

test.c

  • 这个文件包含程序的主体构思,程序用到的一系列函数我们封装在其他文件夹,这里只需要引入头文件即可
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("*********************************************\n");
	printf("**********    1.   进 入 扫 雷   ************\n");
	printf("**********    0.   退       出   ************\n");
	printf("*********************************************\n");
}
void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitBoard(mine, ROWS, COLS,'0');
	InitBoard(show, ROWS, COLS,'*');
	PrintBoard(show, ROW, COL);
	GenerateMine(mine, ROW, COL);
	//PrintBoard(mine, ROW, COL);     测试
	FindMine(mine, show, ROW, COL);

}
int main()
{
	srand((unsigned int)time(NULL));
		int input = 0;
	do
	{
		menu();
		scanf("%d", &input);
		printf("\n");
		switch (input)
		{
		case 1:
			game();
			break;
		case 0 :
			printf("退出成功\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while(input);
}

game.c

  • 进入游戏后需要的所有函数都封装在这里
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int win = 1;
void InitBoard(char str[ROWS][COLS], int row, int col,char set)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			str[i][j] = set;
		}
	}
}

void PrintBoard(char str[ROWS][COLS], int row, int col)
{
	printf("   ");
	for (int i = 1; i <= col; i++)
	{
		printf("%2d ", i);
	}
	printf("\n");
	printf("   ");
	 	for (int i = 1; i <= row; i++)
	{
		printf("---");
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%2d|", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%2c ", str[i][j]);
		}
		printf("\n");
	}
}

void GenerateMine(char mine[ROWS][COLS], int row, int col)
{
	int i = 0;
	while (i<MINE_COUNT) 
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;
		if(mine[x][y]=='0')
		{
			mine[x][y] = '1';
			i++;
		}
	}
}

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{

	int x = 0;
	int y = 0;
	win = 0;
	printf("本局共有%d个雷\n", MINE_COUNT);
	while (win<row*col-MINE_COUNT)
	{
		printf("请输入坐标(?,?),想标记或者取消标记请输入(-1,-1)\n");
		scanf("%d,%d", &x, &y);
		if (x == -1 && y == -1)
		{
			MarkMine(show, row, col);
			continue;
		}
		if (show[x][y] == '!') 
		{
			printf("此处已被标记,想要操作请先取消标记\n");
		}
		else if (x > 0 && x <=ROW && y>0 && y <=COL)
		{
			if (mine[x][y] == '1')
			{
				printf("非常遗憾,你被炸死了。。。\n");
				PrintBoard(mine, ROW, COL);
				break;
			}
			else if (show[x][y] == '*')
			{
				int count = CheckMine(mine, x, y);
				show[x][y] = count + '0';
				win = win+1;
				if ('0' == show[x][y])
				{
					CheckAround(mine, show,x,y);
				}
				PrintBoard(show, ROW, COL);
			}
			else
			{
				printf("请勿重复选择\n");
			}
		}
		else
		{
			printf("请输入1-9的坐标值");
		}
		if (win == ROW * COL - MINE_COUNT)
		{
			printf("排雷成功\n");
		}
	}
}

void MarkMine(char show[ROWS][COLS], int row, int col)
{
	printf("请输入你想标记的坐标(?,?)\n");
	int x = 0;
	int y = 0;
	scanf("%d,%d", &x, &y);
	if (show[x][y] == '*')
	{
		show[x][y] = '!';
		PrintBoard(show, ROW, COL);
	}
	else if (show[x][y] == '!')
	{
		show[x][y] = '*';
		PrintBoard(show, ROW, COL);
	}
	else
	{
		printf("只能对未点开的格子进行操作\n");
	}

}
int CheckMine(char mine[ROWS][COLS], int row, int col)
{
	return  mine[row - 1][col - 1] +
		mine[row - 1][col] +
		mine[row - 1][col + 1] +
		mine[row][col - 1] +
		mine[row][col + 1] +
		mine[row + 1][col - 1] +
		mine[row + 1][col] +
		mine[row + 1][col + 1] - 8 * '0';

}

void CheckAround(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)
{

	for (int i = x-1; i <= x + 1; i++)
	{
		for (int j = y-1 ; j <= y + 1; j++)
		{
			if (show[i][j]=='*'&& i > 0 && i <= ROW && j > 0 && j <= COL)
			{
					char count = CheckMine(mine, i, j) + '0';

					show[i][j] = count;
					win = win+1;
					if (show[i][j] == '0') 
					{
						CheckAround(mine, show, i, j);
					}
				
			}
		}
	}
}

game.h

  • 将主程序所需要的函数全部在头文件中声明,增加代码阅读性
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS  (ROW+2)
#define COLS  (COL+2)
#define MINE_COUNT 3



//棋盘初始化
void InitBoard(char arr[ROWS][COLS], int row, int col, char set);
//打印棋盘
void PrintBoard(char arr[ROWS][COLS], int row, int col);
//生成雷
void GenerateMine(char mine[ROWS][COLS], int row, int col);
//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//标记雷
void MarkMine(char show[ROWS][COLS], int row, int col);
//检查周围有几个雷
int CheckMine(char mine[ROWS][COLS], int row, int col);
//如果某坐标周围雷数是0,将周围八格全部打开
void CheckAround(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);

撒花

这就是实现扫雷的全部内容了,创作不易,还请各位小伙伴多多点赞👍关注收藏⭐,以后也会更新各种小游戏还有关于c语言的博客,撒花!

img

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值