C语言实战 - 扫雷(图形界面-鼠标操作)

这篇具有很好参考价值的文章主要介绍了C语言实战 - 扫雷(图形界面-鼠标操作)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

最终效果预览

预备内容

相关说明

相关教程

用到的知识

EasyX图形库的下载

思路

源代码

最后


 文章来源地址https://www.toymoban.com/news/detail-483220.html

最终效果预览

在学习如何编写扫雷程序之前,我们先来看一下最终写成代码的演示效果

扫雷视频素材

 

预备内容

相关说明

虽然这是C语言的实战项目,但由于easyx图形库需要在C++环境下才能运行,所以在写代码时创建的是.cpp文件而不是.c文件。而头文件依旧是.h文件

编写扫雷过程中用到的图片资源素材链接如下:

经典版本的扫雷图片素材-C文档类资源-CSDN下载经典版本的扫雷图片素材更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/m0_73759312/87398711

 

相关教程

预备内容中用到的一些知识在下方链接都能相应的找到

string.h中常用的函数(memset函数、sprintf函数都有)

有道云笔记_string.h中的相关函数https://note.youdao.com/s/JAuFJcnZ

 EasyX教程(官方文档)

EasyX 文档 - 基本说明https://docs.easyx.cn/zh-cn/intro

MessageBox函数

MessageBox函数http://t.csdn.cn/2HY12

C语言相关的知识

C语言_小白麋鹿的博客-CSDN博客https://blog.csdn.net/m0_73759312/category_12128703.html

用到的知识

  1. 函数、数组、结构体以及C语言的一些基础内容,其中指针没学也可以。懂得如何操作递归。
  2. memset函数,重置变量时会用到(头文件:srting.h)
  3. sprintf函数(头文件:srting.h)
  4. MessageBox函数(Windows封装的MFC内容)就是游戏退出或者游戏结束时弹出的消息框
  5. EasyX图形库的相关内容
  • 本次扫雷程序用到的EasyX的相关函数
  1. initgraph初始化图形
  2. 图片变量是IMAGE类型
  3. loadimage加载图片,如果程序报错可以尝试把地址用括号括起来,前面加上一个 _T
  4. putimage放置图片
  5. 使用图片之前需要预先加载图片
  6. ExMessage是信息类型
  7. peekmessage获取信息类型
  8. BeginBatchDraw()和EndBatchDraw()函数,以防止闪屏
  9. setlinestyle()、rectangle()等绘图函数(画图形、设置图形格式)
  10. setbkcolor、settextcolor、 settextstyle、drawtext等文字设置相关的函数

EasyX图形库的下载

EasyX图形库的安装过程很简单,和平时安装软件一样(后附链接)

C语言实战 - 扫雷(图形界面-鼠标操作)

EasyX图形库下载地址EasyX Graphics Library 是针对 Visual C++ 的绘图库,支持 VC6.0 ~ VC2019,简单易用,学习成本极低,应用领域广泛。目前已有许多大学将 EasyX 应用在教学当中。https://easyx.cn/

思路

我们先要考虑如何实现扫雷这样一个功能,然后再对其进行优化。

大体思路很简单,先创建一个SquareMessage结构体,这个结构体中放的是扫雷时我们所要点击的方块的信息,其中有放置图片的x轴坐标、y轴坐标,状态(是否为雷),标志(是否插旗或者被点开),代码实现如下:

创建方块的结构体类型

struct SquareMessage //方块信息
{
	int CoorX;	//X轴坐标
	int CoorY;	//Y轴坐标
	int	State;	//状况:0 - 非雷 / 1 - 是雷 
	int Mark;	//标志:0 - 正常 / 1 - 插旗 / 2 - 问号 / 3 - 被点开
}Square[15][15];					//方块信息以数组形式存储

然后再对这个数组进行初始化,代码实现如下:

初始化函数

void InitMessage()	//信息初始化
{
	//初始化方块
	for (int x = 15, i = 1; i <= 13; x += 40, i++)
	{
		for (int y = 77, j = 1; j <= 13; y += 40, j++)
		{
			Square[i][j].CoorX = x;
			Square[i][j].CoorY = y;
			Square[i][j].State = 0;
			Square[i][j].Mark = 0;			
		}
	}
	//布置雷
	int MineNum = MineNumber;
	int x, y;
	while (1)
	{
		if (MineNum == 0)
			break;
		x = rand() % 13 + 1;
		y = rand() % 13 + 1;
		if (Square[x][y].State == 0)
		{
			Square[x][y].State = 1;
			MineNum--;
		}
	}
}

然后批量加载我们需要用到的图片资源(因为如果每使用一次就加载一次可能会造成程序卡顿)

代码实现如下:

批量加载图片函数

