俄罗斯方块 Tetris

Posted 林兮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了俄罗斯方块 Tetris相关的知识,希望对你有一定的参考价值。

 

今天,为大家带来一个用Qt C++ (Windows环境下)做的一个简易俄罗斯方块小游戏

思路和模块介绍都在注释里面,其次就是一些项目中遇到的问题以及解决方案,在后面部分说明。

一、效果

测试图样

 

Qt中文显示不容易啊~

 

二、代码

Tetris.pro

#-------------------------------------------------
#
# Project created by QtCreator 2018-02-11T18:16:14
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Tetris
TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES  = main.cpp\\
           tetriswindow.cpp \\
           tetrisitem.cpp \\
           tetrisboard.cpp

HEADERS  = tetriswindow.h \\
           tetrisitem.h \\
           tetrisboard.h
View Code

 

tetrisitem.h

#ifndef TETRISITEM_H
#define TETRISITEM_H

/*******************************************************
//! [ TertrisItem 类 ]
//! [ 这个类主要是俄罗斯方块的方块元素类 设置方块元素的一些属性 ]
//! [ 设置方块的形状  方块的坐标  方块的生成   方块的旋转等    ]
//! [ 方块元素坐标化  以便于之后做处理(定位、显示、绘图等)    ]
********************************************************/

//! [总共是20种形状,NoShape在内总共6大类,同类型的当然可以通过旋转互相转换]
//! [但是为了让初始掉落的时候每个种类的形状都能概率性出现,还是把所有的形状都列了出来]
//! [在之后的绘图等后续工作中还是按6大类进行处理的,只有这里需要细分一下]

enum ItemShape
{
    NoShape,    SquareShape,    Line1Shape,    Line2Shape,
    L1Shape,    L2Shape,    L3Shape,    L4Shape,
    L5Shape,    L6Shape,    L7Shape,    L8Shape,
    Z1Shape,    Z2Shape,    Z3Shape,    Z4Shape,
    T1Shape,    T2Shape,    T3Shape,    T4Shape,
};

class TetrisItem
{
private:
    //! [data-Member]
    static const int coordList[20][4][2];      //! [方块形状总坐标点列表]
    ItemShape m_Shape;                         //! [方块的形状属性]
    int coordinate[4][2];                      //! [方块对应的坐标点列表]

    //! [Member-functions]
    void resetX(const int Index, const int x){ coordinate[Index][0] = x; }
    void resetY(const int Index, const int y){ coordinate[Index][1] = y; }

public:
    explicit TetrisItem( const ItemShape& shape = NoShape ){ resetShape(shape); }

    //! [set-Shape-functions]
    void resetShape(const ItemShape&);          //! [重置方块属性]
    void setRandomShape();                      //! [生成随机方块]
    const TetrisItem Rotate()const;             //! [方块元素旋转]

    //! [get-Data-functions]
    const ItemShape& get_Shpae() const { return m_Shape; }
    const int get_x(const int Index) const { return coordinate[Index][0]; }
    const int get_y(const int Index) const { return coordinate[Index][1]; }
    const int get_most(const bool isMax = true, const bool x = true)const;   //! [注释见下方]
    //! [求取坐标最值,比如:最大x或最小的y坐标值等,4行代码就ok]

};

#endif // TETRISITEM_H
View Code

 

tetrisitem.cpp

#include <QtCore>
#include "tetrisitem.h"

const int TetrisItem::coordList[20][4][2] =
{
    { {  0,  0 }, {  0,  0 }, {  0,  0 }, {  0,  0 } }    //! [ NoShape ]
,   { {  0,  0 }, {  1,  0 }, {  0,  1 }, {  1,  1 } }    //! [ SquareShape ]
,   { {  0,  2 }, {  0,  1 }, {  0,  0 }, {  0, -1 } }    //! [ LineShape]
,   { {  2,  0 }, {  1,  0 }, {  0,  0 }, { -1,  0 } }
,   { {  0, -1 }, { -1, -1 }, { -1,  0 }, { -1,  1 } }    //! [ LShape ]
,   { {  0, -1 }, {  1, -1 }, {  1,  0 }, {  1,  1 } }
,   { { -1,  0 }, { -1, -1 }, {  0, -1 }, {  1, -1 } }
,   { { -1, -1 }, {  0, -1 }, {  1, -1 }, {  1,  0 } }
,   { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0,  1 } }
,   { {  0,  1 }, {  1,  1 }, {  1,  0 }, {  1, -1 } }
,   { { -1,  1 }, { -1,  0 }, {  0,  1 }, {  1,  1 } }
,   { { -1,  1 }, {  0,  1 }, {  1,  1 }, {  1,  0 } }
,   { {  0,  1 }, {  0,  0 }, {  1,  0 }, {  1, -1 } }     //! [ ZShape ]
,   { {  0,  1 }, {  0,  0 }, { -1,  0 }, { -1, -1 } }
,   { { -1,  1 }, {  0,  1 }, {  0,  0 }, {  1,  0 } }
,   { { -1,  0 }, {  0,  0 }, {  0,  1 }, {  1,  1 } }
,   { { -1,  0 }, {  0,  0 }, {  0,  1 }, {  1,  0 } }     //! [ TShape ]
,   { {  0,  1 }, {  0,  0 }, {  1,  0 }, {  0, -1 } }
,   { {  1,  0 }, {  0,  0 }, {  0, -1 }, { -1,  0 } }
,   { {  0, -1 }, {  0,  0 }, { -1,  0 }, {  0,  1 } }
};

