Qt5罗小黑战记(推箱子)

简要介绍:

一、QT实现儿时推箱子,当然界面改良很多,可加载地图

 

 

 

二、播放本地音乐,添加本地音乐列表

 

三、自己可以制作地图

在讲解之前我得感谢我的一位福建 漳州的朋友,我听取了她的很多宝贵意见,然后全都实现了才做出这个多功能的程序来

分节详解

我得简单介绍下我的一个习惯,那就是.cpp文件中注释颇少,因为每个变量以及函数要实现的功能我都会在头文件中详细说明,写代码的时候想用什么我就直接去头文件中copy。

添加一个pushbox.cpp和pushbox.h,往里面添加一个PushBox类。

pushbox.h中包含以下头文件

#include <QWidget>
#include <QPainter>
#include <QImage>
#include <QPalette>
#include <QMediaPlayer>
#include <QPixmap>
#include <QLabel>
#include <QMovie>
#include <QSlider>
#include <QTextBrowser>
#include <QLCDNumber>
#include <QPushButton>
#include <QLayout>
#include <QShortcut>
#include <QKeyEvent>
#include <QCloseEvent>
#include <QFileInfoList>
#include <QMediaPlaylist>
#include <fstream>
#include <QMessageBox>
#include <QFileDialog>
#include <QInputDialog>
#include <Qtime>
#include <qDebug>
#include "makemap.h" //加入自己制作地图

 定义地图元素大小(以QLabel展示,即:QLabel的固定大小)

#ifdef LabelSIZE
#undef LabelSIZE
#endif
#define LabelSIZE 35

定义地图元素,分别是空地、人在空地、人在目的地、箱子在空地、箱子在目的地、目的地、墙壁。

typedef enum
{
    blankMapElement,
    peopleMapElement,
    peopleInDesMapElement,
    boxMapElement,
    markboxMapElement,
    destinationMapElement,
    wallMapElement
} MapElement;

定义移动方向,分别是上、下、左、右。

typedef enum
{
    upDirect,
    downDirect,
    leftDirect,
    rightDirect
} Direct;

为了消边缘空地定义的角扩散方向,分别是↘↙↗↖(在后面对应函数的定义中有详解)。

typedef enum
{
    right_down,
    left_down,
    right_up,
    left_up
} CheckDirect;

为了实现撤销功能而专门记录人的移动所定义一个链表,包含了人移动的方向和该移动是否有导致箱子移动。我查看过网上其他人实现撤销这功能,有人提出了保存人的坐标,我觉得没必要,因为可以利用记录好的方向来反推箱子,这样即节省了内存又容易实现,功能代码也容易实现,因为正常推箱子是包含了人移动不了的情况,而反推箱子的话只可能是游戏人物发生了移动,情况明显不如正常推箱子得多。isMoveBox的作用不好解释,只能在自己写过之后才能明白其中妙义。

typedef struct Structure
{
    Direct direct;
    bool isMoveBox;
    struct Structure *next;
} Document;

PushBox类中的声明,这些变量和函数的功能可以从我的注释中获取,这里我就不做解释了,具体的实现后面有简要介绍。

class PushBox : public QWidget
{
    Q_OBJECT
  public:
    explicit PushBox(QWidget *parent = nullptr);                                                 
    ~PushBox();

  private:
    short W, H;                                                                                  //宽数、长数
    bool isMusicRun;                                                                             //音乐是否播放
    short musicVolume;                                                                           //音乐音量
    MapElement **Map;                                                                            //内地图数组
    MapElement **MapBackUp;                                                                      //内地图恢复数组
    short siteX, siteY;                                                                          //人的坐标
    short score;                                                                                 //分数
    short topScore;                                                                              //总分
    bool banQMessageBox = false;                                                                 //通关仅提示一次
    QString musicFileName;                                                                       //当前播放音乐名
    Document *document;                                                                          //记录走的步骤
    QLabel ***MapLabel;                                                                          //外地图数组
    QPushButton *runMusicBtn;                                                                    //播放或暂停音乐按钮
    QLCDNumber *scoreLcdNumber;                                                                  //显示分数
    QString fileString;                                                                          //文件名
    QShortcut *undoShortcut;                                                                     //撤销快捷键
    short getMapWidth(void);                                                                     //获取宽数
    short getMapHeight(void);                                                                    //获取长数
    void resoureLoad(void);                                                                      //资源加载
    void initUI(void);                                                                           //初始化开始界面
    void updateUI(void);                                                                         //更新地图
    void saveDo(const char d, const char r, bool);                                               //保存走的步骤
    void checkClearLabel(short x, short y, CheckDirect check);                                   //下面函数的子函数
    void ClearBorderLabel();                                                                     //去掉墙壁之外的Label图片
    void operatePeople(int, int);                                                                //游戏人物的响应
    void mouseMoveEvent(QMouseEvent *event);                                                     //重写鼠标移动事件函数
    void keyPressEvent(QKeyEvent *);                                                             //重写按键事件函数
    void closeEvent(QCloseEvent *);                                                              //重写关闭程序事件函数
    void ToString(void);                                                                         //遍历内地图数组
    void DoToString(void);                                                                       //遍历已经走的步骤
    QPixmap BlankPixmap, MainBackGroundPixmap, WallPixmap;                                       //地图图片资源
    QMovieP LXHMovieP, FoodMovieP, LXHHaveFoodMovieP, PeopleMovieP;                              //地图GIF资源
    QPixmap LastMusicPixmap, NextMusicPixmap, StopMusicPixmap, PauseMusicPixmap, RunMusicPixmap; //音乐播放器图片资源
    QMediaPlayer *player;                                                                        //音乐资源
    QStringList musicList;                                                                       //当前音乐资源列表

  signals:

  private slots:
    void undo(void);                                                                             //撤销
    void loadMusicFileListsSlot(void);                                                           //加载本地播放列表
    void musicFileListsSlot(void);                                                               //打开用户音乐列表
    void lastOneMusicSlot(void);                                                                 //上一首
    void nextOneMusicSlot(void);                                                                 //下一首
    void setMusicVolumeSlot(int value);                                                          //调节音量
    void runMusicSlot(void);                                                                     //正在播放按钮触发函数
    void MakeMapSlot(void);                                                                      //转到制作地图窗口(关闭本窗口)
};

pushbox.cpp我以游戏的操作到音乐播放器最后到自制地图的顺序来讲解。

游戏的操作

构造函数中添加“是否需要加载新地图的选择”,以及将文件解调成一幅游戏地图数组,加载程序所需要的资源文件,最后画出UI来。

PushBox::PushBox(QWidget *parent) : QWidget(parent)
{
    qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
    switch (QMessageBox::information(nullptr, QObject::tr("提示"), QObject::tr("是否需要加载新地图"), QMessageBox::Yes | QMessageBox::No))
    {
    case QMessageBox::Yes:
        fileString = QFileDialog::getOpenFileName(this, QObject::tr("打开地图文件"));
        break;
    case QMessageBox::No:
        fileString = "data.chf";
        break;
    default:
        return;
    }
    if (fileString.isEmpty())
        fileString = "data.chf";

    using namespace std;
    ifstream in(fileString.toLocal8Bit());
    if (in.is_open())
    {
        char check;
        short accout = 0;
        do
        {
            in >> check >> check;
            ++accout;
        } while (check != ';');
        W = accout;
        do
        {
            in >> check;
            ++accout;
        } while ((check >= '0' && check < '8') || check == ',' || check == ';');

        H = (accout + W - 1) / W >> 1;
        in.close();
        if (!(H || W))
        {
            QMessageBox::warning(this, tr("打开地图"), tr("失败"));
            this->close();
        }
    }
    else
    {
        QMessageBox::information(this, tr("下载地图"), tr("未找到地图") + fileString);
        this->close();
    }

    Map = new MapElement *[H];
    for (int i = 0; i < H; i++)
        Map[i] = new MapElement[W];
    MapBackUp = new MapElement *[H];
    for (int i = 0; i < H; i++)
        MapBackUp[i] = new MapElement[W];
    in.open(fileString.toLatin1().data());
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W; j++)
        {
            char temp;
            in >> temp;
            MapBackUp[i][j] = Map[i][j] = (MapElement)(temp - '0');
            in >> temp;
        }
    }
    in.close();
    document = NULL;
    resoureLoad(); //首先加载图片及音乐资源
    initUI();
    undoShortcut = new QShortcut(this);
    undoShortcut->setKey(tr("ctrl+z"));
    undoShortcut->setAutoRepeat(true);
    connect(undoShortcut, SIGNAL(activated()), this, SLOT(undo()));
    undoShortcut = new QShortcut(this);
    undoShortcut->setKey(Qt::Key_Escape);
    connect(undoShortcut, SIGNAL(activated()), this, SLOT(close()));
}

