C++结合EasyX写扫雷(new)

【游戏】C++结合EasyX写扫雷(时隔半年后重写)

  • 上一次写扫雷
  • 这一次
  • 实现思路
    • 设置全局变量
    • Grid类
    • Grid类的成员函数
    • 启动画面
    • 死循环监听鼠标事件
  • 全部代码
  • 其他

上一次写扫雷

大约半年之前的寒假期间,我接触了EasyX这个图形库,于是试着写了一个经典小游戏——扫雷
当时大概用了三四天时间,还发布了三篇博客来记录:
【小游戏】用C++结合EasyX制作扫雷1
【小游戏】用C++结合EasyX制作扫雷2
【小游戏】用C++结合EasyX制作扫雷3

这一次

基本上全都是自己重写,没怎么借鉴之前的代码(主要可能是因为看不懂之前自己写的代码了)

编写环境仍然是VS2022,记得把项目字符集改为多字符集

基本思路和之前差不多;引用的图片文件也是一样的(图片文件资源在GitHub储存库中有);另外就是添加了一点点花里胡哨的东西,比如说开始屏幕(Splash Screen)和从命令行传入参数(这个功能还有问题)进行设置等功能

写的过程中,感觉有很多东西忘记了,比如EasyX的一些基础;再就是感觉代码写得更有逻辑了(doge

仍然难免有一些Bug

实现思路

  • 设置几个全局变量
  • 定义Grid类
    • Grid的属性
    • 方法——show 用于显示格子
    • 方法——showMinesNumAround 通过EasyX在对应位置显示出格子周围的雷的数量
    • 方法——onLeftButtonClick 鼠标左键单击时做出的操作
    • 方法——onRightButtonClick 鼠标右键单击时做出的操作
    • 方法——findMinesAround 用遍历的方式找出格子周围雷的数量
  • splashScreen函数实现启动画面
  • main函数
    • 接受来自命令行的参数
    • 初始化画布
    • 加载图像文件
    • 布置雷盘
    • 死循环监测鼠标信息

设置全局变量

(每个变量的作用都在注释中)

// 默认常量
int gridSize = 40;									// 每个格子的大小(长和宽)
int lines = 5, rows = 5;							// 行数和列数
int width = rows * gridSize, height = lines * gridSize; // 窗口宽度和高度
int minesNum = 10;									// 雷的数量
int normalGridsNum = lines * rows - minesNum;		// 除了雷以外的格子数量
IMAGE grid, gridClicked, mine, gridFlag;			// 图像

Grid类

每个属性的作用在注释中都有。
有些成员函数在创建playGround后定义,因为playGround是Grid类型的。(详见全部代码)

// 格子类
class Grid {
public:int posX, posY; // 该格子所处的位置(单位:像素)bool isFlag = false, isClicked = false, isMine = false;int posLine, posRow; // 该格子所处的行和列位置(单位:格子)int minesNumAround = 0; // 该格子周围雷的数量void show(int posLine,int posRow) {//显示this->posLine = posLine;this->posRow = posRow;posX = (this->posRow - 1) * gridSize;posY = (this->posLine - 1) * gridSize;putimage(posX, posY, &grid);}void showMinesNumAround() {if (minesNumAround==0)return;else if (minesNumAround == 1)settextcolor(BLUE);else if (minesNumAround == 2)settextcolor(GREEN);else if (minesNumAround == 3)settextcolor(YELLOW);else if (minesNumAround == 4)settextcolor(RGB(255, 135, 35));elsesettextcolor(RED);char info = minesNumAround + 48;outtextxy(posX+int(gridSize*0.4), posY+int(gridSize*0.1), _T(info));}void onLeftButtonClick();void onRightButtonClick();void findMinesAround();
};

Grid类的成员函数

  • onLeftButtonClick函数:判断这个格子是否被左键单击过(通过Grid的属性isClicked),只有没被点击过才执行接下来的操作。点击后normalGridNum-1,isClicked设置为true(说明已经被左键单击过),然后找出周围雷的数量并显示,再把周围不是雷的格子翻开(让它们执行onLeftButtonClick函数)
  • onRightButtonClick函数:判断格子是否被点击(右键单击左键单击)过。如果有,就把isFlag设置为false(相当于取消旗子),然后设置图片为正常格子;如果没有,就把isFlag设置为true(插旗子),然后把图片设置为旗子的图片。
  • findMinesNum函数:如果这个格子自身不是雷,就遍历周围的八个格子,并以minesNumAround属性作为计数器,得出周围格子的数量。
/*定义Grid类的几个函数*/
// 鼠标左键点击
void Grid::onLeftButtonClick() {if (!isClicked) { // 如果没有被左键点击过isClicked = true;normalGridsNum--;putimage(posX, posY, &gridClicked);findMinesAround();		// 找雷数showMinesNumAround();	// 显示出来周围雷数//翻开周围(4个)不是雷的格子vector<vector<int>> minesPosAround = { {posLine - 1,posRow},{posLine,posRow - 1},{posLine,posRow + 1},{posLine + 1,posRow} };for (int i = 0; i < 4; i++) {if (minesPosAround[i][0] > 0 && minesPosAround[i][0] <= lines && minesPosAround[i][1] > 0 && minesPosAround[i][1] <= rows) {if (playGround[minesPosAround[i][0]][minesPosAround[i][1]].isMine == false) {playGround[minesPosAround[i][0]][minesPosAround[i][1]].onLeftButtonClick();}}}}
}
// 鼠标右键点击
void Grid::onRightButtonClick() {if (!isFlag&&!isClicked) { // 如果没有被右键点击过isFlag = true;putimage(posX, posY, &gridFlag);}else if(isFlag&&!isClicked) {isFlag = false;putimage(posX, posY, &grid);}
}
// 寻找周围8个(或5个)格子中雷的数量
void Grid::findMinesAround() {if (isMine) { // 如果该格子是雷,则返回-1return;}else { // 如果该格子不是雷,继续for (int i = posLine - 1; i < posLine + 1; i++) {for (int j = posRow - 1; j < posRow + 1; j++) {if (i != posLine && j != posRow && playGround[i][j].isMine) {minesNumAround++;}}}}
}

启动画面

for循环不断重绘开始界面的图像,并且每次绘制前清空画布,每次绘制后停顿1毫秒,实现动画效果。BeginBatchDraw,FlushBatchDraw和EndBatchDraw是EasyX的绘图函数,可以使图像绘制更流畅,从而使动画更流畅

// 启动画面
void splashScreen() {IMAGE splashImage;loadimage(&splashImage, "images/mineIcon.png",width,height);putimage(0, 0, &splashImage);Sleep(1500);BeginBatchDraw();for (int i = 0; i <= height; i+=3) {FlushBatchDraw();cleardevice();putimage(0, i, &splashImage);Sleep(1);}EndBatchDraw();cleardevice();
}

死循环监听鼠标事件

如果左键单击,双重for循环判断是点击的哪一个格子。然后判断这个格子是否是雷。如果是,直接判定为;如果不是,执行onLeftButtonClick函数。

如果右键单击,双重for循环判断是点击的哪一个格子。执行onLeftButtonClick函数。

每次循环开始时判断normalGridNum,即正常格子的数量。如果正常格子数量为0,说明所有正常格子已经被翻开,判定为

/*循环监听鼠标事件*/while (true) {if (normalGridsNum == 0) {// 显示提示信息settextcolor(GREEN);BeginBatchDraw();for (int i = height; i >= 10; i -= 3) {FlushBatchDraw();cleardevice();outtextxy(10, i, "你赢啦");Sleep(1);}EndBatchDraw();Sleep(2500);return 0;}ExMessage msg = getmessage(EX_MOUSE);if (msg.message == WM_LBUTTONDOWN) {for (int i = 1; i <= lines; i++) {for (int j = 1; j <= rows; j++) {if (msg.x > playGround[i][j].posX&&msg.x< playGround[i][j].posX+gridSize&&msg.y>playGround[i][j].posY&&msg.y< playGround[i][j].posY+gridSize) {// 判断点击的是那一个格子if (playGround[i][j].isMine) { // 如果是雷putimage(playGround[i][j].posX, playGround[i][j].posY, &mine);for (int i = 0; i < minesNum; i++) {putimage(mines[i].posLine, mines[i].posRow, &mine);}// 显示提示信息settextcolor(RED);BeginBatchDraw();for (int i = height; i >= 10; i -= 3) {FlushBatchDraw();cleardevice();outtextxy(10, i, "你输啦");Sleep(1);}EndBatchDraw();Sleep(2500); // 停顿两秒半return 0;}playGround[i][j].onLeftButtonClick();}}}}else if (msg.message == WM_RBUTTONDOWN) {for (int i = 1; i <= lines; i++) {for (int j = 1; j <= rows; j++) {if (msg.x > playGround[i][j].posX && msg.x< playGround[i][j].posX + gridSize && msg.y>playGround[i][j].posY && msg.y < playGround[i][j].posY + gridSize) {// 判断点击的是那一个格子playGround[i][j].onRightButtonClick();}}}}}

全部代码

#include<iostream>
#include<graphics.h>
#include<vector>
#include<string>
#include<tchar.h>
#include<ctime>using namespace std;// 默认常量
int gridSize = 40;									// 每个格子的大小(长和宽)
int lines = 5, rows = 5;							// 行数和列数
int width = rows * gridSize, height = lines * gridSize; // 窗口宽度和高度
int minesNum = 10;									// 雷的数量
int normalGridsNum = lines * rows - minesNum;		// 除了雷以外的格子数量
IMAGE grid, gridClicked, mine, gridFlag;			// 图像// 格子类
class Grid {
public:int posX, posY; // 该格子所处的位置(单位:像素)bool isFlag = false, isClicked = false, isMine = false;int posLine, posRow; // 该格子所处的行和列位置(单位:格子)int minesNumAround = 0; // 该格子周围雷的数量void show(int posLine,int posRow) {//显示this->posLine = posLine;this->posRow = posRow;posX = (this->posRow - 1) * gridSize;posY = (this->posLine - 1) * gridSize;putimage(posX, posY, &grid);}void showMinesNumAround() {if (minesNumAround==0)return;else if (minesNumAround == 1)settextcolor(BLUE);else if (minesNumAround == 2)settextcolor(GREEN);else if (minesNumAround == 3)settextcolor(YELLOW);else if (minesNumAround == 4)settextcolor(RGB(255, 135, 35));elsesettextcolor(RED);char info = minesNumAround + 48;outtextxy(posX+int(gridSize*0.4), posY+int(gridSize*0.1), _T(info));}void onLeftButtonClick();void onRightButtonClick();void findMinesAround();
};vector<vector<Grid>> playGround(lines+1,vector<Grid>(rows+1));	// 创建整个雷盘
vector<Grid> mines; // 定义一个vector储存是雷的格子/*定义Grid类的几个函数*/
// 鼠标左键点击
void Grid::onLeftButtonClick() {if (!isClicked) { // 如果没有被左键点击过isClicked = true;normalGridsNum--;putimage(posX, posY, &gridClicked);findMinesAround();		// 找雷数showMinesNumAround();	// 显示出来周围雷数//翻开周围(4个)不是雷的格子vector<vector<int>> minesPosAround = { {posLine - 1,posRow},{posLine,posRow - 1},{posLine,posRow + 1},{posLine + 1,posRow} };for (int i = 0; i < 4; i++) {if (minesPosAround[i][0] > 0 && minesPosAround[i][0] <= lines && minesPosAround[i][1] > 0 && minesPosAround[i][1] <= rows) {if (playGround[minesPosAround[i][0]][minesPosAround[i][1]].isMine == false) {playGround[minesPosAround[i][0]][minesPosAround[i][1]].onLeftButtonClick();}}}}
}
// 鼠标右键点击
void Grid::onRightButtonClick() {if (!isFlag&&!isClicked) { // 如果没有被右键点击过isFlag = true;putimage(posX, posY, &gridFlag);}else if(isFlag&&!isClicked) {isFlag = false;putimage(posX, posY, &grid);}
}
// 寻找周围8个(或5个)格子中雷的数量
void Grid::findMinesAround() {if (isMine) { // 如果该格子是雷,则返回-1return;}else { // 如果该格子不是雷,继续for (int i = posLine - 1; i < posLine + 1; i++) {for (int j = posRow - 1; j < posRow + 1; j++) {if (i != posLine && j != posRow && playGround[i][j].isMine) {minesNumAround++;}}}}
}
// 启动画面
void splashScreen() {IMAGE splashImage;loadimage(&splashImage, "images/mineIcon.png",width,height);putimage(0, 0, &splashImage);Sleep(1500);BeginBatchDraw();for (int i = 0; i <= height; i+=3) {FlushBatchDraw();cleardevice();putimage(0, i, &splashImage);Sleep(1);}EndBatchDraw();cleardevice();
}
//主函数
int main(int argc, char* argv[]) {/*接收来自命令行的参数*/if (argc == 2 && argv[1] == "-h") { // 输出帮助信息cout << "帮助" << endl;cout << "-s:每个格子的大小" << endl;cout << "-l:雷盘的行数" << endl << "-r:雷盘的列数" << endl;cout << "-n:雷的个数" << endl;}else if (argc > 2) {for (int i = 1; i < argc-1; i++) {if (argv[i] == "-s") {gridSize = atoi(argv[i + 1]);}else if (argv[i] == "-l") {lines = atoi(argv[i + 1]);}else if (argv[i] == "-r") {rows = atoi(argv[i + 1]);}else if (argv[i] == "-n") {minesNum = atoi(argv[i + 1]);}else {cout << "参数错误" << endl;}}}/*初始化画布*/initgraph(gridSize * rows, height);loadimage(&grid, "images/grid.png", gridSize, gridSize);loadimage(&gridClicked, "images/gridClicked.png", gridSize, gridSize);loadimage(&mine, "images/mine.png", gridSize, gridSize);loadimage(&gridFlag, "images/gridFlag.png", gridSize, gridSize);setbkmode(TRANSPARENT);settextstyle(int(gridSize*0.9), int(gridSize * 0.4), _T("微软雅黑"));setbkcolor(WHITE);cleardevice();/*启动画面*/splashScreen();/*布置雷盘*/// 显示格子for (int i = 1; i <= lines; i++) {for (int j = 1; j <= rows; j++) {playGround[i][j].show(i, j);Sleep(10);}}// 随机布雷srand(time(nullptr));for (int i = 0; i < minesNum; i++) {int x = rand() % rows + 1, y = rand() % lines + 1;playGround[x][y].isMine = true;mines.push_back(playGround[x][y]);}/*循环监听鼠标事件*/while (true) {if (normalGridsNum == 0) {// 显示提示信息settextcolor(GREEN);BeginBatchDraw();for (int i = height; i >= 10; i -= 3) {FlushBatchDraw();cleardevice();outtextxy(10, i, "你赢啦");Sleep(1);}EndBatchDraw();Sleep(2500);return 0;}ExMessage msg = getmessage(EX_MOUSE);if (msg.message == WM_LBUTTONDOWN) {for (int i = 1; i <= lines; i++) {for (int j = 1; j <= rows; j++) {if (msg.x > playGround[i][j].posX&&msg.x< playGround[i][j].posX+gridSize&&msg.y>playGround[i][j].posY&&msg.y< playGround[i][j].posY+gridSize) {// 判断点击的是那一个格子if (playGround[i][j].isMine) { // 如果是雷putimage(playGround[i][j].posX, playGround[i][j].posY, &mine);for (int i = 0; i < minesNum; i++) {putimage(mines[i].posLine, mines[i].posRow, &mine);}// 显示提示信息settextcolor(RED);BeginBatchDraw();for (int i = height; i >= 10; i -= 3) {FlushBatchDraw();cleardevice();outtextxy(10, i, "你输啦");Sleep(1);}EndBatchDraw();Sleep(2500); // 停顿两秒半return 0;}playGround[i][j].onLeftButtonClick();}}}}else if (msg.message == WM_RBUTTONDOWN) {for (int i = 1; i <= lines; i++) {for (int j = 1; j <= rows; j++) {if (msg.x > playGround[i][j].posX && msg.x< playGround[i][j].posX + gridSize && msg.y>playGround[i][j].posY && msg.y < playGround[i][j].posY + gridSize) {// 判断点击的是那一个格子playGround[i][j].onRightButtonClick();}}}}}/*关闭绘图窗口*/closegraph();return 0;
}

其他

上次的扫雷代码详见储存库,图像资源也在上面,这次的用的图像一样的。不过这次写代码时改了一下图片文件的名字。

效果图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/19828.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构--树和森林的遍历

数据结构–树和森林的遍历 树的先根遍历 void PreOrder(TreeNode* R) {if (R ! NULL){visit(R);while (R还有下一个子树T)PreOrder(T);} }树和二叉树的转化后》 树的先根遍历序列与这棵树相应二叉树的先序序列相同。 \color{red}树的先根遍历序列与这棵树相应二叉树的先序序列相…

串口的再认识

常用函数介绍 串口发送/接收函数 HAL_UART_Transmit(); 串口发送数据&#xff0c;使用超时管理机制&#xff08;即在发送成功前一直阻塞&#xff09; HAL_UART_Receive(); 串口接收数据&#xff0c;使用超时管理机制 HAL_UART_Transmit_IT(); 串口中断模式发送 HAL_UART…

C++ 变量类型

C 变量类型 变量其实只不过是程序可操作的存储区的名称。 在 C 中&#xff0c;有多种变量类型可用于存储不同种类的数据。 C 中每个变量都有指定的类型&#xff0c;类型决定了变量存储的大小和布局&#xff0c;该范围内的值都可以存储在内存中&#xff0c;运算符可应用于变量…

Pytorch实现warm up和consine decay

在深度学习领域&#xff0c;模型训练过程中的不稳定性是一个常见的问题。为了解决这个问题&#xff0c;在Resnet这篇论文也提及了Warm Up的方法&#xff0c;通过逐渐增加学习率&#xff0c;引导模型在训练初期更稳定地收敛。同时在warm up之后结合consine decay的方法让训练变得…

辅助驾驶功能开发-功能规范篇(22)-3-L2级辅助驾驶方案功能规范

1.3.3 TLA系统功能定义 1.3.3.1 状态机 1.3.3.2 状态迁移图 1.3.3.3 功能定义 1.3.3.3.1 信号需求列表 1.3.3.3.2 系统开启关闭 1&#xff09;初始化 车辆上电后&#xff0c;交通灯辅助系统&#xff08;TLA&#xff09;进行初始化&#xff0c;控制器需在 220ms 内发出第一帧…

Spring-Interceptor拦截器

使用步骤 申明拦截器bean&#xff0c;并实现HandlerInterceptor接口 true为放行&#xff0c;false为拦截 2.定义配置类&#xff0c;继承WebMvcConfigurationSupport&#xff0c;实现addInterceptors方法&#xff0c;该方法调用具体的拦截器进行拦截 也可以在配子类通过实现W…

JMeter进行WebSocket压力测试

背景 之前两篇内容介绍了一下 WebSocket 和 SocketIO 的基础内容。之后用 Netty-SocketIO 开发了一个简单的服务端&#xff0c;支持服务端主动向客户端发送消息&#xff0c;同时也支持客户端请求&#xff0c;服务端响应方式。本文主要想了解一下服务端的性能怎么样&#xff0c;…

一百二十九、Kettle——从MySQL增量导入到GreenPlum

一、目标 用Kettle从MySQL增量导入数据到GreePlum 二、前提准备 &#xff08;一&#xff09;kettle已连上MySQL &#xff08;二&#xff09;kettle已连上GreenPlum 三、实施步骤 &#xff08;一&#xff09;打开kettle&#xff0c;新建转换任务。拖拽2个表输入、替换NULL…

Spring 如何解决 Bean 的循环依赖(循环引用)

Component public class A {Autowiredprivate B b;}Component public class B {Autowiredprivate A a;}上面的情况就是 循环依赖 Bean的创建初始化过程如下 如果不采取措施&#xff0c;那么循环依赖就会进入死循环 但 Spring 已经帮我们解决了大部分循环依赖问题 具体是如何解…

prompt:有需求就有价值,prompt案例

prompt&#xff1a;有需求就有价值 此文章来自于小七姐 首先来看需求&#xff1a; 客户需要生成1000条俏皮灵动&#xff0c;趣味盎然&#xff0c;比喻精妙的和美食有关的短句子&#xff0c;要求文风优美&#xff0c;句子让人充满食欲。 客户使用这些句子的场景比较奇妙&…

路径规划算法:基于食肉植物优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于食肉植物优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于食肉植物优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

2023年Q2空调行业品牌数据榜单(京东商品数据)

随着夏季的来临&#xff0c;高温天气也带动部分家电行业的销售&#xff0c;以空调为代表的家电市场正逐步恢复活力。结合鲸参谋电商数据分析平台的相关数据&#xff0c;我们来分析一下2023年Q2空调市场的具体销售表现。 根据鲸参谋平台的数据显示&#xff0c;2023年4-6月份&am…