//////////////////////////////////////////////
void TetrisItem::resetShape(const ItemShape& shape)
{
    for(int i = 0; i < 4; ++i)
    {
        coordinate[i][0] = coordList[shape][i][0];
        coordinate[i][1] = coordList[shape][i][1];
    }
    m_Shape = shape;
}

//////////////////////////////////////////////
void TetrisItem::setRandomShape()
{
    resetShape(ItemShape(qrand() % 19 + 1));
}

//////////////////////////////////////////////
const TetrisItem TetrisItem::Rotate()const
{
    if(m_Shape == SquareShape)               //! [如果是田字方块,就无需旋转]
        return*this;

    TetrisItem item(m_Shape);
    for(int i = 0; i < 4; ++i)
    {                                      //! [方块元素旋转90°]
        item.resetX(i, get_y(i));
        item.resetY(i, -1 * get_x(i));
    }
    return item;
}

//////////////////////////////////////////////
const int TetrisItem::get_most(const bool IsMax, const bool x)const
{
    int value = coordinate[0][!x];
    for(int i = 1; i < 4; ++i)
        value = IsMax ? qMax(value, coordinate[i][!x]) : qMin(value, coordinate[i][!x]);
    return value;
}
View Code

 

tetrisboard.h

#ifndef TETRISBOARD_H
#define TETRISBOARD_H

/******************************************
//! [         TetrisBoard 类              ]
//! [     面板类     设定面板相关的属性       ]
//! [  面板上的事件响应设定   信号响应设定     ]
//! [  面板的绘图  事件响应设定  方块移动 等   ]
//! [  设定面板应该具有的一些数据             ]
//! [难度等级的改变是随着方块的下降的数量而改变的]
******************************************/

#include <QFrame>
#include <QPointer>
#include <QBasicTimer>
#include "tetrisitem.h"

QT_BEGIN_NAMESPACE
class QLabel;
class QFont;
class QColor;
QT_END_NAMESPACE

class TetrisBoard : public QFrame
{
    Q_OBJECT

    //! [信号-槽]
public slots:
    void SLOT_start();                    //! [游戏开始设置]
    void SLOT_pause();                    //! [游戏暂停设置]
    void SLOT_reset();                    //! [重新开始设置]

signals:
    void score_change(const int);                //! [改变分数]
    void level_change(const int);                //! [改变等级]
    void Remove_line_change(const int);         //! [改变已消除行数]
    //! [信号-槽 END]

private:
    //! [Data-Member]
    static const int Board_W{ 10 }, Board_H{ 22 }; //! [面板的宽和高]
    static const QColor colorList[6];           //! [6类方块的颜色列表]
    QBasicTimer m_timer;                        //! [ 计时器 ]
    QPointer<QLabel> m_nextItem_L;              //! [QPointer模板类似智能指针]
    TetrisItem currentItem, nextItem;           //! [当前方块  下一个方块]
    bool IsStart, IsPause;                      //! [暂停、开始 与否]
    bool IsFall;                                //! [是否已经落下]
    int currentX, currentY;                     //! [当前的x,y]
    int Lines_moved_num, Item_Fall_num;         //! [消去的行数  下落的方块数]
    int score, level;                           //! [分数  等级]

    ItemShape m_board[Board_W * Board_H];       //! [注释见下方]
    //! [此为俄罗斯方块活动的区域中每一个小格子所属方块类型的数组]

    //! [ Member-functions ]      [ Inline-functions ]
    ItemShape& Item_type(const int x,const int y) //! [获取(x,y)的方块类型]
    { return m_board[y * Board_W + x]; }

    const int TimeOut()const                      //! [设定计时器的流逝速度]
    { return 1000/(1 + level); }

    const int grid_W()const                       //! [求划分的一个小格子的宽]
    { return contentsRect().width()/Board_W; }    //! [con..ect()函数返回面板矩形]

    const int grid_H()const
    { return contentsRect().height()/Board_H; }

