俄罗斯方块:类的布局

Ash*_*Ash 7 c++ class linked-list tetris

我写了一个工作的俄罗斯方块克隆,但它有一个非常凌乱的布局.我可以获得有关如何重构我的类以使我的编码更好的反馈.我专注于使我的代码尽可能通用,试图使它更像是仅使用块的游戏引擎.

每个块都是在游戏中单独创建的.我的游戏有2个BlockLists(链表):StaticBlocks和Tetroid.StaticBlocks显然是所有非移动块的列表,而tetroid是当前tetroid的4个块.

  1. 主要是世界被创造.

  2. 首先创建一个新的tetroid(列表Tetroid中的4个块)(NewTetroid)

  3. 通过(***碰撞)函数检测碰撞,通过使用(If*****)函数比较每个Tetroid和所有StaticBlock.

  4. 当tetroid停止(击中底部/块)时,它被复制(CopyTetroid)到StaticBlocks并且Tetroid变为空,然后通过用(SearchY)搜索StaticBlocks来测试完整的行,销毁/删除块等.

  5. 创建了一个新的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;
};
Run Code Online (Sandbox Code Playgroud)

对不起,如果这有点不清楚或冗长,我只是在寻找一些帮助重组.

jal*_*alf 8

  • 班级的唯一责任是World什么?它只是一个包含几乎所有功能的blob.那不是好设计.一个明显的责任是"代表放置块的网格".但这与创建tetroids或操纵块列表或绘图无关.事实上,大部分内容可能根本不需要在课堂上.我希望该World对象包含BlockList您调用StaticBlocks,以便它可以定义您正在播放的网格.
  • 你为什么定义自己的Blocklist?您说您希望您的代码是通用的,那么为什么不允许使用任何容器?std::vector<Block>如果我想,为什么我不能使用?或者std::set<Block>,或者一些家庭酿造的容器?
  • 使用不重复信息或自相矛盾的简单名称.TranslateTetroid不翻译一个tetroid.它会转换阻止列表中的所有块.所以它应该是TranslateBlocks什么的.但即便如此也是多余的.我们可以从签名(它需要a BlockList&)看到它在块上工作.所以就这么称呼它Translate.
  • 尽量避免C风格的评论(/*...*/).C++ - style(//..)表现得更好一点,如果你在整个代码块中使用C风格的注释,那么如果该块也包含C风格的注释,它将会中断.(作为一个简单的例子,/*/**/*/将无法工作,因为编译器会将第一个*/视为注释的结尾,因此最后一个*/不会被视为注释.
  • 所有(未命名)int参数是什么?它使你的代码无法阅读.
  • 尊重语言功能和惯例.复制对象的方法是使用其复制构造函数.因此,而不是一个CopyTetroid函数,给出BlockList一个复制构造函数.然后,如果我需要复制一个,我可以简单地做BlockList b1 = b0.
  • 而不是void SetX(Y)Y GetX()方法,删除冗余的Get/Set前缀,只需要void X(Y)Y X().我们知道它是一个吸气剂,因为它不需要参数并返回一个值.我们知道另一个是setter因为它接受一个参数并返回void.
  • BlockList不是一个很好的抽象.您对"当前的tetroid"和"当前在网格上的静态块列表"有着非常不同的需求.静态块可以用一个简单的块序列表示(虽然行序列或2D数组可能更方便),但是当前活动的tetroid需要额外的信息,例如旋转中心(不属于World).
    • 表示形状和简化旋转的简单方法可能是使构件块存储与旋转中心的简单偏移.这使得旋转更容易计算,并且意味着在翻译期间根本不必更新成员块.只需旋转旋转中心即可.
    • 在静态列表中,块甚至不能有效地知道它们的位置.相反,网格应该将位置映射到块(如果我问网格"单元格中存在哪个块(5,8),它应该能够返回块.但是块本身不需要存储坐标.如果是,它可以如果由于一些微妙的错误,两个块以相同的坐标结束会怎样?如果块存储它们自己的坐标会发生这种情况,但如果网格包含哪个块的列表,则会发生这种情况.)
    • 这告诉我们,我们需要一个表示"静态块",另一个表示"动态块"(它需要存储来自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);
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们添加一些缺失的部分:

首先,我们需要表示"动态"块,拥有它们的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);
}
Run Code Online (Sandbox Code Playgroud)

我们需要重新实施推测性碰撞检查.鉴于非变异的Translate/Rotate方法,这很简单:我们只创建旋转/翻译副本,并测试碰撞:

// 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))) { ... }
Run Code Online (Sandbox Code Playgroud)