1,简介
QT开发的扫雷小游戏,这个相对比较简单,用了几个小时。
2,效果
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
//是否是雷
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 100
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
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
//爆炸图片
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; //新建一个空list,里面可以装QPoint类型元素
directions.push_back(QPoint(-1,0)); //插入一个QPoint,代表左方向
directions.push_back(QPoint(1,0)); //插入一个QPoint,代表右方向
directions.push_back(QPoint(0,-1)); //插入一个QPoint,代表下方向
directions.push_back(QPoint(0,1)); //插入一个QPoint,代表上方向
for (int i=0; i<directions.size(); i++)//遍历directions,对4个方向处理
{
QPoint ptNew = pt + directions[i]; //原格子pt,加上上面的一个单位的方向值,就是这个方向相邻的一个格子
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,源码
源码已上传至群文件,可在学习群免费下载!
群号码:1149411109
群名称:Qt实战派学习群