基于easyx库的GUI扫雷项目

基于easyx库的GUI扫雷项目


0.观前提醒

本程序为本人原创,没有参考任何其他资料或博客,图片素材基于原版扫雷图片

制作带GUI的扫雷游戏项目主要是为了练习自己的C语言基础和学习使用图形化库,我已经将此项目上传了,欢迎大家点击下面链接直接下载。写这篇博客一是为了记录自己曾经练习写代码的过程,二是分享给更多感兴趣的朋友。我将项目捏碎了给大家讲解了出来,相信只要具有一定的耐心,即使基础很差的朋友,也能随便写出来!

源码里的游戏本体有很多步骤可以放入函数进行执行的,这样代码看起来更加简洁。由于我写这个游戏的时候在高铁上,有些不方便,所以我并没有进行功能合并,有兴趣的朋友,自己new个函数将功能合并吧

源码地址:基于easyx图形库做的GUI版扫雷


1.扫雷游戏项目效果展示

请添加图片描述


2.扫雷游戏项目基本信息

项目名称:扫雷

开发语言:C语言

开发作者:牟建波

开发环境:Visual Studio2019、EasyX图形库、Windows

开发时间:2023-03-12


3.扫雷游戏项目设计思路

请添加图片描述


4.扫雷游戏实现原理

4.1 头文件解析

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> //SetConsoleTextAttribute()函数头文件
#include <graphics.h> //easyx图形库
#include <time.h> //srand()函数头文件


 头文件使用解释:																									   
 	1.使用#include<windows.h>头文件,是为了使用其中的SetConsoleTextAttribute()进行字体颜色修改,方便后期给雷标记颜色区分
 	2.使用#include<graphics.h>头文件,是为了使用eaxys函数库,进行鼠标交互和图形绘制								    
 	3.使用#include<time.h>头文件,是为了使用srand()函数,通过时间播种生成随机数去对应雷
 

4.2 素材解析

  • 扫雷项目中的素材,全部来自官方原版游戏截图和自己画的,我把素材全放在pic文件夹下了,如下图2中各种表情、方块、数字、地雷

  • 在项目中,我们会使用easyx图形库对图像进行提取使用,所以尽量将素材和游戏放在同一目录下,避免一些奇怪的错误

请添加图片描述

请添加图片描述


4.3 变量与矩形布局解析

//三重矩阵布局:我的想法是用三个16*16的矩阵,分别用来表示雷区、地雷的数量、方块的状态,然后将他们进行一张棋盘重合

//这样的布局,可以降低开发难度,调用时也更加不易弄晕矩阵
int Minefield_matrix[16][16] = { 0 };//雷区矩阵
int Mine_Count_matrix[16][16] = { 0 };//地雷计数矩阵
int Block_Status_matrix[16][16] = { 0 };//方块状态矩阵

//公用循环变量:
//这里设定为全局变量,是因为后续会使用很多次,我懒得在里面加int,而且C89标准是不能在for里定义int i的
//所以为了照顾不同编译标准的读者,这里我将其定义为全局变量
int i = 0;
int j = 0;

//中转地雷数组:用于临时中转存储地雷
int Transfer_Mine_matrix[40] = { 0 };

//方块状态flag
int IsMine_flag = 0;//0表示非雷,1表示雷
//游戏胜利flag
int Success_flag = 0;//0表示失败,1表示成功

4.4 随机雷的实现

//生成随机雷
	//思路:
	//1.雷随机生成 16*16=256 从左到右 1~256
	//2.随机生成40个[1,256]范围的随机数,通过对矩阵的位置,将对应的编号设置为雷
	//3.为防止生成的随机数有重复的,每生成一个随机数,就将这个数存入Transfer_Mine_matrix[40]这个中转地雷数组中
	//4.之后生成的随机数需与数组中的元素进行比较,若重复则不会保存在数组中

	srand((unsigned int)time(0));//通过时间播种生成随机数用于表示雷
	for (int Mine_Number = 0; Mine_Number < 40; Mine_Number++)
	{
		int Correct_Number_mine = 0;//合格的雷的随机数字
		Correct_Number_mine = rand() % 256 + 1; //生成1~256范围内随机数

		if (Correct_Number_mine < 0 || Correct_Number_mine > 256)//随机数不符合要求
		{
			Mine_Number--;
		}
		else//随机数符合要求
		{
			for (i = 0; i < 40; i++)
			{
				if (Transfer_Mine_matrix[i] == Correct_Number_mine)//如果合格的雷在中转地雷数组中存在
				{
					Mine_Number--;//不符合的雷去掉,雷数-1
					break;
				}
				if (Transfer_Mine_matrix[i] == 0)//如果合格的雷没有出现过,则存入中转地雷数组中 中转地雷数组初始化是0
				{
					Transfer_Mine_matrix[i] = Correct_Number_mine;
					break;
				}
			}
		}
	}

	//测试雷的位置
	for (int i = 0; i < 40; i++)
	{
		printf("%d ", Transfer_Mine_matrix[i]);
	}

