用Qt实现扫雷

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的认识进一步提高了。

猜你喜欢

转载自blog.csdn.net/livingsu/article/details/104774193
今日推荐