void LoadPicture() //批量加载图片
{
	loadimage(&Background, _T("./PictureFiles/background.png"));
	loadimage(&Normal, _T("./PictureFiles/normal.png"));
	loadimage(&Safe0, _T("./PictureFiles/safe0.png"));
	loadimage(&Safe1, _T("./PictureFiles/safe1.png"));
	loadimage(&Safe2, _T("./PictureFiles/safe2.png"));
	loadimage(&Safe3, _T("./PictureFiles/safe3.png"));
	loadimage(&Safe4, _T("./PictureFiles/safe4.png"));
	loadimage(&Safe5, _T("./PictureFiles/safe5.png"));
	loadimage(&Safe6, _T("./PictureFiles/safe6.png"));
	loadimage(&Safe7, _T("./PictureFiles/safe7.png"));
	loadimage(&Safe8, _T("./PictureFiles/safe8.png"));
	loadimage(&Mine1, _T("./PictureFiles/mine1.png"));
	loadimage(&Mine2, _T("./PictureFiles/mine2.png"));
	loadimage(&Flage1, _T("./PictureFiles/flag1.png"));
	loadimage(&Flage2, _T("./PictureFiles/flag2.png"));
	loadimage(&Face1, _T("./PictureFiles/face1.png"));
	loadimage(&Face2, _T("./PictureFiles/face2.png"));
	loadimage(&Face3, _T("./PictureFiles/face3.png"));
}

以上的这些准备工作都是在正式游戏开始之前进行,接下来我们开始着手写游戏主体。

首先是绘制扫雷界面,这时我们先放置背景,然后再根据情况循环遍历方块(被点开的按情况处理,没被点开的是否插旗等等情况),这时我们先写一个不是雷的各种情况的函数,然后再写一个检测鼠标点击的函数,还需要一个函数用于检测被点击的方块周围雷的个数

获取周围雷数的函数

int MineAroundNumber(int X, int Y)//周围的雷数
{
	int mine_sum = 0;
	for (int i = -1; i < 2; i++)
	{
		for (int j = -1; j < 2; j++)
		{
			//跳过自身
			if (i == 0 && j == 0)
				continue;
			mine_sum += Square[X + i][Y + j].State; 
		}
	}
	return mine_sum;//非雷为0,雷为1,所以sum的值就是周围雷的数量
}

不是雷的函数

void BaseShow(int X, int Y) //不是雷的各种情况
{
	if (X > 0 && Y > 0 && X < 14 && Y < 14)	//限制范围
		switch (MineAroundNumber(X, Y))
		{
		case 0: 
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe0);
			Square[X][Y].Mark = 3;
			break;
		case 1:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe1);
			Square[X][Y].Mark = 3;
			break;
		case 2:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe2);
			Square[X][Y].Mark = 3;
			break;
		case 3:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe3);
			Square[X][Y].Mark = 3;
			break;
		case 4:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe4);
			Square[X][Y].Mark = 3;
			break;
		case 5:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe5);
			Square[X][Y].Mark = 3;
			break;
		case 6:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe6);
			Square[X][Y].Mark = 3;
			break;
		case 7:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe7);
			Square[X][Y].Mark = 3;
			break;
		case 8:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe8);
			Square[X][Y].Mark = 3;
			break;
		}
}

处理鼠标信息的函数

void ReplyMouse() //处理鼠标信息
{
	ExMessage msg = { 0 };
	peekmessage(&msg, EX_MOUSE);//捕获鼠标信息
	int x = (msg.x - 15) / 40 + 1; //放置方块图片的x轴坐标
	int y = (msg.y - 77) / 40 + 1; //放置方块图片的y轴坐标
	if (Square[x][y].Mark != 3 && x > 0 && y > 0 && x < 14 && y < 14) //被点击之后就不能再被点击,限制点击范围
	{
		switch (msg.message)//筛选鼠标信息
		{

		case WM_LBUTTONDOWN: //左键按下

			if (Square[x][y].Mark == 0)
			{
				//防止第一次踩雷,如果不想要这个功能,直接把下面这条if语句注释掉即可
				if (FirstHit == 0)
				{
					switch (Square[x][y].State)
					{
					case 0://不是雷,直接跳出
						break;
					case 1://是雷,重置雷的坐标
						Square[x][y].State = 0;
						int remine_x, remine_y;
						while (1)
						{
							remine_x = rand() % 13 + 1;
							remine_y = rand() % 13 + 1;
							if (Square[x][y].State == 0 && remine_x != x && remine_y != y)
							{
								Square[remine_x][remine_y].State = 1;
								break;//这个break跳出的是while循环
							}
						}
						break;//这个break跳出的是switch
					}
					FirstHit = 1;//取消第一次点击的状态
				}
				Square[x][y].Mark = 3;
				//设置踩雷坐标,踩雷坐标爆红雷
				if (Square[x][y].State == 1)
				{
					MineX = x;
					MineY = y;					
				}
				//周围0个雷的情况
				if (MineAroundNumber(x, y) == 0)
				{
					MouseLeftHit(x, y);
				}
			}
			break;

		case WM_RBUTTONDOWN: //右键按下
			switch (Square[x][y].Mark)
			{
			case 0://正常 -> 旗子	
				if (FlagNumber < MineNumber) //保证旗的数量小于雷的数量
				{
					Square[x][y].Mark = 1;
					FlagNumber++;
				}
				break;
			case 1://旗子 -> 问号
				Square[x][y].Mark = 2;
				FlagNumber--;
				break;
			case 2://问号 -> 正常
				Square[x][y].Mark = 0;
				break;
			}
			break;
		default:
			break;
		}
	}
}

