From 828e0ba144c7c25229fba89fc576581388248f11 Mon Sep 17 00:00:00 2001 From: Qiaoyong Zhong Date: Sun, 20 Apr 2014 04:13:48 +0800 Subject: [PATCH] added fluid animation --- 2048-qt.pro | 6 +- main.cpp | 8 ++ myclass.cpp | 11 +++ myclass.h | 18 +++++ qml.qrc | 1 + qml/2048.js | 203 +++++++++++++++++++++++++++++++++++++++++---------- qml/Tile.qml | 79 ++++++++++++++++++++ qml/main.qml | 24 +++--- 8 files changed, 297 insertions(+), 53 deletions(-) create mode 100644 myclass.cpp create mode 100644 myclass.h create mode 100644 qml/Tile.qml diff --git a/2048-qt.pro b/2048-qt.pro index 2ba36fa..14a5323 100644 --- a/2048-qt.pro +++ b/2048-qt.pro @@ -2,7 +2,8 @@ TEMPLATE = app QT += qml quick widgets -SOURCES += main.cpp +SOURCES += main.cpp \ + myclass.cpp RESOURCES += qml.qrc @@ -15,3 +16,6 @@ include(deployment.pri) # Setting the application icon RC_ICONS = 2048.ico # On Windows ICON = 2048.ico # On Mac OSX + +HEADERS += \ + myclass.h diff --git a/main.cpp b/main.cpp index 978245a..4ea78ff 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,7 @@ #include #include +#include "myclass.h" +//#include int main(int argc, char *argv[]) { @@ -8,5 +10,11 @@ int main(int argc, char *argv[]) QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:///qml/main.qml"))); + MyClass myClass; + +// qDebug() << engine.rootObjects().length(); + QObject *mainWindow = engine.rootObjects()[0]; + QObject::connect(mainWindow, SIGNAL(helpMenuTriggered()), &myClass, SLOT(aboutQt())); + return app.exec(); } diff --git a/myclass.cpp b/myclass.cpp new file mode 100644 index 0000000..2ef5d40 --- /dev/null +++ b/myclass.cpp @@ -0,0 +1,11 @@ +#include "myclass.h" +#include + +MyClass::MyClass(QObject *parent) : + QObject(parent) +{ +} + +void MyClass::aboutQt() { + QApplication::aboutQt(); +} diff --git a/myclass.h b/myclass.h new file mode 100644 index 0000000..c084927 --- /dev/null +++ b/myclass.h @@ -0,0 +1,18 @@ +#ifndef MYCLASS_H +#define MYCLASS_H + +#include + +class MyClass : public QObject +{ + Q_OBJECT +public: + explicit MyClass(QObject *parent = 0); + +signals: + +public slots: + void aboutQt(); +}; + +#endif // MYCLASS_H diff --git a/qml.qrc b/qml.qrc index bccdf4a..daf965b 100644 --- a/qml.qrc +++ b/qml.qrc @@ -2,5 +2,6 @@ qml/main.qml qml/2048.js + qml/Tile.qml diff --git a/qml/2048.js b/qml/2048.js index fca6c09..cb76f3c 100644 --- a/qml/2048.js +++ b/qml/2048.js @@ -2,6 +2,7 @@ var score = 0; var bestScore = 0; var gridSize = 4; var cellValues; +var tileItems = []; var availableCells; //var labels = "PRC"; var labels = "2048"; @@ -38,23 +39,36 @@ function startupFunction() { cellValues[i][j] = 0; } + for (i = 0; i < Math.pow(gridSize, 2); i++) { + try { + tileItems[i].destroy(); + } catch(e) { + } + tileItems[i] = null; + } + updateAvailableCells(); - refreshCellViews(2); + createNewTileItems(true); updateScore(); console.log("Started a new game"); } function moveKey(event) { var isMoved = false; - var i, j, v, v2; + var i, j; + var v, v2, mrg, indices; var oldScore = score; switch (event.key) { case Qt.Key_Left: for (i = 0; i < gridSize; i++) { v = cellValues[i]; - v2 = mergeVector(v); + mrg = mergeVector(v); + v2 = mrg[0]; + indices = mrg[1]; + if (! arraysIdentical(v,v2)) { isMoved = true; + moveMergeTilesLeftRight(i, v, v2, indices, true); cellValues[i] = v2; } } @@ -63,10 +77,18 @@ function moveKey(event) { for (i = 0; i < gridSize; i++) { v = cellValues[i].slice(); v.reverse(); - v2 = mergeVector(v); + mrg = mergeVector(v); + v2 = mrg[0]; + indices = mrg[1]; + if (! arraysIdentical(v,v2)) { isMoved = true; + v.reverse(); v2.reverse(); + indices.reverse(); + for (j = 0; j < indices.length; j++) + indices[j] = gridSize - 1 - indices[j]; + moveMergeTilesLeftRight(i, v, v2, indices, false); cellValues[i] = v2; } } @@ -74,9 +96,13 @@ function moveKey(event) { case Qt.Key_Up: for (i = 0; i < gridSize; i++) { v = cellValues.map(function(row) {return row[i];}); - v2 = mergeVector(v); + mrg = mergeVector(v); + v2 = mrg[0]; + indices = mrg[1]; + if (! arraysIdentical(v,v2)) { isMoved = true; + moveMergeTilesUpDown(i, v, v2, indices, true); for (j = 0; j < gridSize; j++) { cellValues[j][i] = v2[j]; } @@ -87,13 +113,20 @@ function moveKey(event) { for (i = 0; i < gridSize; i++) { v = cellValues.map(function(row) {return row[i];}); v.reverse(); - v2 = mergeVector(v); + mrg = mergeVector(v); + v2 = mrg[0]; + indices = mrg[1]; + if (! arraysIdentical(v,v2)) { isMoved = true; + v.reverse(); v2.reverse(); + indices.reverse(); for (j = 0; j < gridSize; j++) { + indices[j] = gridSize - 1 - indices[j]; cellValues[j][i] = v2[j]; } + moveMergeTilesUpDown(i, v, v2, indices, false); } } break; @@ -101,7 +134,7 @@ function moveKey(event) { if (isMoved) { updateAvailableCells(); - refreshCellViews(1); + createNewTileItems(false); if (oldScore !== score) { if (bestScore < score) { bestScore = score; @@ -126,32 +159,46 @@ function ind2sub(ind) { } function mergeVector(v0) { + var i, j; + var vlen = v0.length; + var indices = []; // Pass 1: remove zero elements - var v = v0.slice(); - var i = v.length; - while (i--) { - if (v[i] === 0) { - v.splice(i, 1); + var v = []; + for (i = 0; i < vlen; i++) { + indices[i] = v.length; + if (v0[i] > 0) { + v.push(v0[i]); } } + // 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); + for (i = 0; i < v.length; i++) { + if (i === v.length - 1) { + // The last element + v2.push(v[i]); } else { - v2.push(v[0]); - v.splice(0, 1); + if (v[i] === v[i+1]) { + // move all right-side elements to left by 1 + for (j = 0; j < vlen; j++) { + if (indices[j] > v2.length) + indices[j] -= 1; + } + // Merge i-1 and i + v2.push(v[i] + 1); + score += Math.pow(2, v[i] + 1); + i++; + } else { + v2.push(v[i]); + } } } // Fill the gaps with zeros - for (i = v2.length; i < v0.length; i++) + for (i = v2.length; i < vlen; i++) v2[i] = 0; - return v2; + return [v2, indices]; } function removeElementsWithValue(arr, val) { @@ -184,37 +231,29 @@ function updateAvailableCells() { } } -function refreshCellViews(n) { - var i, sub; +function createNewTileItems(isStartup) { + var i, sub, nTiles; + + if (isStartup) { + nTiles = 2; + } else { + nTiles = 1; + } // Popup a new number - for (i = 0; i < n; i++) { + for (i = 0; i < nTiles; 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; + tileItems[randomCellId] = createTileObject(randomCellId, oneOrTwo, isStartup); + // 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() { @@ -292,3 +331,87 @@ function maxTileValue() { } return mv; } + +function createTileObject(ind, n, isStartup) { + var component; + var tile; + var sty = computeTileStyle(n); + var tileText = labelFunc(n); + + component = Qt.createComponent("Tile.qml"); + tile = component.createObject(tileGrid, {"x": cells.itemAt(ind).x, "y": cells.itemAt(ind).y, "color": sty.bgColor, "tileColor": sty.fgColor, "tileFontSize": sty.fontSize, "tileText": tileText}); + if (! isStartup) { + tile.runNewTileAnim = true; + } + + if (tile == null) { + // Error Handling + console.log("Error creating a new tile"); + } + + return tile; +} + +function moveMergeTilesLeftRight(i, v, v2, indices, left) { + var j0, j; + for (j0 = 0; j0 < v.length; j0++) { + if (left) { + j = j0; + } else { + j = v.length - 1 - j0; + } + + if (v[j] > 0 && indices[j] !== j) { + if (v2[indices[j]] > v[j] && tileItems[gridSize*i+indices[j]] !== null) { + // Move and merge + tileItems[gridSize*i+j].destroyFlag = true; + tileItems[gridSize*i+j].z = -1; + tileItems[gridSize*i+j].x = cells.itemAt(gridSize*i+indices[j]).x; +// tileItems[gridSize*i+j].destroy(); + + var sty = computeTileStyle(v2[indices[j]]); + tileItems[gridSize*i+indices[j]].tileText = labelFunc(v2[indices[j]]); + tileItems[gridSize*i+indices[j]].color = sty.bgColor; + tileItems[gridSize*i+indices[j]].tileColor = sty.fgColor; + tileItems[gridSize*i+indices[j]].tileFontSize = sty.fontSize; + } else { + // Move only + tileItems[gridSize*i+j].x = cells.itemAt(gridSize*i+indices[j]).x; + tileItems[gridSize*i+indices[j]] = tileItems[gridSize*i+j]; + } + tileItems[gridSize*i+j] = null; + } + } +} + +function moveMergeTilesUpDown(i, v, v2, indices, up) { + var j0, j; + for (j0 = 0; j0 < v.length; j0++) { + if (up) { + j = j0; + } else { + j = v.length - 1 - j0; + } + + if (v[j] > 0 && indices[j] !== j) { + if (v2[indices[j]] > v[j] && tileItems[gridSize*indices[j]+i] !== null) { + // Move and merge + tileItems[gridSize*j+i].destroyFlag = true; + tileItems[gridSize*j+i].z = -1; + tileItems[gridSize*j+i].y = cells.itemAt(gridSize*indices[j]+i).y; +// tileItems[gridSize*j+i].destroy(); + + var sty = computeTileStyle(v2[indices[j]]); + tileItems[gridSize*indices[j]+i].tileText = labelFunc(v2[indices[j]]); + tileItems[gridSize*indices[j]+i].color = sty.bgColor; + tileItems[gridSize*indices[j]+i].tileColor = sty.fgColor; + tileItems[gridSize*indices[j]+i].tileFontSize = sty.fontSize; + } else { + // Move only + tileItems[gridSize*j+i].y = cells.itemAt(gridSize*indices[j]+i).y; + tileItems[gridSize*indices[j]+i] = tileItems[gridSize*j+i]; + } + tileItems[gridSize*j+i] = null; + } + } +} diff --git a/qml/Tile.qml b/qml/Tile.qml new file mode 100644 index 0000000..a1a31cd --- /dev/null +++ b/qml/Tile.qml @@ -0,0 +1,79 @@ +import QtQuick 2.2 + +Rectangle { + id: tileContainer + width: 425/4 + height: 425/4 + radius: 3 + color: "white" + property string tileText: "" + property int tileFontSize: 55 + property color tileColor: "black" + property int moveAnimTime: 100 + property int newTileAnimTime: 200 + property bool runNewTileAnim: false + property bool destroyFlag: false + + Text { + id: tileLabel + text: tileText + color: tileColor + font.pixelSize: tileFontSize + font.bold: true + anchors.centerIn: parent + Behavior on text { + PropertyAnimation { + duration: moveAnimTime + } + } + } + + ParallelAnimation { + running: runNewTileAnim + NumberAnimation { + target: tileContainer + property: "opacity" + from: 0.0 + to: 1.0 + duration: newTileAnimTime + } + + ScaleAnimator { + target: tileContainer + from: 0 + to: 1 + duration: newTileAnimTime + easing.type: Easing.OutQuad + } + } + + Behavior on color { + ColorAnimation { + duration: moveAnimTime + } + } + + Behavior on y { + NumberAnimation { + easing.type: Easing.InQuad + duration: moveAnimTime + onRunningChanged: { + if ((!running) && destroyFlag) { + tileContainer.destroy(); + } + } + } + } + + Behavior on x { + NumberAnimation { + easing.type: Easing.InQuad + duration: moveAnimTime + onRunningChanged: { + if ((!running) && destroyFlag) { + tileContainer.destroy(); + } + } + } + } +} diff --git a/qml/main.qml b/qml/main.qml index 57b2933..5b296b2 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -6,6 +6,7 @@ import QtQuick.Window 2.1 import "2048.js" as MyScript ApplicationWindow { + id: mainWindow visible: true width: 550 height: 740 @@ -15,6 +16,8 @@ ApplicationWindow { x: (Screen.width - width) / 2 y: (Screen.height - height) / 2 + signal helpMenuTriggered + menuBar: MenuBar { Menu { title: qsTr("File") @@ -29,6 +32,14 @@ ApplicationWindow { onTriggered: Qt.quit(); } } + Menu { + id: helpMenu + title: qsTr("Help") + MenuItem { + text: "About Qt" + onTriggered: mainWindow.helpMenuTriggered() + } + } } @@ -132,6 +143,7 @@ ApplicationWindow { radius: 6 Grid { + id: tileGrid x: 15; y: 15; rows: 4; columns: 4; spacing: 15 @@ -143,23 +155,11 @@ ApplicationWindow { 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"