2009-12-26 97 views
7

我寫了一個工作俄羅斯方塊克隆,但它有一個相當凌亂的佈局。我能否獲得關於如何重組我的課程以使我的編碼更好的反饋。我專注於盡可能地使我的代碼儘可能通用,並試圖讓它成爲僅使用塊的遊戲的引擎。俄羅斯方塊:類的佈局

每個區塊都是在遊戲中單獨創建的。 我的遊戲有2個BlockLists(鏈表):StaticBlocks和Tetroid。 StaticBlocks顯然是所有非移動塊的列表,tetroid是當前tetroid的4個塊。

  1. 主要是創建一個世界。

  2. 首先新tetroid(在列表Tetroid 4塊)由(NewTetroid)創建

  3. 碰撞由(***碰撞)功能檢測到的,由每個Tetroid與所有的比較使用(If *****)函數的StaticBlocks。當tetroid停止時(命中底部/塊),它被複制(CopyTetroid)到StaticBlocks並且Tetroid被設置爲空,然後通過搜索StaticBlocks來完成測試,完成所有行,塊被銷燬/丟棄等等。 (SearchY)。

  4. 創建新的tetroid。

(TranslateTetroid)和(RotateTetroid)由一個在Tetroid列表中的一個對每一塊執行操作(我認爲這是不好的做法)。

(DrawBlockList)只是經歷一個列表,爲每個塊運行Draw()函數。

當調用(NewTetroid)時,通過相對於Tetroid中的第一個塊設置旋轉軸來控制旋轉。我的旋轉功能(旋轉)爲每個塊圍繞軸旋轉,使用輸入+ -1進行左/右旋轉。 RotationModes和States用於以2或4種不同方式旋轉的塊,定義它們當前處於什麼狀態,以及它們是左旋還是右旋。 我對這些在「世界」中定義的方式並不滿意,但我不知道將它們放在哪裏,同時仍然保留我的(旋轉)函數通用於每個塊

我的類如下

class World 
{ 
    public: 
    /* Constructor/Destructor */ 
    World(); 
    ~World(); 

    /* Blocks Operations */ 
    void AppendBlock(int, int, BlockList&); 
    void RemoveBlock(Block*, BlockList&);; 

    /* Tetroid Operations */ 
    void NewTetroid(int, int, int, BlockList&); 
    void TranslateTetroid(int, int, BlockList&); 
    void RotateTetroid(int, BlockList&); 
    void CopyTetroid(BlockList&, BlockList&); 

    /* Draw */ 
    void DrawBlockList(BlockList&); 
    void DrawWalls(); 

    /* Collisions */ 
    bool TranslateCollide(int, int, BlockList&, BlockList&); 
    bool RotateCollide(int, BlockList&, BlockList&); 
    bool OverlapCollide(BlockList&, BlockList&); // For end of game 

    /* Game Mechanics */ 
    bool CompleteLine(BlockList&); // Test all line 
    bool CompleteLine(int, BlockList&); // Test specific line 
    void ColourLine(int, BlockList&); 
    void DestroyLine(int, BlockList&); 
    void DropLine(int, BlockList&); // Drops all blocks above line 

    int rotationAxisX; 
    int rotationAxisY; 
    int rotationState; // Which rotation it is currently in 
    int rotationModes; // How many diff rotations possible 

    private: 
    int wallX1; 
    int wallX2; 
    int wallY1; 
    int wallY2; 
}; 

class BlockList 
{ 
    public: 
    BlockList(); 
    ~BlockList(); 

    Block* GetFirst(); 
    Block* GetLast(); 

    /* List Operations */ 
    void Append(int, int); 
    int Remove(Block*); 
    int SearchY(int); 

    private: 
    Block *first; 
    Block *last; 
}; 

class Block 
{ 
    public: 
    Block(int, int); 
    ~Block(); 

    int GetX(); 
    int GetY(); 

    void SetColour(int, int, int); 

    void Translate(int, int); 
    void Rotate(int, int, int); 

    /* Return values simulating the operation (for collision purposes) */ 
    int IfTranslateX(int); 
    int IfTranslateY(int); 
    int IfRotateX(int, int, int); 
    int IfRotateY(int, int, int); 

    void Draw(); 

    Block *next; 

    private: 
    int pX; // position x 
    int pY; // position y 
    int colourR; 
    int colourG; 
    int colourB; 
}; 

