近日把以前写的一个QT的扫雷小项目整理了一下,写下这篇博客,包含所有源代码分析,以及完整项目下载地址,希望能和大家一起分享学习。
据我所知的扫雷只有一个最简单的规则,那就是每次点开的数字代表这个方块周围有几个雷,慢慢点开整块区域的时候就是按照这个规则,快速的排除是雷的方块,这个规则的实现也是代码中核心算法的实现,为了更好的判断这块地区是不是雷,扫雷中可以使用右击在方块上插上旗子,代表你认为这块地区是一个雷,这样可以快速判断其他区域是否是雷,将所有的不是雷的方块点开即可获得游戏成功,左击点到一个雷则失败。下面是本项目扫雷界面:
主界面-带一个8x8按钮来开始游戏 游戏界面-8x8扫雷未开始游戏 游戏失败 游戏成功Qt的安装就不多说了,本项目是在windows上实现的,官网下载:http://download.qt.io/official_releases/qt(版本可自选),下载qt-opensource-windows带mingw的就好了,msvc的配置可能会稍麻烦一点。
界面主要有两页组成,第一页程序刚打开的欢迎界面,第二页玩游戏的游戏界面,这两个窗口通过stacketWidget来切换,所以先在画图主窗口界面拖一个stackWidget控件,添加两个page,在每一页page上各添加一个Graphics View,以便之后在view上面添加图片,主窗口的Graphics View上拖上一个按钮显示8x8,是开始8x8扫雷游戏的按钮,实现按下按钮就切换到第二页玩游戏,第二页Graphics View上再拖上三个控件,1、LCD Number 2、Push Button(表示退出到主界面) 3、Push Button(表示重新开始游戏),这样主界面绘制完成。
窗口布局
右击项目名称->添加新文件->Qt->Qt Resource File,然后取个名字叫做pic,资源的话项目里面就会多一个 资源->pic.qrc文件,在这个qrc文件里面我们能添加图片,之后程序里面就能读到我们添加的图片。在Qt项目中右击qrc文件->Open in Editor,之后下面有个添加,首先点击添加前缀,将前缀/new/prefix改成/,之后再点击添加->添加文件,将我们事先准备好的项目同等级目录下的资源文件夹里面的图片都选中加载进来就好了。之后在代码中引用/source_pic/pic_name就可以找到资源了。
添加资源文件
代码结构主要自己实现五个类,1、mainwindow,2、myscenemain,3、myitemmain,4、myscene,5、myitem。主窗口类主要用来布局和显示所有的控件,包括stackWidget页面的切换,LCD Number的数字显示,判断游戏是否成功或失败,用qmessagebox来显示;myscenemain和myitemmain两个类配合使用用来显示第一页的主窗口欢迎界面;myscene和myitem类主要用来实现游戏界面,因此显而易见我们最最主要的工作还是在myscene和myitem类里面。需要注意的重点是,myscene和myscenemain类继承了QGraphicsScene类,myitem和myitemmain类继承了QGraphicsPixmapItem类,这两个基类都是Qt自带的接口类,QGraphicsPixmapItem可以贴图,显示我们的素材照片,而QGraphicsScene能呈现一个加载图片的QGraphicsPixmapItem类的实体在界面上,这样我们就能通过我们的素材照片来画出我们的扫雷界面。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "myitem.h"
#include "myscene.h"
#include "myscenemain.h"
#include
#include
#include
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void Update();//定时刷新判断游戏是否结束
void updateCount();//刷新游戏进行时间
private slots:
void on_pushButton_clicked();//开始游戏按钮槽函数
void on_pushButtonrestart_clicked();//重新开始游戏按钮槽函数
void on_pushButtonexit_clicked();//退出游戏按钮槽函数
private:
Ui::MainWindow *ui;
myscene *sc;//游戏界面场景画布
myscenemain *scmain;//欢迎界面场景画布
QTimer *timeFailorWin;//计时器判断游戏是否结束
QTime t;//实现秒表功能需要用到的变量
QTimer *currentTime;//计时器在lcd控件上显示当前游戏已经玩了多久(实现秒表功能)
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
extern int gameoverFlag;
extern int flagNum;
int Click = 0;//玩家需要点击了方块才会开始计时
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
scmain = new myscenemain;
ui->graphicsView_main->setScene(scmain);
ui->stackedWidget->setCurrentIndex(0);
timeFailorWin = new QTimer;
currentTime = new QTimer;
connect(timeFailorWin, SIGNAL(timeout()), this, SLOT(Update()));
connect(currentTime, SIGNAL(timeout()), this, SLOT(updateCount()));
currentTime->start(1);//当前时间,每隔1秒调用一次updateCount()
t.setHMS(0, 0, 0);//lcd显示器初始化为0
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::Update()
{
if(gameoverFlag == 1)
{
Click = 0;
t.setHMS(0, 0, 0);
ui->lcdNumber->display("00:00");
QMessageBox::information(this, "info", "you failed!");//8*8 失败
gameoverFlag--;
timeFailorWin->stop();
gameoverFlag--;
}else if(flagNum == 8)//所有的雷都找出来算成功
{
Click = 0;
QMessageBox::information(this, "info", "you win!");//8*8 成功
flagNum = 0;
timeFailorWin->stop();
flagNum = 0;
}
}
void MainWindow::on_pushButton_clicked()
{
sc = new myscene;
ui->graphicsView_8x8->setScene(sc);
ui->stackedWidget->setCurrentIndex(1);
timeFailorWin->start(500);//每个半秒检测是否赢了或者输了,执行Update函数
}
void MainWindow::updateCount()
{
if(Click > 0)//需要玩家开始玩游戏的时候计时器才会开始计时并显示在lcd上
{
currentTime->start(1);
t = t.addMSecs(1.5);
QString stime = t.toString("mm:ss");
ui->lcdNumber->display(stime);
}
}
void MainWindow::on_pushButtonrestart_clicked()
{
delete sc;
sc = new myscene;
ui->graphicsView_8x8->setScene(sc);
flagNum = 0;
timeFailorWin->start(500);
Click = 0;
t.setHMS(0, 0, 0);
ui->lcdNumber->display("00:00");
}
void MainWindow::on_pushButtonexit_clicked()
{
delete sc;
ui->stackedWidget->setCurrentIndex(0);
flagNum = 0;
Click = 0;
t.setHMS(0, 0, 0);
ui->lcdNumber->display("00:00");
}
#ifndef MYSCENEMAIN_H
#define MYSCENEMAIN_H
#include "myitemmain.h"
#include
#include
class myscenemain : public QGraphicsScene
{
Q_OBJECT
public:
explicit myscenemain(QObject *parent = 0);
signals:
public slots:
private:
myitemmain *itemmain;
};
#endif // MYSCENEMAIN_H
#include "myscenemain.h"
myscenemain::myscenemain(QObject *parent) : QGraphicsScene(parent)
{
QString path=":/source_pic/zhujiemian";
itemmain = new myitemmain(path);
this->addItem(itemmain);
}
#ifndef MYITEMMAIN_H
#define MYITEMMAIN_H
#include
#include
class myitemmain : public QGraphicsPixmapItem
{
public:
myitemmain(QString path);
};
#endif // MYITEMMAIN_H
#include "myitemmain.h"
myitemmain::myitemmain(QString path)
{
this->setPixmap(QPixmap(path));
}
#ifndef MYSCENE_H
#define MYSCENE_H
#include "myitem.h"
#include
#include
#include
#include
#include
class myscene : public QGraphicsScene
{
Q_OBJECT
public:
explicit myscene(QObject *parent = 0);
void initImage();
static int index;
signals:
public slots:
void Update();
private:
QTime t;
QTimer *ptimer;
};
#endif // MYSCENE_H
#include "myscene.h"
extern int a[8][8];
extern int gameoverFlag;
myitem *item[64];
int myscene::index = 0;//本类内全局变量
myscene::myscene(QObject *parent) :
QGraphicsScene(parent)
{
// qsrand((unsigned)time(NULL));
initImage();//初始化游戏界面
ptimer = new QTimer;
connect(ptimer, SIGNAL(timeout()), this, SLOT(Update()));
ptimer->start(1);//每秒刷新一次界面
}
void myscene::initImage()
{
for(int i=0; i<64; i++)
{
item[i] = new myitem(":/source_pic/f3");//8x8每个图标初始化图片
item[i]->setPos(item[i]->boundingRect().width()*(i%8), item[i]->boundingRect().height()*(i/8));//8x8排列摆放
this->addItem(item[i]);//每个item添加到scene上面
a[i/8][i%8] = 0;//每个item背后的值赋值为0
}
for(int j=0; j<8; j++)//随机取八个雷,赋值为1
{
index = qrand()%64;//在64内取八个随机数
if(item[index]->flag == 1)//如果重复则重新取
j--;
item[index]->flag = 1;//item成员变量flag设置为1
a[index/8][index%8] = 1;//8x8方格背后的值赋值为1
}
}
void myscene::Update()//游戏结束时调用显示所有的地雷
{
if(gameoverFlag == 1)
{
for(index=0; index<64; index++)
{
if(a[index/8][index%8] == 1)
{
item[index] = new myitem(":/source_pic/l1");//地雷
item[index]->setPos(item[index]->boundingRect().width()*(index%8), item[index]->boundingRect().height()*(index/8));
this->addItem(item[index]);
}
}
ptimer->stop();
}
}
#ifndef MYITEM_H
#define MYITEM_H
#include
#include
#include
#include
#include
class myitem : public QGraphicsPixmapItem
{
public:
myitem(QString path);
int flag;//0表示不是雷,1表示是雷
void setNum(int y, int x);
private:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
int boomNum;//周围雷的个数
int IsflagOrwhiteblock;//0表示还未点开的状态,1表示插上了旗子,2表示点开来变成了白色的
int spreadFlag;//当前块是否已经蔓延过了
int m_x;//当前块的横坐标
int m_y;//当前块的纵坐标
};
#endif // MYITEM_H
#include "myitem.h"
#include
int gameoverFlag = 0;
int a[8][8];
int flagNum;//找到雷的个数(插上旗)
extern int Click;
extern myitem *item[64];
myitem::myitem(QString path)
{
this->setPixmap(QPixmap(path));
IsflagOrwhiteblock = 0;//0表示还未点开的状态
spreadFlag = 0;
flag = 0;
}
void myitem::setNum(int y, int x)
{
boomNum = 0;
if((y-1 >= 0) && (x-1 >= 0) && (a[y-1][x-1] == 1))//四周八块方块一一检测总共有多少个雷
boomNum++;
if((y-1 >= 0) && a[y-1][x] == 1)
boomNum++;
if((y-1 >= 0) && (x+1 <= 7) && (a[y-1][x+1] == 1))
boomNum++;
if((x-1 >= 0) && (a[y][x-1] == 1))
boomNum++;
if((x+1 <= 7) && (a[y][x+1] == 1))
boomNum++;
if((x-1 >= 0) && (y+1 <= 7) && (a[y+1][x-1] == 1))
boomNum++;
if((y+1 <= 7) && (a[y+1][x] == 1))
boomNum++;
if((y+1 <= 7) && (x+1 <= 7) && (a[y+1][x+1] == 1))
boomNum++;
switch(boomNum)//根据周围雷的个数在当前块上显示数字
{
case 1: item[8*y+x]->setPixmap(QPixmap(":/source_pic/11")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 2: item[8*y+x]->setPixmap(QPixmap(":/source_pic/22")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 3: item[8*y+x]->setPixmap(QPixmap(":/source_pic/33")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 4: item[8*y+x]->setPixmap(QPixmap(":/source_pic/44")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 5: item[8*y+x]->setPixmap(QPixmap(":/source_pic/55")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 6: item[8*y+x]->setPixmap(QPixmap(":/source_pic/66")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 7: item[8*y+x]->setPixmap(QPixmap(":/source_pic/77")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 8: item[8*y+x]->setPixmap(QPixmap(":/source_pic/88")); item[8*y+x]->IsflagOrwhiteblock += 2; break;
case 0: item[8*y+x]->setPixmap(QPixmap(":/source_pic/f2"));//表示周围没有雷,下面分不同位置的块按照不同的方法向四周蔓延
if(y < 7 && y > 0 && x < 7 && x > 0 && item[8*y+x]->spreadFlag == 0)//属于中间的方块
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y, x-1);
setNum(y+1, x);
setNum(y, x+1);
}
if(y == 0 && (x != 0) && (x != 7) && item[8*y+x]->spreadFlag == 0)//顶上的方块不包含两边
{
item[8*y+x]->spreadFlag = 1;
setNum(y, x-1);
setNum(y+1, x);
setNum(y, x+1);
}
if((x == 0) && (y != 0) && (y != 7) && item[8*y+x]->spreadFlag == 0)//左边的方块不包含两边
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y+1, x);
setNum(y, x+1);
}
if((y == 7) && (x != 0) && (x != 7) && item[8*y+x]->spreadFlag == 0)//下面的方块不包含两边
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y, x-1);
setNum(y, x+1);
}
if((x == 7) && (y != 0) && (y != 7) && item[8*y+x]->spreadFlag == 0)//右边的方块不包含两边
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y, x-1);
setNum(y+1, x);
}
if((x == 0) && (y == 0) && item[8*y+x]->spreadFlag == 0)//左上角
{
item[8*y+x]->spreadFlag = 1;
setNum(y, x+1);
setNum(y+1, x);
}
if((x == 7) && (y == 7) && item[8*y+x]->spreadFlag == 0)//右下角
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y, x-1);
}
if((y == 7) && (x == 0) && item[8*y+x]->spreadFlag == 0)//左下角
{
item[8*y+x]->spreadFlag = 1;
setNum(y-1, x);
setNum(y, x+1);
}
if((x == 7)&&(y == 0) && item[8*y+x]->spreadFlag == 0)//右上角
{
item[8*y+x]->spreadFlag = 1;
setNum(y+1, x);
setNum(y, x-1);
}
item[8*y+x]->IsflagOrwhiteblock += 2;
break;
}
}
void myitem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton && flag == 0 && IsflagOrwhiteblock == 0)//方块未点击状态,左击鼠标并且不是雷
{
m_x = this->pos().x();
m_y = this->pos().y();//获取此块坐标
setNum(m_y/48, m_x/48);//取[0,0]->[7,7],判断周围雷的个数并显示,或者蔓延没有雷的情况
}else if(event->button() == Qt::LeftButton && flag == 1 && IsflagOrwhiteblock == 0)//方块未点击状态,左击是雷
{
this->setPixmap(QPixmap(":/source_pic/l1"));//设置雷的图标
gameoverFlag = 1;//游戏结束
}else if(event->button() == Qt::RightButton && flag == 1)//右击是雷的方块
{
if(IsflagOrwhiteblock == 0)//方块还没被点过
{
this->setPixmap(QPixmap(":/source_pic/q1"));//插旗
IsflagOrwhiteblock++;//表示此块已经被插上了旗
flagNum++;//找到雷的个数加一
}else if(IsflagOrwhiteblock == 1)//如果这块已经被插上了旗子了
{
this->setPixmap(QPixmap(":/source_pic/f3"));//将旗子取消掉
IsflagOrwhiteblock--;//状态回到为点之前
flagNum--;//找到的雷的个数减一
}
}else if(event->button() == Qt::RightButton && flag == 0)//右击不是雷的方块
{
if(IsflagOrwhiteblock == 0)//没点过则插旗,显示此块已经插旗
{
this->setPixmap(QPixmap(":/source_pic/q1"));
IsflagOrwhiteblock++;
flagNum--;//插错旗-1
}else if(IsflagOrwhiteblock == 1)//已经插旗,则取消插旗
{
this->setPixmap(QPixmap(":/source_pic/f3"));
IsflagOrwhiteblock--;
flagNum++;//取消插错的旗+1
}
}
/*
else if(event->button() == Qt::MidButton && flag == 1 && IsflagOrwhiteblock == 0)
{
this->setPixmap(QPixmap(":/source_pic/q1"));
flagNum++;
}
*/
Click++;//点击的次数加一
}
至此所有的代码已经完成,欢迎大家一起学习。
github地址:
https://github.com/gt19930910/saolei