9
\$\begingroup\$

After doing the Snake Game I decided to go more crazy and made an Arkanoid Clone:

enter image description here enter image description here

Currently this only runs on Windows. I tested it under MSVC2017 with Windows 7. If you want to get it to run on Linux or Mac you have to implement the Part in Console.cpp.

I organized the code as following:

  • GameObject: This class is the base for all objects in the game. It handles all the movements and also collisions between two GameObjects.

  • Platform: Derived from GameObject. The only thing new here is that it can be reset to the init position. Used when a level is restarted.

  • Wall: Derived from GameObject. Not moveable. Can be passed to Ball or Platform to handle collisions between each other

  • Ball: Derived from GameObject. Can be deactivated so it does not move. This is used before the user hits spacebar to start the game. The move routine is override to add a gravity force on the Ball

  • Brick: Derived from GameObject. Not moveable. Has hitpoints and can be flaged destroyed. This is used to not have the need to delete the Brick from the field it just becomes invisible. The init hitpoints are stored as well to be used for different scorings

  • Level: Handles everything which happens in one played Level. It can take a collection of Bricks to load different levels.

  • Game: This routine calls the different levels and announces Game Over if it occurs

  • Grid: Used to display on the console. In the Background everything like Positions of the ball gets calculated with floating point operations. Since the console has very big pixels we only show whole sizes so 10.5 becomes 10 etc.

  • Console: Currently only supporting Windows. Commands to manipulate the console and check for keys pressed

  • MathHelper: Some defines and functions to convert from degree to radient. For readability in the GameObject calculations and debug purposes.

  • Time: Handles the recording of the passed time between two points for the calculations of the movements and also provides a wait function.

  • NearlyEqual: Function to compare double equal with delta similar to ints.

Let me know what can be improved. I want to add more features but first i want to know how is the state of the current code.

Some questions which maybe help to review

  • Is it well organized?
  • Would you have organized it different?
  • How about the math of the collisions. Can it be improved?
  • Are there any bad practices?
  • Is stuff done to complicated
  • Can you understand the code or is it hard to read through it?

Let me know. If you not have the time to read all pick a part and let me know what you think of it.

The Code:

main.cpp

#include "Game.h"

#include <iostream>

int main()
try {
    while (arkanoid::runGame());
}
catch (const std::out_of_range& e) {
    std::cerr << e.what() << "\n";
    std::cin.get();
}
catch (const std::invalid_argument& e) {
    std::cerr << e.what() << "\n";
    std::cin.get();
}
catch (...) {
    std::cerr << "unknown error " << "\n";
    std::cin.get();
}

Game.h

#ifndef GAME_H
#define GAME_H

namespace arkanoid {

    bool runGame();

    bool userInputGameOver(long long score);
}
#endif

Game.cpp

#include "Game.h"

#include "Console.h"
#include "Level.h"

#include <iostream>

namespace arkanoid {

    bool runGame()
    {
        console::resize(500, 453);

        static constexpr auto startScore = 0;
        static constexpr auto startLives = 3;
        long long score = startScore;
        int lives = startLives;
        int level{ 0 };
        for (;;) {

            Level level1{ score, lives,  ++level, makeBricksLevel1() };
            level1.run();
            score = level1.score();
            lives = level1.lives();

            if (level1.isGameOver()) {
                return userInputGameOver(score);
            }

            Level level2{ level1.score(), level1.lives(),  ++level, makeBricksLevel2() };
            level2.run();
            score = level1.score();
            lives = level1.lives();

            if (level2.isGameOver()) {
                return userInputGameOver(score);
            }
        }
    }

    bool userInputGameOver(long long score)
    {
        console::clearScreen();
        std::cout << "Game Over!!!\n"
        << "SCORE:" << score<<'\n'
        << "Press R To Try again or ESC to Quit\n";
        for (;;) {
            if (console::rKeyHoldDown()) {
                return true;
            }
            if (console::escKeyHoldDown()) {
                return false;
            }
        }
        console::clearScreen();
    }
}  // namespace arkanoid

Level.h

#ifndef LEVEL_H
#define LEVEL_H

#include "Ball.h"
#include "Brick.h"
#include "Platform.h"
#include "Wall.h"

#include <vector>

namespace arkanoid {

    class Wall;
    class Brick;
    class Platform;

    class Level
    {
    public:
        Level(long long score, int lives, int level, std::vector<Brick> bricks);
        ~Level() = default;

        void run();

        long long score() const;
        int lives() const;

        bool isGameOver() const;
    private:
        void handleBallMovementsAndCollisions(double elapsedTimeInMS);
        void printToConsole();

        Platform makePlatform();
        Ball makeBall();

        static constexpr int boardWidth{ 26 };
        static constexpr int boardHeight{ 18 };

        static constexpr auto plattformWidth{ 5.0 };
        static constexpr auto wallThickness{ 1.0 };

        static constexpr auto pointsPerBrick{ 100 };

        long long mScore;
        int mLives;
        const int mLevel;

        std::vector<Brick> mBricks;

        const Wall mLeftWall;
        const Wall mRightWall;
        const Wall mTopWall;

        Platform mPlatform;
        Ball mBall;

        bool mIsGameOver;
    };

    void movePlatformBetweenWalls(
        Platform& platform, const Wall& leftWall, const Wall& rightWall, 
        double elapsedTimeInMS);

    Point calculatePlattformInitPosition(
        double plattformSize, double boardWidth, double boardHeight);

    Point calculateBallInitPosition(double boardWidth, double boardHeight);

    bool allBricksAreDestroyed(const std::vector<Brick>& bricks);

    std::vector<Brick> makeBricksLevel1();

    std::vector<Brick> makeBricksLevel2();
}  // namespace arkanoid
#endif

Level.cpp

#include "Level.h"

#include "Console.h"
#include "Grid.h"
#include "Time.h"

#include <algorithm>
#include <iomanip>
#include <iostream>

namespace arkanoid {

    Level::Level(long long score, int lives, int level, 
        const std::vector<Brick> bricks)
        : mScore{score}, mLives{lives}, mLevel{level},
        mBricks{bricks},
        mLeftWall{
            Point{0,0},
            wallThickness, boardHeight 
        },
        mRightWall{
            Point{boardWidth - wallThickness, 0},
            wallThickness, boardHeight
        },
        mTopWall{
            Point{wallThickness, 0},
            boardWidth - 2.0 * wallThickness, wallThickness
        },
        mPlatform{makePlatform()},
        mBall{makeBall()},
        mIsGameOver{false}
    {
    }

