目录
最终效果预览
预备内容
相关说明
相关教程
用到的知识
EasyX图形库的下载
思路
源代码
最后
最终效果预览
在学习如何编写扫雷程序之前,我们先来看一下最终写成代码的演示效果
扫雷视频素材
预备内容
相关说明
虽然这是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函数
最后
本文如果有哪里有缺陷或者待优化的地方,欢迎大家在评论区指出来。让我们一起共同学习,共同进步。