析构函数中需要注意了,一不小心可能就会出现内存泄漏,容易导致程序死机。

PushBox::~PushBox()
{
    delete Map;
    delete MapBackUp;
    delete document;
    delete MapLabel;
    delete scoreLcdNumber;
    delete undoShortcut;
    delete LXHMovieP;
    delete FoodMovieP;
    delete LXHHaveFoodMovieP;
    delete PeopleMovieP;
    delete player;
}

下面这两个函数暂时在我这是多余的,当时写的时候想到了以后可能得从外部查找这个地图的长宽。

short PushBox::getMapWidth()
{
    return this->W;
}

short PushBox::getMapHeight()
{
    return this->H;
}

加载这个程序所需要的资源(图片、音乐),在构造函数中有被调用。

提示:.jpg和.png可以通过QPixmap暂存(.jpg和.png千万不可以在外面直接更改后缀,会导致Qt解调乱码。他们都有其编码方式,分别使用Qt解调就好,没必要全都换成.jpg或者.png)

load函数可以通过文件地址来提取图片,也可以向下面用构造函数来提取

.gif可以通过QMovie暂存并播放(start)

通过构造函数来提取GIF

音乐可以通过QMediaPlayer暂存并播放(play)和暂停(stop),每首音乐其实都对应在你计算机当中的某一地址,可以使用QStringList来保存,如:

使用append来添加某首音乐的地址,clear清空音乐名列表。

QMediaPlayer类可以使用setMedia通过地址(参数中)来提取音乐

setVolume是设置音量的,音量范围在0-100

void PushBox::resoureLoad()
{
    WallPixmap          .load("image/wall.jpg");
    BlankPixmap         .load("image/blank.jpg");
    MainBackGroundPixmap.load("image/mainbackground.jpg");
    LastMusicPixmap     .load("image/lastmusic.jpg");
    NextMusicPixmap     .load("image/nextmusic.jpg");
    StopMusicPixmap     .load("image/stopmusic.jpg");
    PauseMusicPixmap    .load("image/pausemusic.jpg");
    RunMusicPixmap      .load("image/runmusic.jpg");
    PeopleMovieP        = new QMovie("image/people.gif");
    LXHMovieP           = new QMovie("image/lxh.gif");
    FoodMovieP          = new QMovie("image/food.gif");
    LXHHaveFoodMovieP   = new QMovie("image/lxhhavefood.gif");
    PeopleMovieP        ->start();
    LXHMovieP           ->start();
    FoodMovieP          ->start();
    LXHHaveFoodMovieP   ->start();
    musicList           .append("music/你还要我怎样.mp3");
    musicList           .append("music/带你去旅行.mp3");
    musicList           .append("music/《春娇与志明》中文版主题曲.mp3");
    musicList           .append("music/白夜.mp3");
    player              = new QMediaPlayer(this);
    //    connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64)));
    player              ->setMedia(QUrl::fromLocalFile(musicFileName = musicList.at(qrand() % musicList.size())));
    player              ->setVolume(musicVolume = 30);
    player              ->play();
    isMusicRun          = true;
}

加载完资源就可以根据构造函数中解调出来的地图来初始化界面了,在构造函数中有被调用。

setSizeConstraint(QLayout::SetFixedSize);用于固定布局大小保持最适合的size,只能在布局中使用,不能对QWidget对象使用过

setPixmap();和setMovie();是QLabel特有函数,就是图片或者视频放置到QLabel对象中

setScaledContents(true);让QLabel中放置的图片随着QLable的size自动收缩

setSpacing();设置各控件之间的间距

setMargin();设置各窗口之间的间距

setDigitCount(2);设置数码管的位数,这里是两位

display();数码管显示

scaled();QPixmap对象特有的函数,可以收缩QPixmap对象

注意:后面有很多的糟的触发按钮都是在这里被定义的