这时简单的点击方块功能就实现了,接下来就是实现当周围雷数为0时的展开功能和踩雷的情况。

对此,展开的功能用递归来实现,就是当周围雷数为0时,将被点击的方块点开,然后对周围的8个方块进行递归,当周围雷数不为0时结束,跳出递归。

踩雷的情况就是先遍历将所有雷打印出来,将踩雷的坐标再打印红色的雷(特殊标注踩雷的位置)

具体代码实现如下:

鼠标左击的函数(处理展开情况)

void MouseLeftHit(int X, int Y) //鼠标左击的情况
{
	if (Square[X][Y].State == 1) //是雷,直接跳出
		return;
	if (X > 0 && Y > 0 && X < 14 && Y < 14)	//限制范围
		switch (MineAroundNumber(X, Y))
		{
		case 0: //周围无雷,需要展开
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe0);
			if (Square[X][Y].Mark == 0)
				Square[X][Y].Mark = 3;
			//递归展开
			for (int i = X - 1; i < X + 2; i++)
			{
				for (int j = Y - 1; j < Y + 2; j++)
				{
					if (Square[i][j].Mark == 0)
						MouseLeftHit(i, j);
				}
			}
			break;
		default:
			BaseShow(X, Y);
			break;
		}
}

展示所有雷的函数

 (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{
			if (Square[i][j].State == 1) 
			{
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Mine1);
				Square[i][j].Mark = 3; 
			}
		}
	}
}

然后汇总出一个图片输出函数:

图片输出函数

int GraphyShow() //返回值:1-输/2-没输
{
	int tmp_return = 0;
	//绘制背景
	putimage(0, 0, &Background);
	//绘制游戏界面
	for (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{		
			switch(Square[i][j].Mark) 
			{
			case 0: //正常
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Normal); 
				break;
			case 1: //插旗
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Flage1); 
				break;
			case 2: //问号
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Flage2);
				break;
			case 3: //鼠标左击
				if (Square[i][j].State == 0) //没踩到雷
					BaseShow(i, j);
				if (Square[i][j].State == 1) //踩到雷了
				{
					ShowMine(i, j); 
					putimage(Square[MineX][MineY].CoorX, Square[MineX][MineY].CoorY, &Mine2);
					tmp_return = 1; //返回值改变
					FaceKind = 2;	//脸表情改变
				}
				break;
			}
		}
	}

	//绘制提示区域
	ShowTips();
	//函数返回 | 返回值:1-输/2-没输
	return tmp_return;
}

最后再添加一个判断是否赢了的函数

判断赢了的函数

int IsWin()//判断输赢:1 - 赢 / 2 - 没赢
{
	int RestSquare = 0;
	for (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{
			if (Square[i][j].Mark != 3)
				RestSquare++;
		}
	}
	//理论上讲,当所剩的方块数等于雷数时,剩下的就都是雷
	if (RestSquare == MineNumber)//赢了
	{
		//把所有雷插棋子 - 胜利标志
		for (int i = 1; i < 14; i++)
		{
			for (int j = 1; j < 14; j++)
			{
				if (Square[i][j].State == 1)
					Square[i][j].Mark = 1;
			}
		}
		FlagNumber = MineNumber;//旗的数量等于雷的数量
		FaceKind = 3; //脸表情改变
		return 1;
	}
	return 0;
}

至此,扫雷的游戏功能就是实现了,接下来就是对其进行优化了。相关优化的函数内容如下:

绘制表情(赢了戴墨镜,输了大哭脸) 添加到图片输出(GraphyShow)函数中的后面部分

	//绘制脸的表情
	switch(FaceKind)
	{
	case 1:
		putimage(252, 16, &Face1);
		break;
	case 2:
		putimage(252, 16, &Face2);
		break;
	case 3:
		putimage(252, 16, &Face3);
		break;
	}	

游戏提示区域(游戏雷数,时间提示) 添加到图片输出(GraphyShow)函数中的后面部分

