相信很多人都玩过2048,一个简单又有趣的数字游戏,曾看到许多人在地铁上玩来玩去的,感觉挺有意思,便下载个玩了一阵子,不过从来没有突破“2048”,看到有些小伙伴已经玩到“8192”了,确实令人捉急。后来,突然想自己写代码做个2048,于是花了一天时间用C++与QML实现了这个好玩的游戏。
工程从Qt Creator中创建,是个Qt Quick Application,由C++与QML混合编程完成,编程方法与技巧可参考《Qt QML / QML与C++混合编程详解》。代码中,C++负责业务逻辑,QML用来构建UI,主要实现了下列功能:
1、实时显示游戏Score、Best Score、Step和Total 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_HNEDigits.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; } } } } }