目录
最终效果预览
预备内容
相关说明
相关教程
用到的知识
EasyX图形库的下载
思路
源代码
最后
文章来源地址https://www.toymoban.com/news/detail-483220.html
最终效果预览
在学习如何编写扫雷程序之前,我们先来看一下最终写成代码的演示效果
扫雷视频素材
文章来源: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
用到的知识
- 函数、数组、结构体以及C语言的一些基础内容,其中指针没学也可以。懂得如何操作递归。
- memset函数,重置变量时会用到(头文件:srting.h)
- sprintf函数(头文件:srting.h)
- MessageBox函数(Windows封装的MFC内容)就是游戏退出或者游戏结束时弹出的消息框
- EasyX图形库的相关内容
- 本次扫雷程序用到的EasyX的相关函数
- initgraph初始化图形
- 图片变量是IMAGE类型
- loadimage加载图片,如果程序报错可以尝试把地址用括号括起来,前面加上一个 _T
- putimage放置图片
- 使用图片之前需要预先加载图片
- ExMessage是信息类型
- peekmessage获取信息类型
- BeginBatchDraw()和EndBatchDraw()函数,以防止闪屏
- setlinestyle()、rectangle()等绘图函数(画图形、设置图形格式)
- setbkcolor、settextcolor、 settextstyle、drawtext等文字设置相关的函数
EasyX图形库的下载
EasyX图形库的安装过程很简单,和平时安装软件一样(后附链接)
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模板网!