随机雷生成测试结果图:

请添加图片描述


4.5 生成雷位置矩阵的实现

//生成雷的矩阵
//思路:通过Transfer_Mine_matrix中转地雷数组中的随机数,通过对应关系写入雷区矩阵Minefield_matrix[16][16]中,雷的位置表示为1,非雷的位置表示为0
	for (int length = 0; length < 40; length++)
	{
		int count = 0;//计数
		for (i = 0; i < 16; i++)
		{
			for (j = 0; j < 16; j++)
			{
				count++;
				if (Transfer_Mine_matrix[length] == count)
				{
					Minefield_matrix[i][j] = 1;
					IsMine_flag = 1;//1表示雷,0表示非雷
					break;
				}
			}
			if (IsMine_flag == 1)
			{
				break;
			}
		}
		IsMine_flag = 0;//初始化,以免影响下次循环
		count = 0;//初始化,以免影响下次循环
	}
	//雷矩阵调试
	for (int i = 0; i < 16; i++)
	{
		for (int j = 0; j < 16; j++)
		{
			if (Minefield_matrix[i][j] == 1)
			{
				colour(12);//是雷标记为红色
			}
			else
			{
				colour(15);//不是雷标记为白色
			}
			printf("%d ", Minefield_matrix[i][j]);//打印雷矩阵
		}
		colour(15);//让后续字体染色显示不为红色
		printf("\n");
	}
	system("pause");

生成雷位置矩阵调试结果图:

请添加图片描述


4.6 生成雷数矩阵的实现

雷数矩阵处理的想法:

请添加图片描述

  • 上图是我们对于一个矩阵四周遍历的常规过程,可以看出,我们进行遍历都是一边一边的进行遍历,由外向内进行遍历。当我们处理边界的是否就非常头疼了,这时我们可以使用循环不变量法
  • 循环不变量法:比如上图,就是我们在考虑边界问题的时候是非常头疼的,如果我们每边都考虑全部遍历完,就会出现很多边界考虑问题,但是如果我们把遍历方式都设置成一种,那么就让边界问题变成了一种情况,也就是上图的只遍历开始到最后一位的前一位,让下一次循环遍历剩下的元素,从而叫做循环不变量法
  • 但是我们在处理雷数矩阵的时候,要处理的时一个元素四周,所以直接使用循环不变量法不合适的。我们可以使用它的思想,进行雷数的计算
  • 这里我的想法是,先处理矩阵的四个角,然后处理去角的四边,那么我们就剩下了一个15*15的正方形了,处理正方形我们就有了一个通用的方法进行计算,如下图

请添加图片描述

