首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Windows俄罗斯方块游戏

Windows俄罗斯方块游戏
EN

Code Review用户
提问于 2019-03-27 12:59:23
回答 1查看 1.1K关注 0票数 7

我是一名学生,这是我今天在C++中使用OOP创建和完成的第一个应用程序/项目,我想听听您的意见和建议,我做错了什么,做了什么好事,应该改进什么,应该排除什么,关于评论,任何可以帮助我变得更好和改进代码的东西。

要测试代码,您需要复制包含游戏瓷砖的文件tiles.in

为了更好地理解代码,我做了一个UML图:

代码语言:javascript
复制
#include "pch.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define numberOfColumns 11               // number of lines and columns for the game table
#define numberOfLines 21

using namespace std;

class Drawable                          // abstract class that is used to draw different tiles from the game or coordinates
{
protected:
    static short x;
    static short y;
public:
    static int getX();
    static void hidecursor();
    static void MoveTo(short _x, short _y);     // used to move to a specific coordinate in console
    virtual void DeleteDraw() = 0;
    virtual void Draw() = 0;
};

short Drawable::x = 5;                   // represents the coordinates where the game table will be positioning in console
short Drawable::y = 25;

int Drawable::getX()
{
    return x;
}

void Drawable::hidecursor()
{
    CONSOLE_CURSOR_INFO info = { 100,FALSE };
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
}

