目录
效果展示
游戏简介
游戏详情
2048是一个经典的拼图游戏,目标很简单;达到2048。在网格上移动平铺并将相同的数字合并为一个。提前计划,并注意新的瓷砖产卵,这可能会阻止你加入你的数字。
操作指南
每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。
操作技巧
-
最大数尽可能放在角落。
-
数字按顺序紧邻排列。
-
首先满足最大数和次大数在的那一列/行是满的。
-
时刻注意活动较大数(32以上)旁边要有相近的数。
-
以大数所在的一行为主要移动方向
-
不要急于“清理桌面”。
代码实现
QWidget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QMessageBox>
#include <QApplication>
#include <QDesktopWidget>
#include <QFile>
#include <QtGui>
#include "GameWidget.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QPushButton *restartBtn;
QPushButton *closeBtn;
QLabel *titleLabel;
QLabel *tipsLabel;
QLabel *scoreLbl;
QLabel *highScoreLbl;
GameWidget *gameWidget;
// 宽度和高度的缩放比例 用来使窗口部件随主窗口的尺寸改变而改变位置与尺寸
qreal ratioW, ratioH;
int highScore;
QPoint dragPosition;
bool bPressFlag;
protected:
void resizeEvent(QResizeEvent *);
void keyPressEvent(QKeyEvent * event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
public slots:
void onScoreInc(int);
void onGameOver();
};
#endif // WIDGET_H
Widget.cpp
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent, Qt::FramelessWindowHint)
, bPressFlag(false)
{
setAutoFillBackground(true);
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor("#808ad2"));
setPalette(pal);
titleLabel = new QLabel(tr("2048"), this);
titleLabel->setStyleSheet("color:#746D65;");
titleLabel->setFont(QFont("arial", 40, QFont::Black));
tipsLabel = new QLabel(tr("Join the numbers and get to the 2048 tile!"), this);
tipsLabel->setStyleSheet("color:#B3AFA7;");
tipsLabel->setFont(QFont("arial", 8, QFont::Normal));
highScore = 0;
QFile file("score.j");
if (file.open(QIODevice::ReadOnly))
{
file.read((char *)&highScore, sizeof(highScore));
file.close();
}
gameWidget = new GameWidget(this);
gameWidget->setFocus();
connect(gameWidget, SIGNAL(ScoreInc(int)), this, SLOT(onScoreInc(int)));
connect(gameWidget, SIGNAL(GameOver()), this, SLOT(onGameOver()));
connect(gameWidget, SIGNAL(win()), this, SLOT(onWin()));
QFont font;
font.setFamily("Arial");
font.setBold(true);
font.setPixelSize(15);
restartBtn = new QPushButton("New Game", this);
restartBtn->setFont(font);
restartBtn->setStyleSheet("QPushButton{color:#55285f;background:#a7b7e8;border-style:flat;}");
restartBtn->setFocusPolicy(Qt::NoFocus);
connect(restartBtn, SIGNAL(clicked()), gameWidget, SLOT(restart()));
highScoreLbl = new QLabel(QString("BEST\n%1").arg(highScore),this);
highScoreLbl->setFont(font);
highScoreLbl->setAlignment(Qt::AlignCenter);
highScoreLbl->setStyleSheet("QLabel{color:#ee3b34;background:#ccc3b4}");
scoreLbl = new QLabel("SCORE\n0", this);
scoreLbl->setFont(font);
scoreLbl->setAlignment(Qt::AlignCenter);
scoreLbl->setStyleSheet("QLabel{color:#fcb43c;background:#7e7764}");
closeBtn = new QPushButton("x", this);
closeBtn->setFocusPolicy(Qt::NoFocus);
closeBtn->setFont(QFont("Arial", 11, QFont::Normal));
closeBtn->setStyleSheet("QPushButton{border-style:flat;color:#BDAD9F}"
"QPushButton:hover{border-style:flat;color:#FF0000}");
closeBtn->setCursor(Qt::PointingHandCursor);
closeBtn->setGeometry(400 - 18, 3, 15, 15);
connect(closeBtn, SIGNAL(clicked()), this, SLOT(close()));
// 重置窗口大小
resize(400, 510);
move((QApplication::desktop()->width() - width())/2, (QApplication::desktop()->height() - height())/2);
}
Widget::~Widget()
{
delete restartBtn;
delete scoreLbl;
delete highScoreLbl;
delete gameWidget;
}
void Widget::onScoreInc(int score)
{
scoreLbl->setText(QString("Score:\n %1").arg(score));
if (score > highScore)
{
highScore = score;
highScoreLbl->setText(QString("BEST:\n %1").arg(highScore));
// 将新的最高分存入文件
QFile file("score.j");
file.open(QIODevice::WriteOnly);
file.write((char *)&highScore, sizeof(highScore));
file.close();
}
}
void Widget::onGameOver()
{
QMessageBox::information(this, "GameOver", "You lost !");
}
void Widget::resizeEvent(QResizeEvent *)
{
// 计算宽度和高度的缩放比例
ratioW = width() / 400.0f;
ratioH = height() / 510.0f;
// 重置子部件大小和位置
titleLabel->setGeometry(20 * ratioW, 40 * ratioH , 150 * ratioW, 50 * ratioH);
tipsLabel->setGeometry(20 * ratioW, 100 * ratioH , 300 * ratioW, 20 * ratioH);
gameWidget->setGeometry(18 * ratioW, 140 * ratioH, 365 * ratioW, 365 * ratioH);
restartBtn->setGeometry(280 * ratioW, 90 * ratioH, 100 * ratioW, 30 * ratioH);
highScoreLbl->setGeometry(300 * ratioW, 40 * ratioH, 80 * ratioW, 40 * ratioH);
scoreLbl->setGeometry(210 * ratioW, 40 * ratioH, 80 * ratioW, 40 * ratioH);
}
void Widget::keyPressEvent(QKeyEvent *event)
{
gameWidget->setFocus();
QWidget::keyPressEvent(event);
}
void Widget::mousePressEvent ( QMouseEvent * event)
{
bPressFlag = true;
dragPosition = event->pos();
QWidget::mousePressEvent(event);
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if (bPressFlag) {
QPoint relaPos(QCursor::pos() - dragPosition);
move(relaPos);
}
QWidget::mouseMoveEvent(event);
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
bPressFlag = false;
QWidget::mouseReleaseEvent(event);
}
GameWidget.h
#ifndef GAMEWIDGET_H
#define GAMEWIDGET_H
#include <QWidget>
#include <QMouseEvent>
#include <QEventLoop>
#include <QTimer>
#include <QPainter>
#include <QList>
enum GestureDirect
{
LEFT = 0,
RIGHT = 1,
UP = 2,
DOWN = 3
};
// 定义动画的类型
enum AnimationType
{
MOVE = 0, // 方格移动动画
APPEARANCE = 1 // 方格出现动画
};
// 动画结构体
struct Animation
{
AnimationType type; // 动画类型
GestureDirect direct; // 方向
QPointF startPos; // 起始点坐标 出现动画仅仅使用这个坐标
QPointF endPos; // 终止点坐标 移动动画的终点坐标
int digit; // 数码
int digit2; // 第二数码 数码可能被合并
};
class GameWidget : public QWidget
{
Q_OBJECT
public:
explicit GameWidget(QWidget *parent = 0);
protected:
void keyPressEvent(QKeyEvent * event);
private:
int board[4][4];
int digitCount;
int score;
QPoint startPos;
QList<Animation> animationList;
qreal w, h;
QImage *cacheImg;
bool isAnimating;
bool checkGameOver();
int getBitCount(int);
bool playAnimation(Animation&, QPainter&);
void paintEvent(QPaintEvent *);
signals:
void GestureMove(GestureDirect);
void ScoreInc(int);
void GameOver();
public slots:
void onGestureMove(GestureDirect);
void restart();
};
#endif // GAMEWIDGET_H
GameWidget.cpp
#include <QDebug>
#include "GameWidget.h"
// 颜色数组 存储每个数字对应的背景色
QColor digitBkg[11] = {QColor::fromRgb(0xEE, 0xE5, 0xDB), QColor::fromRgb(0xEC, 0xE0, 0xC8),
QColor::fromRgb(0xF2, 0xAF, 0x78), QColor::fromRgb(0xEE, 0x8A, 0x54),
QColor::fromRgb(0xFE, 0x76, 0x5E), QColor::fromRgb(0xE7, 0x58, 0xC),
QColor::fromRgb(0xFF, 0x66, 0x66), QColor::fromRgb(0xF1, 0xCF, 0x48),
QColor::fromRgb(0xCC, 0x33, 0x33), QColor::fromRgb(0xE3, 0xB9, 0x11),
QColor::fromRgb(0xFF, 0x00, 0x00)};
QPointF dPos[5] = {QPointF(-10, 0), QPointF(10, 0), QPointF(0, -10), QPointF(0, 10), QPointF(-2, -2)};
GameWidget::GameWidget(QWidget *parent) :
QWidget(parent)
{
connect(this, SIGNAL(GestureMove(GestureDirect)), SLOT(onGestureMove(GestureDirect)));
memset(board, 0, sizeof(board));
board[rand() % 4][rand() % 4] = 2;
while(true) {
int x = rand() % 4;
int y = rand() % 4;
if (board[x][y] != 0) {
continue;
}
else {
board[x][y] = 2;
break;
}
}
score = 0;
digitCount = 2;
isAnimating = false;
cacheImg = NULL;
}
void GameWidget::keyPressEvent(QKeyEvent *event)
{
if (isAnimating)
return;
switch (event->key()) {
case Qt::Key_Left:
emit GestureMove(LEFT);
break;
case Qt::Key_Right:
emit GestureMove(RIGHT);
break;
case Qt::Key_Down:
emit GestureMove(DOWN);
break;
case Qt::Key_Up:
emit GestureMove(UP);
break;
default:
break;
}
QWidget::keyPressEvent(event);
}
void GameWidget::onGestureMove(GestureDirect direct)
{
int i, j, k;
Animation a;
bool combine = false;
switch (direct)
{
case LEFT:
for (i = 0; i < 4; i++)
{
j = 0, k = 0, combine = false;
while (true)
{
while (j < 4 && board[i][j] == 0)
j++;
if (j > 3)
break;
qSwap(board[i][k], board[i][j]);
// 记录动画信息
a.type = MOVE;
a.startPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i);
a.endPos = QPointF(7 + (w + 5) * k, 7 + (h + 5) * i);
a.digit = a.digit2 = board[i][k];
a.direct = LEFT;
if (!combine && k > 0 && board[i][k] == board[i][k - 1])
{
board[i][k - 1] <<= 1;
board[i][k] = 0;
a.digit2 = board[i][k - 1];
a.endPos = QPointF(7 + (w + 5) * (k - 1), 7 + (h + 5) * i);
score += board[i][k - 1];
emit ScoreInc(score);
digitCount--;
combine = true;
}
else
k++;
j++;
// 添加到动画链表
animationList.append(a);
}
}
break;
case RIGHT:
for (i = 0; i < 4; i++)
{
j = 3, k = 3, combine = false;
while (true)
{
while (j > -1 && board[i][j] == 0)
j--;
if (j < 0)
break;
qSwap(board[i][k], board[i][j]);
a.type = MOVE;
a.startPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i);
a.endPos = QPointF(7 + (w + 5) * k, 7 + (h + 5) * i);
a.digit = a.digit2 = board[i][k];
a.direct = RIGHT;
if (!combine && k < 3 && board[i][k] == board[i][k + 1])
{
board[i][k + 1] <<= 1;
board[i][k] = 0;
a.digit2 = board[i][k + 1];
a.endPos = QPointF(7 + (w + 5) * (k + 1), 7 + (h + 5) * i);
score += board[i][k + 1];
emit ScoreInc(score);
digitCount--;
combine = true;
}
else
k--;
j--;
animationList.append(a);
}
}
break;
case UP:
for (i = 0; i < 4; i++)
{
j = 0, k = 0, combine = false;
while (true)
{
while (j < 4 && board[j][i] == 0)
j++;
if (j > 3)
break;
qSwap(board[k][i], board[j][i]);
a.type = MOVE;
a.startPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * j);
a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * k);
a.digit = a.digit2 = board[k][i];
a.direct = UP;
if (!combine && k > 0 && board[k][i] == board[k - 1][i])
{
board[k - 1][i] <<= 1;
board[k][i] = 0;
a.digit2 = board[k - 1][i];
a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * (k - 1));
score += board[k - 1][i];
emit ScoreInc(score);
digitCount--;
combine = true;
}
else
k++;
j++;
animationList.append(a);
}
}
break;
case DOWN:
for (i = 0; i < 4; i++)
{
j = 3, k = 3, combine = false;
while (true)
{
while (j > -1 && board[j][i] == 0)
j--;
if (j < 0)
break;
qSwap(board[k][i], board[j][i]);
a.type = MOVE;
a.startPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * j);
a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * k);
a.digit = a.digit2 = board[k][i];
a.direct = DOWN;
if (!combine && k < 3 && board[k][i] == board[k + 1][i])
{
board[k + 1][i] <<= 1;
board[k][i] = 0;
a.digit2 = board[k + 1][i];
a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * (k + 1));
score += board[k + 1][i];
emit ScoreInc(score);
digitCount--;
combine = true;
}
else
k--;
j--;
animationList.append(a);
}
}
break;
}
bool flag_move = false;
for(int index = 0; index < animationList.size(); index++) {
if (animationList.at(index).startPos != animationList.at(index).endPos) {
flag_move = true;
break;
}
}
if (digitCount != 16 && flag_move)
{
i = rand() % 4, j = rand() % 4;
while (board[i][j] != 0)
i = rand() % 4, j = rand() % 4;
board[i][j] = 2;
a.type = APPEARANCE;
a.startPos = a.endPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i);
a.startPos += QPointF(w / 2, h / 2);
a.digit = board[i][j];
digitCount++;
}
else
{
if (checkGameOver())
emit GameOver();
}
// 开始绘制动画效果
isAnimating = true;
// 动画列表的迭代器
QList<Animation>::iterator it;
// 事件循环 用于延时
QEventLoop eventLoop;
if (cacheImg)
delete cacheImg;
cacheImg = new QImage(width(), height(), QImage::Format_ARGB32);
cacheImg->fill(0);
QPainter painter(cacheImg);
QFont font;
font.setFamily("Consolas");
font.setBold(true);
font.setPixelSize(40);
painter.setFont(font);
// 标识所有方格动画是否都播放完毕
bool ok = false;
while (true)
{
QBrush brush(QColor::fromRgb(114, 126, 174));
painter.setBrush(brush);
// 设置画笔为空笔 目的是使绘制的图形没有描边
painter.setPen(Qt::NoPen);
painter.drawRect(2, 2, width() - 4, height() - 4);
brush.setColor(QColor::fromRgb(141,166,234));
painter.setBrush(brush);
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h));
ok = true;
for (it = animationList.begin(); it != animationList.end(); it++)
if (!playAnimation(*it, painter))
ok = false;
update();
if (ok)
break;
// 延时5ms
QTimer::singleShot(5, &eventLoop, SLOT(quit()));
eventLoop.exec();
}
// 播放方格出现的动画
while (!playAnimation(a, painter))
{
update();
QTimer::singleShot(5, &eventLoop, SLOT(quit()));
eventLoop.exec();
}
//清除所有动画
animationList.clear();
//刷新当前部件
isAnimating = false;
update();
}
bool GameWidget::playAnimation(Animation& a, QPainter& painter)
{
bool rtn = false;
QBrush brush(Qt::SolidPattern);
// 移动方格位置
if (a.type == MOVE)
{
switch (a.direct)
{
case LEFT:
if (a.startPos.x() > a.endPos.x())
a.startPos += dPos[LEFT];
else
a.startPos = a.endPos, rtn = true;
break;
case RIGHT:
if (a.startPos.x() < a.endPos.x())
a.startPos += dPos[RIGHT];
else
a.startPos = a.endPos, rtn = true;
break;
case UP:
if (a.startPos.y() > a.endPos.y())
a.startPos += dPos[UP];
else
a.startPos = a.endPos, rtn = true;
break;
case DOWN:
if (a.startPos.y() < a.endPos.y())
a.startPos += dPos[DOWN];
else
a.startPos = a.endPos, rtn = true;
}
// 如果方格移动到终点
if (!rtn)
{
brush.setColor(digitBkg[getBitCount(a.digit)]);
painter.setBrush(brush);
painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(), w, h));
painter.setPen(QColor::fromRgb(0, 0, 0));
painter.drawText(QRectF(a.startPos.x(), a.startPos.y(), w, h), Qt::AlignCenter,
QString::number(a.digit));
}
else
{
brush.setColor(digitBkg[getBitCount(a.digit2)]);
painter.setBrush(brush);
painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(), w, h));
painter.setPen(QColor::fromRgb(0, 0, 0));
painter.drawText(QRectF(a.startPos.x(), a.startPos.y(), w, h), Qt::AlignCenter,
QString::number(a.digit2));
}
painter.setPen(Qt::NoPen);
}
else
{
// 方格出现的动画效果
if (a.startPos.x() > a.endPos.x())
a.startPos += dPos[4];
else
a.startPos = a.endPos, rtn = true;
brush.setColor(digitBkg[getBitCount(a.digit)]);
painter.setBrush(brush);
painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(),
w - 2 * (a.startPos.x() - a.endPos.x()),
h - 2 * (a.startPos.y() - a.endPos.y())));
painter.setPen(QColor::fromRgb(0, 0, 0));
painter.drawText(QRectF(a.endPos.x(), a.endPos.y(), w, h),
Qt::AlignCenter, QString::number(a.digit));
painter.setPen(Qt::NoPen);
}
return rtn;
}
void GameWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// 如果正在播放动画效果则绘制缓存位图
if (isAnimating)
{
painter.drawImage(0, 0, *cacheImg);
return;
}
QBrush brush(QColor::fromRgb(114, 126, 174));
painter.setBrush(brush);
// 设置画笔为空笔 目的是使绘制的图形没有描边
painter.setPen(Qt::NoPen);
painter.drawRect(2, 2, width() - 4, height() - 4);
w = width() - 4, h = height() - 4;
w = (w - 25) / 4, h = (h - 25) / 4;
QFont font;
font.setFamily("Consolas");
font.setBold(true);
font.setPixelSize(40);
painter.setFont(font);
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
{
if (board[i][j])
{
brush.setColor(digitBkg[getBitCount(board[i][j])]);
painter.setBrush(brush);
painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h));
painter.setPen(QColor::fromRgb(0, 0, 0));
painter.drawText(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h), Qt::AlignCenter,
QString::number(board[i][j]));
painter.setPen(Qt::NoPen);
}
else
{
brush.setColor(QColor::fromRgb(141,166,234));
painter.setBrush(brush);
painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h));
}
}
}
void GameWidget::restart()
{
// 初始化相关变量 同构造函数
score = 0;
digitCount = 2;
memset(board, 0, sizeof(board));
board[rand() % 4][rand() % 4] = 2;
while(true) {
int x = rand() % 4;
int y = rand() % 4;
if (board[x][y] != 0) {
continue;
}
else {
board[x][y] = 2;
break;
}
}
emit ScoreInc(score);
update();
}
bool GameWidget::checkGameOver()
{
// 循环检测是否含有相邻的相同数码
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
{
if (j != 3 && board[i][j] == board[i][j + 1])
return false;
if (i != 3 && board[i][j] == board[i + 1][j])
return false;
}
return true;
}
int GameWidget::getBitCount(int n)
{
int c = 0;
while (n >>= 1)
c++;
return c - 1;
}
总结
1、编写2048主要考虑几个点;,一个是刷新,需要随机更新2或4,一个是堆叠和合并,因为堆叠过程中由于可能会出现一行4个这些情况,所以合并后需要再次堆叠。
2、游戏中有些小bug,一个是前面提到的一行出现4个2这种情况时堆叠不完美,只会合并前两个;一个是当所有格子出现在一边时,连续向这边移动式,会出现失败提示框
3、若还有其他问题,期待大家帮忙修改雅正(邮箱:1953297167@qq.com)