//通过对matrix矩阵每个元素的周围八个元素计算雷数,并将雷数存入number[16][16]矩阵中
//number矩阵中1~8表示雷数,9表示该元素为雷
//通过对Minefield_matrix雷区矩阵每个元素的周围八个元素进行计算雷数,并将雷存入Mine_Count_matrix地雷计数矩阵中
//Mine_Count_martix地雷计数矩阵中,1~8表示周围雷数量,9表示该元素为雷
for (i = 0; i < 16; i++)
{
	int count = 0;
	for (j = 0; j < 16; j++)
	{
		count = 0;
		if (Minefield_matrix[i][j] == 1)//如果是雷设为9
		{
			Mine_Count_matrix[i][j] = 9;
			continue;
		}
		else
		{
			//思路:
			//1.先处理16*16矩阵的,四个边角,坐标为(0,0)、(0,15)、(15,0)、(15,15)
			//2.再处理矩形的四边,在处理过程把矩阵看成一个17*17的矩阵,这样是为了方便计数设计,数组溢出并不会有什么问题,因为我们没有使用它
			//3.最后我们会剩下一个规整15*15的正方形,然后处理每个方块的八个方向就可以了
			if (i == 0 && j == 0)//左上角
			{
				if (Minefield_matrix[0][1] == 1)//左上角方块右侧
					count++;
				if (Minefield_matrix[1][0] == 1)//左上角方块下侧
					count++;
				if (Minefield_matrix[1][1] == 1)//左上角方块斜右下侧
					count++;
			}
			else if (i == 0 && j == 15)//右上角
			{
				if (Minefield_matrix[0][14] == 1)//右上角方块左侧
					count++;
				if (Minefield_matrix[1][15] == 1)//右上角方块下侧
					count++;
				if (Minefield_matrix[1][14] == 1)//右上角方块斜左下侧
					count++;
			}
			else if (i == 15 && j == 0)//左下角
			{
				if (Minefield_matrix[15][1] == 1)//左下角方块右侧
					count++;
				if (Minefield_matrix[14][0] == 1)//左下角方块侧
					count++;
				if (Minefield_matrix[14][1] == 1)//左下角方块斜右上侧
					count++;
			}
			else if (i == 15 && j == 15)//右下角
			{
				if (Minefield_matrix[15][14] == 1)//右下角方块左侧
					count++;
				if (Minefield_matrix[14][15] == 1)//右下角方块上侧
					count++;
				if (Minefield_matrix[14][14] == 1)//右小角方块斜左上侧
					count++;
			}
			else if (i == 0)//处理顶部,第二个方块开始
			{
				if (Minefield_matrix[i][j - 1] == 1)//处理左侧
					count++;
				if (Minefield_matrix[i][j + 1] == 1)//处理右侧
					count++;
				if (Minefield_matrix[i + 1][j] == 1)//处理下侧
					count++;
				if (Minefield_matrix[i + 1][j - 1] == 1)//处理左下侧
					count++;
				if (Minefield_matrix[i + 1][j + 1] == 1)//处理右下侧
					count++;
			}
			else if (j == 15)//处理右部,第二个方块开始
			{
				if (Minefield_matrix[i - 1][j] == 1)//处理上侧
					count++;
				if (Minefield_matrix[i + 1][j] == 1)//处理右侧
					count++;
				if (Minefield_matrix[i][j - 1] == 1)//处理左侧
					count++;
				if (Minefield_matrix[i - 1][j - 1] == 1)//处理左上侧
					count++;
				if (Minefield_matrix[i + 1][j - 1] == 1)//处理左下侧
					count++;
			}
			else if (i == 15)//处理底部,第二个方块开始
			{
				if (Minefield_matrix[i][j - 1] == 1)//处理左侧
					count++;
				if (Minefield_matrix[i][j + 1] == 1)//处理右侧
					count++;
				if (Minefield_matrix[i - 1][j] == 1)//处理上侧
					count++;
				if (Minefield_matrix[i - 1][j - 1] == 1)//处理左上侧
					count++;
				if (Minefield_matrix[i - 1][j + 1] == 1)//处理右上侧
					count++;
			}
			else if (j == 0)//处理左部,第二个方块开始
			{
				if (Minefield_matrix[i - 1][j] == 1)//处理上侧
					count++;
				if (Minefield_matrix[i + 1][j] == 1)//处理下侧
					count++;
				if (Minefield_matrix[i][j + 1] == 1)//处理右侧
					count++;
				if (Minefield_matrix[i - 1][j + 1] == 1)//处理右上侧
					count++;
				if (Minefield_matrix[i + 1][j + 1] == 1)//处理右下侧
					count++;
			}
			else//处理剩下的规整15*15正方形的八个方向
			{
				if (Minefield_matrix[i - 1][j - 1] == 1)//左上侧
					count++;
				if (Minefield_matrix[i - 1][j] == 1)//上侧
					count++;
				if (Minefield_matrix[i - 1][j + 1] == 1)//右上侧
					count++;
				if (Minefield_matrix[i][j - 1] == 1)//左侧
					count++;
				if (Minefield_matrix[i][j + 1] == 1)//右侧
					count++;
				if (Minefield_matrix[i + 1][j - 1] == 1)//左下侧
					count++;
				if (Minefield_matrix[i + 1][j] == 1)//下侧
					count++;
				if (Minefield_matrix[i + 1][j + 1] == 1)//右下侧
					count++;
			}
		}
		Mine_Count_matrix[i][j] = count;
	}
}

	//数字阵调试
	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			if (Mine_Count_matrix[i][j] == 9)
			{
				colour(12);//red
			}
			else
			{
				colour(15);//white
			}
			printf("%d ", Mine_Count_matrix[i][j]);
		}
		colour(15);
		printf("\n");
	}

