admin管理员组文章数量:1596895
基于easyx库的GUI扫雷项目
文章目录
- 基于easyx库的GUI扫雷项目
- 0.观前提醒
- 1.扫雷游戏项目效果展示
- 2.扫雷游戏项目基本信息
- 3.扫雷游戏项目设计思路
- 4.扫雷游戏实现原理
- 4.1 头文件解析
- 4.2 素材解析
- 4.3 变量与矩形布局解析
- 4.4 随机雷的实现
- 4.5 生成雷位置矩阵的实现
- 4.6 生成雷数矩阵的实现
- 4.7 绘制图像过程
- 4.8 输赢检测规则
- 4.9 检测相邻空元素的方法
- 4.10 扩展空元素区域的方法
- 4.11 绘制状态贴图的方法
- 4.12 展示地雷的方法
- 4.13 鼠标交互逻辑解析
- 4.14 游戏获胜与失败判定原理
- 4.15 自定义颜色制定
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.经过上两步的检测,所有要被贴图的元素状态已被设为2和3,因此仅需遍历status矩阵
2.找到状态为2和3的元素,在对照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代表红色,用红色来标记雷,白色标记非雷
*/
}
版权声明:本文标题:基于easyx库的GUI扫雷项目 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1728257229a1151161.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论