背景
应用Qt5.12.9的QGraphicsItem来实现俄罗斯方块,当初是C++版本,下来还会有python版本,以及不便的接口,来接入算法,由机器人玩俄罗斯方块。
思路
- CustomGraphBase类继承自QGraphicsObject,提供必要的虚函数。
- CustomGraphTetrisBlock类继承自CustomGraphBase,实现最小方块,分边框类型(0)与方块类型(1)。
- CustomGraphTetrisText类继承自CustomGraphBase,显示文字,类型为5。
- Tetris类组合CustomGraphTetrisBlock,显示俄罗斯方块。
Game类为游戏逻辑管制类。
该游戏传统的编程形式,是用一个二维数组来管制游戏空间,相似迷宫的形式。其实抉择QGraphicsItem来实现就是一种很另类的抉择,其实用gdi来做更不便,这种规模,QGraphicsItem没有劣势,只是集体学习摸索的抉择。
我没有用二维数组来管制游戏空间,而是在边际上用了一圏CustomGraphTetrisBlock来定义游戏空间,因为所有的items都能不便的在scene上检索到,所以看一个方块是否能挪动,就须要检索本人的四周是否曾经被其它方块占据。这里有一点,在方块进行旋转的时候,就要判断辨别组成本人的block和他人的方块。
效果图
要害代码剖析
性能尽量内聚,类CustomGraphTetrisBlock封装小方块,Tetris类组合了Block,封装了俄罗斯方块的绝大部分操作,类Game游戏的整体流程。
CustomGraphBase自定义图元基类
class CustomGraphBase : public QGraphicsObject{ Q_OBJECTpublic: CustomGraphBase();public: virtual QRectF boundingRect() const = 0; //占位区域,必须精确,能力很好的显示与革除 virtual int type() const = 0; virtual void relocate() = 0; //挪动,重定位 virtual bool isActive() { return false; };//未落地的方块 virtual int getBlockType() { return 0; }; //方块类型,次要区别边际方块};
CustomGraphTetrisBlock 最小方块,组成俄罗斯方块的根本元素
paint 重绘操作,须要操作边际方块,边际方块只占位,不显示。要留神prepareGeometryChange()函数的应用,不能放在这个函数中,不然会不停的重绘,占用大量CPU资源。具体原理我还没钻研透,我将其放到relocateb函数中了。
void CustomGraphTetrisBlock::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget /*= nullptr*/){ if (blockType) { painter->drawRoundedRect( 0, 0, BLOCKSIDEWIDTH, BLOCKSIDEWIDTH, 2, 2 ); } //prepareGeometryChange();}
relocate元素重定位,只需将其放到scene上正确的坐标
void CustomGraphTetrisBlock::relocate(){ this->setPos(pos * BLOCKSIDEWIDTH); prepareGeometryChange();}
Tetris类,俄罗斯方块类
七类方块的定义
QVector<QVector<int>> SHAPES = { {1, 1, 1, 1}, {0, 1, 1, 1, 0 , 1}, {1, 1, 1, 0, 0, 0, 1}, {0, 1, 1, 0, 0, 1, 1}, {1, 1, 0, 0, 0, 1, 1}, {0, 1, 1, 0, 1, 1}, {0, 1, 0, 0, 1, 1, 1} };
俄罗斯方块的构建
QVector<int> curShape = SHAPES[shape % SHAPES.size()]; for (int i = 0; i < curShape.size(); i++) { if (curShape[i]) { data[1 + i / sideLen][i % sideLen] = true; CustomGraphTetrisBlock* block = new CustomGraphTetrisBlock(pos + QPoint(i % sideLen, 1 + i / sideLen), 2, shape); blocks.push_back(block); //存储组成该方块的所有元素,在落到底之前须要由Tetris类管制其静止 MainWindow::GetApp()->GetScene()->addItem(block); //退出block到scene,显示方块 } }
hasTetrisBlock函数检测地位上是否有方块
CustomGraphTetrisBlock* Tetris::hasTetrisBlock(int x, int y){ auto items = MainWindow::GetApp()->GetScene()->items(QPointF((x + 0.5) * BLOCKSIDEWIDTH, (y + 0.5) * BLOCKSIDEWIDTH)); foreach (auto al , items) { if (!(((CustomGraphBase*)al)->isActive()) && (((CustomGraphBase*)al)->type()) == TETRISBLOCKTYPE) { //要区别组合俄罗斯方块自身的block与其它的block return (CustomGraphTetrisBlock*)al; //返回方块,提供给革除行操作用 } } return nullptr;}
rotate函数进行俄罗斯方块的旋转
bool Tetris::rotate(){ int i, j, t, lenHalf = sideLen / 2, lenJ; for (i = 0; i < lenHalf; i++) { lenJ = sideLen - i - 1; for (j = i; j < lenJ; j++) { //后行判断是否能旋转,要挪动的点不为0时,判断指标点是否曾经有block存在 int lenI = sideLen - j - 1; if (data[i][j] && this->hasTetrisBlock(pos.x() + lenJ, pos.y() + j) || data[lenI][i] && this->hasTetrisBlock(pos.x() + j, pos.y() + i) || data[lenJ][lenI] && this->hasTetrisBlock(pos.x() + i, pos.y() + lenI) || data[j][lenJ] && this->hasTetrisBlock(pos.x() + lenI, pos.y() + lenJ)){ return false; } } } for (i = 0; i < lenHalf; i++) { //抉择了顺时针90度旋转,应用了螺旋挪动算法,网上能够容易搜寻到阐明。 lenJ = sideLen - i - 1; for (j = i; j < lenJ; j++) { int lenI = sideLen - j - 1; t = data[i][j]; data[i][j] = data[lenI][i]; data[lenI][i] = data[lenJ][lenI]; data[lenJ][lenI] = data[j][lenJ]; data[j][lenJ] = t; } } this->relocate(); return true;}
cleanRow函数实现行革除
int Tetris::cleanRow(){ //该革除算法效率不高,是以一行来解决的,这块当前能够优化。 int h = 19, levelCount = 0; while (h >= 0) { int count = 0; for (int i = 0; i < 10; i++) { //判断是否行满 if (!this->hasTetrisBlock(i, h)) { count++; } } if (count == 0) { //行满,须要革除并整体下移 int level = h; levelCount++; bool first = true; while (level >= 0) { int ct = 0; for (int j = 0; j < 10; j++) { if(first) //第一个外循环删除满行上的图元,前面是整体下移 this->erase(j, level); CustomGraphTetrisBlock* block = this->hasTetrisBlock(j, level - 1); if (!block) { ct++; } else { block->relocate(QPoint(j, level)); //下移一个地位 } } first = false; if (ct == 10) { //一行上都没有图元,工作实现,提前结束 break; } else { level--; } } } else if (count == 10) { break; } else { h--; } } return levelCount;}
源代码及运行办法
我的项目采纳cmake组织,请装置cmake3.10以上版本。
cmake -Bbuild .cd buildcmake --build . --config Release
注:本我的项目采纳计划能跨平台运行,曾经适配过windows,linux,mac。
源代码:
https://gitee.com/zhoutk/qtetris.git
或
https://gitee.com/zhoutk/qtdemo/tree/master/tetrisGraphicsItem
或
https://github.com/zhoutk/qtDemo/tree/master/tetrisGraphicsItem