github源代码:Qt扫雷源代码
扫雷是很多人都玩过的小游戏,包括我。无奈win10没有了以前xp自带的很多经典游戏,于是我用Qt实现扫雷,并想要进一步了解Qt的有关机制。
预览效果:
一、游戏基本组件
雷区是一个个的小方格,很容易想到这些都是一个个图元,图元有很多参数来表示自己的状态(是否被挖掘、被标记等),鼠标点击到某个图元就触发这个图元的鼠标函数,从而改变它的状态。
图片素材来自别人的博客,我直接拿来用了:
myItem(图元类)
图元类myItems双继承于QObject(信号与槽机制)和QGraphicsPixmapItem(转化为像素图元)。通过将图元放到场景QGraphicsScene中,然后设置ui组件QGraphicsView->setScene来设置场景,这样就能显示出一个个的图元了。
class myItem : public QObject,public QGraphicsPixmapItem
{
Q_OBJECT
//省略其他函数和信号
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)override;
private: //参数
int row;
int col;
bool sweeped=false;//是否已经挖掘
bool mine=false;//有无地雷
bool mark=false;//有无旗子标记
bool stepMine=false;//是否踩雷
int numOfMines=0;//周围地雷个数
};
myItem类要包含row和col,因为一旦挖到一个没有雷并且周围地雷数也为0的格子时,会自动打开周围的地图,需要发送信号,这个信号必须包含被挖格子的位置信息。
重写鼠标点击函数mousePressEvent:(我一开始写的是鼠标释放函数mouseReleaseEvent(),但是这个函数不响应右键和左右键双击的情况,所以我放弃了)
void myItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
if(event->buttons()==(Qt::LeftButton|Qt::RightButton)){
//左右键同时按下
if((sweeped&&numOfMines!=0)||(!sweeped&&mark)){
//已被挖掘且有数字or未挖掘被标记
emit doubleClickSignal(row,col);//让scene检查并打开周围的空格,调用myScene.findBlock()
}
}else if(event->button()==Qt::LeftButton){
//左键点击
simulateLeftClick();//模拟左键点击动作
}else if(event->button()==Qt::RightButton){
if(!sweeped){
setMark(!mark);
emit markChangedSignal();//改变数字显示器显示的剩余地雷数
}
}
emit checkSignal();//检测游戏是否成功
}
myScene(场景类)
myScene包含一个myItem类对象指针的二维数组。
class myScene : public QObject, public QGraphicsScene
{
//省略其他函数和信号
private: //参数
static const int MAX_HEIGHT=24;
static const int MAX_WIDTH=30;
myItem *items[MAX_WIDTH][MAX_HEIGHT];
int mine=10;//地雷数
int height=9;//地图宽高
int width=9;
};
注意:每次初始化时,调用this->clear()之后需要重新给items指针数组分配空间,因为clear()会自动delete掉原来的空间。
void myScene::initialize(){
this->clear();
for(int i=0;i<MAX_WIDTH;++i){
//状态初始化
for(int j=0;j<MAX_HEIGHT;++j){
this->items[i][j]=new myItem();
//省略其他语句
}
}
for(int i=0;i<width;++i){
//显示大小
for(int j=0;j<height;++j){
this->addItem(this->items[i][j]);
}
}
int nx,ny,numOfMines;
int dx[8]={
-1,-1,-1,0,0,1,1,1};
int dy[8]={
-1,0,1,-1,1,-1,0,1};
for(int i=0;i<width;++i){
//设置周围砖块数
for(int j=0;j<height;++j){
numOfMines=0;
for(int k=0;k<8;++k){
nx=i+dx[k];
ny=j+dy[k];
if(0<=nx&&nx<width&&0<=ny&&ny<height){
if(this->items[nx][ny]->isMine())numOfMines++;
}
}
this->items[i][j]->setNumOfMines(numOfMines);
}
}
//省略其他语句
}
findBlock()函数
整个扫雷比较核心的东西就是自动打开周围格子findBlock()。当左击一个没有地雷并且周围地雷数不为0的格子时,就只打开它自己。当左击一个没有地雷并且周围地雷数为0的格子时,会自动打开这个格子周围所有的格子(模拟左击)。那么用递归时很容易实现的。
void myScene::findBlocks(int row,int col){
items[row][col]->setSweeped(true);//模拟点击
if(items[row][col]->getNumOfMines()!=0)return;//有数字的方块就不展开了
int nx,ny;
int dx[8]={
-1,-1,-1,0,0,1,1,1};
int dy[8]={
-1,0,1,-1,1,-1,0,1};
for(int i=0;i<8;++i){
nx=row+dx[i];
ny=col+dy[i];
if(0<=nx&&nx<width&&0<=ny&&ny<height&&!items[nx][ny]->isSweeped()){
//下标合法并且还未被挖
findBlocks(nx,ny);
}
}
}
二、一些界面细节
1.如何使场景类scene大小和视图类view的显示大致相同呢?
scene的大小取决于所添加的图元大小,比如添加myScene->addItem(items[0][0),设置图元位置items[0)[0)->setPos(0,0),
每个图元大小为20x20,那么myScene的大小就是20x20大小。当scene大小不超过view时,默认设置是将scene的中心和view的中心重合的(中心是矩形两对角线重合处),那么设置view的大小比scene大一点点就可以了,比如设置为23x23就行。
2.view出现了滚动框怎么去掉?
this->ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//去掉横向滚动框
this->ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//去掉竖向滚动框
3.主窗口界面不建议使用布局,因为不适合管理。
用setGeometry设置位置,然后用setFixedSize设置固定大小就很方便。
4.初级、中级、高级、自定义地图设置统一用一个函数写。
写一个自定义函数customSet(int width,int height,int mine),就可以方便的调用。
void MainWindow::on_actionPrimary_triggered()
{
customSet(9,9,10);
}
void MainWindow::on_actionMid_triggered()
{
customSet(16,16,40);
}
void MainWindow::on_actionSenior_triggered()
{
customSet(30,16,99);
}
5.两块数字显示屏(显示3位数字)也可以用同一个类numScene
先写一个numItem图元类,再调用这个类即可。
void numItem::setNum(int a){
if(a==0)this->setPixmap(QPixmap(":/num/pic/num0.png"));
else if(a==1)this->setPixmap(QPixmap(":/num/pic/num1.png"));
else if(a==2)this->setPixmap(QPixmap(":/num/pic/num2.png"));
else if(a==3)this->setPixmap(QPixmap(":/num/pic/num3.png"));
else if(a==4)this->setPixmap(QPixmap(":/num/pic/num4.png"));
else if(a==5)this->setPixmap(QPixmap(":/num/pic/num5.png"));
else if(a==6)this->setPixmap(QPixmap(":/num/pic/num6.png"));
else if(a==7)this->setPixmap(QPixmap(":/num/pic/num7.png"));
else if(a==8)this->setPixmap(QPixmap(":/num/pic/num8.png"));
else if(a==9)this->setPixmap(QPixmap(":/num/pic/num9.png"));
}
void numScene::setNumer(int num){
if(num>=0){
this->clear();
int hundred,ten,one;//百位,十位,个位
hundred=num/100;
ten=(num%100)/10;
one=num%10;
numItem *hunItem,*tenItem,*oneItem;
hunItem=new numItem;
tenItem=new numItem;
oneItem=new numItem;
hunItem->setPos(1,0);
tenItem->setPos(20,0);
oneItem->setPos(39,0);
hunItem->setNum(hundred);
tenItem->setNum(ten);
oneItem->setNum(one);
this->addItem(hunItem);
this->addItem(tenItem);
this->addItem(oneItem);
}
}
结束
通过一个很经典的游戏——扫雷,我对Qt的认识进一步提高了。