void Drawable::MoveTo(short _x, short _y)
{
    COORD coord = { y + _y,x + _x };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

class Coordinates: public Drawable                // class that represents one coordinate in console (x,y)
{
private:
    short x;
    short y;
    static char form;                             // the form that every piece(point) from every tile will have
public:
    Coordinates(short x = 0, short y = 0);
    Coordinates& operator =(const Coordinates &coord);
    // getter and setter
    short getX();
    short getY();
    static char getForm();
    void setX(short x);
    void setY(short y);
    //functions using coordinates(x,y)
    void moveCoordinatesInADirection(char direction);     // used to move the coordinates in a specific direction(right, left, down)
                                                          // Will be used to move every piece(point) from a tile in a direction
    void DeleteDraw() override;
    void Draw() override;
};

char Coordinates::form = '*';

Coordinates::Coordinates(short x, short y)
{
    this->x = x;
    this->y = y;
}

Coordinates& Coordinates::operator=(const Coordinates &coord)
{
    if (this != &coord)
    {
        this->x = coord.x;
        this->y = coord.y;
        this->form = coord.form;
    }
    return *this;
}

char Coordinates::getForm()
{
    return form;
}

short Coordinates::getX()
{
    return x;
}

short Coordinates::getY()
{
    return y;
}

void Coordinates::setX(short x)
{
    this->x = x;
}

void Coordinates::setY(short y)
{
    this->y = y;
}

void Coordinates::moveCoordinatesInADirection(char direction)
{
    switch (direction)
    {
    case 'a':       // move left
        y--;
        break;
    case 'd':       // move right
        y++;
        break;
    case 's':       // move down
        x++;
        break;
    default:
        break;
    }
}

void Coordinates::DeleteDraw()
{
    MoveTo(x + Drawable::x, y + Drawable::y);   // Moves to the coordinates (x,y) and deletes a piece(point) from a tile
    cout << " ";
}

void Coordinates::Draw()
{
    MoveTo(x + Drawable::x, y + Drawable::y);   // Moves to the coordinates (x,y) and draw a piece(point) from a tile
    cout << form;
}


class Tile: public Drawable                     // class that represents a tile and all its methods
{
private:
    Coordinates coordTile[4];                         // any tile is composed of 4 coordinates and a center
    short centerOfTile;
public:
    Tile& operator=(const Tile &tile);
    // getter and setter
    short getcenterOfTile(short position);
    short getcoordX(short position);
    short getcoordY(short position);
    void setcenterOfTile(short centerOfTile);
    void setcoordX(short position, int x);
    void setcoordY(short position, int y);
    //methods using a tile
    void moveTileInADirection(char direction);          // moves the tile in a specific direction(right, left, down)
    void rotateTileInADirection(char direction);       // rotates the tile in a specific direction(right, left)
    void DeleteDraw() override;                       // overrides function DeleteDraw() from Drawable() and is used to delete the tile from the game table
    void Draw() override;                            // overrides function Draw() from Drawable() and is used to draw the tile in the game table
};

Tile& Tile::operator=(const Tile &tile)
{
    if (this != &tile)
    {
        for (short i = 0; i < 4; i++)
        {
            this->coordTile[i] = tile.coordTile[i];
        }
    }
    return *this;
}

short Tile::getcoordX(short position)
{
    return coordTile[position].getX();
}

short Tile::getcoordY(short position)
{
    return coordTile[position].getY();
}

short Tile::getcenterOfTile(short position)
{
    return centerOfTile;
}

void Tile::setcoordX(short position, int x)
{
    coordTile[position].setX(x);
}

void Tile::setcoordY(short position, int y)
{
    coordTile[position].setY(y);
}

void Tile::setcenterOfTile(short centerOfTile)
{
    this->centerOfTile = centerOfTile;
}

void Tile::moveTileInADirection(char direction)
{
    for (short i = 0; i < 4; i++)
    {
        coordTile[i].moveCoordinatesInADirection(direction);
    }
}

void Tile::rotateTileInADirection(char direction)
{
    short dir = 0;

    switch (direction)
    {
    case 'e':           // to rotate the tile to the right we need +90* check formula down
        dir = 1;
        break;
    case 'q':           // to rotate the tile to the left we need -90* check formula down
        dir = -1;
        break;
    default:
        return;
    }

    if (centerOfTile != -1) // If the tile can be rotated
    {
        float centerOfTileX = coordTile[centerOfTile].getX();
        float centerOfTileY = coordTile[centerOfTile].getY();

        float tileX;
        float tileY;

        for (short i = 0; i < 4; i++)  // we rotate every piece(point) from the tile with 90*(to right) or -90*(to left) depends on dir
        {
            tileX = coordTile[i].getX();
            tileY = coordTile[i].getY();
            coordTile[i].setX(round((tileX - centerOfTileX)*cos((90 * 3.14*dir) / 180) + (tileY - centerOfTileY)*sin((90 * 3.14*dir) / 180) + centerOfTileX));
            coordTile[i].setY(round((centerOfTileX - tileX)*sin((90 * 3.14*dir) / 180) + (tileY - centerOfTileY)*cos((90 * 3.14*dir) / 180) + centerOfTileY));
        }
    }
}

void Tile::DeleteDraw()
{
    for (short i = 0; i < 4; i++)
    {
        coordTile[i].DeleteDraw();      // Deleting the tile by deleting every piece(point) of it
    }
}


void Tile::Draw()
{
    for (short i = 0; i < 4; i++)
    {
        coordTile[i].Draw();             // Drawing the tile by drawing every piece(point) of it
    }
}

class Tiles                       // class that represents the number of tiles the game has and all tiles
{
private:
    short numberOfTiles;
    Tile *figuri;
public:
    Tiles();
    Tile getTile(short number);
    short getNumberOfTiles();
    ~Tiles();
};

Tiles::Tiles()
{
    ifstream input("tiles.in");               // reading from a file the number of tiles and than the coordinates of each tile and setting the center for every tile
    input >> numberOfTiles;

    figuri = new Tile[numberOfTiles];
    short auxiliaryVar = 0;
    short counter = 0;

    for (short i = 0; i < numberOfTiles; i++)
    {
        counter = 0;
        for (short j = 0; j < 4; j++)
        {
            for (short k = 0; k < 4; k++)
            {
                input >> auxiliaryVar;
                if (auxiliaryVar != 0)
                {
                    figuri[i].setcoordX(counter, j);
                    figuri[i].setcoordY(counter, k);
                    counter++;
                    if ((j == 1) && (k == 2))
                    {
                        figuri[i].setcenterOfTile(counter - 1);
                    }
                }
            }
        }
    }

    figuri[0].setcenterOfTile(2);
    figuri[3].setcenterOfTile(-1);
    input.close();
}

Tile Tiles::getTile(short number)
{
    return figuri[number];
}

short Tiles::getNumberOfTiles()
{
    return numberOfTiles;
}

Tiles::~Tiles()
{
    delete[] figuri;
}


class Table: public Drawable           // class that represents the game table 
{
private:
    short **table;            // the game table= a matrix with 0 if there is nothing draw in that point and 1 if there is something draw 
    long score;
    Tile actualTile;         // the tile that moves in the game table(the actual tile)
    Tiles allTiles;         // the actual tile will be chosen random from all the tiles possible

public:
    Table();
    long getScore();
    void informationAboutGame();
    void generateRandomTile();
    void deleteLineFromTable(short line);        // after a line from the table is completated, it will be deleted from the game table and the score will rise
    void moveTileDownAutomatically();
    void moveTileInADirection(char direction);
    void possibleMoves(short &time);          // possible moves of a player (right, left, down)
    void positioningTileInTableAfterRotation();
    void rotateTileInADirection(char direction);
    void start();
    void DeleteDraw();
    void Draw();
    bool belongsToActualTile(short x, short y);
    bool checkIfCanMoveInADirection(char direction);
    bool checkIfPlayerLost();
    ~Table();
};

Table::Table()
{
    // creating the game table and initialize the table

    time_t t;
    srand((unsigned)(time(&t)));

    score = 0;

    table = new short*[numberOfLines];
    for (short i = 0; i < numberOfLines; i++)
    {
        table[i] = new short[numberOfColumns];
    }

    for (short i = 0; i < numberOfLines; i++)
    {
        for (short j = 0; j < numberOfColumns; j++)
        {
            table[i][j] = 0;
        }
    }
}

long Table::getScore()
{
    return score;
}

void Table::informationAboutGame()
{
    cout << "\n\n\n\t This is a tetris game.The controls for the game are:\n";
    cout << "\n\t a - move the tile left";
    cout << "\n\t d - move the tile right";
    cout << "\n\t s - move the tile down";
    cout << "\n\t e - rotate the tile right";
    cout << "\n\t q - rotate the tile left";
    cout << "\n\n\t When you are ready, press any key to start the game. Good luck ! ";
    _getch();
}

void Table::generateRandomTile()
{
    // generating a random tile from all the tiles possible and setting its coordinates for the game table

    short randomTile;
    randomTile = rand() % allTiles.getNumberOfTiles();

    actualTile = allTiles.getTile(randomTile);
    actualTile.setcenterOfTile(allTiles.getTile(randomTile).getcenterOfTile(randomTile));

    for (short i = 0; i < 4; i++)
    {
        actualTile.setcoordY(i, numberOfColumns / 2 - actualTile.getcoordY(i) + 2);
    }
}

void Table::deleteLineFromTable(short line) 
{
    // Deleting the line which is completed
    // This is done by replacing every line starting that line by the previous one
    for (short i = line; i > 0; i--)
    {
        for (short j = 0; j < numberOfColumns; j++)
        {
            Drawable::MoveTo(i + Drawable::x, j + Drawable::y);
            if (table[i - 1][j] == 0)
            {
                cout << " ";
            }
            else {
                cout << Coordinates::getForm();
            }

            table[i][j] = table[i - 1][j];
        }
    }

    for (short i = 0; i < numberOfColumns; i++)
    {
        Drawable::MoveTo(0 + Drawable::x, i + Drawable::y);
        cout << " ";
        table[0][i] = 0;
    }
}

void Table::moveTileDownAutomatically()
{
    //Moving the actual tile down every 0.5s and checking if the player wants to make a move(right, left, down) or rotate(right, left) the tile
    actualTile.Draw();

    do {
        short time = 1;

        while (time < 500)
        {
            if (_kbhit())             // if the player presses a key on keyboard
            {
                possibleMoves(time);
            }

            Sleep(1);
            time = time + 1;
        }

        if (checkIfCanMoveInADirection('s'))
        {
            actualTile.DeleteDraw();
            moveTileInADirection('s');
            actualTile.Draw();
        }
        else {
            break;
        }
    } while (true); 
}

void Table::moveTileInADirection(char direction)
{
    // To move the tile in a direction we need to :
    // - delete the previous tile from the game table by putting 0
    // - move the tile to the new coordinates
    // - actualizate the game table by putting 1 on its coordinates 
    for (short i = 0; i < 4; i++)
    {
        table[actualTile.getcoordX(i)][actualTile.getcoordY(i)] = 0;
    }

    actualTile.moveTileInADirection(direction);

    for (short i = 0; i < 4; i++)
    {
        table[actualTile.getcoordX(i)][actualTile.getcoordY(i)] = 1;
    }
}

void Table::possibleMoves(short &time)
{
    //Possible moves that can be effectuated on a tile ( move and rotate )
    char direction = _getch();

    if (checkIfCanMoveInADirection(direction))
    {
        actualTile.DeleteDraw();                  // delete old tile
        moveTileInADirection(direction);          // move the tile in the direction the player wanted
        actualTile.Draw();                        // draw the new tile
        if (direction == 's')
        {
            time = 1;
        }
    }
    // check if the player wanted to rotate the tile (right, left)
    if ((direction == 'e') || (direction == 'q'))
    {
        actualTile.DeleteDraw();
        rotateTileInADirection(direction);
        actualTile.Draw();
    }
}

void Table::positioningTileInTableAfterRotation()
{
    // This method is used to check and correct a tile if it goes out of boundaries of the game table after a rotation
    short index = 0;
    short ok = 1;

    while (index < 4)
    {
        if (actualTile.getcoordY(index) < 0)
        {
            // passed left boundary of the game table
            for (short j = 0; j < 4; j++)
            {
                actualTile.setcoordY(j, actualTile.getcoordY(j) + 1);
            }
            ok = 0;
        }

        if (actualTile.getcoordY(index) > numberOfColumns - 1)
        {
            // passed right boundary of the game table
            for (short j = 0; j < 4; j++)
            {
                actualTile.setcoordY(j, actualTile.getcoordY(j) - 1);
            }
            ok = 0;
        }

        if (actualTile.getcoordX(index) < 0)
        {
            // passed top boundary of the game table and there are cases where the player loses
            for (short j = 0; j < 4; j++)
            {
                actualTile.setcoordX(j, actualTile.getcoordX(j) + 1);
            }

            for (short j = 0; j < 4; j++)
            {
                if ((actualTile.getcoordX(j) > 0) && (table[actualTile.getcoordX(j)][actualTile.getcoordY(j)] == 1))
                {
                    throw 0;
                }
            }
            ok = 0;
        }

        if ((actualTile.getcoordX(index) > numberOfLines - 1) ||
            (table[actualTile.getcoordX(index)][actualTile.getcoordY(index)] == 1))
        {
            // passed the down boundary or reached a possition that is occupied
            for (short j = 0; j < 4; j++)
            {
                actualTile.setcoordX(j, actualTile.getcoordX(j) - 1);
            }
            ok = 0;
        }

        if (ok == 0)
        {
            index = 0;
            ok = 1;
        }
        else {
            index++;
        }
    }
}

void Table::rotateTileInADirection(char direction)
{
    // To rotate the tile in a direction we need to :
    // - delete the previous tile from the game table by putting 0
    // - move the tile to the new coordinates and adjust it so it doesnt pass the boundaries of the game table
    // - actualizate the game table by putting 1 on its coordinates
    for (short i = 0; i < 4; i++)
    {
        table[actualTile.getcoordX(i)][actualTile.getcoordY(i)] = 0;
    }

    actualTile.rotateTileInADirection(direction);
    positioningTileInTableAfterRotation();

    for (short i = 0; i < 4; i++)
    {
        table[actualTile.getcoordX(i)][actualTile.getcoordY(i)] = 1;
    }
}

void Table::start()
{
    Drawable::hidecursor();
    informationAboutGame();

    DeleteDraw();
    Draw();

    short ok = 1;

    while (true)
    {
        // This while will end when the player will lose and the program will end
        // checking if there is any line completed and needs to be deleted
        for (short i = 0; i < numberOfLines; i++)
        {
            ok = 1;

            for (short j = 0; j < numberOfColumns; j++)
            {
                if (table[i][j] == 0)
                {
                    ok = 0;
                    break;
                }
            }
            if (ok)
            {
                deleteLineFromTable(i);
                score++;
            }
        }

        generateRandomTile();

        if (checkIfPlayerLost() == 1)
        {
            moveTileDownAutomatically();
        }
        else {
            Drawable::MoveTo(numberOfLines + 1 + Drawable::x, 0);
            cout << "\n" << "Good job, you made " << score * 1000 << " points.\n";
            break;
        }
    }
}

void Table::DeleteDraw()
{
    // Method used to delete the table
    system("cls");
}

void Table::Draw()
{
    // Method used to draw the table 
    for (short i = -1; i <= numberOfLines; i++)
    {
        MoveTo(i + Drawable::x, -1 + Drawable::y);
        cout << char(219);
        MoveTo(i + Drawable::x, numberOfColumns + Drawable::y);
        cout << char(219);
    }
    for (short i = -1; i <= numberOfColumns; i++)
    {
        Drawable::MoveTo(-1 + Drawable::x, i + Drawable::y);
        cout << char(219);
        Drawable::MoveTo(numberOfLines + Drawable::x, i + Drawable::y);
        cout << char(219);
    }
}

bool Table::belongsToActualTile(short x, short y)
{
    //Checking if a piece(point) of a tile belonds to the actual tile
    for (short i = 0; i < 4; i++)
    {
        if ((actualTile.getcoordX(i) == x) && (actualTile.getcoordY(i) == y))
        {
            return 0;
        }
    }

    return 1;
}

bool Table::checkIfCanMoveInADirection(char direction)
{
    for (short i = 0; i < 4; i++)
    {
        switch (direction)
        {
            // Check if the player can move left
        case'a':
            if ((actualTile.getcoordY(i) - 1 < 0) ||
                ((belongsToActualTile(actualTile.getcoordX(i), actualTile.getcoordY(i) - 1)) &&
                (table[actualTile.getcoordX(i)][actualTile.getcoordY(i) - 1] == 1)))
            {
                return 0;
            }
            break;
            // Check if the player can move right
        case'd':
            if ((actualTile.getcoordY(i) + 1 > numberOfColumns - 1) ||
                ((belongsToActualTile(actualTile.getcoordX(i), actualTile.getcoordY(i) + 1)) &&
                (table[actualTile.getcoordX(i)][actualTile.getcoordY(i) + 1] == 1)))
            {
                return 0;
            }
            break;
            // Check if the player can move down
        case's':
            if ((actualTile.getcoordX(i) + 1 > numberOfLines - 1) ||
                ((belongsToActualTile(actualTile.getcoordX(i) + 1, actualTile.getcoordY(i))) &&
                (table[actualTile.getcoordX(i) + 1][actualTile.getcoordY(i)] == 1)))
            {
                return 0;
            }
            break;
        default:
            break;
        }
    }
    return 1;
}

bool Table::checkIfPlayerLost()
{
    for (short i = 0; i < 4; i++)
    {
        if (table[actualTile.getcoordX(i)][actualTile.getcoordY(i)] == 1)
        {
            return 0;
        }
    }

    return 1;
}

Table::~Table() 
{
    for (short i = 0; i < numberOfLines; i++)
    {
        delete[] table[i];
    }
    delete[] table;
}

int main() 
{
    Table a;
    try {
        a.start();
    }
    catch (...) {
        Drawable::MoveTo(numberOfLines + 1 + Drawable::getX(), 0);
        cout << "\n" << "Good job, you made " << a.getScore() * 1000 << " points.\n";
    }

    return 0;
}

编辑:我根据您的建议@Sandro4912 4912对代码进行了一些更改,我希望这些更改是好的,并使代码更加可读性和易懂性。使每个文件分别声明和实现一个类,更正布尔方法,用静态contexpr重命名定义,删除全局变量,使用double和int,只对需要的部分进行注释,在main中省略返回0,使用更好的随机引擎和其他一些更改。我仍然使用using namespace std;,而且不使用命名空间只是因为我认为这会降低代码的可读性,而且我作为私有成员与一些成员在一起只是因为我虽然不是所有的类都应该能够访问所有其他类。除此之外,我还按照您的建议修改了代码,并添加了一些新方法、新成员和新类。这是到代码:俄罗斯方块游戏的链接。抱歉花了这么长时间。Edit 2:用改进的代码提出了一个新问题,新问题的链接是:用于Windows的俄罗斯方块游戏改进版

EN

回答 1

Code Review用户

回答已采纳

发布于 2019-05-31 19:06:43

我没有时间分析所有的代码。我建议解决这里提到的这些问题,然后对改进后的版本进行反驳。然后我们可以看看你的逻辑。

  1. 不要使用 using namespace std它被认为是不好的做法。请参阅:https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice
  2. 如果使用其他库,则应该将函数和类包装在名称空间中,以防止名称冲突。像这样:命名空间俄罗斯方块{ //您的类和函数}
  3. 每个文件的One类:当前将所有内容放入一个文件中。这样,您的函数和类的分离根本没有帮助。建议在每个文件中放置一个类。
  4. Separate声明和实现。将代码分离到头文件和实现文件中。在标头中,只声明并包含所需的最小值。若要加快编译,请使用前向声明。
  5. Return作为布尔值:boolean::checkIfPlayerLost(){ for (短i= 0;i< 4;i++) { if ( == 1) {== 0;}返回1;}应该是: bool Table::checkIfPlayerLost() { for (短i= 0;i<4);i++) {如果(表== 1) {返回假;}返回真;}
  6. 使用 constexpr for常数。这是:#define 11应该是这样(我们不再使用C语言了):静态constexpr numberOfColumns{11}
  7. 不要使用全局变量。它们被认为是维护上的危险。尝试将它们封装到一个类中;
  8. 不要使用 floatshort。默认情况下,您应该使用doubleint。以下是关于它的一点:https://stackoverflow.com/questions/24371077/when-to-use-short-over-int
  9. 不要评论什么是清楚的。例如:#定义numberOfColumns 11 //游戏表#define 21的行数和列数--您认为这条评论有什么好处吗?我没有。
  10. public在私有之前。在C++中,通常将公共成员放在私有成员之前是很常见的。
  11. 省略 return 0 in main。与C++中旧C中的不同,C++ return 0是由编译器在main末尾自动生成的。
  12. 这:类表:公共可绘制//类,表示游戏表{ Use **表;.}表::Table(){.表=新短表;.}表::~ table (){ for (缩写为I= 0;i< numberOfLines;i++) { delete[]表;} delete[]表;}是这样的。首先,为什么在这里分配动态数组?你想要一个固定的数组。所以你只需要声明一个二维的C数组。由于我们使用的是c++,所以使用std::arraystd::vector作为表表示会更明智。如果确实需要动态分配(这里不需要),那么使用智能指针,比如std::unique_ptrstd::shared_pointer,而不是裸露的新/删除。他们为你安全地释放记忆
  13. <#>Use是一个更好的随机引擎。由于C++11,您不应该使用以下内容:srand(Unsigned)(time(&t);而是使用来自的更好的随机生成器--为什么?https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful
票数 6
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/216333

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档