最近對開源版本的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的事情。