    void clearBoard();                        //! [清空面板(将所有小格子的方块类型置0)]
    void Fall();                              //! [瞬降]
    void down();                              //! [下落]
    void RemoveLine();                        //! [消除一行]
    void Fall_after(const int);               //! [落定之后的数据更新]
    void newItem();                           //! [构建下一个俄罗斯方块]
    void showNext();                          //! [展示下一个方块]
    bool Move_(const TetrisItem&,const int,const int);           //! [移动]
    void draw(QPainter&,const int,const int,const ItemShape&);   //! [描绘小格子]

protected:                                        //! [三个事件]
    void paintEvent(QPaintEvent *)Q_DECL_OVERRIDE;
    void keyPressEvent(QKeyEvent*)Q_DECL_OVERRIDE;
    void timerEvent(QTimerEvent *)Q_DECL_OVERRIDE;

public:
    TetrisBoard(QWidget* parent = 0);
    void setNextItem_L(QLabel*);                  //! [设定标签,用于显示下一个方块的标签]
    QPointer<QLabel> m_Pause_L;                   //! [显示暂停的Label]

};

#endif // TETRISBOARD_H
View Code

 

tetrisboard.cpp

#include <QtWidgets>
#include "tetrisboard.h"

const QColor TetrisBoard::colorList[6] =
{
    QColor(255, 255, 255)
,   QColor(128,  36, 221)
,   QColor( 56, 210,  40)
,   QColor(152, 165,   6)
,   QColor(223,  22,  22)
,   QColor( 37, 237, 244)
};

//////////////////////////////////////////////
TetrisBoard::TetrisBoard(QWidget* parent)
    :QFrame(parent)                   //! [构建基类]
,    IsStart(false)
,    IsPause(false)
,    IsFall(false)
{
    setLineWidth(2);                  //! [边框设定参见QFrame类]
    setMidLineWidth(3);
    setFrameStyle(QFrame::Box | QFrame::Raised);   //! [三个函数用于设定边框]
    setFocusPolicy(Qt::StrongFocus);  //! [设定焦点策略]

    clearBoard();                     //! [清空游戏面板]
    nextItem.setRandomShape();        //! [随机生成下一个方块]

    //! [下面是暂停标签的设置]
    m_Pause_L = new QLabel("Pause",this);
    m_Pause_L->setAlignment(Qt::AlignHCenter);
    QFont* font = new QFont;          //! [创建文字]
    font->setPointSize(50);           //! [设定字体大小]
    QPalette p;                       //! [调色板]
    p.setColor(QPalette::WindowText,Qt::red);  //! [置调色板的颜色]

    m_Pause_L->setPalette(p);         //! [设置标签的调色和文字]
    m_Pause_L->setFont(*font);
    m_Pause_L->setVisible(false);     //! [设置标签可见性,暂停时可见]
}

//////////////////////////////////////////////
void TetrisBoard::setNextItem_L(QLabel* label)
{
    m_nextItem_L = label;
}

//////////////////////////////////////////////
void TetrisBoard::SLOT_start()
{
    if(IsPause||IsStart) return;     //! [如果游戏已经开始或者暂停,则该按钮无效]

    IsStart = true;                  //! [游戏开始时的状态数据初始化]
    IsFall = false;
    level = 1;
    score = Item_Fall_num = Lines_moved_num = 0;
    clearBoard();

    //! [发射信号]
    emit Remove_line_change(Lines_moved_num);
    emit score_change(score);
    emit level_change(level);

    newItem();                        //! [生成新的方块]
    m_timer.start(TimeOut(),this);    //! [时间重新开始,按照para1 毫秒的速度流逝]
}

//////////////////////////////////////////////
void TetrisBoard::SLOT_reset()        //! [重新开始,需要将开始和暂停置为false,然后执行start]
{
    IsStart = false;
    IsPause = false;
    SLOT_start();
}

//////////////////////////////////////////////
void TetrisBoard::SLOT_pause()
{
    if(!IsStart) return;               //! [游戏未开始,无效]

    IsPause = !IsPause;
    if(IsPause)
    {
        m_timer.stop();
        m_Pause_L->setVisible(true);
    }
    else
    {
        m_timer.start(TimeOut(),this);
        m_Pause_L->setVisible(false);
    }

}

