3

background

Use QGraphicsItem of Qt5.12.9 to implement Tetris. Now it is the C++ version. There will be a python version and a convenient interface to access the algorithm. The robot can play Tetris.

Ideas

  • The CustomGraphBase class inherits from QGraphicsObject and provides the necessary virtual functions.
  • The CustomGraphTetrisBlock class inherits from CustomGraphBase and implements the smallest square, divided into border type (0) and square type (1).
  • The CustomGraphTetrisText class inherits from CustomGraphBase, displays text, and the type is 5.
  • The Tetris class combines CustomGraphTetrisBlock to display Tetris.
  • The Game category is a game logic control category.

    The traditional programming method of the game uses a two-dimensional array to control the game space, similar to a maze. In fact, choosing QGraphicsItem to implement it is a very alternative choice, and it is more convenient to use gdi. At this scale, QGraphicsItem has no advantage, but is only a choice for personal study and exploration.
    I did not use a two-dimensional array to control the game space, but used a CustomGraphTetrisBlock on the edge to define the game space, because all items can be easily retrieved on the scene, so to see if a block can move, you need Retrieve whether your surroundings are already occupied by other blocks. There is one point here. When the block is rotating, it is necessary to distinguish between the blocks that make up one's own and the blocks of others.

Effect picture


Key code analysis

The functions are as cohesive as possible. The CustomGraphTetrisBlock class encapsulates the small block, and the Tetris class combines the Block, which encapsulates most of the operations of Tetris and the overall process of the Game-like game.

CustomGraphBase custom graph element base class

class CustomGraphBase : public QGraphicsObject
{
    Q_OBJECT
public:
    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 is the smallest block, the basic element of Tetris

Paint redraw operation, you need to operate the edge block, the edge block only occupies the position and does not display. Pay attention to the use of the prepareGeometryChange() function. It cannot be placed in this function, otherwise it will continue to redraw and take up a lot of CPU resources. I haven't studied the specific principle, so 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();
}

relocate the element to relocate, just put it in the correct coordinates on the scene

void CustomGraphTetrisBlock::relocate()
{
    this->setPos(pos * BLOCKSIDEWIDTH);
    prepareGeometryChange();
}

Tetris class, Tetris class

Definition of seven types of squares

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} 
};

The 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);   //存储组成该方块的所有元素,在落到底之前需要由Tetris类控制其运动
            MainWindow::GetApp()->GetScene()->addItem(block);                                   //加入block到scene,显示方块
        }
    }

The hasTetrisBlock function detects whether 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) {      //要区别组合俄罗斯方块本身的block与其它的block
            return (CustomGraphTetrisBlock*)al;  //返回方块,提供给清除行操作用
        }
    }
    return nullptr;
}

The rotate function rotates 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; 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;
}

The cleanRow function implements row cleaning

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;
}

Source code and operation method

The project is organized by cmake, please install cmake 3.10 or above. The following script is based on MSVC under Windows, and is basically similar on other operating systems, or use qtcreator to open and operate.

cmake -A win32 -Bbuild .
cd build
cmake --build . --config Release

Note: This project can run cross-platform and has been adapted to windows, linux, mac.

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

zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...