首页 > 代码库 > C++与QML混合编程实现2048

C++与QML混合编程实现2048

很多人都玩过2048,一个简单又有趣的数字游戏,曾看到许多人在地铁上玩来玩去的,感觉挺有意思,便下载个玩了一阵子,不过从来没有突破“2048”,看到有些小伙伴已经玩到“8192”了,确实令人捉急。后来,突然想自己写代码做个2048,于是花了一天时间用C++QML实现了这个好玩的游戏。

程从Qt Creator中创建,是个Qt Quick Application,由C++QML混合编程完成,编程方法与技巧可参考《Qt QML / QMLC++混合编程详解》。代码中,C++负责业务逻辑,QML用来构建UI,主要实现了下列功能:

1、实时显示游戏ScoreBest ScoreStepTotal Step

2、数字累加和移动,方向由键盘上的四个方向键控制。

3、数字颜色及背景色变化。

4、“Start”按钮。

5、“Back”按钮,是颗后悔药,返回上一步。

来看一下游戏初始界面:

技术分享

击“Start”按钮后:

技术分享

以玩了撒,玩几下再说,结果如下:

技术分享

到没有大数字到“256”了,现在可以继续玩下去或者“Start”重新开始,如果觉得上一步失误了,还可以“Back”一下,直到游戏初始状态。

面是游戏源码:

NEDigits.h——

#ifndef NE2048_H
#define NE2048_H

#include <QObject>
#include <QColor>

#define ROWS 4
#define COLUMNS 4

class NE2048 : public QObject
{
    Q_OBJECT
    Q_ENUMS(Move_Direction)
    Q_PROPERTY(int score READ score)
    Q_PROPERTY(int bestScore READ bestScore)
    Q_PROPERTY(int step READ step)
    Q_PROPERTY(int totalStep READ totalStep)

public:
    NE2048(QObject *parent = 0);
    ~NE2048();

    enum Move_Direction {
        Move_Up = 0,
        Move_Down,
        Move_Left,
        Move_Right,
        Move_Invalid
    };

    Q_INVOKABLE void start();
    Q_INVOKABLE void move(Move_Direction direction);
    Q_INVOKABLE QColor color(const int &index);
    Q_INVOKABLE QColor numColor(const int &index);

    int score() const;
    int bestScore() const;
    int step() const;
    int totalStep() const;

signals:
    void backed();

public slots:
    int show(const int &index);
    void goBack();

private:
    void initNum();
    void added(Move_Direction direction);
    void moved(Move_Direction direction);
    void freshed(bool fresh);

    int m_score;
    int m_bestScore;
    int m_step;
    int m_totalStep;

    typedef std::vector<int> NEPanel;
    typedef std::vector<NEPanel> NEState;
    NEPanel m_number;
    NEPanel m_index;
    NEState m_state;
    int m_preIndex;
    int m_nextIndex;
    bool m_addedFlag;
    bool m_movedFlag;
};

#endif // NE2048_H
NEDigits.cpp——

#include "NE2048.h"
#include <QDebug>
#include <ctime>

NE2048::NE2048(QObject *parent)
    : QObject(parent)
{
    m_bestScore = 0;
    connect(this, SIGNAL(backed()), this, SLOT(goBack()));
    srand(time(0));
}

NE2048::~NE2048()
{
}

void NE2048::start()
{
    initNum();
}

void NE2048::move(Move_Direction direction)
{
    added(direction);
    moved(direction);
    freshed(m_addedFlag || m_movedFlag);
    if(m_bestScore < m_score) {
        m_bestScore = m_score;
    }
}

QColor NE2048::color(const int &index)
{
    int number = m_number[index];
    QColor color;
    switch(number) {
    case 0: color = QColor(255, 255, 255); break; // white
    case 2: color = QColor(245, 222, 179); break; // wheat
    case 4: color = QColor(238, 130, 238); break; // violet
    case 8: color = QColor(0, 255, 127); break; // springgreen
    case 16: color = QColor(255, 192, 203); break; // pink
    case 32: color = QColor(255, 165, 0); break; // orange
    case 64: color = QColor(173, 255, 47); break; // greenyellow
    case 128: color = QColor(255, 99, 71); break; // tomato
    case 256: color = QColor(154, 205, 50); break; // yellowgreen
    case 512: color = QColor(255, 215, 0); break; // gold
    case 1024: color = QColor(0, 255, 255); break; // cyan
    case 2048: color = QColor(0, 255, 0); break; // green
    case 4096: color = QColor(255, 255, 0); break; // yellow
    case 8192: color = QColor(255, 0, 0); break; // red
    default: color = QColor(0, 0, 0); break; // black
    }
    return color;
}