//////////////////////////////////////////////
void TetrisBoard::paintEvent(QPaintEvent *event)
{
    QFrame::paintEvent(event);         //! [先调用基类的]
    QPainter painter(this);            //! [绘图类]
    QRect rect = contentsRect();       //! [矩形类,该函数在头文件中已介绍过]

    int boardTop = rect.bottom() - Board_H * grid_H();
    for(int i = 0; i < Board_H; ++i)
        for(int j = 0; j < Board_W; ++j)
        {
            ItemShape shape = Item_type(j, Board_H - i - 1);
            if(shape != NoShape)
                draw(painter, rect.left() + j * grid_W(), boardTop + i * grid_H(), shape);
        }

    if(currentItem.get_Shpae() != NoShape)
        for(int i = 0; i < 4; ++i)
        {
            int x = currentX + currentItem.get_x(i);
            int y = currentY - currentItem.get_y(i);
            draw(painter, rect.left() + x * grid_W(),
                 boardTop + (Board_H - y - 1) * grid_H(), currentItem.get_Shpae());
        }
}

//////////////////////////////////////////////
void TetrisBoard::keyPressEvent(QKeyEvent *event)
{
    if(!IsStart || IsPause || currentItem.get_Shpae()  == NoShape)
    {
        QFrame::keyPressEvent(event);
        return;
    }
    switch(event->key())             //! [判别键盘按键]
    {
    case Qt::Key_Left:
    case Qt::Key_A:
        Move_(currentItem, currentX - 1, currentY);
        break;
    case Qt::Key_Right:
    case Qt::Key_D:
        Move_(currentItem, currentX + 1, currentY);
        break;
    case Qt::Key_Up:
    case Qt::Key_W:
        Move_(currentItem.Rotate(), currentX, currentY);
        break;
    case Qt::Key_Down:
    case Qt::Key_S:
        Fall();
        break;
    default:
        QFrame::keyPressEvent(event);
    }
}

//////////////////////////////////////////////
void TetrisBoard::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == m_timer.timerId())  //! [一个时间单位一个时间单位对应刷新相关的设置]
        if(IsFall)                             //! [如果某一时刻的方块已经落下,那么重新生成一个]
        {
            IsFall = false;
            newItem();
            m_timer.start(TimeOut(),this);
        }
        else down();
    else QFrame::timerEvent(event);
}

//////////////////////////////////////////////
void TetrisBoard::clearBoard()
{
    for(int i = 0; i < Board_H * Board_W; ++i)
        m_board[i] = NoShape;
}

//////////////////////////////////////////////
void TetrisBoard::Fall()
{
    int fall_height = 0, y = currentY;
    while(y > 0)
    {
        if(!Move_(currentItem, currentX, y - 1))
            break;
        --y;
        ++fall_height;
    }
    Fall_after(fall_height);
}

//////////////////////////////////////////////
void TetrisBoard::down()
{
    if(!Move_(currentItem, currentX, currentY - 1))
        Fall_after(0);
}

//////////////////////////////////////////////
void TetrisBoard::Fall_after(const int fall_height)
{
    for(int i = 0; i < 4; ++i)               //! [刷新面板上对应位置的方块类型属性]
    {
        int x = currentX + currentItem.get_x(i);
        int y = currentY - currentItem.get_y(i);
        Item_type(x, y) = currentItem.get_Shpae();
    }

    ++Item_Fall_num;
    if(Item_Fall_num % 28 == 0)              //! [如果没28个提升一次等级]
    {
        ++level;
        m_timer.start(TimeOut(), this);      //! [等级提升,刷新时间流逝速度]
        emit level_change(level);
    }

    score += fall_height + 8;
    emit score_change(score);
    RemoveLine();

    if(!IsFall)
        newItem();
}

//////////////////////////////////////////////
void TetrisBoard::RemoveLine()
{
    int Num_remove = 0;
    for(int i = Board_H - 1; i >= 0; --i)
    {
        bool line_Is_Full = true;

        for(int j = 0; j < Board_W; ++j)
            if(Item_type(j, i) == NoShape)
            {
                line_Is_Full = false;
                break;
            }

        if(line_Is_Full)
        {
            ++Num_remove;
            for(int k = i; k < Board_H - 1; ++k)
                for(int j = 0; j < Board_W; ++j)
                    Item_type(j, k) = Item_type(j, k + 1);

            for(int L = 0; L < Board_W; ++L)
                Item_type(L, Board_H - 1) = NoShape;
        }
    }

    if(Num_remove > 0)
    {
        Lines_moved_num += Num_remove;
        score += 13 * Num_remove;
        emit Remove_line_change(Lines_moved_num);
        emit score_change(score);

        IsFall = true;
        currentItem.resetShape(NoShape);
        update();
    }
}

//////////

以上是关于俄罗斯方块 Tetris的主要内容,如果未能解决你的问题,请参考以下文章

Tetris(俄罗斯方块)

Tetris(俄罗斯方块)

Java项目--俄罗斯方块

Java | Tetris

Java实现俄罗斯方块小游戏。(附完整源代码)

使用Python写俄罗斯方块,以游戏的方式学习编程