void PushBox::initUI()
{
    MapLabel = new QLabel **[H];
    for (int i = 0; i < H; i++)
    {
        MapLabel[i] = new QLabel *[W];
        for (int j = 0; j < W; j++)
        {
            MapLabel[i][j] = new QLabel;
        }
    }

    QGridLayout *MapLayout = new QGridLayout;
    MapLayout->setMargin(0);
    MapLayout->setSpacing(0);
    MapLayout->setSizeConstraint(QLayout::SetFixedSize);
    score = 0;
    topScore = 0;
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W; j++)
        {
            if (Map[i][j] == markboxMapElement)
            {
                ++score;
                ++topScore;
            }
            if (Map[i][j] == peopleInDesMapElement || Map[i][j] == destinationMapElement)
                ++topScore;
            switch (Map[i][j])
            {
            case blankMapElement:
                MapLabel[i][j]->setPixmap(BlankPixmap);
                break;
            case peopleMapElement:
            case peopleInDesMapElement:
                siteX = i, siteY = j;
                MapLabel[i][j]->setMovie(PeopleMovieP);
                break;
            case boxMapElement:
                MapLabel[i][j]->setMovie(LXHMovieP);
                break;
            case markboxMapElement:
                MapLabel[i][j]->setMovie(LXHHaveFoodMovieP);
                break;
            case destinationMapElement:
                MapLabel[i][j]->setMovie(FoodMovieP);
                break;
            case wallMapElement:
                MapLabel[i][j]->setPixmap(WallPixmap);
                break;
            }
            MapLabel[i][j]->setFixedSize(LabelSIZE, LabelSIZE);
            MapLabel[i][j]->setScaledContents(true);
            MapLayout->addWidget(MapLabel[i][j], i, j);
        }
    }

    qDebug() << "ENTERClearBorderLabel";
    ClearBorderLabel();

    QHBoxLayout *scoreLayout = new QHBoxLayout;
    scoreLayout->addStretch();
    scoreLayout->addWidget(new QLabel(tr("分数:")));
    scoreLcdNumber = new QLCDNumber;
    scoreLcdNumber->setDigitCount(2);
    scoreLcdNumber->display(QString::number(score));
    scoreLayout->addWidget(scoreLcdNumber);
    scoreLayout->setSizeConstraint(QLayout::SetFixedSize);

    QPushButton *loadMusicFileListsBtn = new QPushButton(tr("添加本地播放列表"));
    loadMusicFileListsBtn->setFocusPolicy(Qt::NoFocus); //让按钮失去焦点
    connect(loadMusicFileListsBtn, SIGNAL(clicked(bool)), this, SLOT(loadMusicFileListsSlot()));

    QPushButton *lastOneMusicBtn = new QPushButton;
    lastOneMusicBtn->setIcon(LastMusicPixmap);
    lastOneMusicBtn->setFocusPolicy(Qt::NoFocus); //让按钮失去焦点
    connect(lastOneMusicBtn, SIGNAL(clicked(bool)), this, SLOT(lastOneMusicSlot()));

    runMusicBtn = new QPushButton;
    runMusicBtn->setIcon(RunMusicPixmap);
    runMusicBtn->setFocusPolicy(Qt::NoFocus); //让按钮失去焦点
    connect(runMusicBtn, SIGNAL(clicked(bool)), this, SLOT(runMusicSlot()));

    QPushButton *nextOneMusicBtn = new QPushButton;
    nextOneMusicBtn->setIcon(NextMusicPixmap);
    nextOneMusicBtn->setFocusPolicy(Qt::NoFocus); //让按钮失去焦点
    connect(nextOneMusicBtn, SIGNAL(clicked(bool)), this, SLOT(nextOneMusicSlot()));

    QSlider *musicVolumeSlider = new QSlider;
    musicVolumeSlider->setFocusPolicy(Qt::NoFocus); //让滑动条失去焦点
    musicVolumeSlider->setValue(musicVolume);
    musicVolumeSlider->setOrientation(Qt::Horizontal); // 水平方向
    musicVolumeSlider->setMinimum(0);
    musicVolumeSlider->setMaximum(100);
    connect(musicVolumeSlider, SIGNAL(valueChanged(int)), this, SLOT(setMusicVolumeSlot(int)));

    QHBoxLayout *musicVolumeLayout = new QHBoxLayout;
    musicVolumeLayout->addWidget(new QLabel(tr("音量控制")));
    musicVolumeLayout->addWidget(musicVolumeSlider);

    QHBoxLayout *musicLayout = new QHBoxLayout;
    musicLayout->setSpacing(0);
    musicLayout->setMargin(0);
    //    musicLayout->addWidget(new QLabel(tr("音乐播放器:")));
    musicLayout->addStretch();
    musicLayout->addWidget(lastOneMusicBtn);
    musicLayout->addWidget(runMusicBtn);
    musicLayout->addWidget(nextOneMusicBtn);
    musicLayout->addStretch();

    QPushButton *makeMapBtn = new QPushButton(tr("自制地图"));
    makeMapBtn->setFocusPolicy(Qt::NoFocus); //让按钮失去焦点
    connect(makeMapBtn, SIGNAL(clicked(bool)), this, SLOT(MakeMapSlot()));
    makeMapBtn->setStyleSheet(tr("color: red"));

    QTextBrowser *displayTextBrowser = new QTextBrowser;
    displayTextBrowser->setFixedSize(180, 80);
    displayTextBrowser->setEnabled(false);
    displayTextBrowser->setTextColor(Qt::GlobalColor::blue);
    displayTextBrowser->setText(tr("此版本已经可以使用鼠标来移动\n"
                                   "上(↑/W)            下(↓/S)\n"
                                   "左(←/A)            右(→/D)\n"
                                   "重来(R)            关闭(Esc)\n"
                                   "撤销(ctrl+z)"));

    QVBoxLayout *clueLayout = new QVBoxLayout;
    clueLayout->addWidget(displayTextBrowser);

    QHBoxLayout *hClueLayout = new QHBoxLayout;
    hClueLayout->addStretch();
    hClueLayout->addLayout(clueLayout);

    QVBoxLayout *rightLayout = new QVBoxLayout;
    rightLayout->addLayout(scoreLayout);
    rightLayout->addStretch();
    rightLayout->addWidget(loadMusicFileListsBtn);
    rightLayout->addLayout(musicLayout);
    rightLayout->addLayout(musicVolumeLayout);
    rightLayout->addStretch();
    rightLayout->addWidget(makeMapBtn);
    rightLayout->addStretch();
    rightLayout->addLayout(hClueLayout);

    QVBoxLayout *leftLayout = new QVBoxLayout;
    leftLayout->addStretch();
    leftLayout->addLayout(MapLayout);
    leftLayout->addStretch();
    QHBoxLayout *mainLayout = new QHBoxLayout(this);
    mainLayout->addLayout(leftLayout);
    mainLayout->addLayout(rightLayout);
    mainLayout->setSizeConstraint(QLayout::SetFixedSize);
    setWindowTitle(tr("罗小黑战记"));
    setAutoFillBackground(true);
    QPalette pal = this->palette();
    QPixmap temp = MainBackGroundPixmap;
    MainBackGroundPixmap = temp.scaled(this->width() * 1.1, this->height() * 1.1);
    pal.setBrush(backgroundRole(), MainBackGroundPixmap);
    setPalette(pal);
}

为了方便调试我特意写了两个遍历函数,如下:

void PushBox::ToString()
{
    QString tempString;
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W - 1; j++)
        {
            tempString = tempString + Map[i][j] + ",";
        }
        tempString = tempString + Map[i][W - 1] + ";\n";
    }
    qDebug() << tempString;
}

void PushBox::DoToString()
{
    if (document == NULL)
    {
        qDebug() << "还未开始走呢!";
    }
    else
    {
        QString tempString;
        Document *temp = document;
        do
        {
            tempString = tempString + QString::number(temp->direct) + "<-";
            temp = temp->next;
        } while (temp != NULL);
        qDebug() << tempString;
    }
}

重写按键点击函数以及鼠标移动函数(鼠标让游戏人物移动)来接收游戏人物的移动信号

geometry();获取QLable所画出的QRect

contains();判断QRect中是否包含某一点(QPoint)

QCursor::pos();获取鼠标位置(QPoint)

void PushBox::keyPressEvent(QKeyEvent *event)
{
    char downGo = 0, rightGo = 0; //人的移动方向
    switch (event->key())
    {
    case Qt::Key_Left:
    case Qt::Key_A:
        downGo = 0, rightGo = -1;
        break;
    case Qt::Key_Right:
    case Qt::Key_D:
        downGo = 0, rightGo = 1;
        break;
    case Qt::Key_Up:
    case Qt::Key_W:
        downGo = -1, rightGo = 0;
        break;
    case Qt::Key_Down:
    case Qt::Key_S:
        downGo = 1, rightGo = 0;
        break;
    case Qt::Key_R: //重新返回,加载初始地图
        updateUI();
        return;
    default:
        return;
    }
    operatePeople(downGo, rightGo);
    scoreLcdNumber->display(QString::number(score));
    if (score == topScore && !banQMessageBox)
    {
        banQMessageBox = true;
        QMessageBox::information(this, tr("推箱子游戏"), tr("恭喜小主人过关了!"));
    }
}
void PushBox::mouseMoveEvent(QMouseEvent *event)
{
    char downGo = 0, rightGo = 0; //人的移动方向

    if (MapLabel[siteX - 1][siteY]->geometry().contains(this->mapFromGlobal(QCursor::pos())))
    {
        downGo = -1, rightGo = 0;
    }
    else if (MapLabel[siteX][siteY - 1]->geometry().contains(this->mapFromGlobal(QCursor::pos())))
    {
        downGo = 0, rightGo = -1;
    }
    else if (MapLabel[siteX + 1][siteY]->geometry().contains(this->mapFromGlobal(QCursor::pos())))
    {
        downGo = 1, rightGo = 0;
    }
    else if (MapLabel[siteX][siteY + 1]->geometry().contains(this->mapFromGlobal(QCursor::pos())))
    {
        downGo = 0, rightGo = 1;
    }
    operatePeople(downGo, rightGo);
    scoreLcdNumber->display(QString::number(score));
    if (score == topScore && !banQMessageBox)
    {
        banQMessageBox = true;
        QMessageBox::information(this, tr("罗小黑战记"), tr("恭喜小主人过关了!"));
    }
    return;
}