雷数矩阵数字阵调试结果图:

请添加图片描述


4.7 绘制图像过程

请添加图片描述

我们在绘制函数initgraph哪里打个断点,让我们继续往后执行,看下图像绘制的过程

  • 通过上图可以很直观的看到,这个图像的绘制时先导入图像,然后先绘制一个(735,883)的长方形,然后绘制中间的笑脸,最后根据我们设定的方块间隔,一块一块的绘制整个图像
//载入素材
	initgraph(735, 883);//测试(745,845)刚好放下16*16的矩形

	//IMAGE 定义一个图片名
	//游戏背景
	IMAGE cube;//方块图标
	IMAGE cube_trigger;//方块触发器:变色
	IMAGE background;//背景

	//游戏符号
	IMAGE mine_eliminate;//地雷图标
	IMAGE mine_trigger;//地雷触发器:变色
	IMAGE mine_flag;//插旗图标
	IMAGE question;//问号图标
	IMAGE question_trigger;//问号触发器:变色

	//游戏脸部
	IMAGE smile;//笑脸图标
	IMAGE smile_trigger;//笑脸触发器:变脸
	IMAGE caution;//谨慎图标
	IMAGE dead;//死亡图标
	IMAGE dead_trigger;//死亡触发器:变脸

	//游戏雷数:雷数0-8颜色,9表示本身为雷
	IMAGE mine_0;//雷数0图标
	IMAGE mine_1;//雷数1图标
	IMAGE mine_2;//雷数2图标
	IMAGE mine_3;//雷数3图标
	IMAGE mine_4;//雷数4图标
	IMAGE mine_5;//雷数5图标
	IMAGE mine_6;//雷数6图标
	IMAGE mine_7;//雷数7图标
	IMAGE mine_8;//雷数8图标
	IMAGE mine_9;//雷数9图标

	//loadimage 从图片中获取图像
	loadimage(&cube, "./pic/cube.jpg");//加载方块图标
	loadimage(&cube_trigger, "./pic/cube_trigger.jpg");//加载方块触发器图标
	loadimage(&background, "./pic/background.jpg");//加载背景图标
	loadimage(&mine_eliminate, "./pic/mine_eliminate.jpg");//加载地雷图标
	loadimage(&mine_trigger, "./pic/mine_trigger.jpg");//加载地雷触发器图标
	loadimage(&mine_flag, "./pic/mine_flag.jpg");//加载插旗图标
	loadimage(&question, "./pic/question.jpg");//加载问号图标
	loadimage(&question_trigger, "./pic/question_trigger.jpg");//加载问号触发器图标
	loadimage(&smile, "./pic/smile.jpg");//加载笑脸图标
	loadimage(&smile_trigger, "./pic/smile_trigger.jpg");//加载笑脸触发器图标
	loadimage(&caution, "./pic/caution.jpg");//加载谨慎图标
	loadimage(&dead, "./pic/dead.jpg");//加载死亡图标
	loadimage(&dead_trigger, "./pic/dead_trigger.jpg");//加载死亡触发器图标
	loadimage(&mine_0, "./pic/mine_0.jpg");//加载雷数0图标
	loadimage(&mine_1, "./pic/mine_1.jpg");//加载雷数1图标
	loadimage(&mine_2, "./pic/mine_2.jpg");//加载雷数2图标
	loadimage(&mine_3, "./pic/mine_3.jpg");//加载雷数3图标
	loadimage(&mine_4, "./pic/mine_4.jpg");//加载雷数4图标
	loadimage(&mine_5, "./pic/mine_5.jpg");//加载雷数5图标
	loadimage(&mine_6, "./pic/mine_6.jpg");//加载雷数6图标
	loadimage(&mine_7, "./pic/mine_7.jpg");//加载雷数7图标
	loadimage(&mine_8, "./pic/mine_8.jpg");//加载雷数8图标

	//贴图
	//putimage 绘制图片到屏幕,图片左上角坐标为(0,0)
	putimage(0, 0, &background);
	putimage(320, 17, &smile);


	//绘制扫雷的矩阵,循环中i、j同时对应Minefield_matrix雷区数组、Mine_Count_matrix地雷计数数组、 Block_Status_matrix方块状态矩阵
	//这样做的好处是方便后面的鼠标机交互坐标检测
	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			putimage(15 + 45 * j, 118 + 45 * i, &cube);
		}
	}


