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

上一次写扫雷

大约半年之前的寒假期间,我接触了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));
		else
			settextcolor(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) {
    
     // 如果该格子是雷,则返回-1
		return;
	}
	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));
		else
			settextcolor(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) {
    
     // 如果该格子是雷,则返回-1
		return;
	}
	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;
}

其他

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

效果图

猜你喜欢

转载自blog.csdn.net/m0_61316509/article/details/131689842
今日推荐