void ShowTips() //绘制提示区域
{
	//设置字体规格	
	setbkcolor(BLACK);						//黑色背景
	settextcolor(RGB(255, 35, 35));			//红色字体
	settextstyle(40, 20, "DigifaceWide");	//高40宽20,"液晶数字"字体

	//绘制雷数提示区域 - 5个"身位"
	RECT words_area = { 20, 17, 135, 57 };
	sprintf(MineNumberTip, "%03d", MineNumber - FlagNumber);
	drawtext(MineNumberTip, &words_area, DT_LEFT | DT_SINGLELINE);

	//绘制时间提示区域 - 5个"身位"
	RECT time_area = { 410, 17, 525, 57 };
	sprintf(GameTimeTip, "%03d", GameTime);
	drawtext(GameTimeTip, &time_area, DT_RIGHT | DT_SINGLELINE);

}

如果想要更规范一些,可以添加一个菜单函数

菜单函数

//菜单页面
int Menu()
{
	int ButtonStateMark = 0;// 0-没有按钮被按下 | 1-开始按钮被按下 | 2-结束按钮被按下
	while(1)
	{
		//处理鼠标信息
		ExMessage msg = { 0 };				//创建信息变量
		peekmessage(&msg, EX_MOUSE);		//捕获鼠标信息		
		if (msg.message == WM_LBUTTONDOWN)	//左键被按下
		{
			//开始游戏
			if (msg.x >= 100 && msg.y >= 140 && msg.x <= 445 && msg.y <= 240)			
				ButtonStateMark = 1;			
			//退出游戏	
			if (msg.x >= 100 && msg.y >= 300 && msg.x <= 445 && msg.y <= 400)
				ButtonStateMark = 2;
		}
		BeginBatchDraw();
		//菜单背景
		IMAGE menu;
		loadimage(&menu, _T("./PictureFiles/menu.png"));
		putimage(0, 0, &menu);
		//开始按钮
		IMAGE StartButton;
		loadimage(&StartButton, _T("./PictureFiles/StartGameButton.png"));
		putimage(100, 140, &StartButton);
		//退出按钮
		IMAGE ExitButton;
		loadimage(&ExitButton, _T("./PictureFiles/ExitGameButton.png"));
		putimage(100, 300, &ExitButton);
		//点击周围出现线条
		setlinecolor(BLACK);
		setlinestyle(PS_DASHDOT);
		switch (ButtonStateMark)
		{
		case 1:
			rectangle(100, 140, 445, 240);
			break;
		case 2:
			rectangle(100, 300, 445, 400);
			break;
		}
		EndBatchDraw();
		//左击按钮,返回退出
		if (ButtonStateMark != 0)
		{
			Sleep(200);
			return ButtonStateMark;
		}
	}
}

 

至此,游戏流程就完成了,完整的程序代码在下面。

源代码

共分为三个文件:game.cpp文件、main.cpp文件、game.h文件

game.cpp文件

#include"game.h"

//创建信息变量
#define MineNumber 29			//雷的数量
struct SquareMessage
{ //方块信息
	int CoorX;	//X轴坐标
	int CoorY;	//Y轴坐标
	int	State;	//状况:0 - 非雷 / 1 - 是雷 
	int Mark;	//标志:0 - 正常 / 1 - 插旗 / 2 - 问号 / 3 - 被点开
}
Square[15][15];					//方块信息以数组形式存储
int FaceKind = 1;				//脸的表情:1-微笑/2-哭脸/3-潇洒
int FlagNumber = 0;				//旗子的数量
int MineX = 0, MineY = 0;		//踩雷的坐标
char MineNumberTip[20] = { 0 };	//雷数的数量提示
int GameTime = 0;				//游戏时间
char GameTimeTip[20] = { 0 };	//游戏时间提示
int FirstHit = 0;				//鼠标是否第一次点击(防止第一次踩雷)0-没点击/1-点击一次

//创建图片变量
IMAGE Background; //背景
IMAGE Normal;	  //未被点击
IMAGE Safe0;	  //周围0个雷
IMAGE Safe1;	  //周围1个雷
IMAGE Safe2;	  //周围2个雷
IMAGE Safe3;	  //周围3个雷
IMAGE Safe4;	  //周围4个雷
IMAGE Safe5;	  //周围5个雷
IMAGE Safe6;	  //周围6个雷
IMAGE Safe7;	  //周围7个雷
IMAGE Safe8;	  //周围8个雷
IMAGE Mine1;	  //黑雷
IMAGE Mine2;	  //红雷
IMAGE Flage1;	  //棋子
IMAGE Flage2;	  //问号
IMAGE Face1;	  //微笑
IMAGE Face2;	  //哭脸
IMAGE Face3;	  //潇洒

