QT项目四:扫雷游戏

时间:2022-01-12 21:01:07

1,简介

QT开发的扫雷小游戏,这个相对比较简单,用了几个小时。


2,效果

QT项目四:扫雷游戏


3,设计思路


背景:一个灰色大矩形

游戏区:默认是初级难度,9*9的矩形阵。可变成16*16,16*30。

每个小矩形元素类 Item.h:

#ifndef ITEM_H#define ITEM_H

#include <QPoint>

class Item
{
public:
Item();
Item(QPoint pos);

QPoint m_pos; //位置

bool m_bIsMine; //是否是雷
bool m_bMarked; //是否已标记为雷

int m_nNumber; //数字
bool m_bOpen; //是否已打开,且非雷
};

#endif // ITEM_H


MainWindow.h:

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include "item.h"#include <QMainWindow>namespace Ui {class MainWindow;}#define RECT_WIDTH      30#define RECT_HEIGHT     30#define START_X         100#define START_Y         100class MainWindow : public QMainWindow{    Q_OBJECTpublic:    explicit MainWindow(QWidget *parent = 0);    ~MainWindow();	void InitItems();	void ReleaseItems();    void NewGame();							void GameSuccess();	void GameFail();	void OpenEmptyItem(QPoint pt);			//点击空白元素(相邻雷数为0)时,递归查找相邻的空白元素,以及空白元素附近的数字元素(数字是雷数)	bool FindAll();	bool PointInGameArea(QPoint pt);		//判断坐标是否超过游戏区域protected:    void paintEvent(QPaintEvent *);    void mousePressEvent(QMouseEvent *);private slots:    void OnMenu_NewGame();    void OnMenu_Settings();	void OnMenu_Level1();	void OnMenu_Level2();	void OnMenu_Level3();private:    void DrawChessboard();    void DrawItems();    void DrawItem(QPainter& painter,Item* pItem);private:    Ui::MainWindow *ui;	QPixmap m_FlagImage;				//小红旗图片	QPixmap m_BombImage;				//爆炸图片	int m_nRows;					//行数	int m_nColumes;					//列数	int m_nMineCount;				//雷数    QVector<QPoint> m_Mines;				//雷点	QVector<QVector<Item*>> m_items;		//所有元素	bool m_bGameFail;				//是否是游戏失败,失败了需要显示雷};#endif // MAINWINDOW_H


随机初始化雷点:

