From 05e4d2e405d960ea2b973c1534c102fdaf8946e4 Mon Sep 17 00:00:00 2001 From: Qiaoyong Zhong Date: Thu, 17 Apr 2014 16:48:12 +0800 Subject: [PATCH] integrated Qml within C++ --- 2048_qt.pro | 13 +++ deployment.pri | 23 ++++ main.cpp | 12 ++ qml.qrc | 6 + qml/2048.js | 298 +++++++++++++++++++++++++++++++++++++++++++++++++ qml/main.qml | 177 +++++++++++++++++++++++++++++ 6 files changed, 529 insertions(+) create mode 100644 2048_qt.pro create mode 100644 deployment.pri create mode 100644 main.cpp create mode 100644 qml.qrc create mode 100644 qml/2048.js create mode 100644 qml/main.qml diff --git a/2048_qt.pro b/2048_qt.pro new file mode 100644 index 0000000..1cdc565 --- /dev/null +++ b/2048_qt.pro @@ -0,0 +1,13 @@ +TEMPLATE = app + +QT += qml quick widgets + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Default rules for deployment. +include(deployment.pri) diff --git a/deployment.pri b/deployment.pri new file mode 100644 index 0000000..839b3b5 --- /dev/null +++ b/deployment.pri @@ -0,0 +1,23 @@ +android-no-sdk { + target.path = /data/user/qt + export(target.path) + INSTALLS += target +} else:android { + x86 { + target.path = /libs/x86 + } else: armeabi-v7a { + target.path = /libs/armeabi-v7a + } else { + target.path = /libs/armeabi + } + export(target.path) + INSTALLS += target +} else:unix { + isEmpty(target.path) { + target.path = /opt/$${TARGET}/bin + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..978245a --- /dev/null +++ b/main.cpp @@ -0,0 +1,12 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:///qml/main.qml"))); + + return app.exec(); +} diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..bccdf4a --- /dev/null +++ b/qml.qrc @@ -0,0 +1,6 @@ + + + qml/main.qml + qml/2048.js + + diff --git a/qml/2048.js b/qml/2048.js new file mode 100644 index 0000000..11d519b --- /dev/null +++ b/qml/2048.js @@ -0,0 +1,298 @@ +var score = 0; +var bestScore = 0; +var gridSize = 4; +var cellValues; +var availableCells; +//var labels = "PRC"; +var labels = "2048"; +var labelFunc; +var targetLevel = 11; +var checkTargetFlag = true; + +switch (labels) { +case "2048": + labelFunc = function(n) { + return Math.pow(2, n).toString(); + }; + break; +case "PRC": + labelFunc = function(n) { + var dynasties = ["商", "周", "秦", "汉", "唐", "宋", "元", "明", "清", "ROC", "PRC"]; + return dynasties[n-1]; + }; + break; +} + + +function startupFunction() { + // Initialize variables + score = 0; + checkTargetFlag = true; + var i; + var j; + + cellValues = new Array(gridSize); + for (i = 0; i < gridSize; i++) { + cellValues[i] = new Array(gridSize); + for (j = 0; j < gridSize; j++) + cellValues[i][j] = 0; + } + + updateAvailableCells(); + refreshCellViews(2); + updateScore(); + console.log("Started a new game"); +} + +function moveKey(event) { + if ((event.key == Qt.Key_Q) && (event.modifiers & Qt.ControlModifier)) { + Qt.quit(); + } + + var isMoved = false; + var i, j, v, v2; + var oldScore = score; + switch (event.key) { + case Qt.Key_Left: + for (i = 0; i < gridSize; i++) { + v = cellValues[i]; + v2 = mergeVector(v); + if (! arraysIdentical(v,v2)) { + isMoved = true; + cellValues[i] = v2; + } + } + break; + case Qt.Key_Right: + for (i = 0; i < gridSize; i++) { + v = cellValues[i].slice(); + v.reverse(); + v2 = mergeVector(v); + if (! arraysIdentical(v,v2)) { + isMoved = true; + v2.reverse(); + cellValues[i] = v2; + } + } + break; + case Qt.Key_Up: + for (i = 0; i < gridSize; i++) { + v = cellValues.map(function(row) {return row[i];}); + v2 = mergeVector(v); + if (! arraysIdentical(v,v2)) { + isMoved = true; + for (j = 0; j < gridSize; j++) { + cellValues[j][i] = v2[j]; + } + } + } + break; + case Qt.Key_Down: + for (i = 0; i < gridSize; i++) { + v = cellValues.map(function(row) {return row[i];}); + v.reverse(); + v2 = mergeVector(v); + if (! arraysIdentical(v,v2)) { + isMoved = true; + v2.reverse(); + for (j = 0; j < gridSize; j++) { + cellValues[j][i] = v2[j]; + } + } + } + break; + } + + if (isMoved) { + updateAvailableCells(); + refreshCellViews(1); + if (oldScore !== score) { + if (bestScore < score) { + bestScore = score; + } + updateScore(); + if (checkTargetFlag && maxTileValue() >= targetLevel) { + winMessage.open(); + } + } + } else { + if (isDead()) { + deadMessage.open(); + } + } +} + +function ind2sub(ind) { + var sub = [0, 0]; + sub[0] = Math.floor(ind / gridSize); + sub[1] = ind % gridSize; + return sub; +} + +function mergeVector(v0) { + // Pass 1: remove zero elements + var v = v0.slice(); + var i = v.length; + while (i--) { + if (v[i] === 0) { + v.splice(i, 1); + } + } + // Pass 2: merge same elements + var v2 = []; + while (v.length > 0) { + if (v.length > 1 && v[0] === v[1]) { + v2.push(v[0] + 1); + score += parseInt(Math.pow(2, v[0]+1)); + v.splice(0, 2); + } else { + v2.push(v[0]); + v.splice(0, 1); + } + } + + // Fill the gaps with zeros + for (i = v2.length; i < v0.length; i++) + v2[i] = 0; + + return v2; +} + +function removeElementsWithValue(arr, val) { + var i = arr.length; + while (i--) { + if (arr[i] === val) { + arr.splice(i, 1); + } + } + return arr; +} + +function arraysIdentical(a, b) { + var i = a.length; + if (i !== b.length) return false; + while (i--) { + if (a[i] !== b[i]) return false; + } + return true; +}; + +function updateAvailableCells() { + availableCells = []; + for (var i = 0; i < gridSize; i++) { + for (var j = 0; j < gridSize; j++) { + if (cellValues[i][j] === 0) { + availableCells.push(i * gridSize + j); + } + } + } +} + +function refreshCellViews(n) { + var i, sub; + + // Popup a new number + for (i = 0; i < n; i++) { + var oneOrTwo = Math.random() < 0.9 ? 1: 2; + var randomCellId = availableCells[Math.floor(Math.random() * availableCells.length)]; + + sub = ind2sub(randomCellId); + cellValues[sub[0]][sub[1]] = oneOrTwo; + + // Mark this cell as unavailable + var idx = availableCells.indexOf(randomCellId); + availableCells.splice(idx, 1); + } + + // Refresh the cell views + for (i = 0; i < cells.count; i++) { + sub = ind2sub(i); + var cv = cellValues[sub[0]][sub[1]]; + var sty = computeTileStyle(cv); + if ( cv === 0) { + cells.itemAt(i).tileText = ""; + } else { + cells.itemAt(i).tileText = labelFunc(cv); + } + cells.itemAt(i).color = sty.bgColor; + cells.itemAt(i).tileColor = sty.fgColor; + cells.itemAt(i).tileFontSize = sty.fontSize; + } + +} + +function updateScore() { + scoreBoard.itemAt(0).scoreText = MyScript.score.toString(); + scoreBoard.itemAt(1).scoreText = MyScript.bestScore.toString(); +} + +function isDead() { + var dead = true; + for (var i = 0; i < gridSize; i++) { + for (var j = 0; j < gridSize; j++) { + if (cellValues[i][j] === 0) { + dead = false; + } + if (i > 0) { + if (cellValues[i-1][j] === cellValues[i][j]) { + dead = false; + } + } + if (j > 0) { + if (cellValues[i][j-1] === cellValues[i][j]) { + dead = false; + } + } + } + } + + return dead; +} + +function computeTileStyle(n) { + var fgColors = ["#776E62", "#F9F6F2"]; + var bgColors = ["#EEE4DA", "#EDE0C8", "#F2B179", "#F59563", "#F67C5F", "#F65E3B", "#EDCF72", "#EDCC61", "#EDC850", "#EDC53F", "#EDC22E", "#3C3A32"]; + var sty = {bgColor: helper.myColors.bggray, + fgColor: fgColors[0], + fontSize: 55 }; + if (n > 0) { + if (n > 2) + sty.fgColor = fgColors[1]; + if (n <= bgColors.length) + sty.bgColor = bgColors[n-1]; + else + sty.bgColor = bgColors[bgColors.length-1]; + } + + if (labels === "2048") { + /* Adjust font size according to size of the number + [2, 100): 55 + [100, 1000): 45 + [1000, 2048]: 35 + > 2048: 30 + */ + var pv = Math.pow(2, n); + if (pv >= 100 && pv < 1000) + sty.fontSize = 45; + else if (pv >= 1000 && pv <= 2048) + sty.fontSize = 35; + else if (pv > 2048) + sty.fontSize = 30; + + } + + return sty; +} + +function maxTileValue() { + var mv = 0; + for (var i = 0; i < gridSize; i++) { + for (var j = 0; j < gridSize; j++) { + var cv = cellValues[i][j]; + if ( mv < cv) { + mv = cv; + } + } + } + return mv; +} diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..11d6ff4 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,177 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 +import "2048.js" as MyScript + +ApplicationWindow { + visible: true + width: 560 + height: 730 + title: qsTr("2048 Game"); + + Rectangle { + anchors.fill: parent + + Item { + id: helper + property var myColors: {"bglight": "#FAF8EF", + "bggray": Qt.rgba(238/255, 228/255, 218/255, 0.35), + "bgdark": "#BBADA0", + "fglight": "#EEE4DA", + "fgdark": "#776E62", + "bgbutton": "#8F7A66", // Background color for the "New Game" button + "fgbutton": "#F9F6F2" // Foreground color for the "New Game" button + } + } + + color: helper.myColors.bglight + focus: true + Keys.onPressed: MyScript.moveKey(event) + + Text { + id: gameName + x: 30 + y: 30 + font.pixelSize: 55 + font.bold: true + text: "2048" + color: helper.myColors.fgdark + } + + Row { + y: 30 + anchors.right: parent.right + anchors.rightMargin: 30 + spacing: 5 + Repeater { + id: scoreBoard + model: 2 + Rectangle { + width: (index == 0) ? 95 : 125 + height: 55 + radius: 3 + color: helper.myColors.bgdark + property string scoreText: (index === 0) ? MyScript.score.toString() : MyScript.bestScore.toString() + Text { + text: (index == 0) ? "SCORE" : "BEST" + anchors.horizontalCenter: parent.horizontalCenter + y: 7 + font.pixelSize: 13 + color: helper.myColors.fglight + } + Text { + text: scoreText + anchors.horizontalCenter: parent.horizontalCenter + y: 25 + font.pixelSize: 25 + font.bold: true + color: "white" + } + } + } + } + + Text { + id: banner + x: 30 + y: 120 + height: 40 + text: "Join the numbers and get to the 2048 tile!" + color: helper.myColors.fgdark + font.pixelSize: 16 + verticalAlignment: Text.AlignVCenter + } + + Button { + width: 129 + height: 40 + y: 120 + anchors.right: parent.right + anchors.rightMargin: 30 + + style: ButtonStyle { + background: Rectangle { + color: helper.myColors.bgbutton + radius: 3 + Text{ + anchors.centerIn: parent + text: "New Game" + color: helper.myColors.fgbutton + font.pixelSize: 18 + font.bold: true + } + } + } + onClicked: MyScript.startupFunction() + } + + Rectangle { + x: 30 + y: 200 + width: 500 + height: 500 + color: helper.myColors.bgdark + radius: 6 + + Grid { + x: 15; + y: 15; + rows: 4; columns: 4; spacing: 15 + + Repeater { + id: cells + model: 16 + Rectangle { + width: 425/4; height: 425/4 + radius: 3 + color: helper.myColors.bggray + property string tileText: "" + property int tileFontSize: 55 + property color tileColor: helper.myColors.fgdark + + Text { + text: tileText + color: tileColor + font.pixelSize: tileFontSize + font.bold: true + anchors.centerIn: parent + } + } + } + } + } + + + MessageDialog { + id: deadMessage + title: "Game Over" + text: "Game Over" + standardButtons: StandardButton.Retry | StandardButton.Abort + onAccepted: { + MyScript.startupFunction(); + } + onRejected: { + Qt.quit(); + } + } + + MessageDialog { + id: winMessage + title: "You Win" + text: "You win! Continue playing?" + standardButtons: StandardButton.Yes | StandardButton.No + onYes: { + MyScript.checkTargetFlag = false; + close() + } + onNo: MyScript.startupFunction() + onRejected: { + MyScript.checkTargetFlag = false; + close() + } + } + + Component.onCompleted: MyScript.startupFunction() + } +}