//菜单页面
int Menu()
{
	int ButtonStateMark = 0;// 0-没有按钮被按下 | 1-开始按钮被按下 | 2-结束按钮被按下
	while(1)
	{
		//处理鼠标信息
		ExMessage msg = { 0 };				//创建信息变量
		peekmessage(&msg, EX_MOUSE);		//捕获鼠标信息		
		if (msg.message == WM_LBUTTONDOWN)	//左键被按下
		{
			//开始游戏
			if (msg.x >= 100 && msg.y >= 140 && msg.x <= 445 && msg.y <= 240)			
				ButtonStateMark = 1;			
			//退出游戏	
			if (msg.x >= 100 && msg.y >= 300 && msg.x <= 445 && msg.y <= 400)
				ButtonStateMark = 2;
		}
		BeginBatchDraw();
		//菜单背景
		IMAGE menu;
		loadimage(&menu, _T("./PictureFiles/menu.png"));
		putimage(0, 0, &menu);
		//开始按钮
		IMAGE StartButton;
		loadimage(&StartButton, _T("./PictureFiles/StartGameButton.png"));
		putimage(100, 140, &StartButton);
		//退出按钮
		IMAGE ExitButton;
		loadimage(&ExitButton, _T("./PictureFiles/ExitGameButton.png"));
		putimage(100, 300, &ExitButton);
		//点击周围出现线条
		setlinecolor(BLACK);
		setlinestyle(PS_DASHDOT);
		switch (ButtonStateMark)
		{
		case 1:
			rectangle(100, 140, 445, 240);
			break;
		case 2:
			rectangle(100, 300, 445, 400);
			break;
		}
		EndBatchDraw();
		//左击按钮,返回退出
		if (ButtonStateMark != 0)
		{
			Sleep(200);
			return ButtonStateMark;
		}
	}
}
//游戏内容
void InitMessage()	//信息初始化
{
	//全局变量重置
	memset(Square, 0, sizeof(Square));
	FaceKind = 1;				
	FlagNumber = 0;				
	MineX = 0;
	MineY = 0;
	memset(MineNumberTip, ' ', 20);
	GameTime = 0;
	memset(GameTimeTip, ' ', 20);	
	FirstHit = 0;
	//初始化方块
	for (int x = 15, i = 1; i <= 13; x += 40, i++)
	{
		for (int y = 77, j = 1; j <= 13; y += 40, j++)
		{
			Square[i][j].CoorX = x;
			Square[i][j].CoorY = y;
			Square[i][j].State = 0;
			Square[i][j].Mark = 0;			
		}
	}
	//布置雷
	int MineNum = MineNumber;
	int x, y;
	while (1)
	{
		if (MineNum == 0)
			break;
		x = rand() % 13 + 1;
		y = rand() % 13 + 1;
		if (Square[x][y].State == 0)
		{
			Square[x][y].State = 1;
			MineNum--;
		}
	}
}
void LoadPicture() //批量加载图片
{
	loadimage(&Background, _T("./PictureFiles/background.png"));
	loadimage(&Normal, _T("./PictureFiles/normal.png"));
	loadimage(&Safe0, _T("./PictureFiles/safe0.png"));
	loadimage(&Safe1, _T("./PictureFiles/safe1.png"));
	loadimage(&Safe2, _T("./PictureFiles/safe2.png"));
	loadimage(&Safe3, _T("./PictureFiles/safe3.png"));
	loadimage(&Safe4, _T("./PictureFiles/safe4.png"));
	loadimage(&Safe5, _T("./PictureFiles/safe5.png"));
	loadimage(&Safe6, _T("./PictureFiles/safe6.png"));
	loadimage(&Safe7, _T("./PictureFiles/safe7.png"));
	loadimage(&Safe8, _T("./PictureFiles/safe8.png"));
	loadimage(&Mine1, _T("./PictureFiles/mine1.png"));
	loadimage(&Mine2, _T("./PictureFiles/mine2.png"));
	loadimage(&Flage1, _T("./PictureFiles/flag1.png"));
	loadimage(&Flage2, _T("./PictureFiles/flag2.png"));
	loadimage(&Face1, _T("./PictureFiles/face1.png"));
	loadimage(&Face2, _T("./PictureFiles/face2.png"));
	loadimage(&Face3, _T("./PictureFiles/face3.png"));
}
int MineAroundNumber(int X, int Y)//周围的雷数
{
	int mine_sum = 0;
	for (int i = -1; i < 2; i++)
	{
		for (int j = -1; j < 2; j++)
		{
			//跳过自身
			if (i == 0 && j == 0)
				continue;
			mine_sum += Square[X + i][Y + j].State; 
		}
	}
	return mine_sum;//非雷为0,雷为1,所以sum的值就是周围雷的数量
}
void BaseShow(int X, int Y) //不是雷的各种情况
{
	if (X > 0 && Y > 0 && X < 14 && Y < 14)	//限制范围
		switch (MineAroundNumber(X, Y))
		{
		case 0: 
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe0);
			Square[X][Y].Mark = 3;
			break;
		case 1:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe1);
			Square[X][Y].Mark = 3;
			break;
		case 2:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe2);
			Square[X][Y].Mark = 3;
			break;
		case 3:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe3);
			Square[X][Y].Mark = 3;
			break;
		case 4:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe4);
			Square[X][Y].Mark = 3;
			break;
		case 5:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe5);
			Square[X][Y].Mark = 3;
			break;
		case 6:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe6);
			Square[X][Y].Mark = 3;
			break;
		case 7:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe7);
			Square[X][Y].Mark = 3;
			break;
		case 8:
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe8);
			Square[X][Y].Mark = 3;
			break;
		}
}
void ShowMine(int X, int Y) //是雷的情况
{
	//展示所有雷
	for (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{
			if (Square[i][j].State == 1) 
			{
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Mine1);
				Square[i][j].Mark = 3; 
			}
		}
	}
}
void ShowTips() //绘制提示区域
{
	//设置字体规格	
	setbkcolor(BLACK);						//黑色背景
	settextcolor(RGB(255, 35, 35));			//红色字体
	settextstyle(40, 20, "DigifaceWide");	//高40宽20,"液晶数字"字体

	//绘制雷数提示区域 - 5个"身位"
	RECT words_area = { 20, 17, 135, 57 };
	sprintf(MineNumberTip, "%03d", MineNumber - FlagNumber);
	drawtext(MineNumberTip, &words_area, DT_LEFT | DT_SINGLELINE);

	//绘制时间提示区域 - 5个"身位"
	RECT time_area = { 410, 17, 525, 57 };
	sprintf(GameTimeTip, "%03d", GameTime);
	drawtext(GameTimeTip, &time_area, DT_RIGHT | DT_SINGLELINE);

}
int GraphyShow() //返回值:1-输/2-没输
{
	int tmp_return = 0;
	//绘制背景
	putimage(0, 0, &Background);
	//绘制游戏界面
	for (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{		
			switch(Square[i][j].Mark) 
			{
			case 0: //正常
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Normal); 
				break;
			case 1: //插旗
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Flage1); 
				break;
			case 2: //问号
				putimage(Square[i][j].CoorX, Square[i][j].CoorY, &Flage2);
				break;
			case 3: //鼠标左击
				if (Square[i][j].State == 0) //没踩到雷
					BaseShow(i, j);
				if (Square[i][j].State == 1) //踩到雷了
				{
					ShowMine(i, j); 
					putimage(Square[MineX][MineY].CoorX, Square[MineX][MineY].CoorY, &Mine2);
					tmp_return = 1; //返回值改变
					FaceKind = 2;	//脸表情改变
				}
				break;
			}
		}
	}
	//绘制脸的表情
	switch(FaceKind)
	{
	case 1:
		putimage(252, 16, &Face1);
		break;
	case 2:
		putimage(252, 16, &Face2);
		break;
	case 3:
		putimage(252, 16, &Face3);
		break;
	}	
	//绘制提示区域
	ShowTips();
	//函数返回 | 返回值:1-输/2-没输
	return tmp_return;
}
void MouseLeftHit(int X, int Y) //鼠标左击的情况
{
	if (Square[X][Y].State == 1) //是雷,直接跳出
		return;
	if (X > 0 && Y > 0 && X < 14 && Y < 14)	//限制范围
		switch (MineAroundNumber(X, Y))
		{
		case 0: //周围无雷,需要展开
			putimage(Square[X][Y].CoorX, Square[X][Y].CoorY, &Safe0);
			if (Square[X][Y].Mark == 0)
				Square[X][Y].Mark = 3;
			//递归展开
			for (int i = X - 1; i < X + 2; i++)
			{
				for (int j = Y - 1; j < Y + 2; j++)
				{
					if (Square[i][j].Mark == 0)
						MouseLeftHit(i, j);
				}
			}
			break;
		default:
			BaseShow(X, Y);
			break;
		}
}
void ReplyMouse() //处理鼠标信息
{
	ExMessage msg = { 0 };
	peekmessage(&msg, EX_MOUSE);//捕获鼠标信息
	int x = (msg.x - 15) / 40 + 1; //放置方块图片的x轴坐标
	int y = (msg.y - 77) / 40 + 1; //放置方块图片的y轴坐标
	if (Square[x][y].Mark != 3 && x > 0 && y > 0 && x < 14 && y < 14) //被点击之后就不能再被点击,限制点击范围
	{
		switch (msg.message)//筛选鼠标信息
		{

		case WM_LBUTTONDOWN: //左键按下

			if (Square[x][y].Mark == 0)
			{
				//防止第一次踩雷,如果不想要这个功能,直接把下面这条if语句注释掉即可
				if (FirstHit == 0)
				{
					switch (Square[x][y].State)
					{
					case 0://不是雷,直接跳出
						break;
					case 1://是雷,重置雷的坐标
						Square[x][y].State = 0;
						int remine_x, remine_y;
						while (1)
						{
							remine_x = rand() % 13 + 1;
							remine_y = rand() % 13 + 1;
							if (Square[x][y].State == 0 && remine_x != x && remine_y != y)
							{
								Square[remine_x][remine_y].State = 1;
								break;//这个break跳出的是while循环
							}
						}
						break;//这个break跳出的是switch
					}
					FirstHit = 1;//取消第一次点击的状态
				}
				Square[x][y].Mark = 3;
				//设置踩雷坐标,踩雷坐标爆红雷
				if (Square[x][y].State == 1)
				{
					MineX = x;
					MineY = y;					
				}
				//周围0个雷的情况
				if (MineAroundNumber(x, y) == 0)
				{
					MouseLeftHit(x, y);
				}
			}
			break;

		case WM_RBUTTONDOWN: //右键按下
			switch (Square[x][y].Mark)
			{
			case 0://正常 -> 旗子	
				if (FlagNumber < MineNumber) //保证旗的数量小于雷的数量
				{
					Square[x][y].Mark = 1;
					FlagNumber++;
				}
				break;
			case 1://旗子 -> 问号
				Square[x][y].Mark = 2;
				FlagNumber--;
				break;
			case 2://问号 -> 正常
				Square[x][y].Mark = 0;
				break;
			}
			break;
		default:
			break;
		}
	}
}
int IsWin()//判断输赢:1 - 赢 / 2 - 没赢
{
	int RestSquare = 0;
	for (int i = 1; i < 14; i++)
	{
		for (int j = 1; j < 14; j++)
		{
			if (Square[i][j].Mark != 3)
				RestSquare++;
		}
	}
	//理论上讲,当所剩的方块数等于雷数时,剩下的就都是雷
	if (RestSquare == MineNumber)//赢了
	{
		//把所有雷插棋子 - 胜利标志
		for (int i = 1; i < 14; i++)
		{
			for (int j = 1; j < 14; j++)
			{
				if (Square[i][j].State == 1)
					Square[i][j].Mark = 1;
			}
		}
		FlagNumber = MineNumber;//旗的数量等于雷的数量
		FaceKind = 3; //脸表情改变
		return 1;
	}
	return 0;
}
void Game()
{
	int begin = clock();	//程序开始时间
	int jud_lose = 0;		//判断输变量
	int	jud_win = 0;		//判断赢变量
	LoadPicture();			//批量加载图片
	InitMessage();			//初始化信息
	//进入扫雷环节
	while(1)				
	{
		jud_win = IsWin();				//判断是否赢了
		BeginBatchDraw();				//开始绘制图形
		jud_lose = GraphyShow();		//展示画面并判断是否输了
		EndBatchDraw();					//结束图形绘制
		ReplyMouse();					//处理鼠标消息
		int end = clock();				//当前时间时间
		GameTime = (end - begin) / 1000;//计数器时间
		if (jud_lose == 1)	break;		//踩雷跳出 - 输了			
		if (jud_win == 1)	break;		//排雷成功 - 赢了			
	}
}

