俄罗斯方块的数据结构及实现 struct of a tetris

最近对开源版本的QT中的俄罗斯方块的实现做了一些分析,顺便记录一下。本文遵守GNU GPL。

 

一直想做自己的游戏,俄罗斯方块或者五子棋这样的应该都是初学者想去完成的例子,最近找了一些关于tetris的代码,发现QT里面的例子做的比较好,它用C++实现了一个很好的类,我也用Win32的GDI重写了一个不用QT版本的。这里记录一下QT中tetrix的实现。

 

1、类结构:

 

QT的tetrix在其安装目录的examples/widgets/tetrix下(我使用的是QT4.2.3 for windows opensource mingw版本)。里面有这样两个类: TetrixPiece和TetrixBoard。其中TetrixPiece定义了tetrix中各种形状方块,TetrixBoard实现了整个游戏的实现以及逻辑。说实话,在看了几天QT的代码后,发现用QT写GUI的东西还是很方便地。里面的模块十分清晰,写出来的代码很漂亮,可读性极高,比用MFC向导生成那些工程要清晰很多,也比用WIN32写代码省好多笔墨。

 

2、数据结构:

 

在没有选择QT的tetrix之前,也从CodeGuru和CodeProject上找了一些tetrix的实现,但是,其中不是用了某些图形库(如cximage),就是数据结构定义很冗余。当然,这之前,我自己也想过其数据结构的实现,您也可以先在想想,对于里面的方块,我们该如何定义它的数据结构呢?

 

最初我的想法是,定义一个4X4的矩阵(简单的使用二维数组),并定义几个状态数来表示每一个图形。比如,要定义一个长条装的图形,我们可以需要这样一个二维数组:

 

1 0 0 0

1 0 0 0

1 0 0 0

1 0 0 0

 

这里面1表示有图形,0表示无,这样我们可以写出一个简单的图像驱动,在给他发送一个2维数组的样式后,帮助我们把它画出来。那么,对于一个长条状的图形,我们大概需要两个这样的矩阵。方块好一点,可能只需要一个,但是里面的Z形状以及T形状的图形,每一个都需要4个这样的阵来表示。这样的实现固然可行,只是在其数据结构的定义时,代码中需要一个长长的静态三维数组来表示这些图形。

 

对于tetrix的Board,实现起来应该简单一些,复杂的是它里面的逻辑控制。它只需要一个大的矩阵来存储整个图形区域的状态就好了。

 

当然,还有其他的表示方法,我们就不多讨论了。我们现在来看看QT例子中的实现: TetrixPiece.h and TetrixPiece.cpp

 

/****************************************************************************
** TetrixPiece.h

**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
****************************************************************************/

#ifndef TETRIXPIECE_H
#define TETRIXPIECE_H

enum TetrixShape { NoShape, ZShape, SShape, LineShape, TShape, SquareShape,
                   LShape, MirroredLShape };

class TetrixPiece
{
public:
    TetrixPiece() { setShape(NoShape); }

    void setRandomShape();
    void setShape(TetrixShape shape);

    TetrixShape shape() const { return pieceShape; }
    int x(int index) const { return coords[index][0]; }
    int y(int index) const { return coords[index][1]; }
    int minX() const;
    int maxX() const;
    int minY() const;
    int maxY() const;
    TetrixPiece rotatedLeft() const;
    TetrixPiece rotatedRight() const;

private:
    void setX(int index, int x) { coords[index][0] = x; }
    void setY(int index, int y) { coords[index][1] = y; }

    TetrixShape pieceShape;
    int coords[4][2];
};

#endif

/****************************************************************************

** TetrixPiece.cpp

**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
****************************************************************************/

#include <QtCore>

#include <stdlib.h>

#include "tetrixpiece.h"

void TetrixPiece::setRandomShape()
{
    setShape(TetrixShape(qrand() % 7 + 1));
}