    void Level::run()
    {
        std::chrono::time_point<std::chrono::high_resolution_clock> t1;
        std::chrono::time_point<std::chrono::high_resolution_clock> t2;
        double elapsedTimeInMS{ 0.0 };

        for (;;) {
            t1 = getCurrentTime();

            if (!mBall.isActive() && console::spaceKeyHoldDown()) {
                mBall.activate();
            }

            movePlatformBetweenWalls(
                mPlatform, mLeftWall, mRightWall, elapsedTimeInMS);

            if (mBall.isActive()) {
                handleBallMovementsAndCollisions(elapsedTimeInMS);
            }

            if (allBricksAreDestroyed(mBricks)) {
                return;
            }

            if (mIsGameOver) {
                return;
            }

            printToConsole();

            t2 = getCurrentTime();
            elapsedTimeInMS = arkanoid::getElapsedTime(t1, t2);
        }
    }

    long long Level::score() const
    {
        return mScore;
    }

    int Level::lives() const
    {
        return mLives;
    }

    bool Level::isGameOver() const
    {
        return mIsGameOver;
    }

    void Level::handleBallMovementsAndCollisions(double elapsedTimeInMS)
    {
        if (mBall.reflectIfHit(mRightWall)) {
            ;
        }
        else if (mBall.reflectIfHit(mLeftWall)) {
            ;
        }
        else if (mBall.reflectIfHit(mTopWall)) {
            ;
        }
        else if (mBall.reflectIfHit(mPlatform)) {
            ;
        }
        else if (mBall.bottomRight().y >= boardHeight) {

            wait(std::chrono::milliseconds{ 1000 });

            --mLives;
            if (mLives == 0) {
                mIsGameOver = true;
            }
            mBall.deactivate();
            mPlatform.setToInitPosition();
            return;
        }

        for (auto& brick : mBricks) {
            if (brick.isDestroyed()) {
                continue;
            }
            if (mBall.reflectIfHit(brick)) {
                brick.decreaseHitpoints();

                if (brick.isDestroyed()) {
                    mScore +=
                        pointsPerBrick * brick.startHitpoints();
                }
                break;
            }
        }

        mBall.move(elapsedTimeInMS);
    }

    void Level::printToConsole()
    {
        console::putCursorToStartOfConsole();

        Grid grid(
            static_cast<int>(boardWidth),
            static_cast<int>(boardHeight)
        );

        grid.add(mBall);
        grid.add(mPlatform);
        grid.add(mLeftWall);
        grid.add(mRightWall);
        grid.add(mTopWall);

        for (const auto& brick : mBricks) {
            grid.add(brick);
        }

        console::putCursorToStartOfConsole();
        std::cout << "Score:" << std::setw(15) << mScore << "     "
            << "Lives:" << std::setw(4) << mLives << "     "
            << "Level:" << std::setw(4) << mLevel << '\n'
            << grid << '\n';

        //std::cout << std::setw(5) << mBall.topLeft().x << "   "
        //  << std::setw(5) << mBall.topLeft().y << "     "
        //  << std::setw(10) << radientToDegrees(mBall.angle())
        //  << std::setw(10) << radientToDegrees(mBall.quadrantAngle())
        //  << std::setw(3) << static_cast<int>(mBall.quadrant()) + 1 << '\n';

        //std::cout << std::setw(5) << mPlatform.topLeft().x << "   "
        //  << std::setw(5) << mPlatform.topLeft().y << "     "
        //  << std::setw(10) << radientToDegrees(mPlatform.angle())
        //  << std::setw(10) << radientToDegrees(mPlatform.quadrantAngle())
        //  << std::setw(3) << static_cast<int>(mPlatform.quadrant()) + 1 << '\n';
    }

    Platform Level::makePlatform()
    {
        return Platform{
            calculatePlattformInitPosition(
            plattformWidth, boardWidth, boardHeight),
            static_cast<double>(boardWidth),
            plattformWidth,
        };
    }

    Ball Level::makeBall()
    {
        return Ball{ calculateBallInitPosition(boardWidth, boardHeight),
            static_cast<double>(boardWidth),
            static_cast<double>(boardHeight)
        };
    }

    void movePlatformBetweenWalls(
        Platform& platform, const Wall& leftWall, const Wall& rightWall, 
        double elapsedTimeInMS)
    {
        if (console::rightKeyHoldDown()) {

            platform.setAngle(0.0);
            platform.move(elapsedTimeInMS);
        }
        else if (console::leftKeyHoldDown()) {

            platform.setAngle(deg_180);
            platform.move(elapsedTimeInMS);
        }

        if (platform.reflectIfHit(rightWall)) {
            ;
        }
        else if (platform.reflectIfHit(leftWall)) {
            ;
        }
    }

    Point calculatePlattformInitPosition(
        double plattformSize, double boardWidth, double boardHeight)
    {
        return Point{
            boardWidth / 2.0 - plattformSize / 2.0,
            boardHeight - 3.0
        };
    }

    Point calculateBallInitPosition(double boardWidth, double boardHeight)
    {
        return Point{
            boardWidth / 2.0 - 1,
            boardHeight - 4.0
        };
    }

    bool allBricksAreDestroyed(const std::vector<Brick>& bricks)
    {
        return std::find_if(bricks.begin(), bricks.end(),
            [](const Brick& b)
            {
                return !b.isDestroyed();
            }) == bricks.end();
    }

    std::vector<Brick> makeBricksLevel1()
    {
        constexpr auto brickLength = 3.0;
        constexpr auto brickHeight = 1.0;

        return std::vector<Brick>
        {
                Brick{ Point{4,2},brickLength,brickHeight,1 },
                Brick{ Point{7,2},brickLength,brickHeight,1 },
                Brick{ Point{10,2},brickLength,brickHeight,1 },
                Brick{ Point{13,2},brickLength,brickHeight,1 },
                Brick{ Point{16,2},brickLength,brickHeight,1 },
                Brick{ Point{19,2},brickLength,brickHeight,1 },

                Brick{ Point{4,3},brickLength,brickHeight,1 },
                Brick{ Point{19,3},brickLength,brickHeight,1 },

                Brick{ Point{4,4},brickLength,brickHeight,1 },
                Brick{ Point{10,4},brickLength,brickHeight,2 },
                Brick{ Point{13,4},brickLength,brickHeight,2 },
                Brick{ Point{19,4},brickLength,brickHeight,1 },

                Brick{ Point{4,5},brickLength,brickHeight,1 },
                Brick{ Point{19,5},brickLength,brickHeight,1 },

                Brick{ Point{4,6},brickLength,brickHeight,1 },
                Brick{ Point{7,6},brickLength,brickHeight,1 },
                Brick{ Point{10,6},brickLength,brickHeight,1 },
                Brick{ Point{13,6},brickLength,brickHeight,1 },
                Brick{ Point{16,6},brickLength,brickHeight,1 },
                Brick{ Point{19,6},brickLength,brickHeight,1 },
        };
    }