4.8 输赢检测规则

//在游戏逻辑大循环前进行输赢检测,通过遍历Block_Status_matrix矩阵中的值进行计数,若元素值4的数量到达216即判定为胜利
	/*
	|-----------------------------------------------------------------------------------------------|
	|	Block_Status_matrix矩阵内元素值的含义:													     |
	|		0 = 未触发的元素,相当于普通白方块														      |
	|																							    |
	|		1 = 鼠标左键点击某元素后与其相邻的number矩阵中值为0的,但还未检测周边元素的元素			          |
	|																							    |
	|		2 = 鼠标左键点击某元素后触发拓展后与number矩阵中值为0的元素相邻的number矩阵中值为1~8的元素	         |
	|																							    |
	|		3 = 鼠标左键点击某元素后与其相邻的number矩阵中值为0的,且已检测周边元素的元素				      |
	|																							    |
	|		4 = 界面上已被贴图位置(空贴图,及1~8数字贴图)所对应的元素								       |
	|																							    |
	|		5 = 鼠标右键点击某元素后,该元素位置被贴为红旗的元素										    |
	|																							    |
	|		6 = 鼠标右键点击某元素后,该元素位置被贴为问号的元素										    |
	|-----------------------------------------------------------------------------------------------|
	*/

	while (1)
	{
		int Block_Status4_Number = 0;//Block_Status4_Number矩阵内元素值4的贴图数量
		for (i = 0; i < 16; i++)
		{
			for (j = 0; j < 16; j++)
			{
				if (Block_Status_matrix[i][j] == 5)//如果贴上红旗
					if (Minefield_matrix[i][j] == 1)//如果红旗下是雷
						Block_Status4_Number++;
			}
		}

		if (Block_Status4_Number == 40)//40个雷都给正确排出来了
		{
			Success_flag = 1;//游戏成功旗帜
			break;
		}
		Block_Status4_Number = 0;//排除特殊情况,初始化为0开始正常情况

		//正常情况:界面贴图216游戏结束,还有40个是雷
		for (i = 0; i < 16; i++)
		{
			for (j = 0; j < 16; j++)
			{
				if (Block_Status_matrix[i][j] == 4)
					Block_Status4_Number++;
			}
		}
		if (Block_Status4_Number == 216)
		{
			Success_flag = 1;//游戏成功旗帜
			break;
		}
		Block_Status4_Number = 0;

4.9 检测相邻空元素的方法

//检测相邻空元素函数解释:																							   
//1.此处运用递归方法,实现了对触发空元素的所有相邻空元素的检测,检测完成的空元素,status矩阵中的状态值会被调整为3;				 
//2.被检测出但还未检测其本身的元素状态值将会被设为1,当status矩阵中不存在值为1的元素时,即表示所有相邻空元素已检测完成,递归停止	
    
//检测相邻空元素函数:Detects_adjacent_empty_elements(int, int, int[16][16], int[16][16])
void Detects_adjacent_empty_elements(int i, int j, int number[16][16], int status[16][16])
{
	//1.检测初始状态为0、没有被检测的0、没有被贴红旗的0、没有被贴为问号的0
	//2.将符合条件的元素状态变更为1,即此元素已经被检测过的0
	if (i != 0)
		if (number[i - 1][j] == 0 && status[i - 1][j] != 3 && status[i - 1][j] != 5 && status[i - 1][j] != 6)
			status[i - 1][j] = 1;
	if (i != 15)
		if (number[i + 1][j] == 0 && status[i + 1][j] != 3 && status[i + 1][j] != 5 && status[i + 1][j] != 6)
			status[i + 1][j] = 1;
	if (j != 0)
		if (number[i][j - 1] == 0 && status[i][j - 1] != 3 && status[i][j - 1] != 5 && status[i][j - 1] != 6)
			status[i][j - 1] = 1;
	if (j != 15)
		if (number[i][j + 1] == 0 && status[i][j + 1] != 3 && status[i][j + 1] != 5 && status[i][j + 1] != 6)
			status[i][j + 1] = 1;
	//将此元素调整为3,即表示:此元素周围元素都被检测了
	status[i][j] = 3;//调整状态为3

	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			if (status[i][j] == 1)
				Detects_adjacent_empty_elements(i, j, number, status);
		}
	}
}

