background
Tetris is implemented using QGraphicSitem in QT5.12.9. It is now in C++, and will come in Python, as well as a convenient interface for accessing algorithms, and for robots to play Tetris.
Train of thought
- The CustomGraphBase class inherits from QGraphicsObject and provides the necessary virtual functions.
- The CustomGraphTetRisBlock class inherits from CustomGraphBase and implements the smallest square, with a border type (0) and a square type (1).
- The CustomGraphteText class inherits from CustomGraphBase, displays text, and has type 5.
- The Tetris class combines the CustomGraphTetRisBlock to display the Tetris.
-
Game class is the Game logic control class.
The traditional programming of the game is to use a two-dimensional array to control the game space, similar to the way of a maze. In fact, choosing QGraphicSitem to implement it is a very different choice. In fact, it is more convenient to do it with GDI. At this scale, QGraphicSitem has no advantage, but it is only a choice for personal learning and exploration. Instead of using a 2-D array to control the game space, I use a Circle CustomGraphetRisBlock on the edge to define the game space. Because all items are easily retrievable on the scene, to see if a block can move, I need to retrieve whether it is already occupied by another block. The point here is that when the block is rotated, it is important to distinguish between your own block and others’ blocks.
rendering
Critical code analysis
The function is as cohesive as possible. The class CustomGraphTetRisBlock encapsulates small blocks, while the class Tetris combines blocks to encapsulate most of the operations of Tetris and the overall process of the Game like Game.
CustomGraphBase customizes the primitive base class
class CustomGraphBase : public QGraphicsObject { Q_OBJECT public: CustomGraphBase(); public: virtual QRectF boundingRect() const = 0; Void int type() const = 0; void int type() const = 0; virtual void relocate() = 0; // move, reposition virtual bool isActive() {return false; }; Virtual int getBlockType() {return 0; }; // the type of block, the main difference between the edge of the block};
CustomGraphTetRisBlock The smallest square that makes up the basic element of Tetris
The Paint repaint operation requires the edge box to be used. The edge box is placeholder, not displayed. Pay attention to the use of prepareGeometryChange(). You can’t put prepareGeometryChange() in this function because it will redraw constantly and consume a lot of CPU resources. I haven’t figured it out yet, but I put it in the relocateb function.
void CustomGraphTetrisBlock::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget /*= nullptr*/) { if (blockType) { painter->drawRoundedRect( 0, 0, BLOCKSIDEWIDTH, BLOCKSIDEWIDTH, 2, 2); } //prepareGeometryChange(); }
The relocate element simply places it at the correct coordinates on the scene
void CustomGraphTetrisBlock::relocate()
{
this->setPos(pos * BLOCKSIDEWIDTH);
prepareGeometryChange();
}
Tetris class, Tetris class
Definitions of the seven types of blocks
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}};
Construction of Tetris
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); MainWindow:: getApp ()-> getScene ()->addItem(block); // Add a block to the scene}}
The HastetRisBlock function detects if there is a block in the position
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) { Return (customGraphTetRisBlock *)al; return (customGraphTetRisBlock *)al; }} return nullptr;} return nullptr; }
The rotate function does the rotation of Tetris
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; Int lenI = sidelen-j-1; int lenI = sidelen-j-1; 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++) {// The rotation is 90 degrees clockwise and the spiral movement algorithm is used. The explanation is easily searchable on the web. 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; }
The cleanRow function implements row cleanup
Int Tetris:: CleanRow () {// This cleanup algorithm is inefficient and is handled on a single line, which can be optimized later. int h = 19, levelCount = 0; while (h >= 0) { int count = 0; for (int i = 0; i < 10; I++) {// check 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) erase(j, level); if(first) erase(j, level); CustomGraphTetrisBlock* block = this->hasTetrisBlock(j, level - 1); if (! block) { ct++; } else { block->relocate(QPoint(j, level)); } first = false; If (ct == 1) {break (ct == 1); } else { level--; } } } else if (count == 10) { break; } else { h--; } } return levelCount; }
Source code and running method
The project is organized with cmake, please install cmake3.10 or above. The following script is MSVC-based for Windows, similar for other operating systems, or opened using QtCreator.
cmake -A win32 -Bbuild .
cd build
cmake --build . --config Release
Note: The solution adopted in this project can run across platforms and has been adapted to Windows, Linux and Mac.
The source code:
https://gitee.com/zhoutk/qtetris.git
or
https://gitee.com/zhoutk/qtdemo/tree/master/tetrisGraphicsItem
or
https://github.com/zhoutk/qtDemo/tree/master/tetrisGraphicsItem