【游戏】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;
}
其他
上次的扫雷代码详见储存库,图像资源也在上面,这次的用的图像一样的。不过这次写代码时改了一下图片文件的名字。