游戏人物的移动我另做出来一个名为operatePeople的操作函数

void PushBox::operatePeople(int downGo, int rightGo)
{
    switch (Map[siteX + downGo][siteY + rightGo])
    {
    case blankMapElement: //人前面是空地
        Map[siteX + downGo][siteY + rightGo] = peopleMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        saveDo(downGo, rightGo, false);
        if (Map[siteX][siteY] == peopleMapElement)
        {
            Map[siteX][siteY] = blankMapElement;
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        }
        else
        {
            Map[siteX][siteY] = destinationMapElement;
            MapLabel[siteX][siteY]->setMovie(FoodMovieP);
        }
        siteX += downGo, siteY += rightGo;
        break;
    case boxMapElement://人前面是在空地上的箱子
        if ((Map[siteX + 2 * downGo][siteY + 2 * rightGo] == wallMapElement)
                || (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == boxMapElement)
                || (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == markboxMapElement)) //判断箱子是否可以移动
            break;                                                                                                                                                                   //退出
        Map[siteX + downGo][siteY + rightGo] = peopleMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        ;
        saveDo(downGo, rightGo, true);
        if (Map[siteX][siteY] == peopleMapElement)
        {
            Map[siteX][siteY] = blankMapElement;
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        }
        else
        {
            Map[siteX][siteY] = destinationMapElement;
            MapLabel[siteX][siteY]->setMovie(FoodMovieP);
        }
        if (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == blankMapElement)
        {
            Map[siteX + 2 * downGo][siteY + 2 * rightGo] = boxMapElement;
            MapLabel[siteX + 2 * downGo][siteY + 2 * rightGo]->setMovie(LXHMovieP);
        }
        else
        {
            ++score;
            Map[siteX + 2 * downGo][siteY + 2 * rightGo] = markboxMapElement;
            MapLabel[siteX + 2 * downGo][siteY + 2 * rightGo]->setMovie(LXHHaveFoodMovieP);
        }
        siteX += downGo, siteY += rightGo;
        break;
    case markboxMapElement:                                                                                                                                                                        //人前面是在目的地的箱子
        if ((Map[siteX + 2 * downGo][siteY + 2 * rightGo] == wallMapElement)
                || (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == boxMapElement)
                || (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == markboxMapElement)) //判断箱子是否可以移动
            break;                                                                                                                                                                   //退出
        Map[siteX + downGo][siteY + rightGo] = peopleInDesMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        saveDo(downGo, rightGo, true);
        if (Map[siteX][siteY] == peopleMapElement)
        {
            Map[siteX][siteY] = blankMapElement;
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        }
        else
        {
            Map[siteX][siteY] = destinationMapElement;
            MapLabel[siteX][siteY]->setMovie(FoodMovieP);
        }
        if (Map[siteX + 2 * downGo][siteY + 2 * rightGo] == blankMapElement)
        {
            --score;
            Map[siteX + 2 * downGo][siteY + 2 * rightGo] = boxMapElement;
            MapLabel[siteX + 2 * downGo][siteY + 2 * rightGo]->setMovie(LXHMovieP);
        }
        else
        {
            Map[siteX + 2 * downGo][siteY + 2 * rightGo] = markboxMapElement;
            MapLabel[siteX + 2 * downGo][siteY + 2 * rightGo]->setMovie(LXHHaveFoodMovieP);
        }
        siteX += downGo, siteY += rightGo;
        break;
    case destinationMapElement: //人前面是目的地
        Map[siteX + downGo][siteY + rightGo] = peopleInDesMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        saveDo(downGo, rightGo, false);
        if (Map[siteX][siteY] == peopleMapElement)
        {
            Map[siteX][siteY] = blankMapElement;
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        }
        else
        {
            Map[siteX][siteY] = destinationMapElement;
            MapLabel[siteX][siteY]->setMovie(FoodMovieP);
        }
        siteX += downGo, siteY += rightGo;
        break;
    default:
        break;
    }
}

为了去掉墙壁之外空地,所以写了ClearBorderLabel函数,另外的checkClearLabel函数是配合前者而写的

去掉之前:

去掉之后:

这样一对比相信大家应该能看明白区别,功能代码如下:(提供下思路:实现如何最快扫完一圈得到分开的块)

void PushBox::checkClearLabel(short x, short y, CheckDirect check)
{
    switch (check)
    {
    case right_down:
        if (Map[x][y] == blankMapElement)
        {
            MapLabel[x][y]->clear();
            if (x == H - 1 || y == W - 1)
                ;
            else
            {
                checkClearLabel(x + 1, y, right_down);
                checkClearLabel(x, y + 1, right_down);
            }
        }
        return;
    case left_down:
        if (Map[x][y] == blankMapElement)
        {
            MapLabel[x][y]->clear();
            if (x == H - 1 || y == 0)
                ;
            else
            {
                checkClearLabel(x + 1, y, left_down);
                checkClearLabel(x, y - 1, left_down);
            }
        }
        return;
    case right_up:
        if (Map[x][y] == blankMapElement)
        {
            MapLabel[x][y]->clear();
            if (x == 0 || y == W - 1)
                ;
            else
            {
                checkClearLabel(x - 1, y, right_up);
                checkClearLabel(x, y + 1, right_up);
            }
        }
        return;
    case left_up:
        if (Map[x][y] == blankMapElement)
        {
            MapLabel[x][y]->clear();
            if (x == 0 || y == 0)
                ;
            else
            {
                checkClearLabel(x - 1, y, left_up);
                checkClearLabel(x, y - 1, left_up);
            }
        }
        return;
    }
}

void PushBox::ClearBorderLabel()
{
    int x = 0, y = 0; //right-down
    while (x < H - 1)
    {
        while (Map[x][0] != blankMapElement && x < H - 1) //障碍物
            x += 1;
        checkClearLabel(x, 0, right_down);
        x += 1;
    }
    while (y < W - 1)
    {
        while (Map[0][y] != blankMapElement && y < W - 1)
            y += 1;
        checkClearLabel(0, y, right_down);
        y += 1;
    }
    x = H - 1, y = W - 1; //left-up
    while (x > 0)
    {
        while (Map[x][W - 1] && x > 0)
            x -= 1;
        checkClearLabel(x, W - 1, left_up);
        x -= 1;
    }
    while (y > 0)
    {
        while (Map[H - 1][y] && y > 0)
            y -= 1;
        checkClearLabel(H - 1, y, left_up);
        y -= 1;
    }
}

因为我想要添加一个撤销的功能,所以凡是每回游戏人物移动了就得saveDo,功能就是向链表中添加一个头

void PushBox::saveDo(const char d, const char r, bool mov)
{
    if (document == NULL)
    {
        document = new Document;
        document->next = NULL;
        document->direct = (Direct)((d == 0) ? (r + 5) >> 1 : (d + 1) >> 1);
        document->isMoveBox = mov;
    }
    else
    {
        Document *temp = new Document;
        temp->direct = (Direct)((d == 0) ? (r + 5) >> 1 : (d + 1) >> 1);
        temp->isMoveBox = mov;
        temp->next = document;
        document = temp;
    }
}

链表已经建好,接下来我想要实现“撤销”功能。注意:每次撤销了就得删去链表的头

QMessageBox::warning();警告弹出框