    std::vector<Brick> makeBricksLevel2()
    {
        constexpr auto brickLength = 3.0;
        constexpr auto brickHeight = 1.0;

        return std::vector<Brick>
        {
            // draw a C
            Brick{ Point{4,2},brickLength,brickHeight,1 },
            Brick{ Point{7,2},brickLength,brickHeight,1 },
            Brick{ Point{10,2},brickLength,brickHeight,1 },
            Brick{ Point{13,2},brickLength,brickHeight,1 },
            Brick{ Point{4,3},brickLength,brickHeight,1 },
            Brick{ Point{4,4},brickLength,brickHeight,1 },
            Brick{ Point{4,5},brickLength,brickHeight,1 },
            Brick{ Point{4,6},brickLength,brickHeight,1 },
            Brick{ Point{4,7},brickLength,brickHeight,1 },
            Brick{ Point{4,8},brickLength,brickHeight,1 },
            Brick{ Point{4,9},brickLength,brickHeight,1 },
            Brick{ Point{4,10},brickLength,brickHeight,1 },
            Brick{ Point{7,10},brickLength,brickHeight,1 },
            Brick{ Point{10,10},brickLength,brickHeight,1 },
            Brick{ Point{13,10},brickLength,brickHeight,1 },

            // draw first +
            Brick{ Point{13,6},brickHeight,brickHeight,2 },
            Brick{ Point{14,6},brickHeight,brickHeight,3 },
            Brick{ Point{15,4},brickHeight,brickHeight,2 },
            Brick{ Point{15,5},brickHeight,brickHeight,3 },
            Brick{ Point{15,6},brickHeight,brickHeight,4 },
            Brick{ Point{15,7},brickHeight,brickHeight,3 },
            Brick{ Point{15,8},brickHeight,brickHeight,2 },
            Brick{ Point{16,6},brickHeight,brickHeight,3 },
            Brick{ Point{17,6},brickHeight,brickHeight,2 },

            // draw second +
            Brick{ Point{19,6},brickHeight,brickHeight,5 },
            Brick{ Point{20,6},brickHeight,brickHeight,6 },
            Brick{ Point{21,4},brickHeight,brickHeight,5 },
            Brick{ Point{21,5},brickHeight,brickHeight,3 },
            Brick{ Point{21,6},brickHeight,brickHeight,7 },
            Brick{ Point{21,7},brickHeight,brickHeight,6 },
            Brick{ Point{21,8},brickHeight,brickHeight,5 },
            Brick{ Point{22,6},brickHeight,brickHeight,6 },
            Brick{ Point{23,6},brickHeight,brickHeight,5 },
        };
    }
}  // namespace arkanoid

GameObject.h

#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H

#include "Point.h"

namespace arkanoid {

    enum class Quadrant {
        I,
        II,
        III,
        IV
    };

    class GameObject
    {
    public:
        GameObject(
            Point topLeft, double maxPositioX, double maxPositionY,
            double width, double height,
            double velocity, double angle);

        virtual ~GameObject() = 0;

        Point topLeft() const;
        void setTopLeft(Point topLeft);
        Point bottomRight() const;

        double velocity() const;
        void setVelocity(double velocity);

        double angle() const;
        void setAngle(double angle);

        double quadrantAngle() const;
        Quadrant quadrant() const;

        double width() const;
        double height() const;

        virtual void move(double elapsedTimeInMS);

        bool reflectIfHit(const GameObject& obj);

    private:
        void reflectFromQuadrantOneIfHit(const GameObject& obj);
        void reflectFromQuadrantTwoIfHit(const GameObject& obj);
        void reflectFromQuadrantThreeIfHit(const GameObject& obj);
        void reflectFromQuadrantFourIfHit(const GameObject& obj);

        void reflectToQuadrantFourIfIntersectsWithX(const GameObject& obj);
        void reflectToQuadrantTwoIfIntersectsWithY(const GameObject& obj);

        void reflectToQuadrantThreeIfIntersectsWithX(const GameObject& obj);
        void reflectToQuadrantOneIfIntersectsWithY(const GameObject& obj);

        void reflectToQuadrantTwoIfIntersectsWithX(const GameObject& obj);
        void reflectToQuadrantFourIfIntersectsWithY(const GameObject& obj);

        void reflectToQuadrantOneIfIntersectsWithX(const GameObject& obj);
        void reflectToQuadrantThreeIfIntersectsWithY(const GameObject& obj);

        void toQuadrantOne();
        void toQuadrantTwo();
        void toQuadrantThree();
        void toQuadrantFour();

        Point mTopLeft;
        const double mMaxPositionX;
        const double mMaxPositionY;

        const double mWidth;
        const double mHeight;

        double mVelocity;
        Quadrant mQuadrant;
        double mQuadrantAngle;
    };

    bool isInQuadrantOne(double angle);
    bool isInQuadrantTwo(double angle);
    bool isInQuadrantThree(double angle);
    bool isInQuadrantFour(double angle);

    bool interectsWithRightX(const GameObject& a, const GameObject& b);
    void putBeforeIntersectsWithRightX(GameObject& a, const GameObject& b);

    bool interectsWithLeftX(const GameObject& a, const GameObject& b);
    void putBeforeIntersectsWithLeftX(GameObject& a, const GameObject& b);

    bool interectsWithBottomY(const GameObject& a, const GameObject& b);
    void putBeforeIntersectsWithBottomY(GameObject& a, const GameObject& b);

    bool interectsWithTopY(const GameObject& a, const GameObject& b);
    void putBeforeIntersectsWithTopY(GameObject& a, const GameObject& b);

    bool isInsideWithY(const GameObject& a, const GameObject& b);
    bool isInsideWithX(const GameObject& a, const GameObject& b);

    bool intersectsFromRigthWithX(const GameObject& a, const GameObject& b);
    bool intersectsFromLeftWithX(const GameObject& a, const GameObject& b);
    bool intersectsFromTopWithY(const GameObject& a, const GameObject& b);
    bool intersectsFromBottomWithY(const GameObject& a, const GameObject& b);

    Point calcDelta(double quadrantAngle, Quadrant quadrant, double sideC);

    double calcTraveldWay(double deltaTimeMS, double velocityInS);

    double calcAlphaIfOver360(double alpha);

    Quadrant calcQuadrant(double alpha);

    double angleToQuadrantAngle(double angle, Quadrant quadrant);

    double qudrantAngleToAngle(double quadrantAngle, Quadrant quadrant);

    double increaseAngle(double quadrantAngle);

    double decreaseAngle(double quadrantAngle);

    double mirror(double quadrantAngle);
}  // namespace arkanoid
#endif

GameObject.cpp

#include "GameObject.h"

#include "MathHelper.h"
#include "NearlyEqual.h"

#include <algorithm>
#include <stdexcept>

#include <tuple>

namespace arkanoid {

