相信很多人都玩过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
#include
#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 NEPanel;
typedef std::vector 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
#include
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
#include
#include
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType("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;
}
}
}
}
}