main.cpp文件

#include"game.h"

int main()
{
	srand(time(NULL));//预设种子 - 布雷用
	initgraph(545, 610);
	int exit_reconfirm = 0;
	while(1)
	{
		switch (Menu())//加载菜单
		{
		case 1://左击开始键
			Game();
			MessageBox(NULL, "游戏结束", " ", MB_OK);
			break;
		case 2://左击退出键
			exit_reconfirm = MessageBox(NULL, "是否确定退出扫雷程序?", " ", MB_YESNO | MB_ICONQUESTION);
			switch (exit_reconfirm)
			{
			case IDYES:
				exit(0);
				break;
			}
			break;
		}
	}
	return 0;
}

game.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
#include<easyx.h>
#include<graphics.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>

int Menu();	//声明menu函数
void Game();//声明Game函数

最后

本文如果有哪里有缺陷或者待优化的地方,欢迎大家在评论区指出来。让我们一起共同学习,共同进步。

 

 

到了这里,关于C语言实战 - 扫雷(图形界面-鼠标操作)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux操作系统设置图形化界面及目录和文件管理常用命令

    目录 1.安装图形化界面  2.开机启动图形化界面 dos界面与图形化界面切换快捷键 3.Windows与Linux文件系统的差别  4.Linux文件系统常用命令  5.使用pwd命令显示工作目录路径 6.绝对路径和相对路径  7.使用ls命令列出目录和文件信息 Linux默认情况下是不会安装图形界面的,所以需要

    2024年02月05日
    浏览(45)
  • C语言实战 - 贪吃蛇(图形界面)

    由于本人精力有限,暂时先把素材和代码放上,等以后有空再补教程。 目录 效果预览 准备工作 EasyX图形库 音频素材 代码编写 Transfer.h文件 game.cpp文件 main.c文件 先来看一下最终成品效果 贪吃蛇图形界面 这个贪吃蛇项目是基于EasyX图形库写的,所以需要安装一个easy图像库,

    2024年02月05日
    浏览(34)
  • 【Python自然语言处理+tkinter图形化界面】实现智能医疗客服问答机器人实战(附源码、数据集、演示 超详细)

    需要源码和数据集请点赞关注收藏后评论区留言私信~~~ QA问答是Question-and-Answer的缩写,根据用户提出的问题检索答案,并用用户可以理解的自然语言回答用户,问答型客服注重一问一答处理,侧重知识的推理。 从应用领域视角,可将问答系统分为限定域问答系统和开放域问

    2023年04月12日
    浏览(44)
  • C语言实战——扫雷游戏

    1.1扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏 游戏可以通过菜单实现继续玩或者退出游戏 扫雷的棋盘是9*9的格子 默认随机布置10个雷 可以排查雷 如果位置不是雷,就显示周围有几个雷 如果位置是雷,就炸死游戏结束 把除10个雷之外的所有⾮雷都找出来,排雷成功

    2024年03月15日
    浏览(31)
  • ubuntu18.04图形界面卡死,鼠标键盘失灵, 通过MAC共享网络给Ubuntu解决!

    背景 故事的起因是pip install tensorflow-gpu, 出去上个厕所的功夫,回来页面就卡死了,重启了一下,死的更彻底了,在用户登陆页面鼠标和键盘都失灵了,根本无法输入密码然后进入系统。接下来讲下处理办法。 搜索出来很多的方案都是说可以 Ctrl+Alt+F1~F6 中的任意一个, 切换

    2024年04月15日
    浏览(35)
  • 【Java AWT 图形界面编程】使用鼠标滚轮缩放 Canvas 画布中绘制的背景图像 ( 绘制超大图像 + 鼠标拖动 + 鼠标滚轮缩放 + 以当前鼠标指针位置为缩放中心 示例 )

    鼠标指针指向界面中的 Canvas 画布某个位置 , Canvas 画布中绘制着一张超大图片 , 以该位置为中心 , 滑动鼠标滚轮时进行缩放 ; 使用鼠标滚轮缩放后 , 在 Canvas 中绘制的图片的尺寸肯定是放大或者缩小了 , 尺寸发生了改变 ; 图片缩放时 , 鼠标指针指向一个位置 , 该位置对应着一

    2024年02月15日
    浏览(26)
  • C语言编写图形界面

    使用的是VSCode + MinGW; VSCode配置C语言的环境就不讲了,具体可以看一下这篇文章:VSCode配置C语言环境 先说一下本篇文章编译的条件吧。 本篇文章需要编译器链接Windows GDI32库,所以如果你用的是VSCode+MinGW,就需要修改task.json文件,使其在链接的时候,链接Window GDI32库。 修改

    2024年02月11日
    浏览(24)
  • C语言之详解数组【附三子棋和扫雷游戏实战】

    1、数组的创建 数组是一组相同类型元素的集合。 数组的创建方式: 首先我们就来看看数组如何创建~~ 对于整型、字符型、浮点型的数据可以创建 [] 内的数字便是这个数组的大小,表示这个数组中可以存放多少元素。 除了数字也可以是一个表达式放里面 虽然指定数组大小可

    2024年02月03日
    浏览(26)
  • Git常见命令行操作和IDEA图形化界面操作

    在安装完Git以后需要设置用户和签名,至于为什么要设置用户签名可以看一下这篇文章【学了就忘】Git基础 — 11.配置Git用户签名说明 - 简书 (jianshu.com) 基本语法: git config --global user.name 用户名 git config --global user.email 邮箱 查看是否设置成功: 方式一:在git控制台中输入命令

    2024年04月29日
    浏览(25)
  • WPF+Halcon 培训项目实战 完结(13):HS 鼠标绘制图形

    为了更好地去学习WPF+Halcon,我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方的Up主的提供的教程。这里只做笔记分享,想要源码或者教学视频可以和他联系一下。 微软系列技术教程 WPF 年度公益课程 Halcon开发 CSDN博客专栏 个人学习的Gitee 项目地址

    2024年02月03日
    浏览(76)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包