void MainWindow::InitItems(){	//随机初始化雷	m_Mines.clear();	for(int i = 0; i<m_nMineCount; i++)	{		qsrand(QTime::currentTime().msec());		int x = qrand()%m_nColumes;		int y = qrand()%m_nRows;		while(m_Mines.contains(QPoint(x,y)))		{			x = qrand()%m_nColumes;			y = qrand()%m_nRows;		}		m_Mines.append(QPoint(x,y));	}	//建立2维数组保存所有元素位置,方便索引	for(int i=0; i<m_nColumes; i++)	{		QVector<Item*> rowItems;		for(int j=0; j<m_nRows; j++)		{			QPoint pos = QPoint(i,j);			Item* pItem = new Item(pos);			if(m_Mines.contains(pos))   //该位置是雷			{				pItem->m_bIsMine = true;			}			rowItems.append(pItem);				}		m_items.append(rowItems);	}	//计算雷附近格子的数字	for(int i=0; i<m_nColumes; i++)	{		for(int j=0; j<m_nRows; j++)		{			if (m_items[i][j]->m_bIsMine)			{				continue;			}			int nCountMines = 0;			//求每个点附近的8个点的是雷的总数			for (int m=-1;m<=1;m++)			{				for (int n=-1; n<=1;n++)				{					if (m==0 && n==0)					{						continue;					}					QPoint ptNew = QPoint(i+m,j+n);					if (!PointInGameArea(ptNew))					{						continue;					}					if (m_items[i+m][j+n]->m_bIsMine)					{						nCountMines++;					}				}			}			m_items[i][j]->m_nNumber = nCountMines;		}	}}

核心函数,鼠标点击处理:

void MainWindow::mousePressEvent(QMouseEvent * e){	//得到鼠标处的格子坐标	QPoint pt;	pt.setX( (e->pos().x() - START_X ) / RECT_WIDTH);	pt.setY( (e->pos().y() - START_X ) / RECT_HEIGHT);	//是否点在游戏区域内	if (!PointInGameArea(pt))	{		return;	}	//获取所点击矩形元素	Item* pItem = m_items[pt.x()][pt.y()];	//左键打开元素,右键插旗帜标记	if(e->button()==Qt::LeftButton)	{		//不是已标记的或已打开的空白点,也就是未处理的		if(!pItem->m_bMarked && !pItem->m_bOpen)		{			//如果是雷,就GAME OVER			if (pItem->m_bIsMine)			{				//QMessageBox::information(NULL,  "GAME OVER","FAIL!", QMessageBox::Yes , QMessageBox::Yes);				GameFail();				return;			}			else			{				//打开				pItem->m_bOpen = true;				if (pItem->m_nNumber == 0)				{					//如果数字是0,也就是不含任何相邻雷的元素,那么递归打开所有的相邻数字是0的元素					//也就是点到一个空白处,一下打开一大片的效果					OpenEmptyItem(pt);				}				//如果已找到所有雷				if (FindAll())				{					QMessageBox::information(NULL,  "GAME OVER","SUCCESS!", QMessageBox::Yes , QMessageBox::Yes);					//GameSuccess();					return;				}			}		}	}	else if(e->button()==Qt::RightButton)	{		//已标记过的,取消标记		if (pItem->m_bMarked)		{			pItem->m_bMarked = false;		}		else if (!pItem->m_bOpen)		{			//没标记也没打开,就是未处理的,就插旗帜标记上			pItem->m_bMarked = true;			if (FindAll())			{				QMessageBox::information(NULL,  "GAME OVER","SUCCESS!", QMessageBox::Yes , QMessageBox::Yes);				//GameSuccess();				return;			}		}	}}

其中OpenEmptyItem函数,可能会打开空白一大片:

//运气好时点到一个空白元素,可能打开挨着的一大片void MainWindow::OpenEmptyItem(QPoint pt){	//对于空白元素,有上下左右4个方向挨着的空白元素,就打开并继续查找空白元素	QVector<QPoint> directions;	directions.push_back(QPoint(-1,0));	directions.push_back(QPoint(1,0));	directions.push_back(QPoint(0,-1));	directions.push_back(QPoint(0,1));	for (int i=0; i<directions.size(); i++)	{		QPoint ptNew = pt + directions[i];		if (!PointInGameArea(ptNew))		{			continue;		}		Item* pItem = m_items[ptNew.x()][ptNew.y()];		if (!pItem->m_bIsMine && !pItem->m_bOpen && !pItem->m_bMarked && pItem->m_nNumber == 0)		{			pItem->m_bOpen = true;			//对于找到的空白元素,在它的8个方向上有数字元素就打开			QVector<QPoint> directions2 = directions;			directions2.push_back(QPoint(-1,-1));			directions2.push_back(QPoint(1,1));			directions2.push_back(QPoint(1,-1));			directions2.push_back(QPoint(-1,1));			for (int j=0; j<directions2.size(); j++)			{				QPoint ptNew2 = ptNew + directions2[j];				if(!PointInGameArea(ptNew2))				{					continue;				}				Item* pItem2 = m_items[ptNew2.x()][ptNew2.y()];				if (!pItem2->m_bIsMine && !pItem2->m_bOpen && !pItem2->m_bMarked && pItem2->m_nNumber > 0)				{					pItem2->m_bOpen = true;				}			}			//递归查找上下左右4个方向的空白元素			OpenEmptyItem(ptNew);		}	}}

//是否找完bool MainWindow::FindAll(){	bool bFindAll = true;	//遍历二维数组 QVector<QVector<Item*>> m_items	for (int i=0; i<m_items.size(); i++)	{		for (int j=0;j<m_items[i].size(); j++)		{			//只要存在一个雷没被标记,或存在一个非雷被没打开,都不算找完			Item* pItem = m_items[i][j];			if (pItem->m_bIsMine)			{				if (!pItem->m_bMarked)				{					bFindAll = false;				}			}			else			{				if (!pItem->m_bOpen)				{					bFindAll = false;				}			}		}	}	return bFindAll;}



4,源码

百度云:https://pan.baidu.com/s/1I3y4gADYnasWfncj_kBYzA