4.10 扩展空元素区域的方法

//扩展空元素区域函数解释:																						
//1.经上一步检测出的空元素后,要将其相邻的数值为1~8的元素也贴上图,因此将status矩阵中值为3的元素的周围的值		
//2.不为3的元素的状态值变为2,即表示即将要被贴图的不为空的元素															
 
//扩展空元素区域函数:Expand_empty_element_area(int[16][16],int[16][16])
void Expand_empty_element_area(int status[16][16])
{
	int i = 0;
	int j = 0;

	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			if (status[i][j] == 3)
			{
				//1.检测状态不为3、不为5、不为6的元素
				//2.将符合条件的元素状态更改为2
				if (i != 0)
					if (status[i - 1][j] != 3 && status[i - 1][j] != 5 && status[i - 1][j] != 6)
						status[i - 1][j] = 2;
				if (i != 15)
					if (status[i + 1][j] != 3 && status[i + 1][j] != 5 && status[i + 1][j] != 6)
						status[i + 1][j] = 2;
				if (j != 0)
					if (status[i][j - 1] != 3 && status[i][j - 1] != 5 && status[i][j - 1] != 6)
						status[i][j - 1] = 2;
				if (j != 15)
					if (status[i][j + 1] != 3 && status[i][j + 1] != 5 && status[i][j + 1] != 6)
						status[i][j + 1] = 2;
				if (i != 0 && j != 0)
					if (status[i - 1][j - 1] != 3 && status[i - 1][j - 1] != 5 && status[i - 1][j - 1] != 6)
						status[i - 1][j - 1] = 2;
				if (i != 0 && j != 15)
					if (status[i - 1][j + 1] != 3 && status[i - 1][j + 1] != 5 && status[i - 1][j + 1] != 6)
						status[i - 1][j + 1] = 2;
				if (i != 15 && j != 0)
					if (status[i + 1][j - 1] != 3 && status[i + 1][j - 1] != 5 && status[i + 1][j - 1] != 6)
						status[i + 1][j - 1] = 2;
				if (i != 15 && j != 15)
					if (status[i + 1][j + 1] != 3 && status[i + 1][j + 1] != 5 && status[i + 1][j + 1] != 6)
						status[i + 1][j + 1] = 2;
			}
		}
	}
}

4.11 绘制状态贴图的方法

状态绘图函数解释:																								
1.经过上两步的检测,所有要被贴图的元素状态已被设为23,因此仅需遍历status矩阵					                             
2.找到状态为23的元素,在对照number矩阵中的值贴上空以及1~8的图片素材							

//状态绘图函数:State_mapping(int[16][16], int[16][16], IMAGE, IMAGE, IMAGE, IMAGE, IMAGE, IMAGE, IMAGE, IMAGE, IMAGE)
void State_mapping(int Mine_Count_matrix[16][16], int Block_Status_matrix[16][16], IMAGE ZERO, IMAGE ONE, IMAGE TWO, IMAGE THREE, IMAGE FOUR, IMAGE FIVE, IMAGE SIX, IMAGE SEVEN, IMAGE EIGHT)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			//满足状态2和3的元素,根据状态码进行贴图处理
			if (Block_Status_matrix[i][j] == 2 || Block_Status_matrix[i][j] == 3)
			{
				switch (Mine_Count_matrix[i][j])
				{
					case 0: putimage(15 + 45 * j, 118 + 45 * i, &ZERO); break;
					case 1: putimage(15 + 45 * j, 118 + 45 * i, &ONE); break;
					case 2: putimage(15 + 45 * j, 118 + 45 * i, &TWO); break;
					case 3: putimage(15 + 45 * j, 118 + 45 * i, &THREE); break;
					case 4: putimage(15 + 45 * j, 118 + 45 * i, &FOUR); break;
					case 5: putimage(15 + 45 * j, 118 + 45 * i, &FIVE); break;
					case 6: putimage(15 + 45 * j, 118 + 45 * i, &SIX); break;
					case 7: putimage(15 + 45 * j, 118 + 45 * i, &SEVEN); break;
					case 8: putimage(15 + 45 * j, 118 + 45 * i, &EIGHT); break;
				}
				Block_Status_matrix[i][j] == 4;
			}
		}
	}
}