很抱歉,如果這是一個有點不清楚或長篇大論,我只是在尋找一些幫助重組。

回答

8
  • 什麼World類的責任? 這只是一個包含幾乎所有功能的blob。這不是很好的設計。一個明顯的責任是「代表塊放置在其上的網格」。但是這與創建tetroids或操作塊列表或繪圖無關。事實上,其中大部分可能根本不需要在課堂上。我期望World對象包含您調用StaticBlocks的BlockList,以便它可以定義您正在播放的網格。
  • 你爲什麼要定義你自己的Blocklist?你說你想要你的代碼是通用的,那麼爲什麼不允許使用任何容器?如果我想要,爲什麼我不能使用std::vector<Block>?或者是一個std::set<Block>,還是一些自制的容器?
  • 使用不重複信息或自相矛盾的簡單名稱。 TranslateTetroid不翻譯tetroid。它翻譯塊列表中的所有塊。所以它應該是TranslateBlocks什麼的。但即使這是多餘的。我們可以從簽名中看到它(它需要BlockList&)它可以在塊上工作。所以就把它叫做Translate
  • 儘量避免使用C風格的評論(/*...*/)。 C++風格(//..)的行爲有點更好,因爲如果你在整個代碼塊中使用C風格的註釋,那麼如果該塊還包含C風格的註釋,它將會中斷。 (作爲一個簡單的例子,/*/**/*/將不起作用,因爲編譯器會將第一個*/看作評論的結尾,所以最後的*/將不會被視爲評論
  • 什麼是所有(未命名) int參數?它讓你的代碼無法讀取。
  • 尊重語言特性和約定。複製的對象的方式是使用它的拷貝構造函數。所以,而非CopyTetroid功能,給BlockList一個拷貝構造函數,然後,如果我需要複製一個,我可以簡單地做BlockList b1 = b0
  • 不是void SetX(Y)Y GetX()方法,刪除多餘的get/set前修復和簡單地有void X(Y)Y X()。我們知道這是一個getter,因爲它不需要參數並返回一個值。而我們知道另一個是setter,因爲它需要一個參數並返回void。
  • BlockList不是一個很好的抽象。您對「當前tetroid」和「當前在網格上的靜態塊列表」有着非常不同的需求。靜態塊可以用一個簡單的塊序列來表示(雖然一系列的行或2D數組可能更方便),但是當前活動的tetroid需要附加信息,例如旋轉中心(其中不屬於World)。
    • 表示tetroid並簡化旋轉的簡單方法可能是讓成員塊存儲距旋轉中心的簡單偏移量。這使得旋轉更容易計算,並且意味着在翻譯期間不必更新成員塊。旋轉的中心必須移動。
    • 在靜態列表中,對於塊知道它們的位置,效率並不高。相反,網格應該將位置映射到塊(如果我詢問網格「單元格(5,8)中存在哪個塊,它應該能夠返回塊,但塊本身不需要存儲座標,如果它確實存在,可能會成爲一個維護頭痛,如果由於一些細微的錯誤,兩個塊最終具有相同的座標?如果塊存儲它們自己的座標,會發生這種情況,但如果網格包含哪個塊的列表,則不會發生。)
    • 這告訴我們,我們需要一個「靜態塊」的表示,另一個表示「動態塊」(它需要存儲從tetroid中心的偏移量)。事實上,「靜態」塊可以被降低要點:網格中的一個單元格包含一個塊,並且該塊有一個顏色,或者它不包含一個塊。沒有進一步的行爲與這些塊關聯,因此它可能是它放置的單元格那個sh應該改爲建模。
    • 我們需要一個表示可移動/動態tetroid的類。
  • 由於大量的碰撞檢測是「預測性」的,因爲它涉及「如果我將對象移到這裏」,實現非變異翻譯/旋轉功能可能會更簡單。這些應該保持原始對象不變,並且返回旋轉/翻譯的副本。

因此,這裏是第一遍代碼,只需重命名,註釋和刪除代碼而不會更改結構太多。

class World 
{ 
public: 
    // Constructor/Destructor 
    // the constructor should bring the object into a useful state. 
    // For that, it needs to know the dimensions of the grid it is creating, does it not? 
    World(int width, int height); 
    ~World(); 

    // none of thes have anything to do with the world 
    ///* Blocks Operations */ 
    //void AppendBlock(int, int, BlockList&); 
    //void RemoveBlock(Block*, BlockList&);; 

    // Tetroid Operations 
    // What's wrong with using BlockList's constructor for, well, constructing BlockLists? Why do you need NewTetroid? 
    //void NewTetroid(int, int, int, BlockList&); 

    // none of these belong in the World class. They deal with BlockLists, not the entire world. 
    //void TranslateTetroid(int, int, BlockList&); 
    //void RotateTetroid(int, BlockList&); 
    //void CopyTetroid(BlockList&, BlockList&); 

    // Drawing isn't the responsibility of the world 
    ///* Draw */ 
    //void DrawBlockList(BlockList&); 
    //void DrawWalls(); 

    // these are generic functions used to test for collisions between any two blocklists. So don't place them in the grid/world class. 
    ///* Collisions */ 
    //bool TranslateCollide(int, int, BlockList&, BlockList&); 
    //bool RotateCollide(int, BlockList&, BlockList&); 
    //bool OverlapCollide(BlockList&, BlockList&); // For end of game 

    // given that these functions take the blocklist on which they're operating as an argument, why do they need to be members of this, or any, class? 
    // Game Mechanics 
    bool AnyCompleteLines(BlockList&); // Renamed. I assume that it returns true if *any* line is complete? 
    bool IsLineComplete(int line, BlockList&); // Renamed. Avoid ambiguous names like "CompleteLine". is that a command? (complete this line) or a question (is this line complete)? 
    void ColourLine(int line, BlockList&); // how is the line supposed to be coloured? Which colour? 
    void DestroyLine(int line, BlockList&); 
    void DropLine(int, BlockList&); // Drops all blocks above line 

    // bad terminology. The objects are rotated about the Z axis. The x/y coordinates around which it is rotated are not axes, just a point. 
    int rotationAxisX; 
    int rotationAxisY; 
    // what's this for? How many rotation states exist? what are they? 
    int rotationState; // Which rotation it is currently in 
    // same as above. What is this, what is it for? 
    int rotationModes; // How many diff rotations possible 

private: 
    int wallX1; 
    int wallX2; 
    int wallY1; 
    int wallY2; 
}; 

// The language already has perfectly well defined containers. No need to reinvent the wheel 
//class BlockList 
//{ 
//public: 
// BlockList(); 
// ~BlockList(); 
// 
// Block* GetFirst(); 
// Block* GetLast(); 
// 
// /* List Operations */ 
// void Append(int, int); 
// int Remove(Block*); 
// int SearchY(int); 
// 
//private: 
// Block *first; 
// Block *last; 
//}; 

struct Colour { 
    int r, g, b; 
}; 

class Block 
{ 
public: 
    Block(int x, int y); 
    ~Block(); 

    int X(); 
    int Y(); 

    void Colour(const Colour& col); 

    void Translate(int down, int left); // add parameter names so we know the direction in which it is being translated 
    // what were the three original parameters for? Surely we just need to know how many 90-degree rotations in a fixed direction (clockwise, for example) are desired? 
    void Rotate(int cwSteps); 

    // If rotate/translate is non-mutating and instead create new objects, we don't need these predictive collision functions.x ½ 
    //// Return values simulating the operation (for collision purposes) 
    //int IfTranslateX(int); 
    //int IfTranslateY(int); 
    //int IfRotateX(int, int, int); 
    //int IfRotateY(int, int, int); 

    // the object shouldn't know how to draw itself. That's building an awful lot of complexity into the class 
    //void Draw(); 

    //Block *next; // is there a next? How come? What does it mean? In which context? 

private: 
    int x; // position x 
    int y; // position y 
    Colour col; 
    //int colourR; 
    //int colourG; 
    //int colourB; 
}; 

// Because the argument block is passed by value it is implicitly copied, so we can modify that and return it 
Block Translate(Block bl, int down, int left) { 
    return bl.Translate(down, left); 
} 
Block Rotate(Block bl, cwSteps) { 
    return bl.Rotate(cwSteps); 
} 

現在,讓我們添加一些缺失的部分組成:

首先,我們需要代表在網格中的「動態」模塊中,tetroid擁有他們,靜態塊或細胞。 (我們還將添加一個簡單的「碰撞」的方式向世界/網格類)

class Grid 
{ 
public: 
    // Constructor/Destructor 
    Grid(int width, int height); 
    ~Grid(); 

    // perhaps these should be moved out into a separate "game mechanics" object 
    bool AnyCompleteLines(); 
    bool IsLineComplete(int line); 
    void ColourLine(int line, Colour col);Which colour? 
    void DestroyLine(int line); 
    void DropLine(int); 

    int findFirstInColumn(int x, int y); // Starting from cell (x,y), find the first non-empty cell directly below it. This corresponds to the SearchY function in the old BlockList class 
    // To find the contents of cell (x,y) we can do cells[x + width*y]. Write a wrapper for this: 
    Cell& operator()(int x, int y) { return cells[x + width*y]; } 
    bool Collides(Tetroid& tet); // test if a tetroid collides with the blocks currently in the grid 

private: 
    // we can compute the wall positions on demand from the grid dimensions 
    int leftWallX() { return 0; } 
    int rightWallX() { return width; } 
    int topWallY() { return 0; } 
    int bottomWallY { return height; } 

    int width; 
    int height; 

    // let this contain all the cells in the grid. 
    std::vector<Cell> cells; 

}; 

// represents a cell in the game board grid 
class Cell { 
public: 
    bool hasBlock(); 
    Colour Colour(); 
}; 

struct Colour { 
    int r, g, b; 
}; 

class Block 
{ 
public: 
    Block(int x, int y, Colour col); 
    ~Block(); 

    int X(); 
    int Y(); 
void X(int); 
void Y(int); 

    void Colour(const Colour& col); 

private: 
    int x; // x-offset from center 
    int y; // y-offset from center 
    Colour col; // this could be moved to the Tetroid class, if you assume that tetroids are always single-coloured 
}; 

class Tetroid { // since you want this generalized for more than just Tetris, perhaps this is a bad name 
public: 
    template <typename BlockIter> 
    Tetroid(BlockIter first, BlockIter last); // given a range of blocks, as represented by an iterator pair, store the blocks in the tetroid 

    void Translate(int down, int left) { 
     centerX += left; 
     centerY += down; 
    } 
    void Rotate(int cwSteps) { 
     typedef std::vector<Block>::iterator iter; 
     for (iter cur = blocks.begin(); cur != blocks.end(); ++cur){ 
      // rotate the block (*cur) cwSteps times 90 degrees clockwise. 
        // a naive (but inefficient, especially for large rotations) solution could be this: 
     // while there is clockwise rotation left to perform 
     for (; cwSteps > 0; --cwSteps){ 
      int x = -cur->Y(); // assuming the Y axis points downwards, the new X offset is simply the old Y offset negated 
      int y = cur->X(); // and the new Y offset is the old X offset unmodified 
      cur->X(x); 
      cur->Y(y); 
     } 
     // if there is any counter-clockwise rotation to perform (if cwSteps was negative) 
     for (; cwSteps < 0; --cwSteps){ 
      int x = cur->Y(); 
      int y = -cur->X(); 
      cur->X(x); 
      cur->Y(y); 
     } 
     } 
    } 

private: 
    int centerX, centerY; 
    std::vector<Block> blocks; 
}; 

Tetroid Translate(Tetroid tet, int down, int left) { 
    return tet.Translate(down, left); 
} 
Tetroid Rotate(Tetroid tet, cwSteps) { 
    return tet.Rotate(cwSteps); 
} 

,我們需要重新實現投機碰撞檢測。鑑於非不同誘變平移/旋轉的方法,說起來很簡單:我們只需創建旋轉/翻譯件,並測試那些碰撞:

// test if a tetroid t would collide with the grid g if it was translated (x,y) units 
if (g.Collides(Translate(t, x, y))) { ... } 

// test if a tetroid t would collide with the grid g if it was rotated x times clockwise 
if (g.Collides(Rotate(t, x))) { ... } 
+1

這是絕對令人難以置信,正是我想要的!非常感謝你! – Ash 2009-12-28 15:11:27

2

我個人將靜態塊丟棄並作爲行進行處理。有一個靜態塊,你保存了比你需要更多的信息。

世界是由行組成的,它是一個單一的正方形數組。正方形可以是空的,也可以是一種顏色(如果有特殊的塊,則可以擴展它)。

世界也擁有一個活動塊,就像現在一樣。該類應該有一個旋轉和翻譯方法。該區塊顯然需要保持對世界的參考,以確定它是否會與現有的磚塊或棋盤邊緣發生碰撞。

當活動模塊退出遊戲時,它會調用類似world.update()的函數,它會將活動模塊的各部分添加到相應的行中,清除所有完整行,決定是否丟失等等,最後根據需要創建一個新的活動塊。