首页 > 代码库 > Qt-俄罗斯方块
Qt-俄罗斯方块
声明: 仅个人小记
整个有效项目的文件已经上传csdn: http://download.csdn.net/detail/qq_25847123/9718822
目录:
1.前言
2.效果展示
3.主要代码
4.开发日志
5.小结
1.前言
整个程序的完成花了我不少时间, 有许多知识细节不够清楚,边学边做,断断续续完成的。之前有用C++直接做过一次俄罗斯方块,界面简陋,是在控制台运行的。这次用Qt实现,沿用了之前的总体思想,技术细节有所改动。
2.效果展示
刚开始:
开始游戏:
背景界面随着分数进行随机的切换
3.主要代码
项目构架:
**基本单元方块类(Elem):**
#ifndef ELEM_H
#define ELEM_H
#include <QGraphicsObject>
#include <QPainter>
#include <QTime>
enum elemColor {
red, yellow, blue, green, lightBlue, purple,gray,randomColor
};
const QColor ElemColorArray[] = {
QColor(255,0,0,100), QColor(255,255,0,100),QColor(0,0,255,100),
QColor(0,255,0,100),QColor(0,255,255,100),QColor(255,0,255,100),
QColor(150,100,100,100)
};
class Elem : public QGraphicsObject
{
public:
Elem(QPointF pos = QPointF(0,0), int colorId = elemColor::randomColor, QGraphicsItem *parent = Q_NULLPTR);
void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget);
QRectF boundingRect() const;
int getCurrentStatus();
void setCurrentStatus(int status);
private:
int colorId;
int currentStatus;
};
#endif // ELEM_H
**基本单元方块类Elem 实现部分:**
#include "elem.h"
Elem::Elem(QPointF pos,int colorId, QGraphicsItem *parent):QGraphicsObject(parent)
{
this->currentStatus = 0;
this->setPos(this->mapFromParent(pos));
this->colorId = colorId;
if (this->colorId == elemColor::randomColor) {
qsrand(QTime().currentTime().second());
this->colorId = qrand() % 7;
}
}
void Elem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->drawPixmap(0,0,20,20,QPixmap(":/image/images/box.gif"));
painter->setBrush(QBrush(ElemColorArray[this->colorId]));
painter->setPen(QPen(ElemColorArray[this->colorId]));
painter->drawRect(0,0,19,19);
}
QRectF Elem::boundingRect() const
{
return QRectF(-0.5,-0.5,21,21);
}
int Elem::getCurrentStatus()
{
return this->currentStatus;
}
void Elem::setCurrentStatus(int status)
{
this->currentStatus = status;
}
**形状基类MyShape:**
#ifndef MYSHAPE_H
#define MYSHAPE_H
#include <QGraphicsItemGroup>
#include <QKeyEvent>
#include <QObject>
#include <QTime> // 为了引入 随机数
#include "elem.h"
enum shapeCode {
shapeT,shapeL,shapeMirrorL,shapeSquare,shapeZ,shapeMirrorZ,shapeLine,shapeRandom
};// 这里面的值 0~7
enum shapeCoorId {// 形状坐标id
LeftTop, RightTop,RightBottom,LeftBottom
};// 分别是 左上, 右上, 右下, 左下 为原点 在旋转的时候坐标系整体旋转
class MyShape : public QObject,public QGraphicsItemGroup
{
Q_OBJECT
public:
MyShape(QPoint origin = QPoint(300,40));
QPoint getOrigin();
virtual void rolate();
virtual void rolateBack();
int getCurrentStatus();
protected:
QPoint origin;
// 下面的变量用来标明当前使用的是哪一个形状, 在体现 旋转中心不同 等形状个性 的时候需要用到
protected:
int currentStatus;// 指明了当前的旋转角度, 同时也表明了当前坐标系的状态,也说明了自己当前属于自己的形态的哪一种
int colorId;
Elem * elem[4];
};
#endif // MYSHAPE_H
**MyShape实现部分**:
#include "myshape.h"
MyShape::MyShape(QPoint origin)
{
this->origin = origin;
this->setPos(this->origin);
}
QPoint MyShape::getOrigin()
{
return this->origin;
}
void MyShape::rolate()
{
this->currentStatus = (this->currentStatus + 90) % 360;
this->setTransformOriginPoint(20,20);
this->setRotation(this->currentStatus);
elem[0]->setCurrentStatus(this->currentStatus);
elem[1]->setCurrentStatus(this->currentStatus);
elem[2]->setCurrentStatus(this->currentStatus);
elem[3]->setCurrentStatus(this->currentStatus);
}
void MyShape::rolateBack()
{
this->currentStatus = (this->currentStatus-90 + 360) % 360;
this->setTransformOriginPoint(20,20);
this->setRotation(this->currentStatus);
elem[0]->setCurrentStatus(this->currentStatus);
elem[1]->setCurrentStatus(this->currentStatus);
elem[2]->setCurrentStatus(this->currentStatus);
elem[3]->setCurrentStatus(this->currentStatus);
}
int MyShape::getCurrentStatus()
{
return this->currentStatus;
}
**各个具体的形状这里我只举例一个ShapeLine类:**
#ifndef SHAPELINE_H
#define SHAPELINE_H
#include "myshape.h"
class ShapeLine : public MyShape
{
public:
ShapeLine(int currentStatus = 0,QPoint origin = QPoint(300,40),int colorId = elemColor::gray);
virtual void rolate();
};
#endif // SHAPELINE_H
**ShapeLine类实现部分:**
#include "shapeline.h"
ShapeLine::ShapeLine(int currentStatus, QPoint origin,int colorId) : MyShape(origin)
{
//this->shapeId = shapeCode::shapeLine;
if (currentStatus % 90 || currentStatus < 0) {
currentStatus = 0;
this->currentStatus = currentStatus % 180;
}
else {
qsrand(QTime::currentTime().second());
this->currentStatus = (qrand() % 2) * 90;
}
this->colorId = colorId;
elem[0] = new Elem(QPointF(0,0),this->colorId,this);
elem[1] = new Elem(QPointF(0,20),this->colorId,this);
elem[2] = new Elem(QPointF(0,40),this->colorId,this);
elem[3] = new Elem(QPointF(0,60),this->colorId,this);
this->addToGroup(elem[0]);
this->addToGroup(elem[1]);
this->addToGroup(elem[2]);
this->addToGroup(elem[3]);
while (this->currentStatus != 0) {
this->rolate();
this->currentStatus -= 90;
}
elem[0]->setCurrentStatus(this->currentStatus);
elem[1]->setCurrentStatus(this->currentStatus);
elem[2]->setCurrentStatus(this->currentStatus);
elem[3]->setCurrentStatus(this->currentStatus);
}
void ShapeLine::rolate()
{
this->currentStatus = (this->currentStatus + 90) % 180;
this->setTransformOriginPoint(20,40);
this->setRotation(this->currentStatus);
elem[0]->setCurrentStatus(this->currentStatus);
elem[1]->setCurrentStatus(this->currentStatus);
elem[2]->setCurrentStatus(this->currentStatus);
elem[3]->setCurrentStatus(this->currentStatus);
}
**负责核心操控的Game类:**
#ifndef GAME_H
#define GAME_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGuiApplication>
#include <QLabel>
#include <QPushButton>
#include "myshape.h"
#include <QTimer>
#include <QList>
#include <QPalette>
#include <QLabel>
#include <QGraphicsWidget>
#include <QMediaPlayer>
#include <QScreen>
#include <QDir>
#include "shapet.h"
#include "shapel.h"
#include "shapemirrorl.h"
#include "shapez.h"
#include "shapemirrorz.h"
#include "shapeline.h"
#include "shapesquare.h"
#include <QVector>
#include <iostream>
#include <QMessageBox>
using namespace std;
class Game : public QGraphicsView
{
Q_OBJECT
public:
Game();
void keyPressEvent(QKeyEvent *event);
private slots:
void init(); // 需要写一个初始化函数,明明已经有了构造函数,构造函数不久可一实现初始化吗,为什么还要重写一个init函数呢? 因为游戏会重新开局!!这个重新开局不是关闭游戏再重新打开游戏,而构造函数在整个过程中只是会执行一次,除非关闭程序重新运行才可以执行构造函数,显然着不符合用户需求,用户希望的是在不需要重新打开游戏的前提下,重新游戏
private:
void startMenu();
bool isShapeColliding(); // 方块之间冲突
bool isBorderColliding(); // 方块和边界冲突
bool moveDownOneStep();// 只是单纯的下降一步,返回值表示下降是否成功
void clearUpAndRenewShape(); // 消除判断 和 新生一个形状
void clearShapeWithNoChild(); // 清除没有孩子的形状
MyShape * newShape(int shapeId = shapeCode::shapeRandom,int status = -1,QPoint landedPoint = QPoint(300,40));// shapeId 用于选择哪个形状, status 用于确定形状初始的形态 -1 表示随机, landedPoint 指定该形状的落点
QRectF getShapeCurrentBoundingRectInScene();//这个是用来获取当前形状,无论是否被旋转过,都一律返回当前在scene中的以左上角为中心点的包围矩形
void gameOver(); // 当消除后,当时形状的上界能够触碰到方块活动区域上边界,那么游戏结束,调用gameOver,做好一些后续处理工作
void setFlag(bool flag);
protected slots:
void timeSlice();
void pause();
void screenShotSlot(); // 保存游戏界面截图
protected:
void mousePressEvent(QMouseEvent * event);
void mouseMoveEvent(QMouseEvent * event);
void mouseReleaseEvent(QMouseEvent *event);
private:
MyShape * myshape;
// 下面变量和用来指定方块活动区域范围
QPoint areaOrigin; // 区域原点
int areaWidth; // 区域宽
int areaHeight; // 区域高
QGraphicsItem * leftLine; // 左边界线
QGraphicsItem * bottomLine;// 下边界线
QGraphicsItem * rightLine;// 右边界线
QGraphicsItem * topLine;// 上边界线
// 定时器
QTimer * timer;
int timeCount; // 时间计数器 用来配合时间片的分配
// 下面的变量用来记录方块下降速度 数值越大代表速度越高
int speed;
// 下面的变量用来存放 当前所有的形状, 这些形状包括是完整的形状和残缺的形状,一旦该形状一个孩子都没有,就把它删除并且从 下面的这个链表中删除
QList<MyShape * > currentShapelist;
int currentShapeId;
QVector<QVector<bool>> sceneArray;// 这个大概就是我的终极方案了
// 下面是关于分数的设置
int grades;
int rowsNumRemoves; //消除的行数
int totalNumOfShapesUsed;
QGraphicsTextItem * gradeText;
QGraphicsTextItem * gradeNumber;
QGraphicsWidget * mask;// 遮罩
QPushButton * startButton;// 游戏开始按钮
QPushButton * optionButton;// 选择项按钮
QPushButton * helpButton;// 帮助按钮
QPushButton * exitButton;// 游戏退出按钮
QPushButton * exitButton_1;
QPushButton * replay;// 重新游戏
QPushButton * pauseButton; // 游戏暂停按钮
QLabel * gameOverLabel;// 游戏结束文本显示
QLabel * gradeLabel;
bool gameStatus; // 游戏状态 运行 或者暂停 这个变量是为辅助实现暂停功能的
// 下面是音效 背景音乐
QMediaPlayer * bgm;
// 下面是关于重新实现框口拖动需要的变量
QPoint mousePressPos;
bool mousePressed; // 标记鼠标按下
int cnt;//用来辅助消除框体移动过程发生抖动现象 对应代码中有详解
// 保存游戏截图
QPixmap screenShot;
QPushButton * screenShotButton;
};
#endif // GAME_H
**负责核心操控的Game类的实现部分:**
#include "game.h"
Game::Game()
{// 在构造函数中的代码都是在整个程序运行过程中只需要做一次的代码
this->setWindowFlags(Qt::FramelessWindowHint);
this->setWindowTitle(QStringLiteral("俄罗斯方块"));
this->setWindowIcon(QIcon(":/image/images/icon.png"));
// 设置整个游戏界面的尺寸
this->setMinimumSize(810,510);
this->setMaximumSize(810,510);
// 下面是 设置 方块活动区域
this->areaOrigin.setX(200);
this->areaOrigin.setY(40);
this->areaWidth = 240;
this->areaHeight = 400;
// 下面是 准备场景
QGraphicsScene * scene = new QGraphicsScene;
this->setScene(scene);
scene->setSceneRect(5,5,800,500);
scene->setBackgroundBrush(QBrush(QPixmap(":/image/images/Tetris_background_1.jpg")));
// 绘制方块活动区域边界线 以及线条的颜色
this->leftLine = scene->addLine(this->areaOrigin.x(),this->areaOrigin.y(),this->areaOrigin.x(),this->areaOrigin.y()+this->areaHeight,QPen(QBrush(QColor(40,40,120,80)),2));
this->bottomLine = scene->addLine(this->areaOrigin.x(),this->areaOrigin.y()+this->areaHeight,this->areaOrigin.x()+this->areaWidth,this->areaOrigin.y()+this->areaHeight,QPen(QBrush(QColor(40,40,120,80)),2));
this->rightLine = scene->addLine(this->areaOrigin.x()+this->areaWidth,this->areaOrigin.y()+this->areaHeight,this->areaOrigin.x()+this->areaWidth,this->areaOrigin.y(),QPen(QBrush(QColor(40,40,120,80)),2));
this->topLine = scene->addLine(this->areaOrigin.x()+this->areaWidth,this->areaOrigin.y(),this->areaOrigin.x(),this->areaOrigin.y(),QPen(QBrush(QColor(40,40,120,80)),2));
// 准备时间器
this->timer = new QTimer(this);
this->timer->start(80);
this->timer->stop();
QObject::connect(this->timer,SIGNAL(timeout()),this,SLOT(timeSlice()));
// 规划场景数组
this->sceneArray.resize(this->areaHeight/20);
for (int i = 0; i < this->sceneArray.size(); i ++)
this->sceneArray[i].resize(this->areaWidth/20);
// 下面是关于分数
this->gradeText = scene->addText(QStringLiteral("得分: "),QFont("Times",22,QFont::Bold));
this->gradeNumber = scene->addText(QString::number(0),QFont("Times",22,QFont::Bold));
this->gradeNumber->moveBy(600,80);
this->gradeText->moveBy(520,80);
this->pauseButton = new QPushButton(QStringLiteral("暂停"),this);
this->pauseButton->move(520,150);
QObject::connect(this->pauseButton,SIGNAL(clicked(bool)),this,SLOT(pause()));
this->exitButton_1 = new QPushButton(QStringLiteral("结束"),this);
this->exitButton_1->move(520,190);
QObject::connect(this->exitButton_1,SIGNAL(clicked(bool)),this,SLOT(close()));
this->replay = new QPushButton(QStringLiteral("重玩"),this);
this->replay->move(520,230);
QObject::connect(this->replay,SIGNAL(clicked(bool)),this,SLOT(init()));
this->screenShotButton = new QPushButton(QStringLiteral("截图"),this);
this->screenShotButton->move(520,270);
QObject::connect(this->screenShotButton,SIGNAL(clicked(bool)),this,SLOT(screenShotSlot()));
// 音效
this->bgm = new QMediaPlayer(this);
// 开始界面设置
this->startMenu();
}
void Game::startMenu()
{
this->mousePressed = false; // 初始鼠标没有被按下
this->cnt = 0;
// 刚开始是隐藏的
this->pauseButton->hide();
this->exitButton_1->hide();
this->replay->hide();
this->screenShotButton->hide();
this->gradeText->hide();
this->gradeNumber->hide();
this->leftLine->hide();
this->bottomLine->hide();
this->rightLine->hide();
this->topLine->hide();
QWidget * mask = new QWidget();
mask->setAutoFillBackground(true);
mask->setPalette(QPalette(QColor(0,0,0,120)));
mask->resize(810,510);
this->mask = (QGraphicsWidget *)this->scene()->addWidget(mask);
this->mask->setZValue(1);
this->startButton = new QPushButton("开始",this);
this->optionButton = new QPushButton("选项",this);
this->helpButton = new QPushButton("帮助",this);
this->exitButton = new QPushButton("退出",this);
this->gameOverLabel = new QLabel("游戏结束",this);
this->gameOverLabel->hide();
this->gameOverLabel->setGeometry(300,80,150,50);
this->gameOverLabel->setFont(QFont("Times",20,QFont::Bold));
this->gameOverLabel->setPalette(QPalette(QColor(120,40,20)));
this->scene()->addWidget(this->gameOverLabel);
this->gradeLabel = new QLabel("得分 : ",this);
this->gradeLabel->hide();
this->gradeLabel->setGeometry(300,120,150,50);
this->gradeLabel->setFont(QFont("Times",20,QFont::Bold));
this->gradeLabel->setPalette(QPalette(QColor(120,40,20)));
this->scene()->addWidget(this->gradeLabel);
this->startButton->move(350,100);
this->optionButton->move(350,150);
this->helpButton->move(350,200);
this->exitButton->move(350,250);
QObject::connect(startButton,SIGNAL(clicked(bool)),this,SLOT(init()));
QObject::connect(exitButton,SIGNAL(clicked(bool)),this,SLOT(close()));
return ;
}
void Game::init()//会被多次调用 每次重新开局都是调用 这个函数
{
this->mousePressed = false; // 初始鼠标没有被按下
this->bgm->setMedia(QUrl::fromLocalFile("/home/jacklu/music/Summer.mp3"));
this->bgm->play();
// 下面是清空形状列表 考虑到重玩的时候需要清场
QList<MyShape*>::iterator i = this->currentShapelist.begin();
while (i !=this->currentShapelist.end()){
this->scene()->removeItem(*i);// 删除该形状
i = this->currentShapelist.erase(i);// 从当前有效形状表中删除
}
// 设置数组初始值
for (int i = 0; i < this->sceneArray.size(); i ++)
for (int j = 0; j < this->sceneArray[i].size(); j ++)
this->sceneArray[i][j] = false;
this->gameOverLabel->hide();
this->pauseButton->setEnabled(true);
this->gameStatus = true;// 只是标记下游戏的状态 当前为运行状态
this->mask->hide();
this->startButton->hide();
this->optionButton->hide();
this->helpButton->hide();
this->exitButton->hide();
this->leftLine->show();
this->bottomLine->show();
this->rightLine->show();
this->topLine->show();
this->pauseButton->show();
this->exitButton_1->show();
this->replay->show();
this->screenShotButton->show();
this->gradeText->show();
this->gradeNumber->show();
// 关于分数
this->totalNumOfShapesUsed=0;
this->rowsNumRemoves = 0;
this->grades = 0;
this->gradeNumber->setPlainText(QString::number(this->grades));
this->myshape = this->newShape(shapeCode::shapeRandom);
this->scene()->addItem(this->myshape);
// 时间器开始
this->timer->start();
this->timeCount = 0;
// 设置方块下降速度
this->speed = 1;
}
void Game::keyPressEvent(QKeyEvent *event)
{
if (!this->myshape)
return ;
if (!this->gameStatus)// 如果不是运行态,比如暂停态
return ;
if (event->key() == Qt::Key_Left) {
this->setFlag(false);
this->myshape->moveBy(-20,0);
// 边界线冲突判断 以及 方块之间的判度阿森纳
if (this->isBorderColliding() || this->isShapeColliding()) {
this->myshape->moveBy(20,0);
this->setFlag(true);
return ;
}
}
else if (event->key() == Qt::Key_Right) {
this->setFlag(false);
this->myshape->moveBy(20,0);
// 边界线冲突判断 以及 形状之间冲突判断
if (this->isBorderColliding() || this->isShapeColliding()) {
this->myshape->moveBy(-20,0);
this->setFlag(true);
return ;
}
}
// else if (event->key() == Qt::Key_Up) {
// this->setFlag(false);
// this->myshape->moveBy(0,-20);
// this->setFlag(true);
// }
else if (event->key() == Qt::Key_Down) {
for (int i = 0; i < 2; i ++) { // 用 for 循环来表达做同样的事情
if (!this->moveDownOneStep())
break;
}
}
else if (event->key() == Qt::Key_Space) {
this->setFlag(false);//清除原有标志
this->myshape->rolate();
// 边界线冲突判断 以及 方块之间冲突判断
// 下面的代码同时使得旋转时候因为靠边原因导致有足够的空间却不能旋转的问题得到很好的解决,灵活度得到提升
if (this->isBorderColliding() || this->isShapeColliding()) {//冲突
this->myshape->moveBy(20,0);// 尝试右移动
if (this->isBorderColliding() || this->isShapeColliding()) {// 仍然冲突
this->myshape->moveBy(-40,0); // 尝试左移动
if (this->isBorderColliding() || this->isShapeColliding()) { // 还是发生冲突
if (this->currentShapeId == shapeCode::shapeLine) {// shapeLine 特殊一点
this->myshape->moveBy(-20,0);
if (!(this->isBorderColliding() || this->isShapeColliding())) {
return ;
}
}
this->myshape->moveBy(20,0);
this->myshape->rolateBack();
this->setFlag(true);// 恢复标志
return ;
}
}
}
this->setFlag(true);
}
return ;
}
bool Game::isShapeColliding()
{
// 只是判断方块之间的冲突
int i, j;
foreach (QGraphicsItem * item, this->myshape->childItems()) {
switch(this->myshape->getCurrentStatus()) {// 也是因为坐标原点因为旋转而发生了改变引起的动态需求
case 0:
i = (item->scenePos().y()-this->areaOrigin.y())/20;
j = (item->scenePos().x()-this->areaOrigin.x())/20;
break;
case 90:
i = (item->scenePos().y()-this->areaOrigin.y())/20;
j = (item->scenePos().x()-this->areaOrigin.x())/20-1;
break;
case 180:
i = (item->scenePos().y()-this->areaOrigin.y())/20-1;
j = (item->scenePos().x()-this->areaOrigin.x())/20-1;
break;
case 270:
i = (item->scenePos().y()-this->areaOrigin.y())/20-1;
j = (item->scenePos().x()-this->areaOrigin.x())/20;
break;
}
if (this->sceneArray[i][j]) // 如果涉及的范围中存在 true ,则说明会产生冲突
return true;
}
return false;
}
bool Game::isBorderColliding()
{
// 判断的方法 : 根据 sceneBoundingRect() 提供的值我们得到该形状的包围矩形,判断该矩形各条边是否与边界线冲突
QRectF rect = this->getShapeCurrentBoundingRectInScene();
// if ( minX < this->areaOrigin.x() || minY < this->areaOrigin.y() || maxX > (this->areaOrigin.x()+this->areaWidth) || maxY > (this->areaOrigin.y() + this->areaHeight))
// 不包括对上边界的判度,因为只有游戏结束的时候才会触碰上边界,游戏过程中的触碰都不作数,所以把上边界独立出来,不在判断边界冲突的函数中写出
if ( rect.x() < this->areaOrigin.x() || (rect.x()+rect.width()) > (this->areaOrigin.x()+this->areaWidth) || (rect.y()+rect.height()) > (this->areaOrigin.y() + this->areaHeight))
return true;
return false;
}
bool Game::moveDownOneStep()// 只是单纯的下降一步,返回值表示下降是否成功
{
this->setFlag(false);// 先清除原占有标志
this->myshape->moveBy(0,20);
//下面是 边界线冲突判断 以及 形状之间冲突判断
if (this->isBorderColliding() || this->isShapeColliding()) {// 检测到有冲突 则下移动失败 采取相应的动作
this->myshape->moveBy(0,-20);
this->setFlag(true);// 恢复原占有标志
// 下降失败 这意味着本次形状运行结束
// 进入消除判断,然后紧接着 判断是否触碰上边界,因为消除完成后还触碰着上边界意味着游戏结束,如果没有结束,之后才是 新生一个形状
this->clearUpAndRenewShape();
return false;
}
// 没有发生冲突 直接占有当前位置的所有标志
this->setFlag(true);
return true;
}
void Game::clearUpAndRenewShape()
{
int count = 0; // 用来记录已经消除了的行数
// 下面是行消除判断和处理
for (int y = this->areaOrigin.y()+this->areaHeight-20; y >= 0; y -= 20) {
QList<QGraphicsItem *> list = this->scene()->items(QRectF(this->areaOrigin.x()-1,y-1,this->areaWidth+2,22),Qt::ContainsItemShape);
if (list.count() == 0) {
break;
}
QList<QGraphicsItem *>::iterator i = list.begin();
while (i != list.end()) {
if (!((*i)->sceneBoundingRect().width() <25)) {// 过滤掉 不是基本单位方块的 item
i = list.erase(i);// erase之后返回值为原来i的下一个节点
}
else
i ++;
}
int rowsForArray = (y-this->areaOrigin.y())/20;
if (list.count() == (this->areaWidth/20)) {// 普通行满
for (int k = 0; k < this->sceneArray[rowsForArray].size(); k ++)
this->sceneArray[rowsForArray][k] = false;
foreach (QGraphicsItem * item, list) {
this->scene()->removeItem(item);
}
count ++;
}
else if (count > 0){// 没有满,但是之前发生了消除, 采取的措施就是,下降相应的行数(count即是要下降的行数)
this->rowsNumRemoves+=count;
for (int k = 0; k < this->sceneArray[rowsForArray].size(); k ++) {
this->sceneArray[rowsForArray+count][k] = this->sceneArray[rowsForArray][k];
this->sceneArray[rowsForArray][k] = false;
}
foreach (QGraphicsItem * item, list) {
Elem * p = (Elem *)item;
switch(p->getCurrentStatus()) {
case 0:
p->moveBy(0,count*20);
break;
case 90:
p->moveBy(count*20,0);
break;
case 180:
p->moveBy(0,0-count*20);
break;
case 270:
p->moveBy(0-count*20,0);
break;
default: QMessageBox::warning(this,"错误","Here , something wrong in game.cpp"); break;
}
}
}
}
// 上面的代码进行的是检查和完成消除方块工作,紧接着,如果上面有消除发生,我们就对当前所有形状检查,是否有形状无效(即没有孩子)
if (count > 0) { // 根据消除的行数来判断是否发生了消除
this->clearShapeWithNoChild();
}
// 下面是判断当前方块是否接触了上边界 ,也就是意味着判断游戏是否结束
qDebug() << "this->getShapeCurrentBoundingRectInScene.y() " << this->getShapeCurrentBoundingRectInScene().y() << endl;
if (this->getShapeCurrentBoundingRectInScene().y() <= this->areaOrigin.y()) {
/* 游戏结束 */
this->gameOver();
return ;
}
// 下面是分数设置
this->totalNumOfShapesUsed ++;
this->grades = this->totalNumOfShapesUsed*5 + this->rowsNumRemoves*20;
this->gradeNumber->setPlainText(QString::number(this->grades));
// 根据分数对速度进行调整
if (this->grades > this->speed*this->speed*(100 - this->speed * 2)) {
if (++(this->speed) > 10) {
this->speed--;
}
QString path(":/image/images/Tetris_background_");
qsrand(QTime().currentTime().second());
path += (QString::number(qrand() % 21 + 1) + ".png");
QPixmap pixmap(path);
this->scene()->setBackgroundBrush(QBrush(pixmap));
}
// 下面是新生一个形状
this->myshape = this->newShape(shapeCode::shapeRandom);
this->scene()->addItem(this->myshape);
}
void Game::clearShapeWithNoChild()
{
QList<MyShape*>::iterator i = this->currentShapelist.begin();
while (i !=this->currentShapelist.end()){
if ((*i)->childItems().count() == 0) {
this->scene()->removeItem(*i);// 删除该形状
i = this->currentShapelist.erase(i);// 从当前有效形状表中删除
}
else
i ++;
}
}
MyShape * Game::newShape(int shapeId,int status, QPoint landedPoint)
{
if (shapeId == shapeCode::shapeRandom) {
qsrand(QTime().currentTime().second());
shapeId = qrand() % 7;
}
this->currentShapeId = shapeId;
MyShape * newShape = NULL;
switch (shapeId) {
case shapeCode::shapeT:
newShape = new ShapeT(status,landedPoint);
break;
case shapeCode::shapeL :
newShape = new ShapeL(status,landedPoint);
break;
case shapeCode::shapeMirrorL :
newShape = new ShapeMirrorL(status,landedPoint);
break;
case shapeCode::shapeSquare :
newShape = new ShapeSquare(status,landedPoint);
break;
case shapeCode::shapeZ :
newShape = new ShapeZ(status,landedPoint);
break;
case shapeCode::shapeMirrorZ :
newShape = new ShapeMirrorZ(status,landedPoint);
break;
case shapeCode::shapeLine :
newShape = new ShapeLine(status,landedPoint);
break;
}
this->currentShapelist.append(newShape);
// 下面是为新生形状设置形状数组
int i, j;
foreach (QGraphicsItem * item, newShape->childItems()) {
i = (item->scenePos().y()-this->areaOrigin.y())/20;
j = (item->scenePos().x()-this->areaOrigin.x())/20;
//cout << "i = " << i << " j = " << j << endl;
this->sceneArray[i][j] = true;
}
return newShape;
}
QRectF Game::getShapeCurrentBoundingRectInScene()
{
// 注意的是: 形状的 x,y 会随着旋转而移动,也就是说,x,y不是固定的左上角。考虑到这点,我们需要分别计算四种情况
int minX, minY, maxX, maxY;
switch(this->myshape->getCurrentStatus()) {
case 0:
minX = this->myshape->scenePos().x();
minY = this->myshape->scenePos().y();
maxX = minX + (this->myshape->sceneBoundingRect().width()-1);
maxY = minY + (this->myshape->sceneBoundingRect().height()-1);
break;
case 90:
maxX = this->myshape->scenePos().x();
minY = this->myshape->scenePos().y();
minX = maxX - (this->myshape->sceneBoundingRect().width()-1);
maxY = minY + (this->myshape->sceneBoundingRect().height()-1);
break;
case 180:
maxX = this->myshape->scenePos().x();
maxY = this->myshape->scenePos().y();
minX = maxX - (this->myshape->sceneBoundingRect().width()-1);
minY = maxY - (this->myshape->sceneBoundingRect().height()-1);
break;
case 270:
minX = this->myshape->scenePos().x();
maxY = this->myshape->scenePos().y();
maxX = minX + (this->myshape->sceneBoundingRect().width()-1);
minY = maxY - (this->myshape->sceneBoundingRect().height()-1);
break;
}
return QRectF(minX,minY,maxX-minX,maxY-minY);
}
void Game::gameOver()
{
this->myshape = NULL;
this->timer->stop();
this->mask->show();
this->gameOverLabel->show(); this->gradeLabel->setText(QString("得分 : ")+QString::number(this->grades));
this->gradeLabel->show();
this->pauseButton->setEnabled(false);
}
void Game::setFlag(bool flag)
{
int i, j;
switch(this->myshape->getCurrentStatus()) {// 这里分四种情况讨论,是因为坐标的问题
case 0:
foreach (QGraphicsItem * item, this->myshape->childItems()) {
i = (item->scenePos().y()-this->areaOrigin.y())/20;
j = (item->scenePos().x()-this->areaOrigin.x())/20;
this->sceneArray[i][j] = flag;
}
break;
case 90:
foreach (QGraphicsItem * item, this->myshape->childItems()) {
i = (item->scenePos().y()-this->areaOrigin.y())/20;
j = (item->scenePos().x()-this->areaOrigin.x()-20)/20;
this->sceneArray[i][j] = flag;
}
break;
case 180:
foreach (QGraphicsItem * item, this->myshape->childItems()) {
i = (item->scenePos().y()-this->areaOrigin.y()-20)/20;
j = (item->scenePos().x()-this->areaOrigin.x()-20)/20;
this->sceneArray[i][j] = flag;
}
break;
case 270:
foreach (QGraphicsItem * item, this->myshape->childItems()) {
i = (item->scenePos().y()-this->areaOrigin.y()-20)/20;
j = (item->scenePos().x()-this->areaOrigin.x())/20;
this->sceneArray[i][j] = flag;
}
break;
default: qDebug() << "Here , setFlag is not certain in game.cpp" << endl; break;
}
}
void Game::timeSlice()
{
if (++(this->timeCount) == (11-this->speed)) {
this->timeCount = 0;
this->moveDownOneStep();
}
}
void Game::pause()
{
if (this->gameStatus) {
this->timer->stop();
this->pauseButton->setText(QStringLiteral("继续"));
this->gameStatus = false;
}
else {
this->timer->start();
this->pauseButton->setText(QStringLiteral("暂停"));
this->gameStatus = true;
}
this->pauseButton->clearFocus();
}
void Game::screenShotSlot()
{
QScreen * screen = QGuiApplication::primaryScreen();
QDir * screenshot = new QDir;
screenshot->mkdir("screenshot");
//QDir::mkdir("screenshot");
QDir screenshotDir("screenshot");
// qDebug() << screenshotDir.count();// 因为 一个文件夹下至少有 . .. 这两个文件夹
int cnt = screenshotDir.count();
screen->grabWindow(this->winId()).save(QString("screenshot/")+QString::number(cnt-2)+QString(".jpg"),"jpg");
//qDebug() << "screen shot slot";
}
void Game::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
this->mousePressed = true;
this->mousePressPos = event->pos();
qDebug() << "mouse press pos " << event->pos() ;
qDebug() << "global pos : " << this->pos() ;
}
}
void Game::mouseMoveEvent(QMouseEvent *event)
{
if (this->mousePressed) { // 如果哦鼠标被按下,这个过程中发生鼠标拖动
if (this->cnt ++ == 10) {// cnt 用来消除抖动 因为鼠标每移动一个像素点 整个框体都要执行一次移动,这样导致框口拖动过程中的抖动现象的产生 现在我加入一个 cnt 变量来控制使得每移动10个像素点才会执行一次真正的移动, 显然有很好的效果
this->move((this->pos()+=(event->pos()-=this->mousePressPos)));
this->cnt = 0;
}
}
}
void Game::mouseReleaseEvent(QMouseEvent *event)
{
this->mousePressed = false;
}
4.开发日志
时间 2016/10/02 21:50 遇到问题,就是冲突检测 collidingItems() 的问题,估计是我理解的不够全面,现在我得把画边界的矩形线改成一条一条直线来画
估计真的是我没有理解,我感觉Qt提供的冲突检测很傻,我决定还是自己亲自来解决冲突检测的问题
时间 2016/10/02 22:13 关于冲突检测,我又想了想,我们不能把边界交界的情况视为冲突,因为边界交界的情况太多了,不如俄罗斯方库中的直线,在下降的过程中,如果遇到凹槽,那么,该竖线的冲突会增加,增加,再增加,这样冲突的数量不稳定,我们不好加以判断是否可以在方向上可以移动。 改进: 不以边界为判度断依据,而是以交融的方块为依据,即改组是否有成员方块和其他方块交融
冲突检测的目的就是实现“移动预判”
分析: 整个范围内存在的 QGraphicsItem 除了边界是直线打出来的,其他的都会是方块,故而,暂时不考虑边界,如果发生冲突情况,必然是有方块交融的现象产生,所以,我们预判是否会发生方块交融,从而明定该方向是否生效
或许我可以该一该游戏的规则,比如游戏界面宽度为15个小方块,而我们只要达到某一行10个方块,那么就可以执行消除
时间: 2016/10/03 08:35 对自定义的Shape类的使用有问题,Shape 继承自 QGraphicsItemGroup
error: ‘staticMetaObject’ is not a member of ‘QGraphicsItemGroup’
{ &QGraphicsItemGroup::staticMetaObject, qt_meta_stringdata_MyShape.data,
^
解决方法:使得该类 继承自 QObject 同时加上头文件 #include
冲突问题: 目前方块之间的冲突解决,采用 Qt::containsBoundingRect 表示必须外接矩形重合,根据现在的情况,我们设计的每一个形状都必须是有基本单位方块组成,而我们的冲突检测的实质是基本单位方块冲突检测
时间 11:09 我把冲突检测加入到Shape类中,这一设计和我之前用C++在控制台上设计的理念是相违背的,因为我当时认为冲突检测不应该属于Shape的一个能力,一个shape所拥有的不就是应该是 形状的定义,上下左右移动,变形 这三项。我突然觉得,还应该是按照以前的理念设计,当时的理念很合理,Qt这边也是可以实现的
时间 11:18 我个人认为我参考的那份俄罗斯方块代码的类方面的设计没有我的好,尤其是刚刚提及到的这个概念
时间 12:50 问题来了,怎么判定方块和边界的直线冲突?解决方案: 配合方块的外界矩形明定长宽,以及方块的当前位置坐标,得到当前方块的上下左右边界。得到的边界数据配合已经固定的方块活动区域边界的值,我们可以判断出是否出界
时间 16:25 有这么一个概念涌入,即一个任意一个item的入场位置为该item的坐标原点,暂时我还不知道如何切换该item的坐标系
时间 16:45 注意点 boundingRect 外接矩形的长宽都会因为边界线条而增加两个0.5, 比如 一个item设定为 (10,10,20,30) 那么它的外界矩的长宽为 21,31
时间 17:01 本次设计中的冲突分两类: 1.与边界冲突 2.基本单位方块之间的冲突
时间 17:05 用户的操控 只能是 左右以及向下移动
时间 18:33 设置各个方块形态的时候,注意不要用到坐标 减法 (我在 反 L 形状设置的时候就使用了减法,这引发边界冲突判度出错,还是和坐标有关系,这里不好多讲,以后遇到也能推出来)
时间: 20:34 现在要做的是决定什么时候产生下一个方块
时间 : 20:37 我把向下移动的代码单独的写成一个函数, 因为别的地方也用到向下移动,之前的只是用于键盘按下向下键生效,现在我们需要让方块自己下落,同样用到向下移动的代码
时间: 21:32 判断消除 和 形状旋转
时间: 10/04 10:24 我觉得,虽说Qt直接提供了旋转函数,但是那个是纯粹的旋转,并不很好的适合本次俄罗斯方块的设计,因为方块的基本单位长度为 10,而使用旋转函数会不便
时间: 11:19 关于旋转,我不想使用Qt自带的旋转,我决定自己一个一个写(我写的旋转并非严格按照某固定中心旋转)
关于旋转,分两步骤: 1. 设置旋转中心点 setTransformOriginPoint(x,y) 2.指定旋转角度 setRolation(angle) 需要注意的是,旋转也需要冲突预判
考虑到每个形状旋转时候的中心点不同,我觉得这是每个形状的个性,我以前的方案是为每个形状设计单独的类,当然,它们都继承自形状类,这样可以张扬个性。但,我得要写7个类,我想了想,不是我偷懒,而是设计过程中的取舍问题。 我想到,不采用这种方案。我计划着写一个 getRolationPoint(shapeId) 根据shapeId 来或者该形状的旋转中心点,这样是我觉得不错的方法。由此可见,在类的设计过程中,子类的单独彰显 适巧 也是可以较好的融于同一个父类当中
时间:14:11 坐标系问题
时间: 22:18 对坐标系一番研究,发现,原来,item, scene, view 各自拥有坐标系,我们使用坐标时候要统一坐标系。另外,以某个点为中心旋转,旋转的item真个坐标系发生旋转,它的原点同步旋转过去,所以,原点坐标值发生改变,按照新的坐标系重新绘制各个成员
时间: 22:29 场景中的每个item有场景位置(QGraphicsItem::scenePos())与包围矩形(QGraphicsItem::sceneBoundingRect()), 另外,每个item都有本地位置(QGraphicsItem::pos)与包围矩形(QGraphicsItem::boundingRect()) 当我们移动 item时候,会发现,场景位置的值才是真实反映了item的移动,而本地位置值保持不变
时间 10/05 13:08 如果没有实施旋转,或者缩放等,只是移动了组内成员的位置,sceneBoundingRect() 不发生变化
时间 20:42 消除部分,我使用 item(QRectF(x,y,w,h),Qt::ContainItemShape) 来获取 y 行的item个数。出了一个问题,就是,最下面的以行始终多一个item,我很纳闷,最后发现原来是下边界的那条直线item 被算进去了。
时间 21:13 紧接着的就是如何消除 item的问题了 ,目前,不考虑使用QGraphicsObject 作为基本元素,我看到网上有借助QGraphicsObject::deleteLater() 来实现删除,而QGraphicsItem 中没有这个函数。 我又在QGraphicsScene类 中看到 removeItem 函数,我试一下,删除的效果是有的,目前看上去好像是能满足我的需求。先试用它
时间 22:30 尴尬了,出现问题了,第一个形状完好落下,然后后来的方块就落不下来了
时间 23:12 原因出在 只是方形形状会有这样的现象,因为方形形状类的代码有小问题,即 currentStatus 没有知名,而 isBorderColliding 中又用到 currentStatus,故而引发了问题
时间 23:50 真是有意思,有出现了一个问题,我想明白了为什么。 shapeLine 就是形状为直线的那个,真是特殊性,特殊在它是一条直线,不跨行。 因为我的消除判断是针对某一行,使用一个“行矩形” QList’<’QGraphicsItem *> list = this->scene()->items(QRectF(this->areaOrigin.x()-1,y-1,this->areaWidth+2,22),Qt::ContainsItemShape);,包含在该行矩形中的 item 都作数,而我不希望 组item 也作数,只是希望 基本单位方块item作数,之前没有细细思考,以为所有的形状都是跨行的,只要限制了 “行矩形”的高度,就不可能把一个 组item 包含在内。而实际上, shapeLine 是可以被包含在我提供的”行矩形中的”。 这就是为什么 一个 方形方块, 两条直线横放 就会引发消除(1+ 2 + (4 + 1) + (4 + 1) = 13)。
所以,该怎么解决呢??? 我现在严重怀疑网上我参考的那一份俄罗斯方块的代码有很多bug
时间 13:07 今天要做的,一,解决昨天留下的bug,二,给方块上色,图案,三,背景图案
时间 14:44 问题真是接踵而至,没有一个顺畅的
时间 15:21 提及一个知识点: qt 中删除一个对象时候,一,删除掉该对象的所有孩子,二,将自己从父对象的孩子链表中删除,三,删除自己
时间 15:41 考虑到 形状的回收问题,因为,我们删除的是面向一个一个小的基本单位方块,如果只这样,一个形状的孩子都被删除了,这是一个无用的形状,但是没有被删除,占用着内存,同时也可能带来其他的问题(比如shapeLine 的横向放置作数的问题) 。 针对这个问题,我设立了一个currentShapeList 来记录所有当前还有孩子的形状,如果该形状的孩子个数为0,那么即刻删除。
时间16:03 目前看上去,我额外设立的currentShapeList 有良好的效果
时间 16:13 闭着眼睛都知道,肯定还会有bug
时间: 17:13 基本逻辑试行通过,下面就是上色
时间: 18:10 有是一个坐标系问题, 当有行消除,该行上面的方块要下降,我使用item->move(0,count*20) 出问题了,看上去是下降的行为,但是,这个只是在item自己的坐标系中下降,在scene中有时并不是下降 解决方法: item->move(0,count*20)更换为 item->setPos(item->mapFromScene(item->scenePos().x(),item->scenePos().y()+count*20));
时间 19:31 void init(); // 需要写一个初始化函数,明明已经有了构造函数,构造函数不久可一实现初始化吗,为什么还要重写一个init函数呢? 因为游戏会重新开局!!这个重新开局不是关闭游戏再重新打开游戏,而构造函数在整个过程中只是会执行一次,除非关闭程序重新运行才可以执行构造函数,显然着不符合用户需求,用户希望的是在不需要重新打开游戏的前提下,重新游戏
时间: 19:34 心得,如果你尝试着模仿别人做的东西,很多时候,你乍一看会有很多不能够理解的地方,为什么他这样做,为什么他那样做,为什么这样多此一举,我想说的是,一个东西做出来,在基本逻辑完好之后,大体的逻辑会很大幅度的修改,这些修改多是考虑到实际或代码设计的情况。而这些情况,你在一开始的时候顾及不到,也是没有那个精力去想到,因为不是整个东西的主线。你要做的是,自己心里构思,该怎么做,你认为应该是怎样的,然后参考着别人做的东西。这个过程你会发现你需要一直修改,因为在这个过程中你才会遇到各种问题,你需要为这些问题对代码进行修改。很多的修改之后,可能发现自己修改出的东西和别人做好的东西很相似。这是一个由内到外的过程,你对这个东西掌握力会很强。
时间: 下面应该做的是 “上色” ,但是我想先把 旋转时候 出现 靠边旋转不了的情况解决,其实这个问题还是有点小复杂的
时间: 23:20 出了问题,找了好长时间。case 0: …; case 90: …; case 180: …; case 270: …; 我一昧的认为没有其他的情况,实际上存在,比如rolateBack的时候,出现-90,这时候就出现了问题了,我的数据就不是可控的数据,从而导致了bug的产生
时间: 今天的任务: 1. 上色 2.增加自定义形状 3.写博客总结\
时间: 16:05 出现bug,上色的形状的方块冲突检测失效
时间 23:05 心碎一地,我不知道问什么包含性冲突不发生。
时间 2016/11/06 23:00 都过了一个月了,这些天里,零零散散的对这个小项目进行改进。 对于形状冲突检测问题,前些日子,我直接摈弃使用colliding函数,自己使用后台数组来实现,即用一个数组来记录当前游戏的状态,所有位置的状态
时间 2016/11/14 各种新问题来,我也是很蛋疼。 程序编译时候会直接卡死电脑,我最不爽的就是强制重启电脑了。 而且最近重启电脑都是输命令进入系统的。 还有就是 编码问题,以前一直都是好好的,现在,,一转到windows8.1上就出毛病,编码统统有问题。 现在我想做的是,不是我想,而是windows8.1 下的那个边框真是太太太难看了,所以我决定把边框去掉(因为暂时还不会处理边框,索性直接消除掉),引进的新问题就是,我需要重新实现以下窗口的移动问题。
时间 2016/11/14 20:07 现象,鼠标拖动过程,框体发生抖动现象,因为鼠标每移动一个像素点 整个框体都要执行一次移动,这样导致框口拖动过程中的抖动现象的产生 现在我引入一个 cnt 变量来控制使得每移动10个像素点才会执行一次真正的移动, 显然有很好的效果
时间 11/18 00:16 想到增加一个功能, 游戏截屏功能 刚刚室友被我拉过来玩了一会儿,怎么说呢,他不是玩的很短的时间,这呵呵,怎么说呢,我觉得,不知道到底是什么情况,我是感受到一点点的尊重,自己做的的东西别人在使用,不是那种很随意对待,很不屑对待的那种。 额,不多扯。
相关的Qt小记:
7> 给应用程序加图标,很简单 使用this->setWindowIcon(QIcon(“:/image/icon.png”)); 就是引入资源文件里的图片就好了
8> linux下程序转移到 windows下 源码中的中文在编译期间提示 “常量中有换行符” 而且之前中文一直显示乱码,
有这么一个方法,暂时就记着有这么个方法吧 编辑->select Encode -> system 同时, QStringLiteral(“我是中文”) 。 对于这个方法,额,我不想多说什么,反正奏效了
9> 今天晚上还仿佛解决了一个问题, 额, 就是之前添加了资源文件之后就会发生编译卡顿的问题,之前是因为长时间的卡顿,我忍受不了就直接强制关机,重启,很不爽的行为。 今天,正好没什么额外的事情,编译又卡住了,我就没有去搭理,过了好一会,我突然发现,它竟然编译好了。 呵呵。所以,目前的解决方法,就是“等”(可能是资源文件有点大)
5.总结
内容有点多,有点小尴尬。提供代码仅供参考,代码中也有相应的注释,关于代码编写过程的思路,在我的日志部分多有体现。有些地方,可能看起来费解,因为往往那些代码并不是我的最初想法,而是在后期开发过程中考虑到种种事先没有考虑到的地方,然后对其进行修改。
By Jack Lu 2016/12/22 17:38
Qt-俄罗斯方块