4.12 展示地雷的方法

//展示地雷函数解释:																							
//1.游戏失败后,需向玩家展示所有地雷的位置,根据雷位置所在元素进行贴图即可													

//展示地雷:Display_mine(int matrix[16][16], IMAGE mine_eliminate)
void Display_mine(int matrix[16][16], IMAGE mine_eliminate)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			if (matrix[i][j] == 1)
			{
				putimage(15 + 45 * j, 118 + 45 * i, &mine_eliminate);
			}
		}
	}
}

4.13 鼠标交互逻辑解析

//鼠标交互逻辑
MOUSEMSG mousemsg = GetMouseMsg();
//解释:
//1.MOUSEMSG:定义结构体鼠标
//2.GetMouseMsg():获取当前鼠标信息
for (i = 0; i < 16; i++)
{
	for (j = 0; j < 16; j++)
	{
		if (15 + 45 * j <= mousemsg.x && mousemsg.x <= 15 + 45 * j + 42 && 118 + 45 * i <= mousemsg.y
			&& mousemsg.y <= 118 + 45 * i + 42 && (Block_Status_matrix[i][j] == 0
            || Block_Status_matrix[i][j] == 5|| Block_Status_matrix[i][j] == 6))
		{
			if (mousemsg.uMsg == WM_LBUTTONDOWN && Block_Status_matrix[i][j] != 5)//如果左键点击,并且没贴红旗
			{
				putimage(320, 17, &caution);//界面上方笑脸图标切换为谨慎图标 

				if(Mine_Count_matrix[i][j] == 9)//如果点击的元素为雷
				{
					putimage(15 + 45 * j, 118 + 45 * i, &mine_trigger);//绘制雷的图片 
					Success_flag = 0;//游戏失败旗帜
					IsMine_flag = 1;//是雷旗帜
					break;
				}
				else if (Mine_Count_matrix[i][j] != 0)//如果点击的元素不为雷而为空
				{

					switch (Mine_Count_matrix[i][j])//按照相邻元素中雷的数量进行贴图
					{
						case 1: putimage(15 + 45 * j, 118 + 45 * i, &mine_1); break;
						case 2: putimage(15 + 45 * j, 118 + 45 * i, &mine_2); break;
						case 3: putimage(15 + 45 * j, 118 + 45 * i, &mine_3); break;
						case 4: putimage(15 + 45 * j, 118 + 45 * i, &mine_4); break;
						case 5: putimage(15 + 45 * j, 118 + 45 * i, &mine_5); break;
						case 6: putimage(15 + 45 * j, 118 + 45 * i, &mine_6); break;
						case 7: putimage(15 + 45 * j, 118 + 45 * i, &mine_7); break;
						case 8: putimage(15 + 45 * j, 118 + 45 * i, &mine_8); break;
					}

					Block_Status_matrix[i][j] = 4;//状态转换为已贴图
				}
				else//该元素为雷
				{
					Block_Status_matrix[i][j] = 1;
					Detects_adjacent_empty_elements(i, j, Mine_Count_matrix, Block_Status_matrix);//扫描周围的所有空元素
					Expand_empty_element_area(Block_Status_matrix);//对周围空元素进行扩展
					State_mapping(Mine_Count_matrix, Block_Status_matrix, mine_0, mine_1, mine_2, mine_3, mine_4, mine_5, mine_6, mine_7, mine_8);//绘制状态图片
				}

				Sleep(250);//睡眠0.25秒,让谨慎脸转换笑脸自然一点
				putimage(320, 17, &smile);//界面上方的笑脸
			}
			else if (mousemsg.uMsg == WM_RBUTTONDOWN)//如果右键点击
			{
				//将状态在未触发0、插旗状态5、问号状态6之间切换
				if (Block_Status_matrix[i][j] == 0)//不是雷
				{
					Block_Status_matrix[i][j] = 5;//贴插旗状态
					putimage(15 + 45 * j, 118 + 45 * i, &mine_flag);//贴上插旗图标 
				}
				else if (Block_Status_matrix[i][j] == 5)//如果已经是红旗状态了
					Block_Status_matrix[i][j] = 6;//状态改为问号
				else if (Block_Status_matrix[i][j] == 6)//如果已经是问号状态了
					Block_Status_matrix[i][j] = 0;//状态改为未触发状态
			}
			//将触发状态图标在未触发、问号之间切换
			else
			{
				//未触发图标
				if (Block_Status_matrix[i][j] == 0)
					putimage(15 + 45 * j, 118 + 45 * i, &cube_trigger);
				//问号图标
				else if (Block_Status_matrix[i][j] == 6)
					putimage(15 + 45 * j, 118 + 45 * i, &question_trigger);
			}

		}
		//将图标在未触发、问号之间切换
		else if (Block_Status_matrix[i][j] == 0 || Block_Status_matrix[i][j] == 6)//鼠标交互反应贴图
		{
			if (Block_Status_matrix[i][j] == 0)
				putimage(15 + 45 * j, 118 + 45 * i, &cube);
			else
				putimage(15 + 45 * j, 118 + 45 * i, &question);
		}
	}
	if (IsMine_flag == 1)
		break;
}
if (IsMine_flag == 1)
{
	IsMine_flag = 0;
	break;
}
if (333 <= mousemsg.x && mousemsg.x <= 413 && 15 <= mousemsg.y && mousemsg.y <= 95)
{
	if (mousemsg.uMsg == WM_LBUTTONDOWN)//如果点击笑脸,重新开始游戏
	{
		putimage(320, 17, &smile_trigger);
		Sleep(200);
		goto start;//goto函数进行跳转
	}
}