    GameObject::GameObject(Point topLeft,
        double maxPositioX, double maxPositionY,
        double width, double height,
        double velocity, double angle)
        :mTopLeft{ topLeft },
        mMaxPositionX(maxPositioX),
        mMaxPositionY(maxPositionY),
        mWidth{ width },
        mHeight{ height },
        mVelocity{ velocity },
        mQuadrant{ calcQuadrant(angle) },
        mQuadrantAngle{ angleToQuadrantAngle(angle, mQuadrant) }
    {
    }

    GameObject::~GameObject() = default;

    Point GameObject::topLeft() const
    {
        return mTopLeft;
    }

    void GameObject::setTopLeft(Point topLeft)
    {
        mTopLeft = topLeft;
    }

    Point GameObject::bottomRight() const
    {
        return Point{ mTopLeft.x + mWidth , mTopLeft.y + mHeight };
    }

    double GameObject::velocity() const
    {
        return mVelocity;
    }

    void GameObject::setVelocity(double velocity)
    {
        mVelocity = velocity;
    }

    double GameObject::angle() const
    {
        return qudrantAngleToAngle(mQuadrantAngle, mQuadrant);
    }

    void GameObject::setAngle(double angle)
    {
        angle = calcAlphaIfOver360(angle);
        mQuadrant = calcQuadrant(angle);
        mQuadrantAngle = angleToQuadrantAngle(angle, mQuadrant);
    }

    double GameObject::quadrantAngle() const
    {
        return mQuadrantAngle;
    }

    Quadrant GameObject::quadrant() const
    {
        return mQuadrant;
    }

    double GameObject::width() const
    {
        return mWidth;
    }

    double GameObject::height() const
    {
        return mHeight;
    }

    void GameObject::move(double elapsedTimeInMS)
    {
        auto distance = calcTraveldWay(elapsedTimeInMS, mVelocity);
        auto traveldWay = calcDelta(mQuadrantAngle, mQuadrant, distance);

        mTopLeft.x += traveldWay.x;
        mTopLeft.y += traveldWay.y;

        auto maxX = mTopLeft.x + mWidth;
        mTopLeft.x = std::clamp(maxX, mWidth, mMaxPositionX) - mWidth;

        auto maxY = mTopLeft.y + mHeight;
        mTopLeft.y = std::clamp(maxY, mHeight, mMaxPositionY) - mHeight;
    }

    bool GameObject::reflectIfHit(const GameObject& obj)
    {
        auto oldQuadrant = mQuadrant;

        switch (mQuadrant)
        {
        case Quadrant::I:
            reflectFromQuadrantOneIfHit(obj);
            break;
        case Quadrant::II:
            reflectFromQuadrantTwoIfHit(obj);
            break;
        case Quadrant::III:
            reflectFromQuadrantThreeIfHit(obj);
            break;
        case Quadrant::IV:
            reflectFromQuadrantFourIfHit(obj);
            break;
        }

        return mQuadrant != oldQuadrant;
    }

    void GameObject::reflectFromQuadrantOneIfHit(const GameObject& obj)
    {
        if (interectsWithBottomY(*this, obj)) {
            reflectToQuadrantFourIfIntersectsWithX(obj);
        }
        else if (interectsWithRightX(*this, obj)) {
            reflectToQuadrantTwoIfIntersectsWithY(obj);
        }
    }

    void GameObject::reflectFromQuadrantTwoIfHit(const GameObject& obj)
    {
        if (interectsWithLeftX(*this, obj)) {
            reflectToQuadrantOneIfIntersectsWithY(obj);
        }
        else if (interectsWithBottomY(*this, obj)) {
            reflectToQuadrantThreeIfIntersectsWithX(obj);
        }
    }

    void GameObject::reflectFromQuadrantThreeIfHit(const GameObject& obj)
    {
        if (interectsWithLeftX(*this, obj)) {
            reflectToQuadrantFourIfIntersectsWithY(obj);
        }
        else if (interectsWithTopY(*this, obj)) {
            reflectToQuadrantTwoIfIntersectsWithX(obj);
        }
    }

    void GameObject::reflectFromQuadrantFourIfHit(const GameObject& obj)
    {
        if (interectsWithRightX(*this, obj)) {
            reflectToQuadrantThreeIfIntersectsWithY(obj);
        }
        else if (interectsWithTopY(*this, obj)) {
            reflectToQuadrantOneIfIntersectsWithX(obj);
        }
    }