void PushBox::undo()
{
    if (document == NULL)
    {
        QMessageBox::warning(this, tr("警告"), tr("不能再撤销了!"));
        return;
    }

    char downGo = 0, rightGo = 0; //人的移动方向
    switch (document->direct)
    {
    case upDirect:
        downGo = 1, rightGo = 0; //Direct = 1;
        break;
    case downDirect:
        downGo = -1, rightGo = 0; //Direct = 0
        break;
    case leftDirect:
        downGo = 0, rightGo = 1; //Direct = 3
        break;
    case rightDirect:
        downGo = 0, rightGo = -1; //Direct = 2
        break;
    default:
        return;
    }
    switch (Map[siteX + downGo][siteY + rightGo])
    {
    case blankMapElement: //人从空地而来
        Map[siteX + downGo][siteY + rightGo] = peopleMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        break;
    case destinationMapElement: //人从目的地而来
        Map[siteX + downGo][siteY + rightGo] = peopleInDesMapElement;
        MapLabel[siteX + downGo][siteY + rightGo]->setMovie(PeopleMovieP);
        break;
    default:
        return;
    }
    if (document->isMoveBox)
    {
        switch (Map[siteX - downGo][siteY - rightGo])
        {
        case boxMapElement:
            Map[siteX - downGo][siteY - rightGo] = blankMapElement;
            MapLabel[siteX - downGo][siteY - rightGo]->setPixmap(BlankPixmap);
            break;
        case markboxMapElement:
            Map[siteX - downGo][siteY - rightGo] = destinationMapElement;
            MapLabel[siteX - downGo][siteY - rightGo]->setMovie(FoodMovieP);
            score -= 1;
            break;
        }
        switch (Map[siteX][siteY])
        {
        case peopleMapElement:
            Map[siteX][siteY] = boxMapElement;
            MapLabel[siteX][siteY]->setMovie(LXHMovieP);
            break;
        case peopleInDesMapElement:
            Map[siteX][siteY] = markboxMapElement;
            MapLabel[siteX][siteY]->setMovie(LXHHaveFoodMovieP);
            score += 1;
        }
    }
    else
    {
        switch (Map[siteX][siteY])
        {
        case peopleMapElement:
            Map[siteX][siteY] = blankMapElement;
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
            break;
        case peopleInDesMapElement:
            Map[siteX][siteY] = destinationMapElement;
            MapLabel[siteX][siteY]->setMovie(FoodMovieP);
            break;
        }
    }
    siteX += downGo, siteY += rightGo;
    document = document->next;
    scoreLcdNumber->display(QString::number(score));
}

为了实现重新开始,重新加载地图数据(在按键点击事件中),还要重新绘制UI界面

void PushBox::updateUI()
{
    score = 0;
    for (char i = 0; i < H; ++i)
    {
        for (char j = 0; j < W; ++j)
        {
            switch (Map[i][j] = MapBackUp[i][j])
            {
            case blankMapElement: //空地
                MapLabel[i][j]->setPixmap(BlankPixmap);
                break;
            case peopleMapElement:
            case peopleInDesMapElement: //人
                siteX = i, siteY = j;
                MapLabel[i][j]->setMovie(PeopleMovieP);
                break;
            case boxMapElement: //箱子
                MapLabel[i][j]->setMovie(LXHMovieP);
                break;
            case markboxMapElement: //目的地的箱子
                ++score;
                MapLabel[i][j]->setMovie(LXHHaveFoodMovieP);
                break;
            case destinationMapElement: //目的地
                MapLabel[i][j]->setMovie(FoodMovieP);
                break;
            case wallMapElement: //墙
                MapLabel[i][j]->setPixmap(WallPixmap);
                break;
            }
        }
    }
    ClearBorderLabel();
}

我还想实现一个功能——退出保存记录,下次如果不读入新地图就开始上次结束时的地图。所以我又重写了关闭窗口事件函数