4.14 游戏获胜与失败判定原理

	//游戏失败
	if (Success_flag == 0)
	{
		settextcolor(RED);
		setbkmode(TRANSPARENT);
		settextstyle(100, 0, "黑体");
		outtextxy(110, 5, "GAME  OVER");
		putimage(320, 17, &dead);
		Sleep(10);
		Display_mine(Minefield_matrix, mine_eliminate);
		putimage(15 + 45 * j, 118 + 45 * i, &mine_trigger);

		while (1)
		{
			MOUSEMSG mouse = GetMouseMsg();
			if (333 <= mouse.x && mouse.x <= 413 && 15 <= mouse.y && mouse.y <= 95)//点击哭脸可重新开始游戏
			{
				if (mouse.uMsg == WM_LBUTTONDOWN)
				{
					putimage(320, 17, &dead_trigger);
					Sleep(200);
					goto start;//goto跳转
				}
			}
		}
	}

	//游戏成功
	if (Success_flag == 1)
	{
		settextcolor(RED);
		setbkmode(TRANSPARENT);
		settextstyle(100, 0, "黑体");
		outtextxy(110, 5, "GAME  WIN!");
		putimage(320, 17, &smile);
		Sleep(10);
		Display_mine(Minefield_matrix, mine_eliminate);
		system("pause");
		return 0;
	}

	printf("\n");
	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			printf("%d ", Block_Status_matrix[i][j]);
		}
		printf("\n");
	}
	system("pause");
}

4.15 自定义颜色制定

//自定义文字颜色函数:colour(short x)
void colour(short x)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);//用这个函数方便我们给雷标记颜色

	/*

	函数解释:SetConsoleTextAttribute()函数是一个API设置字体颜色和背景色的函数。参数表中使用两个属性(属性之间用,隔开),
	不同于system(color),SetConsoleTextAttribute()可以改变界面多种颜色,而system()只能修改为一种!

	函数原型:SetConsoleTextAttribute(_In_ HANDLE hConsoleOutput, _In_ WORD wAttributes);
	第一个属性获得句柄(即要设置颜色的地方),第二个属性设置颜色

	句柄:STD_INPUT_HANDLE		含义:标准输入的句柄
	句柄:STD_OUTPUT_HANDLE	含义:标准输出的句柄
	句柄:STD_ERROR_HANDLE		含义:标准错误的句柄

	颜色设置(三种方法):
	1.SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);第二个参数填十六进制数字
	2.SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);直接填十进制数字
	3.SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_BLACK | FOREGROUND_RED);BACKGROUND代表背景,FOREGROUND代表前景
	4.数字代表的什么请搜索一下,这里我用的是十进制数字,15代表白色,12代表红色,用红色来标记雷,白色标记非雷

	*/
}

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值