void TetrixPiece::setShape(TetrixShape shape)
{
    static const int coordsTable[8][4][2] = {
        { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
        { { 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 } },
        { { 0, -1 },  { 0, 0 },   { 1, 0 },   { 1, 1 } },
        { { 0, -1 },  { 0, 0 },   { 0, 1 },   { 0, 2 } },
        { { -1, 0 },  { 0, 0 },   { 1, 0 },   { 0, 1 } },
        { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
        { { -1, -1 }, { 0, -1 },  { 0, 0 },   { 0, 1 } },
        { { 1, -1 },  { 0, -1 },  { 0, 0 },   { 0, 1 } }
    };

    for (int i = 0; i < 4 ; i++) {
        for (int j = 0; j < 2; ++j)
            coords[i][j] = coordsTable[shape][i][j];
    }
    pieceShape = shape;
}

int TetrixPiece::minX() const
{
    int min = coords[0][0];
    for (int i = 1; i < 4; ++i)
        min = qMin(min, coords[i][0]);
    return min;
}

int TetrixPiece::maxX() const
{
    int max = coords[0][0];
    for (int i = 1; i < 4; ++i)
        max = qMax(max, coords[i][0]);
    return max;
}

int TetrixPiece::minY() const
{
    int min = coords[0][1];
    for (int i = 1; i < 4; ++i)
        min = qMin(min, coords[i][1]);
    return min;
}

int TetrixPiece::maxY() const
{
    int max = coords[0][1];
    for (int i = 1; i < 4; ++i)
        max = qMax(max, coords[i][1]);
    return max;
}

TetrixPiece TetrixPiece::rotatedLeft() const
{
    if (pieceShape == SquareShape)
        return *this;

    TetrixPiece result;
    result.pieceShape = pieceShape;
    for (int i = 0; i < 4; ++i) {
        result.setX(i, y(i));
        result.setY(i, -x(i));
    }
    return result;
}

TetrixPiece TetrixPiece::rotatedRight() const
{
    if (pieceShape == SquareShape)
        return *this;

    TetrixPiece result;
    result.pieceShape = pieceShape;
    for (int i = 0; i < 4; ++i) {
        result.setX(i, -y(i));
        result.setY(i, x(i));
    }
    return result;
}
 

首先看一下TetrixShape ,这个enum定义了一些图形的索引方法。用于表示各式的图形。我们可以在setShape方法中找到这个static const int coordsTable[8][4][2]。静态的三维数组,就是它,按照enum的顺序,定义了各个图形的真实表示。

 然后我们看看它到底是如何表示的,我们随便找一个ZShape吧,它的定义是:{ 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 }  用矩阵表示出来就是

 0   -1

 0    0

-1    0

-1    1

 

这个明显没有上面我们讨论的那个直观,当初我看这个数据结构时候也是迷惑了好一阵。这个矩阵是如何表示Z图形的呢?

 

了解了这个类中的成员函数以及后面TetrixBoard的实现才明白,TetrixPiece用4对坐标来表示一个图形。你看不管是哪种图形,其实它都是由4个方块组成的,所以,我们可以用4组坐标来表示一个图形。

看看这个Zshape,按照这个坐标来瞄点得到

   -1   0  1

-1      *

 0 *    *

 1 *

 

看,形状出来了吧。在这个二维数组中,前面的代表x坐标,后面的代表y坐标。理解了这个,那么下面的这些函数的意义就比较好懂了。

    int x(int index) const { return coords[index][0]; }  //返回图形的第index个坐标的x
    int y(int index) const { return coords[index][1]; }  //返回图形的第index个坐标的y
    int minX() const;                //最小x坐标值
    int maxX() const;               //最大x坐标值
    int minY() const;               //最小y坐标
    int maxY() const;               //最大y坐标  上面这4个函数,用于绘制图形时确定图形的外形大小。
    TetrixPiece rotatedLeft() const;  //坐标转换向左向右。
    TetrixPiece rotatedRight() const;

 

 

关于 TetrixPiece我们就聊这么多了。有了这些东西,看明白这个例子已经很容易鸟。

下面我们择一些有意思的地方说说:

 

void TetrixBoard::drawSquare(QPainter &painter, int x, int y, TetrixShape shape)

   这个函数,是用来画方块的,QPainter 类似于DGI中的DC,x和y指出在什么坐标上画,最后给出画的方块是什么图形的(这个tetrix里面不用方块用不同图形表示)。你可以看到在这个源代码中有两个这样静态的数组,他们都被定义到了成员函数里,一个是上面的coordsTable,一个是这个函数里面的colorTable。说实话,我对c++一知半解,并不知道这样的实现会不会有什么不好的地方,但是这种什么时候需要什么时候用,尽量把相关代码写到一起的风格,是哥们我很赞许地。

 

 bool TetrixBoard::tryMove(const TetrixPiece &newPiece, int newX, int newY)

   这个函数用于移动图形,给出try,说明它的返回值也很有用,在判断类似碰撞时起作用,就是让它不能随便Move。

 

        for (int i = 0; i < 4; ++i) {
            int x = curX + curPiece.x(i);
            int y = curY - curPiece.y(i);
            drawSquare(painter, rect.left() + x * squareWidth(),
                       boardTop + (BoardHeight - y - 1) * squareHeight(),
                       curPiece.shape());
        }
上面这段代码是用于绘制一个图形的代码,对于每一对坐标,计算了x和y的值,我们看到,它作了一些计算,主要是将坐标原点重新计算,让drawSquare在绘制的时候,可以按照一个合适的顺序完成。这里还有一个需要说明的,这组坐标,在通过计算max-min的值得到了它x和y坐标各自所占的大小。
还拿上面的ZShape做例子:

 0   -1

 0    0

-1    0

-1    1


程序通过下面的规则来计算:MAX(x)-MIN(x)+1 为x轴所占的大小即0-(-1)+1=2   ,MAX(y)-MIN(y)+1 为y轴所占的大小即1-(-1)+1=3。

我们可以跟一下对于绘制ZShape时候的情况:假设Zshape刚从最上面掉下来。
    curX = BoardWidth / 2 + 1=10/2+1=6  //居中
    curY = BoardHeight - 1 + curPiece.minY()= 22-1+(-1)=22

    x=curX+curPiece.x(i)=5[6]

    y=curY - curPiece.y(i)=23[22][21]


   drawSquare(painter, rect.left() + x * squareWidth(), //最左边坐标+x坐标
                          boardTop + (BoardHeight - y - 1) * squareHeight(), //最上端坐标 +相对最高处的修正 图形只显示一个方块.
                          curPiece.shape());



还有一个比较有意思的函数

TetrixShape &shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }

 

这个函数在使用的时候可以作为一个表达式的左值,like:shapeAt(1,2)=3; 我不熟悉c++的语法,但这个的确是我第一次遇见。shapeAt(1,2)=3;

相当于board[(2* BoardWidth) + 1]=3;

 

 大概就能想到这么多要说的东西了,如果大家有兴趣可以email和我讨论tetrix的事情。

 

 

你可能感兴趣的:(数据结构,C++,struct,qt,OpenSource,图形)