- 什麼
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))) { ... }
這是絕對令人難以置信,正是我想要的!非常感謝你! – Ash 2009-12-28 15:11:27