void PushBox::closeEvent(QCloseEvent *)
{
    switch (QMessageBox::question(this, tr("正在关闭中"), tr("是否保存进度?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes))
    {
    case QMessageBox::Yes:
        using namespace std;
    {
        ofstream out("data.chf");
        if (out.is_open())
        {
            for (int i = 0; i < H; i++)
            {
                for (int j = 0; j < W - 1; j++)
                {
                    out << Map[i][j] << ',';
                }
                out << Map[i][W - 1] << ';' << '\n';
            }
            out << EOF;
        }
        out.close();
    }
        break;
    case QMessageBox::No:;
        break;
    }
}

音乐播放器

基本的按钮肯定是暂停播放和上一首下一首和调音量啦

首先实现暂停和播放功能,我将这两个事件和在一起处理了,这就得引入一个新的变量,然后就可以进入糟并且知道当前状态来选择处理方式。

void PushBox::runMusicSlot()
{
    if (isMusicRun)
    {
        isMusicRun = false;
        runMusicBtn->setIcon(PauseMusicPixmap);
        player->pause();
    }
    else
    {
        isMusicRun = true;
        runMusicBtn->setIcon(RunMusicPixmap);
        player->play();
    }
}

然后就是实现上一首和下一首,这个我是用两个按钮分别对其事件处理。

void PushBox::lastOneMusicSlot()
{
    if (musicList.isEmpty())
        return;
    short currentMusic = musicList.indexOf(musicFileName);
    if (currentMusic == -1)
        musicFileName = musicList.at(qrand() % musicList.size());
    else if (currentMusic == 0)
        musicFileName = musicList.at(musicList.size() - 1);
    else
        musicFileName = musicList.at(currentMusic - 1);
    player->setMedia(QUrl::fromLocalFile(musicFileName));
    player->setVolume(30);
    player->play();
    runMusicBtn->setIcon(RunMusicPixmap);
}

void PushBox::nextOneMusicSlot()
{
    if (musicList.isEmpty())
        return;
    short currentMusic = musicList.indexOf(musicFileName);
    if (currentMusic == -1)
        musicFileName = musicList.at(qrand() % musicList.size());
    else if (currentMusic == musicList.size() - 1)
        musicFileName = musicList.at(0);
    else
        musicFileName = musicList.at(currentMusic + 1);
    player->setMedia(QUrl::fromLocalFile(musicFileName));
    player->setVolume(30);
    player->play();
    runMusicBtn->setIcon(RunMusicPixmap);
}

怎么调音量呢?很简单(在initUI中有),可以看到我使用到了一个QSlider控件,就像window的音量调节一样。

void PushBox::setMusicVolumeSlot(int value)
{
    musicVolume = value;
    player->setVolume(musicVolume);
}

 在这里音乐播放器还没有结束,我特意写了一个选择当地播放列表(在initUI中有)。在这里我强调一下,我在按钮的建立时都做了一个操作——让按钮失去焦点,为什么呢?因为不这么做的话键盘上的上下左右按键就不会去执行游戏人物的操作了,而是去选择相邻的按钮,然后等待你的回车,回车了那个按钮就算按下了。这样解释大家应该能懂吧!如下:

void PushBox::musicFileListsSlot()
{
    musicList.clear();
    musicList = QFileDialog::getOpenFileNames(this, tr("文件"), "music/", tr("音频文件(*.mp3)"));
}

 

自制地图

(在initUI中有),使用makeMapBtn来触发该事件。

简单介绍下,做一幅地图肯定需要知道该地图的横向和纵向元素个数,并且个数都不可能为0。所以在放置制作新地图之前得判断一下,其实这种判断也可以放在制作新地图的类里面,所以我在那个类里我做了两个构造函数,如你所见,前者传入的没有那两个参数。其实我就是把它给放在了构造函数里面。(这个是在makemap.cpp和makemap.h里写的MakeMap类)

QInputDialog::getInt();获取整型标准输入框

void PushBox::MakeMapSlot()
{
    int w = QInputDialog::getInt(this, tr("请输入"), tr("地图横向元素个数:"));
    int h = QInputDialog::getInt(this, tr("请输入"), tr("地图纵向元素个数:"));
    qDebug() << w << h;
    if (!(w || h))
    {
        QMessageBox::warning(this, tr("制作地图"), tr("输入有误!"));
        return;
    }
    else
    {
        MakeMap *newMap = new MakeMap(w, h);
        this->close();
    }
}

添加一个makemap.cpp和makemap.h,往里面添加一个MakeMap类。

makemap.h中包含以下头文件

#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QTextBrowser>
#include <QKeyEvent>
#include <QTimer>
#include <QLayout>
#include <QSplitter>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <fstream>
#include <qDebug>

定义地图元素大小

#ifdef LabelSIZE
#undef LabelSIZE
#endif
#define LabelSIZE 30

定义地图元素,分别是墙壁、人在空地、人在目的地、箱子在空地、箱子在目的地、空地、目的地

typedef enum
{
    wallElement,
    peopleElement,
    peopleInDesElement,
    boxElement,
    markboxElement,
    blankElement,
    destinationElement
} willAddElement;

MakeMap类中的声明,这些变量和函数的功能可以从我的代码中获取,这里我也就不做解释了,具体的实现后面有简要介绍。

class MakeMap : public QWidget
{
    Q_OBJECT
  public:
    explicit MakeMap(QWidget *parent = nullptr);
    MakeMap(int w, int h);
    ~MakeMap();

  private:
    short W, H;                                                                                                       //宽数、长数
    short siteX, siteY;                                                                                               //当前坐标
    willAddElement currentElement = blankElement;                                                                     //预判将要添加的元素
    char **Map;                                                                                                       //内地图数组
    QLabel ***MapLabel;                                                                                               //外地图数组
    QTextBrowser *clueTextBrowser;                                                                                    //用来提示当前坐标位置
    QTimer *m_pTimer;                                                                                                 //让地图元素闪动,来表示当前位置
    QPushButton *wallPushButton;                                                                                      //元素(墙壁)
    QPushButton *peoplePushButton;                                                                                    //元素(人在空地)
    QPushButton *peopleInDesPushButton;                                                                               //元素(人在目的地)
    QPushButton *boxPushButton;                                                                                       //元素(箱子在空地)
    QPushButton *markboxPushButton;                                                                                   //元素(箱子在目的地)
    QPushButton *blankPushButton;                                                                                     //元素(空地)
    QPushButton *destinationPushButton;                                                                               //元素(目的地)
    void addBorder();                                                                                                 //给坐标的标签画上框
    void deleteBorder();                                                                                              //给坐标的标签清次框
    void recoverFlash();                                                                                              //防止坐标的突然移动而导致闪出了问题
    void resoureLoad(void);                                                                                           //资源加载
    void initUI(void);                                                                                                //初始化开始界面
    void keyPressEvent(QKeyEvent *);                                                                                  //重写按键事件函数
    void mouseMoveEvent(QMouseEvent *);                                                                               //重写鼠标移动事件函数
    void mouseReleaseEvent(QMouseEvent *);                                                                            //重写鼠标释放事件函数
    void ToString(void);                                                                                              //遍历内地图数组
    QPixmap BlankPixmap, MainBackGroundPixmap, PeoplePixmap, BoxPixmap, MarkBoxPixmap, DestinationPixmap, WallPixmap; //图片资源

  signals:

  public slots:
    void saveMapSlot();                 //保存地图
    void handleTimeout();               //m_pTimer的中断
    void changeWillAddElementBtnSlot(); //改变将要添加的元素
};

构造函数写了两个,在上面PushBox的一个接口里我有过讲解,这里就不讲了

MakeMap::MakeMap(QWidget *parent) : QWidget(parent), wallPushButton(new QPushButton), peoplePushButton(new QPushButton), peopleInDesPushButton(new QPushButton), boxPushButton(new QPushButton), markboxPushButton(new QPushButton), blankPushButton(new QPushButton), destinationPushButton(new QPushButton), m_pTimer(new QTimer(this))
{
    W = QInputDialog::getInt(this, tr("请输入"), tr("地图横向元素个数:"));
    H = QInputDialog::getInt(this, tr("请输入"), tr("地图纵向元素个数:"));
    if (!(W || H))
    {
        QMessageBox::warning(this, tr("制作地图"), tr("输入有误!"));
        return;
    }
    Map = new char *[H];
    for (int i = 0; i < H; i++)
        Map[i] = new char[W];
    for (int i = 0; i < H; i++)
        for (int j = 0; j < W; j++)
            Map[i][j] = '0';
    siteX = siteY = 0;
    resoureLoad();
    initUI();
    connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
    m_pTimer->start(300);
    this->show();
}

MakeMap::MakeMap(int w, int h)
    : wallPushButton(new QPushButton)
    , peoplePushButton(new QPushButton)
    , peopleInDesPushButton(new QPushButton)
    , boxPushButton(new QPushButton)
    , markboxPushButton(new QPushButton)
    , blankPushButton(new QPushButton)
    , destinationPushButton(new QPushButton)
    , m_pTimer(new QTimer(this))
{
    W = w, H = h;
    Map = new char *[H];
    for (int i = 0; i < H; i++)
        Map[i] = new char[W];
    for (int i = 0; i < H; i++)
        for (int j = 0; j < W; j++)
            Map[i][j] = '0';
    siteX = siteY = 0;
    resoureLoad();
    initUI();
    connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
    m_pTimer->start(300);
    this->show();
}

析构函数:

MakeMap::~MakeMap()
{
    delete Map;
    delete MapLabel;
    delete m_pTimer;
}

与PushBox类相同,先加载资源文件,再初始化UI,上面有详细解释,此处我直接过了。

void MakeMap::resoureLoad()
{
    WallPixmap          .load("image/wall.jpg");
    PeoplePixmap        .load("image/people.jpg");
    BoxPixmap           .load("image/lxh.jpg");
    MarkBoxPixmap       .load("image/lxhhavefood.jpg");
    BlankPixmap         .load("image/blank.jpg");
    DestinationPixmap   .load("image/food.jpg");
    MainBackGroundPixmap.load("image/makemapbackground.jpg");
}

void MakeMap::initUI()
{
    MapLabel = new QLabel **[H];
    for (int i = 0; i < H; i++) //生成地图样板元素
    {
        MapLabel[i] = new QLabel *[W];
        for (int j = 0; j < W; j++)
        {
            MapLabel[i][j] = new QLabel;
        }
    }

    QGridLayout *MapLayout = new QGridLayout(); //地图样板布局
    MapLayout->setMargin(0);
    MapLayout->setSpacing(2);
    MapLayout->setSizeConstraint(QLayout::SetFixedSize);
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W; j++)
        {
            MapLabel[i][j]->setPixmap(BlankPixmap);
            MapLabel[i][j]->setFixedSize(LabelSIZE, LabelSIZE); //固定地图元素大小
            MapLabel[i][j]->setScaledContents(true);            //图片根据QLabel大小自动收缩
            MapLayout->addWidget(MapLabel[i][j], i, j);         //将地图元素添加入MapLayout布局之中
        }
    }

    wallPushButton->setFocusPolicy(Qt::NoFocus);             //失去焦点,避免干扰到按键事件函数
    peoplePushButton->setFocusPolicy(Qt::NoFocus);           //同上
    peopleInDesPushButton->setFocusPolicy(Qt::NoFocus);      //同上
    boxPushButton->setFocusPolicy(Qt::NoFocus);              //同上
    markboxPushButton->setFocusPolicy(Qt::NoFocus);          //同上
    blankPushButton->setFocusPolicy(Qt::NoFocus);            //同上
    destinationPushButton->setFocusPolicy(Qt::NoFocus);      //同上
    wallPushButton->setStyleSheet("text-align:left");        //按钮内容左对齐
    peoplePushButton->setStyleSheet("text-align:left");      //同上
    peopleInDesPushButton->setStyleSheet("text-align:left"); //同上
    boxPushButton->setStyleSheet("text-align:left");         //同上
    markboxPushButton->setStyleSheet("text-align:left");     //同上
    blankPushButton->setStyleSheet("text-align:left");       //同上
    destinationPushButton->setStyleSheet("text-align:left"); //同上
    wallPushButton->setText(tr("[6]墙壁"));
    peoplePushButton->setText(tr("[1]人"));
    peopleInDesPushButton->setText(tr("[2]人(目的地)"));
    boxPushButton->setText(tr("[3]罗小黑"));
    markboxPushButton->setText(tr("[4]罗小黑(目的地)"));
    blankPushButton->setText(tr("[0]空地"));
    destinationPushButton->setText(tr("[5]目的地"));
    wallPushButton->setIcon(WallPixmap);                 //给按钮添加图标
    peoplePushButton->setIcon(PeoplePixmap);             //同上
    peopleInDesPushButton->setIcon(PeoplePixmap);        //同上
    boxPushButton->setIcon(BoxPixmap);                   //同上
    markboxPushButton->setIcon(MarkBoxPixmap);           //同上
    blankPushButton->setIcon(BlankPixmap);               //同上
    destinationPushButton->setIcon(DestinationPixmap);   //同上
    wallPushButton->setToolTip(tr("墙壁"));              //设置鼠标放置提示信息
    peoplePushButton->setToolTip(tr("人在空地"));         //同上
    peopleInDesPushButton->setToolTip(tr("人在目的地"));  //同上
    boxPushButton->setToolTip(tr("罗小黑在空地"));        //同上
    markboxPushButton->setToolTip(tr("罗小黑在目的地"));  //同上
    blankPushButton->setToolTip(tr("空地"));             //同上
    destinationPushButton->setToolTip(tr("目的地"));     //同上
    connect(wallPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(peoplePushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(peopleInDesPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(boxPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(markboxPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(blankPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    connect(destinationPushButton, SIGNAL(clicked(bool)), this, SLOT(changeWillAddElementBtnSlot()));
    QLabel *elementLabel = new QLabel(tr("请选择:"));
    elementLabel->setToolTip(tr("以下是地图元素"));
    elementLabel->setToolTipDuration(1000);
    clueTextBrowser = new QTextBrowser;
    clueTextBrowser->setFocusPolicy(Qt::NoFocus);
    clueTextBrowser->setFixedSize(180, 52);
    clueTextBrowser->setTextColor(Qt::GlobalColor::blue);
    clueTextBrowser->setText(tr("请先选择右侧的地图元素,\n再在左边的地图样板上\n添加你所选的地图元素。"));

    QVBoxLayout *clueLayout = new QVBoxLayout();
    clueLayout->setSpacing(2);
    clueLayout->setSizeConstraint(QLayout::SetFixedSize);
    clueLayout->addWidget(blankPushButton);
    clueLayout->addWidget(peoplePushButton);
    clueLayout->addWidget(peopleInDesPushButton);
    clueLayout->addWidget(boxPushButton);
    clueLayout->addWidget(markboxPushButton);
    clueLayout->addWidget(destinationPushButton);
    clueLayout->addWidget(wallPushButton);
    QPushButton *saveMapBtn = new QPushButton(tr("保存地图(&s)"));
    saveMapBtn->setToolTip(tr("最好保存为英文名称"));
    saveMapBtn->setShortcut(tr("ctrl+s"));
    saveMapBtn->setFocusPolicy(Qt::NoFocus);
    connect(saveMapBtn, SIGNAL(clicked(bool)), this, SLOT(saveMapSlot()));
    clueLayout->addWidget(saveMapBtn);

    QVBoxLayout *rightLayout = new QVBoxLayout();
    rightLayout->addWidget(elementLabel);
    rightLayout->addLayout(clueLayout);
    rightLayout->addStretch();
    rightLayout->addWidget(clueTextBrowser);

    QVBoxLayout *leftLayout = new QVBoxLayout;
    leftLayout->addLayout(MapLayout);
    leftLayout->addStretch();

    QHBoxLayout *mainLayout = new QHBoxLayout(this);
    mainLayout->addLayout(leftLayout);
    mainLayout->addLayout(rightLayout);
    mainLayout->setSizeConstraint(QLayout::SetFixedSize);
    addBorder(); //给(0,0)地图元素画上边框
    setWindowTitle(tr("罗小黑战记"));
    QPalette pal = this->palette();
    QPixmap temp = MainBackGroundPixmap;
    MainBackGroundPixmap = temp.scaled(this->width() + 100, this->height() + 100);
    pal.setBrush(backgroundRole(), MainBackGroundPixmap);
    setPalette(pal);
}

为了实现肉眼识别当前将更改元素的位置,我就专门做了一个定时器中断,通过定时器的中断来让该位置的元素闪烁

m_pTimer->start(400);以传入参数为定时时间,时间到达就会触发中断事件
m_pTimer->stop();关闭定时器
void MakeMap::addBorder()
{
    MapLabel[siteX][siteY]->setFrameShape(QFrame::Box);
    MapLabel[siteX][siteY]->setStyleSheet("border-width: 1px;border-style: solid;border-color: rgb(255, 170, 0);");
}

void MakeMap::deleteBorder()
{
    MapLabel[siteX][siteY]->setFrameShape(QFrame::Box);
    MapLabel[siteX][siteY]->setStyleSheet("border-width: 1px;border-style: solid;border-color: rgb(255, 255, 255);");
}
void MakeMap::handleTimeout()
{
    static bool isClear = false;
    if (isClear)
    {
        isClear = false;
        MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        m_pTimer->start(400);
    }
    else
    {
        isClear = true;
        switch (Map[siteX][siteY])
        {
        case '0': //空地
            MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
            break;
        case '1':
        case '2': //人
            MapLabel[siteX][siteY]->setPixmap(PeoplePixmap);
            break;
        case '3': //箱子
            MapLabel[siteX][siteY]->setPixmap(BoxPixmap);
            break;
        case '4': //目的地的箱子
            MapLabel[siteX][siteY]->setPixmap(MarkBoxPixmap);
            break;
        case '5': //目的地
            MapLabel[siteX][siteY]->setPixmap(DestinationPixmap);
            break;
        case '6': //墙
            MapLabel[siteX][siteY]->setPixmap(WallPixmap);
            break;
        }
        m_pTimer->start(800);
    }
}

在此基础上为了防止位置发成移动后而导致原来位置因为闪烁而失去对应元素模样,所以写了一个保证位置移动后能让原位置的元素正确显示的函数

void MakeMap::recoverFlash()
{
    switch (Map[siteX][siteY])
    {
    case '0': //空地
        MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        break;
    case '1':
    case '2': //人
        MapLabel[siteX][siteY]->setPixmap(PeoplePixmap);
        break;
    case '3': //箱子
        MapLabel[siteX][siteY]->setPixmap(BoxPixmap);
        break;
    case '4': //目的地的箱子
        MapLabel[siteX][siteY]->setPixmap(MarkBoxPixmap);
        break;
    case '5': //目的地
        MapLabel[siteX][siteY]->setPixmap(DestinationPixmap);
        break;
    case '6': //墙
        MapLabel[siteX][siteY]->setPixmap(WallPixmap);
        break;
    }
}

鼠标点击元素按钮来选择元素

void MakeMap::changeWillAddElementBtnSlot()
{
    QPushButton *btn = qobject_cast<QPushButton *>(sender());
    if (btn == wallPushButton)
        currentElement = wallElement;
    if (btn == peoplePushButton)
        currentElement = peopleElement;
    if (btn == peopleInDesPushButton)
        currentElement = peopleInDesElement;
    if (btn == boxPushButton)
        currentElement = boxElement;
    if (btn == markboxPushButton)
        currentElement = markboxElement;
    if (btn == blankPushButton)
        currentElement = blankElement;
    if (btn == destinationPushButton)
        currentElement = destinationElement;
}

重写按键点击事件函数,功能:通过上下左右按键可以移动当前将更改元素的位置,然后数字按键0-6对应各自的元素,按下就将当前位置换成对应元素

void MakeMap::keyPressEvent(QKeyEvent *event)
{
    switch (event->key())
    {
    case Qt::Key_Left:
    case Qt::Key_A:

        if (siteY > 0)
        {
            m_pTimer->stop();  //关闭定时器
            recoverFlash();    //防止坐标的突然移动而导致闪出了问题
            deleteBorder();    //删除边框
            siteY -= 1;        //移动坐标位置
            addBorder();       //添加边框
            m_pTimer->start(); //再打开定时器
        }
        return;
    case Qt::Key_Right:
    case Qt::Key_D:
        if (siteY < W - 1)
        {
            m_pTimer->stop();
            recoverFlash();
            deleteBorder();
            siteY += 1;
            addBorder();
            m_pTimer->start();
        }
        return;
    case Qt::Key_Up:
    case Qt::Key_W:
        if (siteX > 0)
        {
            m_pTimer->stop();
            recoverFlash();
            deleteBorder();
            siteX -= 1;
            addBorder();
            m_pTimer->start();
        }
        return;
    case Qt::Key_Down:
    case Qt::Key_S:
        if (siteX < H - 1)
        {
            m_pTimer->stop();
            recoverFlash();
            deleteBorder();
            siteX += 1;
            addBorder();
            m_pTimer->start();
        }
        return;
    case Qt::Key_0:
        currentElement = blankElement;
        break;
    case Qt::Key_1:
        currentElement = peopleElement;
        break;
    case Qt::Key_2:
        currentElement = peopleInDesElement;
        break;
    case Qt::Key_3:
        currentElement = boxElement;
        break;
    case Qt::Key_4:
        currentElement = markboxElement;
        break;
    case Qt::Key_5:
        currentElement = destinationElement;
        break;
    case Qt::Key_6:
        currentElement = wallElement;
        break;
    }
    switch (currentElement)
    {
    case wallElement:
        Map[siteX][siteY] = '6';
        MapLabel[siteX][siteY]->setPixmap(WallPixmap);
        break;
    case peopleElement:
        Map[siteX][siteY] = '1';
        MapLabel[siteX][siteY]->setPixmap(PeoplePixmap);
        break;
    case peopleInDesElement:
        Map[siteX][siteY] = '2';
        MapLabel[siteX][siteY]->setPixmap(PeoplePixmap);
        break;
    case boxElement:
        Map[siteX][siteY] = '3';
        MapLabel[siteX][siteY]->setPixmap(BoxPixmap);
        break;
    case markboxElement:
        Map[siteX][siteY] = '4';
        MapLabel[siteX][siteY]->setPixmap(MarkBoxPixmap);
        break;
    case blankElement:
        Map[siteX][siteY] = '0';
        MapLabel[siteX][siteY]->setPixmap(BlankPixmap);
        break;
    case destinationElement:
        Map[siteX][siteY] = '5';
        MapLabel[siteX][siteY]->setPixmap(DestinationPixmap);
        break;
    }
}

重写鼠标释放和移动事件函数,功能:通过鼠标移动时来快速添加地图元素

void MakeMap::mouseReleaseEvent(QMouseEvent *)
{
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W; j++)
        {
            if (MapLabel[i][j]->geometry().contains(this->mapFromGlobal(QCursor::pos()))) //判断该Label上是否有鼠标
            {
                m_pTimer->stop();
                recoverFlash();
                deleteBorder();
                siteX = i, siteY = j;
                addBorder();
                switch (currentElement)
                {
                case wallElement:
                    Map[i][j] = '6';
                    MapLabel[i][j]->setPixmap(WallPixmap);
                    break;
                case peopleElement:
                    Map[i][j] = '1';
                    MapLabel[i][j]->setPixmap(PeoplePixmap);
                    break;
                case peopleInDesElement:
                    Map[i][j] = '2';
                    MapLabel[i][j]->setPixmap(PeoplePixmap);
                    break;
                case boxElement:
                    Map[i][j] = '3';
                    MapLabel[i][j]->setPixmap(BoxPixmap);
                    break;
                case markboxElement:
                    Map[i][j] = '4';
                    MapLabel[i][j]->setPixmap(MarkBoxPixmap);
                    break;
                case blankElement:
                    Map[i][j] = '0';
                    MapLabel[i][j]->setPixmap(BlankPixmap);
                    break;
                case destinationElement:
                    Map[i][j] = '5';
                    MapLabel[i][j]->setPixmap(DestinationPixmap);
                    break;
                }
                m_pTimer->start();
            }
        }
    }
}

void MakeMap::mouseMoveEvent(QMouseEvent *)
{
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W; j++)
        {
            if (MapLabel[i][j]->geometry().contains(this->mapFromGlobal(QCursor::pos()))) //判断该Label上是否有鼠标
            {
                m_pTimer->stop();
                recoverFlash();
                deleteBorder();
                siteX = i, siteY = j;
                addBorder();
                switch (currentElement)
                {
                case wallElement:
                    Map[i][j] = '6';
                    MapLabel[i][j]->setPixmap(WallPixmap);
                    break;
                case peopleElement:
                    Map[i][j] = '1';
                    MapLabel[i][j]->setPixmap(PeoplePixmap);
                    break;
                case peopleInDesElement:
                    Map[i][j] = '2';
                    MapLabel[i][j]->setPixmap(PeoplePixmap);
                    break;
                case boxElement:
                    Map[i][j] = '3';
                    MapLabel[i][j]->setPixmap(BoxPixmap);
                    break;
                case markboxElement:
                    Map[i][j] = '4';
                    MapLabel[i][j]->setPixmap(MarkBoxPixmap);
                    break;
                case blankElement:
                    Map[i][j] = '0';
                    MapLabel[i][j]->setPixmap(BlankPixmap);
                    break;
                case destinationElement:
                    Map[i][j] = '5';
                    MapLabel[i][j]->setPixmap(DestinationPixmap);
                    break;
                }
                m_pTimer->start();
            }
        }
    }
}

跟PushBox相同,我专门写了一个方便调试的遍历函数

void MakeMap::ToString()
{
    QString tempString;
    for (int i = 0; i < H; i++)
    {
        for (int j = 0; j < W - 1; j++)
        {
            tempString = tempString + QString::number(Map[i][j]) + ",";
        }
        tempString = tempString + QString::number(Map[i][W - 1]) + ";\n";
    }
    qDebug() << tempString;
}

地图制作完成后就可以保存地图了,由于地图数据并不复杂,所以我使用的是C++中的文件流来处理

void MakeMap::saveMapSlot()
{
    QString fileString = QFileDialog::getSaveFileName(this, tr("Save Map"), "./未命名.chf", tr("map (*.chf)"));
    using namespace std;
    ofstream out(fileString.toLocal8Bit());
    if (out.is_open())
    {
        for (int i = 0; i < H; i++)
        {
            for (int j = 0; j < W - 1; j++)
            {
                out << Map[i][j] << ',';
            }
            out << Map[i][W - 1] << ';' << '\n';
        }
        out << EOF;
    }
    out.close();
}

最后再改变一波应用程序的图标

新建一个windowico.rc文件,其中的ooopic.ico就是在当前目录下的图标文件了

然后右键添加到项目中,还得在.pro中添加RC_FILE = windowico.rc

这样就大功告成了

IDI_ICON1               ICON     DISCARDABLE     "ooopic.ico"
  • 13
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值