QColor NE2048::numColor(const int &index)
{
    if(8192 > m_number[index]) {
        return QColor(0, 0, 0);
    } else {
        return QColor(255, 255, 255);
    }
}

int NE2048::score() const
{
    return m_score;
}

int NE2048::bestScore() const
{
    return m_bestScore;
}

int NE2048::step() const
{
    return m_step;
}

int NE2048::totalStep() const
{
    return m_totalStep;
}


int NE2048::show(const int &index)
{
    return m_number[index];
}

void NE2048::goBack()
{
    if(0 < m_step) {
        m_number = m_state[m_step - 1];
        m_state.pop_back();
        m_step -= 1;
    }
}

void NE2048::initNum()
{
    m_number.clear();
    m_number = NEPanel(16, 0);
    int firstNum = rand() % 16;
    int secondNum = rand() % 16;
    while(firstNum == secondNum) {
        secondNum = rand() % 16;
    }
    m_number[firstNum] = 2;
    m_number[secondNum] = 2;
    m_score = 0;
    m_step = 0;
    m_totalStep = 0;
    m_state.clear();
    m_state.push_back(m_number);
}

void NE2048::added(Move_Direction direction)
{
    if(Move_Down == direction) {
        m_addedFlag = false;
        for(int c = 0; c < COLUMNS; c++) {
            m_preIndex = c;
            m_nextIndex = m_preIndex + 4;
            while(m_nextIndex <= c + 12) {
                if(0 == m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 4;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex += 4;
                    continue;
                }
                if(m_number[m_preIndex] != m_number[m_nextIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 4;
                } else {
                    m_number[m_preIndex] = 0;
                    m_number[m_nextIndex] += m_number[m_nextIndex];
                    m_score += m_number[m_nextIndex];
                    m_preIndex = m_nextIndex + 4;
                    m_nextIndex = m_preIndex + 4;
                    m_addedFlag = true;
                }
            }
        }
    }

    if(Move_Up == direction) {
        m_addedFlag = false;
        for(int c = 0; c < COLUMNS; c++) {
            m_preIndex = c + 12;
            m_nextIndex = m_preIndex - 4;
            while(m_nextIndex >= c) {
                if(0 == m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 4;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex -= 4;
                    continue;
                }
                if(m_number[m_preIndex] != m_number[m_nextIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 4;
                } else {
                    m_number[m_preIndex] = 0;
                    m_number[m_nextIndex] += m_number[m_nextIndex];
                    m_score += m_number[m_nextIndex];
                    m_preIndex = m_nextIndex - 4;
                    m_nextIndex = m_preIndex - 4;
                    m_addedFlag = true;
                }
            }
        }
    }

    if(Move_Right == direction) {
        m_addedFlag = false;
        for(int r = 0; r < ROWS; r++) {
            m_preIndex = r * 4;
            m_nextIndex = m_preIndex + 1;
            while(m_nextIndex <= r * 4 + 3) {
                if(0 == m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 1;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex += 1;
                    continue;
                }
                if(m_number[m_preIndex] != m_number[m_nextIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 1;
                } else {
                    m_number[m_preIndex] = 0;
                    m_number[m_nextIndex] += m_number[m_nextIndex];
                    m_score += m_number[m_nextIndex];
                    m_preIndex = m_nextIndex + 1;
                    m_nextIndex = m_preIndex + 1;
                    m_addedFlag = true;
                }
            }
        }
    }

    if(Move_Left == direction) {
        m_addedFlag = false;
        for(int r = 0; r < ROWS; r++) {
            m_preIndex = r * 4 + 3;
            m_nextIndex = m_preIndex - 1;
            while(m_nextIndex >= r * 4) {
                if(0 == m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 1;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex -= 1;
                    continue;
                }
                if(m_number[m_preIndex] != m_number[m_nextIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 1;
                } else {
                    m_number[m_preIndex] = 0;
                    m_number[m_nextIndex] += m_number[m_nextIndex];
                    m_score += m_number[m_nextIndex];
                    m_preIndex = m_nextIndex - 1;
                    m_nextIndex = m_preIndex - 1;
                    m_addedFlag = true;
                }
            }
        }
    }
}

void NE2048::moved(Move_Direction direction)
{
    if(Move_Down == direction) {
        m_movedFlag = false;
        for(int c = 0; c < COLUMNS; c++) {
            m_preIndex = c + 12;
            m_nextIndex = m_preIndex - 4;
            while(m_nextIndex >= c) {
                if(0 != m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 4;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex -= 4;
                    continue;
                } else {
                    m_number[m_preIndex] = m_number[m_nextIndex];
                    m_number[m_nextIndex] = 0;
                    m_preIndex = m_preIndex - 4;
                    m_nextIndex = m_nextIndex - 4;
                    m_movedFlag = true;
                }
            }
        }
    }

    if(Move_Up == direction) {
        m_movedFlag = false;
        for(int c = 0; c < COLUMNS; c++) {
            m_preIndex = c;
            m_nextIndex = m_preIndex + 4;
            while(m_nextIndex <= c + 12) {
                if(0 != m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 4;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex += 4;
                    continue;
                } else {
                    m_number[m_preIndex] = m_number[m_nextIndex];
                    m_number[m_nextIndex] = 0;
                    m_preIndex = m_preIndex + 4;
                    m_nextIndex = m_nextIndex + 4;
                    m_movedFlag = true;
                }
            }
        }
    }

    if(Move_Right == direction) {
        m_movedFlag = false;
        for(int r = 0; r < ROWS; r++) {
            m_preIndex = r * 4 + 3;
            m_nextIndex = m_preIndex - 1;
            while(m_nextIndex >= r * 4) {
                if(0 != m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex - 1;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex -= 1;
                    continue;
                } else {
                    m_number[m_preIndex] = m_number[m_nextIndex];
                    m_number[m_nextIndex] = 0;
                    m_preIndex = m_preIndex - 1;
                    m_nextIndex = m_nextIndex - 1;
                    m_movedFlag = true;
                }
            }
        }
    }

    if(Move_Left == direction) {
        m_movedFlag = false;
        for(int r = 0; r < ROWS; r++) {
            m_preIndex = r * 4;
            m_nextIndex = m_preIndex + 1;
            while(m_nextIndex <= r * 4 + 3) {
                if(0 != m_number[m_preIndex]) {
                    m_preIndex = m_nextIndex;
                    m_nextIndex = m_preIndex + 1;
                    continue;
                }
                if(0 == m_number[m_nextIndex]) {
                    m_nextIndex += 1;
                    continue;
                } else {
                    m_number[m_preIndex] = m_number[m_nextIndex];
                    m_number[m_nextIndex] = 0;
                    m_preIndex = m_preIndex + 1;
                    m_nextIndex = m_nextIndex + 1;
                    m_movedFlag = true;
                }
            }
        }
    }
}

void NE2048::freshed(bool fresh)
{
    if(fresh) {
        m_step += 1;
        m_totalStep = m_step;
        m_index.clear();
        for(size_t s = 0; s < m_number.size(); s++) {
            if(!m_number[s]) {
                m_index.push_back(s);
            }
        }
        int randIndex = rand() % m_index.size();
        m_number[m_index[randIndex]] = 2;
        m_state.push_back(m_number);
    }
}
main.cpp——

#include "NE2048.h"
#include <QGuiApplication>
#include <QQuickView>
#include <QtQml>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    qmlRegisterType<NE2048>("naturEarth", 1, 0, "NE2048");

    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
    view.show();

    return app.exec();
}
EInformation.qml——

import QtQuick 2.3

Rectangle {
    property alias eScore: score.text
    property alias eBestScore: bestScore.text
    property alias eStep: step.text
    property alias eTotalStep: totalStep.text

    width: 360; height: 100
    color: "lightyellow"

    Grid {
        columns: 4

        Text {
            width: 90; height: 50
            text: "Score:"
            color: "blue"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        Text {
            id: score
            width: 90; height: 50
            font.bold: true
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
        }
        Text {
            width: 90; height: 50
            text: "Best Score:"
            color: "red"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        Text {
            id: bestScore
            width: 90; height: 50
            font.bold: true
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
        }
        Text {
            width: 90; height: 50
            text: "Step:"
            color: "blue"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        Text {
            id: step
            width: 90; height: 50
            font.bold: true
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
        }
        Text {
            width: 90; height: 50
            text: "Total Step:"
            color: "red"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        Text {
            id: totalStep
            width: 90; height: 50
            font.bold: true
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
        }
    }
}
ETip.qml——

import QtQuick 2.3
import QtQuick.Controls 1.2

Item {
    property alias eEnBack: back.enabled

    signal eStart()
    signal eBack()

    width: 360; height: 100

    Button {
        width: 180; height: 100
        text: qsTr("Start")
        onClicked: parent.eStart()
    }

    Button {
        id: back
        enabled: false
        x: 180
        width: 180; height: 100
        text: qsTr("Back")
        onClicked: parent.eBack()
    }

    Text {
        anchors.bottom: parent.bottom
        anchors.right: parent.right
        text: "natruEarth"
        font.italic: true
        font.underline: true
        color: "blue"
    }
}
EItem.qml——

import QtQuick 2.3

Rectangle {
    property alias eNum: innerNum.text
    property alias eNumColor: innerNum.color

    width: 100; height: width // changed later
    color: "white" // default
    radius: 10

    Text {
        id: innerNum
        anchors.centerIn: parent
        color: "black" // default
    }
}
EPanel.qml——

import QtQuick 2.3

Rectangle {
    id: root

    property var eNums: repeater

    width: 360 - radius * 2
    height: width
    color: "lightblue"
    radius: 20

    Grid {
        id: grid
        anchors.centerIn: parent
        columns: 4
        spacing: 10

        Repeater {
            id: repeater
            model: 16

            EItem {
                width: (root.width - grid.spacing * 5) / 4
                height: width
            }
        }
    }
}
main.qml——

import QtQuick 2.3
import naturEarth 1.0

Item {
    id: root

    property int eI

    function eClear() {
        for(eI = 0; eI < 16; eI++) {
            panel.eNums.itemAt(eI).eNum = "";
            panel.eNums.itemAt(eI).color = "white";
            panel.eNums.itemAt(eI).eNumColor= "black";
        }
    }

    function eShow() {
        eClear();
        for(eI = 0; eI < 16; eI++) {
            if(numProvider.show(eI)) {
                panel.eNums.itemAt(eI).eNum = numProvider.show(eI);
                panel.eNums.itemAt(eI).color = numProvider.color(eI);
                panel.eNums.itemAt(eI).eNumColor = numProvider.numColor(eI);
            }
        }
        infomation.eScore = numProvider.score;
        infomation.eStep = numProvider.step;
        infomation.eBestScore = numProvider.bestScore;
        infomation.eTotalStep = numProvider.totalStep;
        if(0 < numProvider.step) {
            tip.eEnBack = true;
        }
    }

    width: 360; height: 560

    Keys.onPressed: {
        switch(event.key) {
        case Qt.Key_Up:
            numProvider.move(NE2048.Move_Up);
            root.eShow();
            break;
        case Qt.Key_Down:
            numProvider.move(NE2048.Move_Down);
            root.eShow();
            break;
        case Qt.Key_Left:
            numProvider.move(NE2048.Move_Left);
            root.eShow();
            break;
        case Qt.Key_Right:
            numProvider.move(NE2048.Move_Right);
            root.eShow();
            break;
        default:
            break;
        }
    }

    NE2048 { id: numProvider }

    Rectangle {
        id: container
        anchors.fill: parent
        color: "white"

        EInformation { id: infomation }

        EPanel {
            id: panel
            anchors.centerIn: parent
        }

        ETip {
            id: tip
            anchors.bottom: parent.bottom
            onEStart: {
                numProvider.start()
                root.eShow();
                root.focus = true;
                tip.eEnBack = false;
            }
            onEBack: {
                numProvider.backed();
                root.eShow();
                if(!numProvider.step) {
                    tip.eEnBack = false;
                }
            }
        }
    }
}

C++与QML混合编程实现2048