    void GameObject::reflectToQuadrantFourIfIntersectsWithX(
        const GameObject& obj)
    {
        if (isInsideWithX(*this, obj)) {
            toQuadrantFour();
        }
        else if (intersectsFromRigthWithX(*this, obj) ||
            intersectsFromLeftWithX(*this, obj)) {
            toQuadrantFour();
            mQuadrantAngle = increaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithBottomY(*this, obj);
    }

    void GameObject::reflectToQuadrantTwoIfIntersectsWithY(
        const GameObject& obj)
    {
        if (isInsideWithY(*this, obj)) {
            toQuadrantTwo();
        }
        else if (intersectsFromTopWithY(*this, obj) ||
            intersectsFromBottomWithY(*this, obj)) {
            toQuadrantTwo();
            mQuadrantAngle = increaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithRightX(*this, obj);
    }

    void GameObject::reflectToQuadrantThreeIfIntersectsWithX(
        const GameObject& obj)
    {
        if (isInsideWithX(*this, obj)) {
            toQuadrantThree();
        }
        else if (intersectsFromRigthWithX(*this, obj) ||
            intersectsFromLeftWithX(*this, obj)) {
            toQuadrantThree();
            mQuadrantAngle = decreaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithBottomY(*this, obj);
    }

    void GameObject::reflectToQuadrantOneIfIntersectsWithY(
        const GameObject& obj)
    {
        if (isInsideWithY(*this, obj)) {
            toQuadrantOne();
        }
        else if (intersectsFromTopWithY(*this, obj) ||
            intersectsFromBottomWithY(*this, obj)) {
            toQuadrantOne();
            mQuadrantAngle = decreaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithLeftX(*this, obj);
    }

    void GameObject::reflectToQuadrantTwoIfIntersectsWithX(
        const GameObject& obj)
    {
        if (isInsideWithX(*this, obj)) {
            toQuadrantTwo();
        }
        else if (intersectsFromRigthWithX(*this, obj) ||
            intersectsFromLeftWithX(*this, obj)) {
            toQuadrantTwo();
            mQuadrantAngle = increaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithTopY(*this, obj);
    }

    void GameObject::reflectToQuadrantFourIfIntersectsWithY(
        const GameObject& obj)
    {
        if (isInsideWithY(*this, obj)) {
            toQuadrantFour();
        }
        else if (intersectsFromTopWithY(*this, obj) ||
            intersectsFromBottomWithY(*this, obj)) {
            toQuadrantFour();
            mQuadrantAngle = increaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithLeftX(*this, obj);
    }

    void GameObject::reflectToQuadrantOneIfIntersectsWithX(
        const GameObject& obj)
    {
        if (isInsideWithX(*this, obj)) {
            toQuadrantOne();
        }
        else if (intersectsFromRigthWithX(*this, obj) ||
            intersectsFromLeftWithX(*this, obj)) {
            toQuadrantOne();
            mQuadrantAngle = decreaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithTopY(*this, obj);
    }

    void GameObject::reflectToQuadrantThreeIfIntersectsWithY(
        const GameObject& obj)
    {
        if (isInsideWithY(*this, obj)) {
            toQuadrantThree();
        }
        else if (intersectsFromTopWithY(*this, obj) ||
            intersectsFromBottomWithY(*this, obj)) {
            toQuadrantThree();
            mQuadrantAngle = decreaseAngle(mQuadrantAngle);
        }
        else {
            return;
        }
        putBeforeIntersectsWithRightX(*this, obj);
    }

    void GameObject::toQuadrantOne()
    {
        mQuadrant = Quadrant::I;
        mQuadrantAngle = mirror(mQuadrantAngle);
    }

    void GameObject::toQuadrantTwo()
    {
        mQuadrant = Quadrant::II;
        mQuadrantAngle = mirror(mQuadrantAngle);
    }

    void GameObject::toQuadrantThree()
    {
        mQuadrant = Quadrant::III;
        mQuadrantAngle = mirror(mQuadrantAngle);
    }

    void GameObject::toQuadrantFour()
    {
        mQuadrant = Quadrant::IV;
        mQuadrantAngle = mirror(mQuadrantAngle);
    }

    bool isInQuadrantOne(double angle)
    {
        return angle >= deg_0 && angle <= deg_90;
    }

    bool isInQuadrantTwo(double angle)
    {
        return angle > deg_90 && angle <= deg_180;
    }

    bool isInQuadrantThree(double angle)
    {
        return angle > deg_180 && angle <= deg_270;
    }

    bool isInQuadrantFour(double angle)
    {
        return angle > deg_270 && angle <= deg_360;
    }

    bool interectsWithRightX(const GameObject& a, const GameObject& b)
    {
        return a.bottomRight().x >= b.topLeft().x &&
            a.topLeft().x < b.topLeft().x;
    }

    void putBeforeIntersectsWithRightX(GameObject& a, const GameObject& b)
    {
        Point p = a.topLeft();
        p.x = b.topLeft().x - a.width();
        a.setTopLeft(p);
    }

    bool interectsWithLeftX(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().x <= b.bottomRight().x &&
            a.bottomRight().x > b.bottomRight().x;
    }

    void putBeforeIntersectsWithLeftX(GameObject& a, const GameObject& b)
    {
        Point p = a.topLeft();
        p.x = b.bottomRight().x;
        a.setTopLeft(p);
    }

    bool interectsWithBottomY(const GameObject& a, const GameObject& b)
    {
        return a.bottomRight().y >= b.topLeft().y &&
            a.topLeft().y < b.topLeft().y;
    }

    void putBeforeIntersectsWithBottomY(GameObject& a, const GameObject& b)
    {
        Point p = a.topLeft();
        p.y = b.topLeft().y - a.height();
        a.setTopLeft(p);
    }

    bool interectsWithTopY(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().y <= b.bottomRight().y &&
            a.bottomRight().y > b.bottomRight().y;
    }

    void putBeforeIntersectsWithTopY(GameObject& a, const GameObject& b)
    {
        Point p = a.topLeft();
        p.y = b.bottomRight().y;
        a.setTopLeft(p);
    }

    bool isInsideWithY(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().y >= b.topLeft().y &&
            a.bottomRight().y <= b.bottomRight().y;
    }

    bool isInsideWithX(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().x >= b.topLeft().x &&
            a.bottomRight().x <= b.bottomRight().x;
    }

    bool intersectsFromRigthWithX(const GameObject& a, const GameObject& b)
    {
        return a.bottomRight().x >= b.topLeft().x &&
            a.bottomRight().x <= b.bottomRight().x &&
            a.topLeft().x < b.topLeft().x;
    }

    bool intersectsFromLeftWithX(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().x >= b.topLeft().x &&
            a.topLeft().x <= b.bottomRight().x &&
            a.bottomRight().x > b.bottomRight().x;
    }

    bool intersectsFromTopWithY(const GameObject& a, const GameObject& b)
    {
        return a.bottomRight().y >= b.topLeft().y &&
            a.bottomRight().y <= b.bottomRight().y &&
            a.topLeft().y < b.topLeft().y;
    }

    bool intersectsFromBottomWithY(const GameObject& a, const GameObject& b)
    {
        return a.topLeft().y >= b.topLeft().y &&
            a.topLeft().y <= b.bottomRight().y &&
            a.bottomRight().y > b.bottomRight().y;
    }

    Point calcDelta(double quadrantAngle, Quadrant quadrant, double sideC)
    {
        if (nearlyEqual(sideC, 0.0)) {
            return Point{ 0,0 };
        }

        auto sideA = sin(quadrantAngle) * sideC;
        auto sideB = cos(quadrantAngle) * sideC;

        Point ret;
        switch (quadrant)
        {
        case Quadrant::I:
            ret.x = sideB;
            ret.y = sideA;
            break;
        case Quadrant::II:
            ret.x = -sideA;
            ret.y = sideB;
            break;
        case Quadrant::III:
            ret.x = -sideB;
            ret.y = -sideA;
            break;
        case Quadrant::IV:
            ret.x = sideA;
            ret.y = -sideB;
            break;
        }
        return ret;
    }

    double calcTraveldWay(double deltaTimeMS, double velocityInS)
    {
        return deltaTimeMS / 1000.0 * velocityInS;
    }

    double calcAlphaIfOver360(double alpha)
    {
        if (alpha > deg_360) {
            alpha -= deg_360;
        }
        return alpha;
    }

    Quadrant calcQuadrant(double alpha)
    {
        if (isInQuadrantOne(alpha)) {
            return Quadrant::I;
        }
        if (isInQuadrantTwo(alpha)) {
            return Quadrant::II;
        }
        if (isInQuadrantThree(alpha)) {
            return Quadrant::III;
        }
        return Quadrant::IV;
    }

    double angleToQuadrantAngle(double angle, Quadrant quadrant)
    {
        return angle - deg_90 * static_cast<int>(quadrant);
    }

    double qudrantAngleToAngle(double quadrantAngle, Quadrant quadrant)
    {
        return quadrantAngle + deg_90 * static_cast<int>(quadrant);
    }

    double increaseAngle(double quadrantAngle)
    {
        quadrantAngle *= 1.03;
        return std::clamp(quadrantAngle, 0.0, deg_60);
    }

    double decreaseAngle(double quadrantAngle)
    {
        return quadrantAngle * 0.97;
    }

    double mirror(double quadrantAngle)
    {
        return deg_90 - quadrantAngle;
    }

}  // namespace arkanoid

Ball.h

#ifndef BALL_H
#define BALL_H

#include "GameObject.h"
#include "MathHelper.h"

namespace arkanoid {

    class Ball : public GameObject
    {
    public:
        Ball(Point topLeft, double maxPositionX, double maxPositionY);
        ~Ball() override = default;

        bool isActive();
        void activate();
        void deactivate();

        void move(double elapsedTimeInMS) override;

    private:
        const Point mInitPosition;
        bool mIsActive;

        static constexpr auto startAngle{ deg_135 };
        static constexpr auto startVelocity{ 9.0 };
        static constexpr auto gravityVelocity{ 2.5 };

        static constexpr auto width{ 1 };
        static constexpr auto height{ 1 };
    };
}  // namespace arkanoid
#endif

Ball.cpp

#include "Ball.h"

#include <algorithm>

namespace arkanoid {

    Ball::Ball(Point topLeft, double maxPositionX, double maxPositionY)
        :GameObject(topLeft, 
            maxPositionX, maxPositionY, width, height, 0.0, 0.0),
        mInitPosition{ topLeft },
        mIsActive{false}
    {
    }

    bool Ball::isActive()
    {
        return mIsActive;
    }

    void Ball::deactivate()
    {
        mIsActive = false;
        setAngle(0.0);
        setVelocity(0.0);
        setTopLeft(mInitPosition);
    }

    void Ball::activate()
    {
        mIsActive = true;
        setAngle(startAngle);
        setVelocity(startVelocity);
    }

    void Ball::move(double elapsedTimeInMS) 
    {
        auto distanceY = calcTraveldWay(elapsedTimeInMS, gravityVelocity);

        auto p = topLeft();
        p.y += distanceY;
        setTopLeft(p);

        GameObject::move(elapsedTimeInMS);
    }

}  // namespace arkanoid

Brick.h

#ifndef BRICK_H
#define BRICK_H

#include "GameObject.h"

namespace arkanoid {

    class Brick : public GameObject
    {
    public:
        Brick(Point topLeft, double width, double height, std::size_t hitpoints);
        ~Brick() override = default;

        double velocity() const = delete;
        void setVelocity(double velocity) = delete;

        double angle() const = delete;
        void setAngle(double angle) = delete;

        void move(const Point& delta) = delete;

        std::size_t startHitpoints() const;
        std::size_t hitpoints() const;

        void decreaseHitpoints();
        bool isDestroyed() const;
    private:
        const std::size_t mStartHitpoints;
        std::size_t mHitpoints;
    };
}  // namespace arkanoid
#endif

Brick.cpp

#include "Brick.h"

namespace arkanoid {

    Brick::Brick(Point topLeft, 
        double width, double height, std::size_t hitpoints)
        :GameObject(topLeft, 
            topLeft.x + width, topLeft.y + height, 
            width, height, 0, 0),
        mHitpoints(hitpoints),
        mStartHitpoints(hitpoints)
    {
    }

    std::size_t Brick::startHitpoints() const
    {
        return mStartHitpoints;
    }

    std::size_t Brick::hitpoints() const
    {
        return mHitpoints;
    }

    void Brick::decreaseHitpoints()
    {
        --mHitpoints;
    }

    bool Brick::isDestroyed() const
    {
        return mHitpoints == 0;
    }
}  // namespace arkanoid

Platform.h

#ifndef PLATFORM_H
#define PLATFORM_H

#include "GameObject.h"

namespace arkanoid {

    class Platform : public GameObject
    {
    public:
        Platform(Point topLeft, double maxPositioX, double length);
        ~Platform() override = default;

        void setToInitPosition();
    private:
        const Point mInitPosition;

        static constexpr auto mStartAngle{ 0 };
        static constexpr auto mStartVelocity{ 10.0 }; 
    };
}  // namespace arkanoid
#endif

Platform.cpp

#include "Platform.h"

namespace arkanoid {

    Platform::Platform(
        Point topLeft, double maxPositioX, double length)
        : GameObject(
            topLeft, maxPositioX, topLeft.y + 1, 
            length, 1, mStartVelocity, mStartAngle),
        mInitPosition{ topLeft }
    {
    }

    void Platform::setToInitPosition()
    {
        setTopLeft(mInitPosition);
    }
}  // namespace arkanoid

Wall.h

#ifndef WALL_H
#define WALL_H

#include "GameObject.h"

namespace arkanoid {

    class Wall : public GameObject
    {
    public:
        Wall(Point topLeft, double width, double height);
        ~Wall() override = default;

        double velocity() const = delete;
        void setVelocity(double velocity) = delete;

        double angle() const = delete;
        void setAngle(double angle) = delete;

        void move(const Point& delta) = delete;
    };
}  // namespace arkanoid
#endif

Wall.cpp

#include "Wall.h"

namespace arkanoid {

    Wall::Wall(Point topLeft, double width, double height)
        :GameObject(topLeft, 
            topLeft.x + width, topLeft.y + height, 
            width, height, 0, 0)
    {
    }

}

Point.h

#ifndef POINT_H
#define POINT_H

#include <cstddef>

namespace arkanoid {

    struct Point {
        double x{ 0.0 };
        double y{ 0.0 };
    };

}// namespace arkanoid
#endif

Time.h

#ifndef TIME_H
#define TIME_H

#include <chrono>

namespace arkanoid {

    std::chrono::time_point<std::chrono::high_resolution_clock>
        getCurrentTime();

    double getElapsedTime(
        const std::chrono::time_point<
        std::chrono::high_resolution_clock>& first,
        const std::chrono::time_point<
        std::chrono::high_resolution_clock>& last);

    void wait(const std::chrono::milliseconds& milliseconds);
}  // namespace arkanoid
#endif

Time.cpp

#include "Time.h"

#include <thread>

namespace arkanoid {

    std::chrono::time_point<std::chrono::high_resolution_clock>
        getCurrentTime()
    {
        return std::chrono::high_resolution_clock::now();
    }

    double getElapsedTime(
        const std::chrono::time_point<
        std::chrono::high_resolution_clock>& first,
        const std::chrono::time_point<
        std::chrono::high_resolution_clock>& last)
    {
        return static_cast<int>(
            std::chrono::duration<double, std::milli>(last - first).count());
    }

    void wait(const std::chrono::milliseconds& milliseconds)
    {
        std::this_thread::sleep_for(milliseconds);
    }
}

Grid.h

#ifndef GRID_H
#define GRID_H

#include <iosfwd>
#include <vector>

namespace arkanoid {

    class Ball;
    class Brick;
    class Platform;
    class Wall;
    class GameObject;

    enum class Field {
        ball,
        brick1Hits,
        brick2Hits,
        brick3Hits,
        brick4Hits,
        brick5Hits,
        brick6Hits,
        brick7Hits,
        brick8Hits,
        brick9Hits,
        empty,
        plattform,
        wall
    };

    class Grid
    {
    public:
        Grid(std::size_t width, std::size_t height);
        ~Grid() = default;

        void add(const Ball& ball);
        void add(const Brick& brick);
        void add(const Platform& plattform);
        void add(const Wall& wall);
    private:
        void add(const GameObject& gameObject, const Field& field);

        std::vector<std::vector<Field>> mFields;
        friend std::ostream& operator<<(std::ostream& os, const Grid& obj);
    };

    std::vector<std::vector<Field>> init(
        std::size_t width, std::size_t height);

    std::ostream& operator<<(std::ostream& os, const Grid& obj);
}  // namespace arkanoid
#endif

Grid.cpp

#include "Grid.h"

#include "Ball.h"
#include "Brick.h"
#include "Platform.h"
#include "Wall.h"

#include <algorithm>
#include <cmath>
#include <iostream>

namespace arkanoid {

    Grid::Grid(std::size_t width, std::size_t height)
        : mFields{ init(width, height) }
    {
    }

    void Grid::add(const Ball& ball)
    {
        add(ball, Field::ball);
    }

    void Grid::add(const Brick& brick)
    {
        if (brick.isDestroyed()) {
            return;
        }

        auto brickType = static_cast<int>(Field::brick1Hits);
        brickType = brickType + brick.hitpoints() - 1;
        add(brick, static_cast<Field>(brickType));
    }

    void Grid::add(const Platform& plattform)
    {
        add(plattform, Field::plattform);
    }

    void Grid::add(const Wall& wall)
    {
        add(wall, Field::wall);
    }

    void Grid::add(const GameObject& gameObject, const Field& field)
    {
        auto x_begin = 
            static_cast<std::size_t>(gameObject.topLeft().x);
        auto x_end =
            static_cast<std::size_t>(gameObject.bottomRight().x);

        auto y_begin = 
            static_cast<std::size_t>(gameObject.topLeft().y);
        auto y_end = 
            static_cast<std::size_t>(gameObject.bottomRight().y);

        for (auto y = y_begin; y < y_end; ++y) {
            for (auto x = x_begin; x < x_end; ++x) {
                mFields.at(y).at(x) = field;
            }
        }
    }

    std::vector<std::vector<Field>> init(std::size_t width, std::size_t height)
    {
        std::vector<Field> row(width, Field::empty);
        std::vector<std::vector<Field>> fields(height, row);
        return fields;
    }

    std::ostream& operator<<(std::ostream& os, const Grid& obj)
    {
        auto symbolWall = "# ";
        auto symbolBall = "O ";
        auto symbolBrick = "1 ";
        auto symbolPlattform = "= ";
        auto symbolEmpty = ". ";

        auto symbolBrick1Hit = "1 ";
        auto symbolBrick2Hit = "2 ";
        auto symbolBrick3Hit = "3 ";
        auto symbolBrick4Hit = "4 ";
        auto symbolBrick5Hit = "5 ";
        auto symbolBrick6Hit = "6 ";
        auto symbolBrick7Hit = "7 ";
        auto symbolBrick8Hit = "8 ";
        auto symbolBrick9Hit = "9 ";

        auto size_y = obj.mFields.size();
        auto size_x = obj.mFields.at(0).size();

        for (const auto& row : obj.mFields) {

            for (const auto& field : row) {

                switch (field) {
                case Field::ball:
                    os << symbolBall;
                    break;
                case Field::brick1Hits:
                    os << symbolBrick1Hit;
                    break;
                case Field::brick2Hits:
                    os << symbolBrick2Hit;
                    break;
                case Field::brick3Hits:
                    os << symbolBrick3Hit;
                    break;
                case Field::brick4Hits:
                    os << symbolBrick4Hit;
                    break;
                case Field::brick5Hits:
                    os << symbolBrick5Hit;
                    break;
                case Field::brick6Hits:
                    os << symbolBrick6Hit;
                    break;
                case Field::brick7Hits:
                    os << symbolBrick7Hit;
                    break;
                case Field::brick8Hits:
                    os << symbolBrick8Hit;
                    break;
                case Field::brick9Hits:
                    os << symbolBrick9Hit;
                    break;
                case Field::empty:
                    os << symbolEmpty;
                    break;
                case Field::plattform:
                    os << symbolPlattform;
                    break;
                case Field::wall:
                    os << symbolWall;
                    break;
                }
            }
            os << '\n';
        }
        return os;
    }
}  // namespace arkanoid

Console.h

#ifndef CONSOLE_H
#define CONSOLE_H

#include <cstddef>

namespace console {

    void resize(std::size_t x, std::size_t y);

    void putCursorToStartOfConsole();

    void clearScreen();

    bool keyWasPressed();

    char getKey();

    bool leftKeyHoldDown();
    bool rightKeyHoldDown();
    bool spaceKeyHoldDown();
    bool escKeyHoldDown();
    bool rKeyHoldDown();

    bool isKeyDown(int key_code);

}  // namespace console
#endif

Console.cpp

#include "Console.h"

#include <cstdlib>

#include <conio.h>
#include <Windows.h>
#include <WinUser.h>

namespace console {

    void resize(std::size_t x, std::size_t y)
    {
        HWND console = GetConsoleWindow();
        RECT r;
        GetWindowRect(console, &r); //stores the console's current dimensions
        MoveWindow(console, r.left, r.top, x, y, true); 
    }

    void putCursorToStartOfConsole()
    {
        HANDLE hOut;
        COORD Position;

        hOut = GetStdHandle(STD_OUTPUT_HANDLE);

        Position.X = 0;
        Position.Y = 0;
        SetConsoleCursorPosition(hOut, Position);
    }


    void clearScreen()
    {
        std::system("cls");
    }

    bool keyWasPressed()
    {
        return static_cast<bool>(_kbhit());
    }

    char getKey()
    {
        return _getch();
    }

    bool leftKeyHoldDown()
    {
        return isKeyDown(VK_LEFT);
    }

    bool rightKeyHoldDown()
    {
        return isKeyDown(VK_RIGHT);
    }

    bool spaceKeyHoldDown()
    {
        return isKeyDown(VK_SPACE);
    }

    bool escKeyHoldDown()
    {
        return isKeyDown(VK_ESCAPE);
    }

    bool rKeyHoldDown()
    {
        return isKeyDown(0x52);
    }

    bool isKeyDown(int key_code)
    {
        return GetAsyncKeyState(key_code) & -32768;
    }
}

NearlyEqual.h

#ifndef NEARLYEQUAL_H
#define NEARLYEQUAL_H

namespace arkanoid{
    bool nearlyEqual(double a, double b);

    bool nearlyEqual(double a, double b, int factor);
}
#endif

NearlyEqual.cpp

#include "NearlyEqual.h"

#include <limits>
#include <cmath>

namespace arkanoid {

    bool nearlyEqual(double a, double b)
    {
        return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
            && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
    }

    bool nearlyEqual(double a, double b, int factor)
    {
        double min_a = a - (a - std::nextafter(
            a, std::numeric_limits<double>::lowest())) * factor;
        double max_a = a + (
            std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

        return min_a <= b && max_a >= b;
    }
} // namespace arkanoid

MathHelper.h

#ifndef MATHHELPER_H
#define MATHHELPER_H

#define _USE_MATH_DEFINES
#include <math.h>

#include <limits>

namespace arkanoid {

    constexpr double pi = M_PI;

    constexpr double deg_0 = 0.0;
    constexpr double deg_15 = pi / 12.0;
    constexpr double deg_30 = 2 * deg_15;
    constexpr double deg_45 = pi / 4.0;
    constexpr double deg_60 = deg_45 + deg_15;
    constexpr double deg_75 = deg_60 + deg_15;
    constexpr double deg_90 = pi / 2.0;
    constexpr double deg_105 = deg_90 + deg_15;
    constexpr double deg_120 = deg_105 + deg_15;
    constexpr double deg_135 = deg_120 + deg_15;
    constexpr double deg_150 = deg_135 + deg_15;
    constexpr double deg_165 = deg_150 + deg_15;
    constexpr double deg_180 = pi;
    constexpr double deg_195 = deg_180 + deg_15;
    constexpr double deg_210 = deg_195 + deg_15;
    constexpr double deg_225 = deg_210 + deg_15;
    constexpr double deg_240 = deg_225 + deg_15;
    constexpr double deg_255 = deg_240 + deg_15;
    constexpr double deg_270 = 3 * deg_90;
    constexpr double deg_285 = deg_270 + deg_15;
    constexpr double deg_300 = deg_285 + deg_15;
    constexpr double deg_315 = deg_270 + deg_45;
    constexpr double deg_330 = deg_315 + deg_15;
    constexpr double deg_345 = deg_330 + deg_15;
    constexpr double deg_360 = 2 * pi;

    constexpr double degreesToRadient(double degree)
    {
        return degree * pi / 180.0;
    }

    constexpr double radientToDegrees(double radient)
    {
        return radient * 180.0 / pi;
    }
}  // namespace arkanoid
#endif
\$\endgroup\$
  • \$\begingroup\$ By "after doing the Snake Game" do you mean codereview.stackexchange.com/q/219886? \$\endgroup\$ – L. F. May 21 '19 at 10:21
  • \$\begingroup\$ yes i also made a qt version of it after in annother question \$\endgroup\$ – Sandro4912 May 21 '19 at 10:22
  • \$\begingroup\$ Well, codereview.stackexchange.com/q/220147, presumably. They seem quite good! \$\endgroup\$ – L. F. May 21 '19 at 10:24
  • \$\begingroup\$ "plattform" --> German has kicked in ;-) \$\endgroup\$ – AlexV May 21 '19 at 10:41
  • \$\begingroup\$ i could swear i had fixed that typo everywere :(. \$\endgroup\$ – Sandro4912 May 21 '19 at 10:44
2
\$\begingroup\$

Here are some things that may help you improve your code.

Fix the bug

It might work well on your machine, but my computer apparently runs faster and so in Level::run() no time elapses in the main loop and therefore nothing moves. I'd suggest using a timer to drive movement rather than looping on the time.

Allow for portability

Rather than coding the display mechanics directly in the Level code, an improvement might be to implement a console::show(std::stringstream &ss) that would isolate the platform-specfic code to just Console.cpp.

Use user-defined literals

The MathHelper.h has a number of lines that look like this:

constexpr double deg_255 = deg_240 + deg_15;

And then it defines radientToDegrees() which is never used. First, it's "radian" not "radient". Second, this is a good place to use a user-defined literals. Here's how:

constexpr long double operator"" _deg(long double deg) {
    return deg*M_PI/180;
}

Now anywhere you need degrees, instead of deg_255, just use 255.0_deg and all of those constants go away.

Use const where practical

There are several places, such as Ball::isActive() which simply report back something about the underlying object without altering it. Those should be const like this:

bool Ball:isActive() const { return mIsActive; }

Make constructors more useful

The Level file contains code that creates a vector of Brick objects like this:

    return std::vector<Brick>
    {
            Brick{ Point{4,2},brickLength,brickHeight,1 },
            Brick{ Point{7,2},brickLength,brickHeight,1 },
            Brick{ Point{10,2},brickLength,brickHeight,1 },
            Brick{ Point{13,2},brickLength,brickHeight,1 },
            Brick{ Point{16,2},brickLength,brickHeight,1 },
            Brick{ Point{19,2},brickLength,brickHeight,1 },

I'm exhausted just looking at it! Instead, make brickLength and brickHeight default values of the Brick constructor and then the code could look like this:

    return std::vector<Brick>
    {
            { {4,2},1 },
            { {7,2},1 },
            { {10,2},1 },
            { {13,2},1 },
            { {16,2},1 },
            { {19,2},1 },

I'd probably even rearrange the constructor to put hitpoints just after topLeft and give it a default value of 1. Also, there seems little point to having makePlatform and makeBall when all they do is call constructors anyway.

Eliminate redundant code

In Level::printToConsole() there is no need to call console::putCursorToStartOfConsole() twice. In fact, with a minor bit of restructuring and creation of a console::show() routine as mentioned above, that bit of code could be placed within show().

Eliminate unused variables

Unused variables are a sign of poor quality code, and you don't want to write poor quality code. In this code, in Grid.cpp, symbolBrick, size_y and size_x are all unused. Your compiler is smart enough to tell you about this if you ask it nicely.

Eliminate unused code

There are several unused functions in this code including the one mentioned earlier and one of the nearlyEqual forms. Search and destroy them.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for the many helpfull suggestions. I totally forgot about the existance of operator"" thats a nice addition in this case i guess. I did not made the bricks height and lengths default because i want to support bricks with many different shapes later. \$\endgroup\$ – Sandro4912 May 31 '19 at 18:01
3
\$\begingroup\$

Composition over inheritance. You have a base class GameObject which only does collision and bounding box type stuff. There is no need for it to be a base class that you inherit from.

Make it a member variable instead and access it when you need to. Adding gravity to the ball and moving the platform in response to keyboard doesn't need virtual functions.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.