C++ combined with EasyX to write minesweeper (new)

The last time I wrote Minesweeper

During the winter vacation about half a year ago, I came into contact with the graphics library EasyX, so I tried to write a classic mini-game - Minesweeper.
It took about three or four days, and I also published three blogs to record it:
[Mini Game] Use C++ combined with EasyX to make Minesweeper 1
[Small game] Use C++ combined with EasyX to make Minesweeper 2
[Small game] Use C++ combined with EasyX to make Minesweeper 3

this time

Basically, I rewrote everything myself, and didn’t draw much from the previous code (mainly probably because I can’t understand the code I wrote before)

The writing environment is still VS2022, remember to change the project character set to multiple character sets

The basic idea is similar to before; the referenced image files are also the same (the image file resources are in the GitHub repository); in addition, a few bells and whistles have been added, such as the start screen (Splash Screen) and parameters passed in from the command line. (There are still problems with this function) Perform settings and other functions

In the process of writing, I feel that I have forgotten many things, such as some basics of EasyX; and I feel that the code is written more logically (doge

There are still some bugs

Implementation ideas

  • Set several global variables
  • Define Grid class
    • Grid properties
    • Method - show is used to display the grid
    • Method - showMinesNumAround displays the number of mines around the grid at the corresponding position through EasyX
    • Method - onLeftButtonClick The operation performed when the left mouse button is clicked
    • Method - onRightButtonClick The operation performed when the mouse right button clicks
    • Method - findMinesAround uses traversal to find the number of mines around the grid
  • The splashScreen function realizes the splash screen
  • main function
    • accepts arguments from the command line
    • Initialize canvas
    • Load image file
    • Lay out thunder disks
    • Infinite loop monitoring mouse information

set global variables

(The role of each variable is in the comments)

// 默认常量
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 class

The role of each attribute is given in the annotation.
Some member functions are defined after creating playGround, because playGround is of type Grid. (see full code for details)

// 格子类
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();
};

Member functions of the Grid class

  • onLeftButtonClick function: Determine whether the grid has been left-clicked (through the Grid's property isClicked). The next operation will be performed only if it has not been clicked. After clicking, normalGridNum-1, isClicked is set to true (indicating that it has been left-clicked), then find out the number of surrounding mines and display them, and then open the grids that are not surrounded by mines (let them execute the onLeftButtonClick function)
  • onRightButtonClick function: Determine whether the grid has been clicked (right click or left click). If there is, set isFlag to false (equivalent to canceling the flag), and then set the picture to a normal grid; if not, set isFlag to true (insert the flag), and then set the picture to the flag picture.
  • findMinesNum function: If the grid itself is not a mine, it traverses the eight surrounding grids and uses the minesNumAround attribute as a counter to get the number of surrounding grids.
/*定义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++;
				}
			}
		}
	}
}

splash screen

The for loop continuously redraws the image of the starting interface, clears the canvas before each drawing, and pauses for 1 millisecond after each drawing to achieve animation effects. BeginBatchDraw, FlushBatchDraw and EndBatchDraw are the drawing functions of EasyX, which can make the image drawing smoother and thus the animation smoother.

// 启动画面
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();
}

Infinite loop listening to mouse events

If the left button is clicked, the double for loop determines which grid was clicked. Then judge whether this grid is mine. If it is, it is directly determined as a loss ; if not, the onLeftButtonClick function is executed.

If you right-click, a double for loop determines which grid was clicked. Execute the onLeftButtonClick function.

At the beginning of each loop, normalGridNum is judged, which is the number of normal grids. If the number of normal grids is 0, it means that all normal grids have been revealed and it is determined as a win.

/*循环监听鼠标事件*/
	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();
					}
				}
			}
		}
	}

All code

#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;
}

other

The last minesweeper code is detailed in the repository , and the image resources are also above. The images used this time are the same. However, this time I changed the name of the image file when I wrote the code.

renderings

Guess you like

Origin blog.csdn.net/m0_61316509/article/details/131689842