2
\$\begingroup\$

Previous question:

Text-based Tetris game with CRTP - follow-up 2

Summary of improvements:

  • Fixed Issues preventing the code from compiling on Clang
  • Implemented helper class range_impl
  • Removed all std::initializer_list
  • Elimination of magic numbers

How can I improve this code further?

Tetris.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <memory>
#include <chrono>

#include "rlutil.h"

using Matrix = std::vector<std::vector<int>>;

struct Point2D
{
    int x, y;
};

namespace
{
    enum Field
    {
        EMPTY = 0,
        WALL  = 9
    };

    enum Menu
    {
        PLAY = 1,
        EXIT = 0
    };

    enum Block_Edges
    {
        FROM = 0,
        TO   = 4,
    };

    enum Column_Edges 
    {
        CE_FROM  = 0,
        CE_TO    = 21
    };

    enum Row_Edges 
    {
        RE_FROM  = 0,
        RE_TO    = 12
    };

    template<typename T, bool enable = std::is_enum<T>::value>
    T& operator++(T& e)
    {
        return e = static_cast<T>(e + 1);
    }
};

template<typename T, bool enable = std::is_integral<T>::value || std::is_enum<T>::value>
struct range_impl
{
    struct iterator
    {
        const T operator * () const noexcept
        { 
            return value;
        }

        iterator& operator ++() noexcept
        { 
            ++value;
            return *this; 
        }

        friend  const bool operator != (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.value != rhs.value;
        }

        T value;
    };

    std::size_t size() const
    { 
        return last - first;
    }

    const iterator begin() const noexcept
    {
        return{ first };
    }

    const iterator end() const noexcept
    {
        return{ last };
    }

    T first;
    T last;
};

template<typename T>
struct range_impl<T, false>
{
    range_impl(T first, T last)
        : first(first)
        , last(last)
    {}

    std::size_t size() const
    { 
        return std::distance(first, last);
    }

    const T begin() const noexcept
    { 
        return{ first };
    }

    const T end() const noexcept
    {
        return{ last };
    }

    T first;
    T last;
};

template<typename T1, typename T2> 
range_impl<typename std::common_type<T1, T2>::type>
range(T1 first, T2 last) noexcept
{
    return{ first, last };
}

class Shape
{
public:
    Shape() = default;

    virtual ~Shape() = default;
    virtual Shape *clone() const = 0;
    virtual int getDot(std::size_t i, std::size_t j) const = 0;
    virtual Matrix rotate() = 0;
    virtual Matrix& shape() = 0;

    std::size_t size() const
    {
        return range(Block_Edges::FROM, Block_Edges::TO).size();
    }
};


template <typename Derived>
struct Interfaceable : public Shape
{
    virtual Shape *clone() const override
    {
        return new Derived(static_cast<const Derived&>(*this));
    }

    virtual int getDot(std::size_t i, std::size_t j) const override
    {
        return static_cast<const Derived&>(*this).shape[i][j];
    }

    virtual Matrix rotate() override
    {
        for (const auto i : range(Block_Edges::FROM, Block_Edges::TO))
        {
            for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
            {
                if (i < j)
                {
                    std::swap(static_cast<Derived&>(*this).shape[i][j], static_cast<Derived&>(*this).shape[j][i]);
                }
            }

            std::reverse(static_cast< Derived&>(*this).shape[i].begin(), static_cast<Derived&>(*this).shape[i].end());
        }

        return static_cast<Derived&>(*this).shape;
    }

    virtual Matrix& shape() override
    {
        return static_cast<Derived&>(*this).shape;
    }
};

namespace shapes
{
    class O : public Interfaceable<O>
    {
    public:
        O() = default;
        virtual ~O() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class L : public Interfaceable<L>
    {
    public:
        L() = default;
        virtual ~L() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 1, 0 },
                { 0, 0, 1, 0 }
            }
        };
    };

    class M : public Interfaceable<M>
    {
    public:
        M() = default;
        virtual ~M() = default;

        Matrix shape
        {
            {
                { 0, 1, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class N : public Interfaceable<N>
    {
    public:
        N() = default;
        virtual ~N() = default;

        Matrix shape
        {
            {
                { 0, 0, 1, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 0, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class T : public Interfaceable<T>
    {
    public:
        T() = default;
        virtual ~T() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 0, 0 },
                { 1, 1, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class I : public Interfaceable<I>
    {
    public:
        I() = default;
        virtual ~I() = default;

        Matrix shape
        {
            {
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 }
            }
        };
    };

    class S : public Interfaceable<S>
    {
    public:
        S() = default;
        virtual ~S() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 }
            }
        };
    };
};

class NonCopyable
{
public:
    NonCopyable() = default;
    virtual ~NonCopyable() = default;

    NonCopyable(const NonCopyable &) = delete;
    NonCopyable(const NonCopyable &&) = delete;
    NonCopyable& operator = (const NonCopyable&) = delete;
};

class Tetris : private NonCopyable
{
public:
    using Ptr = std::unique_ptr<Shape>;

    Tetris();

    void moveBlock(std::size_t, std::size_t);
    bool isCollide(std::size_t, std::size_t);
    void spawnBlock();
    bool applyRotate();
    bool isFull();

    Point2D getPosition()
    {
        return position;
    }

private:
    void initField();
    void makeBlocks();
    void checkLine();
    void makeSolid();

    Matrix mStage;

    Point2D position;

    Shape *shape;

    void draw(std::ostream& stream) const;

    friend std::ostream& operator<<(std::ostream& stream, const Tetris& self)
    {
        self.draw(stream);
        return stream;
    }

    int blockType = 0;
    int mScore = 0;
    Matrix mBoard;

    std::minstd_rand rndEngine;

    std::vector<Ptr> shapes;

    std::size_t columnSize() const
    {
        return range(Column_Edges::CE_FROM, Column_Edges::CE_TO).size();
    }

    std::size_t rowSize() const
    {
        return range(Row_Edges::RE_FROM, Row_Edges::RE_TO).size();
    }

};

Tetris::Tetris()
{
    mBoard.resize(columnSize(), std::vector<int>(rowSize(), 0));
    mStage.resize(columnSize(), std::vector<int>(rowSize(), 0));

    shapes.emplace_back(std::move(std::make_unique<shapes::T>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::M>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::N>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::I>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::O>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::L>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::S>()->clone()));

    initField();
}

void Tetris::initField()
{
    for (const auto& i : range(Column_Edges::CE_FROM, Column_Edges::CE_TO - 1)) 
    {
        for (const auto& j : range(Row_Edges::RE_FROM, Row_Edges::RE_TO - 1))
        {
            if ((j == 0) || (j == rowSize() - 2) || (i == columnSize() - 2))
            {
                mBoard[i][j] = mStage[i][j] = WALL;
            }
            else
            {
                mBoard[i][j] = mStage[i][j] = EMPTY;
            }
        }
    }

    makeBlocks();
}

void Tetris::makeBlocks()
{
    position.x = shape->size();
    position.y = 0;

    int shapeCounts = 7;

    blockType = rndEngine() % shapeCounts;

    shape = shapes[blockType].get();

    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            mBoard[i][j + shape->size()] += shapes[blockType]->getDot(i, j);
        }
    }
}

bool Tetris::isFull()
{
    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            if (mBoard[i][j + shape->size()] > 1)
            {
                return true;
            }
        }
    }

    return false;
}
void Tetris::moveBlock(std::size_t x2, std::size_t y2)
{

    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            mBoard[position.y + i][position.x + j] -= shapes[blockType]->getDot(i, j);;
        }
    }

    position.x = x2;
    position.y = y2;

    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            mBoard[position.y + i][position.x + j] += shapes[blockType]->getDot(i, j);
        }
    }
}

void Tetris::checkLine()
{
    std::copy(mBoard.begin(), mBoard.end(), mStage.begin());

    for (const auto& i : range(Column_Edges::CE_FROM + 1, Column_Edges::CE_TO - 2))
    {
        bool isCompeteLine = true;

        for (const auto& j : range(Row_Edges::RE_FROM + 1, Row_Edges::RE_TO - 1))
        {
            if (mStage[i][j] == 0)
            {
                isCompeteLine = false;
            }
        }

        if (isCompeteLine)
        {
            mScore += 10;

            for (const auto& k : range(Block_Edges::FROM, Block_Edges::TO))
            {
                std::copy(mStage[i - 1 - k].begin(), mStage[i - 1 - k].end(), mStage[i - k].begin());
            }
        }
    }

    std::copy(mStage.begin(), mStage.end(), mBoard.begin());
}

bool Tetris::isCollide(std::size_t x, std::size_t y)
{
    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            if (shapes[blockType]->getDot(i, j) && mStage[y + i][x + j] != 0)
            {
                return true;
            }
        }
    }
    return false;
}

void Tetris::makeSolid()
{
    if (isCollide(position.x, position.y + 1))
    {
        for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
        {
            for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
            {
                if (shapes[blockType]->getDot(i, j) != 0)
                {
                    mBoard[position.y + i][position.x + j] = WALL;
                }
            }
        }
    }
}

bool Tetris::applyRotate()
{
    Matrix temp(shape->size(), std::vector<int>(shape->size(), 0));

    std::copy(shapes[blockType]->shape().begin(), shapes[blockType]->shape().end(), temp.begin());

    shape->rotate();

    if (isCollide(position.x, position.y))
    {
        std::copy(temp.begin(), temp.end(), shapes[blockType]->shape().begin());

        return true;
    }

    for (const auto& i : range(Block_Edges::FROM, Block_Edges::TO))
    {
        for (const auto& j : range(Block_Edges::FROM, Block_Edges::TO))
        {
            mBoard[position.y + i][position.x + j] -= temp[i][j];
            mBoard[position.y + i][position.x + j] += shapes[blockType]->getDot(i, j);
        }
    }

    return false;
}

void Tetris::spawnBlock()
{
    if (!isCollide(position.x, position.y + 1))
    {
        moveBlock(position.x, position.y + 1);
    }
    else
    {
        makeSolid();
        checkLine();
        makeBlocks();
    }
}

void Tetris::draw(std::ostream& stream) const
{
    for (auto i : mBoard)
    {
        for (auto j : i)
        {
            switch (j)
            {
            case EMPTY:
                stream << ' ';
                break;
            case WALL:
                rlutil::setColor(rlutil::CYAN);
                stream << '@';
                break;
            default:
                if (blockType == 0)
                {
                    rlutil::setColor(rlutil::GREY);
                }
                else
                {
                    rlutil::setColor(blockType);
                }
                stream << '#';
                break;
            }
        }

        stream << '\n';
    }

    rlutil::setColor(rlutil::GREY);

    stream << "Score : " << mScore
        << "\n\narrow keys left: ["
        << static_cast<char>(27) << "]\t down:["
        << static_cast<char>(25) << "]\t right:["
        << static_cast<char>(26) << "]\t Rotation:["
        << static_cast<char>(24) << "]";
}

class Game : private NonCopyable
{
public:
    int menu();
    void gameLoop();
private:
    void introScreen();
    void userInput();
    void display();
    void gameOverScreen();

    Tetris tetris;
};

void Game::gameOverScreen()
{
    gotoxy(10, 10);
    rlutil::setColor(rlutil::RED);

    std::cout << "\n"
        " #####     #    #     # ####### ####### #     # ####### ######\n"
        "#     #   # #   ##   ## #       #     # #     # #       #     #\n"
        "#        #   #  # # # # #       #     # #     # #       #     #\n"
        "#  #### #     # #  #  # #####   #     # #     # #####   ######\n"
        "#     # ####### #     # #       #     #  #   #  #       #   #\n"
        "#     # #     # #     # #       #     #   # #   #       #    #\n"
        " #####  #     # #     # ####### #######    #    ####### #     #\n"
        "\n\nPress enter to exit\n";

    std::cin.ignore();
    std::cin.get();
}

void Game::gameLoop()
{
    auto start = std::chrono::high_resolution_clock::now();

    while (!tetris.isFull())
    {
        auto end = std::chrono::high_resolution_clock::now();

        double timeTakenInSeconds = (end - start).count()
            * (static_cast<double>(std::chrono::high_resolution_clock::period::num)
            / std::chrono::high_resolution_clock::period::den);

        if (kbhit())
        {
            userInput();
        }

        if (timeTakenInSeconds > 0.3)
        {
            tetris.spawnBlock();
            display();
            start = std::chrono::high_resolution_clock::now();
        }
    }

    rlutil::cls();

    gameOverScreen();
}

int Game::menu()
{
    introScreen();

    int selectNum = 0;

    std::cin >> selectNum;

    switch (selectNum)
    {
    case PLAY:
    case EXIT:
        break;
    default:
        selectNum = 0;
        break;
    }

    return selectNum;
}

void Game::introScreen()
{
    rlutil::cls();
    std::cout << "#==============================================================================#\n"
        "####### ####### ####### ######    ###    #####\n"
        "   #    #          #    #     #    #    #     #\n"
        "   #    #          #    #     #    #    #\n"
        "   #    #####      #    ######     #     #####\n"
        "   #    #          #    #   #      #          #\n"
        "   #    #          #    #    #     #    #     #\n"
        "   #    #######    #    #     #   ###    #####\t\tmade for fun \n"
        "\n\n\n\n"

        "\t<Menu>\n"
        "\t1: Start Game\n\t2: Quit\n\n"
        "#==============================================================================#\n"
        "Choose >> ";
}

void Game::display()
{
    rlutil::cls();

    std::cout << tetris;
}

void Game::userInput()
{
    const int k = rlutil::getkey();
    switch (k)
    {
    case rlutil::KEY_RIGHT:
        if (!tetris.isCollide(tetris.getPosition().x + 1, tetris.getPosition().y))
        {
            tetris.moveBlock(tetris.getPosition().x + 1, tetris.getPosition().y);
        }
        break;
    case rlutil::KEY_LEFT:
        if (!tetris.isCollide(tetris.getPosition().x - 1, tetris.getPosition().y))
        {
            tetris.moveBlock(tetris.getPosition().x - 1, tetris.getPosition().y);
        }
        break;
    case rlutil::KEY_DOWN:
        if (!tetris.isCollide(tetris.getPosition().x, tetris.getPosition().y + 1))
        {
            tetris.moveBlock(tetris.getPosition().x, tetris.getPosition().y + 1);
        }
        break;
    case rlutil::KEY_UP:
        tetris.applyRotate();
        break;
    }
}

int main()
{
    Game game;

    switch (game.menu())
    {
    case PLAY:
        game.gameLoop();
        break;
    case EXIT:
        return 0;
    default:
        std::cerr << "Choose 1~2" << std::endl;
        return -1;
    }
}

rhutil.h

Strong type enum version

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Use enum class. Instead of encoding the type of an enum such as RE_FROM and CE_FROM write something like this:

enum class Block_Edges
{
    from = 0,
    to   = 4,
};

enum class Column_Edges 
{
    from  = 0,
    to    = 21
};

enum class Row_Edges 
{
    from  = 0,
    to    = 12
};

Now you get more type checking and have less repetitive typing to do (Column_Edges::from instead of Column_Edges::CE_FROM). I did not capitalize from because it is not a macro-ish constant anymore.

A side effect of this is that you cannot do Row_Edges::from + 1 anymore because enum class does not decay into an int like enum does. Instead you can define addition and subtraction operations such as this:

int operator+(const Row_Edges &re, int i)
{
    const int rei = i + static_cast<int>(re);
    if (rei < static_cast<int>(Row_Edges::from) || rei > static_cast<int>(Row_Edges::to))
        throw std::out_of_range("Addition to Row_Edges would create out of bounds index");
    return rei;
}

You can now add ints to Row_Edges with optional runtime checks.

\$\endgroup\$
4
  • \$\begingroup\$ i don't think this will work. i have tried strong type enum before but VC++ won't compile it. \$\endgroup\$
    – MORTAL
    Commented Jan 15, 2015 at 18:33
  • \$\begingroup\$ @MORTAL I tested the code in Visual Studio Express 2013 update 4 and it worked fine. \$\endgroup\$
    – nwp
    Commented Jan 15, 2015 at 18:37
  • \$\begingroup\$ You had some small errors in your code (#include <exception> instead of #include <stdexcept>, missing #include <type_traits>, confusing FROM with from, wrong operator, mixing enums and ints). Try this one. Also adding operators for all enum classes is not a good idea. \$\endgroup\$
    – nwp
    Commented Jan 15, 2015 at 19:20
  • \$\begingroup\$ sorry for that link it wasn't the code i meant it. i'm working on code hopefully i will fix it. \$\endgroup\$
    – MORTAL
    Commented Jan 15, 2015 at 19:36
0
\$\begingroup\$

@nwp is right and very right when he says

the problem with enum class does not decay into an int like enum does

the generic functions for letting enum class acceptRow_Edges::from + 1 and Row_Edges::from - 1 would be like this

template<typename T1, typename T2, bool enable = std::is_enum<T1>::value>
auto operator+(const T1& e1, const T2& e2) 
    -> decltype(static_cast<int>(e1) + static_cast<int>(e2))
{
    auto result = static_cast<int>(e1) + static_cast<int>(e2);

    if (result < static_cast<int>(T1::from) || result > static_cast<int>(T1::to))
    {
        throw std::out_of_range("subtration to enum would create out of bounds index");
    }

    return  result;
}

template<typename T1, typename T2, bool enable = std::is_enum<T1>::value>
auto operator-(const T1& e1, const T2& e2)
    -> decltype(static_cast<int>(e1) - static_cast<int>(e2))
{
    auto result = static_cast<int>(e1) - static_cast<int>(e2);

    if (result < static_cast<int>(T1::from) || result > static_cast<int>(T1::to))
    {
        throw std::out_of_range("subtration to enum would create out of bounds index");
    }

    return  result;
}

however, it has drawback when it needed to be assign to vector mStage[i][j] = static_cast<int>(Field::wall). To over come this we need to treat enum class by its underlying type throughout the code. to achieve this we need to declare new function class make_integer()

template <typename Enumeration>
auto make_integer(const Enumeration& value)
    -> typename std::underlying_type<Enumeration>::type
{
    static_assert(std::is_enum<Enumeration>::value, "parameter is not of type enum or enum class");
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
} 

no need for auto operator-(const T1& e1, const T2& e2)and auto operator-(const T1& e1, const T2& e2). we can apply arithmetic operations like this make_integer(Row_Edges::to) - 1 and mStage[i][j] = make_integer(Field::wall). but still we need to casting with switch statements.

Solution

to over come the encoding the type of an enum. it can be done by namespace of unknown enum like this

namespace Field
{
    enum
    {
        empty = 0,
        wall = 9
    };
}

namespace Menu
{
    enum
    {
        play = 1,
        exit = 0
    };
}

namespace Block_Edges
{
    enum
    {
        from = 0,
        to   = 4,
    };
}

namespace Column_Edges
{
    enum
    {
        from = 0,
        to   = 21
    };
}

namespace Row_Edges
{
    enum
    {
        from = 0,
        to   = 12
    };
}

advantages of this method, it allows the overloading the arithmetic operators like Row_Edges::from + 1 and Row_Edges::from - 1. Also, it allows the value of the enum type to be as constant that would be much easier and no need for casting in switch statements and = assign it to vector's elements later on.

complete code

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <memory>
#include <chrono>
#include <type_traits>

#include "rlutil.h"

using Matrix = std::vector<std::vector<int>>;

struct Point2D
{
    int x, y;
};

namespace Field
{
    enum
    {
        empty = 0,
        wall = 9
    };
}

namespace Menu
{
    enum
    {
        play = 1,
        exit = 0
    };
}

namespace Block_Edges
{
    enum
    {
        from = 0,
        to   = 4,
    };
}

namespace Column_Edges
{
    enum
    {
        from = 0,
        to   = 21
    };
}

namespace Row_Edges
{
    enum
    {
        from = 0,
        to   = 12
    };
}

template<typename Enumeration, bool enable = std::is_enum<Enumeration>::value>
Enumeration& operator++(Enumeration& e)
{
    static_assert(std::is_enum<Enumeration>::value, "parameter is not of type enum or enum class");
    return e = static_cast<typename Enumeration>(e + 1);
}

template<typename T, bool enable = std::is_integral<T>::value || std::is_enum<T>::value>
struct range_impl
{
    struct iterator
    {
        const T operator * () const noexcept
        {
            return value;
        }

            iterator& operator ++() noexcept
        {
            ++value;
            return *this;
        }

            friend  const bool operator != (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.value != rhs.value;
        }

        T value;
    };

    std::size_t size() const
    {
        return last - first;
    }

    const iterator begin() const noexcept
    {
        return{ first };
    }

        const iterator end() const noexcept
    {
        return{ last };
    }

    T first;
    T last;
};

template<typename T>
struct range_impl<T, false>
{
    range_impl(T first, T last)
        : first(first)
        , last(last)
    {}

    std::size_t size() const
    {
        return std::distance(first, last);
    }

    const T begin() const noexcept
    {
        return{ first };
    }

        const T end() const noexcept
    {
        return{ last };
    }

    T first;
    T last;
};

template<typename T1, typename T2>
range_impl<typename std::common_type<T1, T2>::type> range(T1 first, T2 last) noexcept
{
    return{ first, last };
}

class Shape
{
public:
    Shape() = default;

    virtual ~Shape() = default;
    virtual Shape *clone() const = 0;
    virtual int getDot(std::size_t i, std::size_t j) const = 0;
    virtual Matrix rotate() = 0;
    virtual Matrix& shape() = 0;

    std::size_t size() const
    {
        return range(Block_Edges::from, Block_Edges::to).size();
    }
};


template <typename Derived>
struct Interfaceable : public Shape
{
    virtual Shape *clone() const override
    {
        return new Derived(static_cast<const Derived&>(*this));
    }

    virtual int getDot(std::size_t i, std::size_t j) const override
    {
        return static_cast<const Derived&>(*this).shape[i][j];
    }

    virtual Matrix rotate() override
    {
        for (const auto& i : range(Block_Edges::from, Block_Edges::to))
        {
            for (const auto& j : range(Block_Edges::from, Block_Edges::to))
            {
                if (i < j)
                {
                    std::swap(static_cast<Derived&>(*this).shape[i][j], static_cast<Derived&>(*this).shape[j][i]);
                }
            }

            std::reverse(static_cast< Derived&>(*this).shape[i].begin(), static_cast<Derived&>(*this).shape[i].end());
        }

        return static_cast<Derived&>(*this).shape;
    }

    virtual Matrix& shape() override
    {
        return static_cast<Derived&>(*this).shape;
    }
};

namespace shapes
{
    class O : public Interfaceable<O>
    {
    public:
        O() = default;
        virtual ~O() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class L : public Interfaceable<L>
    {
    public:
        L() = default;
        virtual ~L() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 1, 0 },
                { 0, 0, 1, 0 }
            }
        };
    };

    class M : public Interfaceable<M>
    {
    public:
        M() = default;
        virtual ~M() = default;

        Matrix shape
        {
            {
                { 0, 1, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 0, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class N : public Interfaceable<N>
    {
    public:
        N() = default;
        virtual ~N() = default;

        Matrix shape
        {
            {
                { 0, 0, 1, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 0, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class T : public Interfaceable<T>
    {
    public:
        T() = default;
        virtual ~T() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 0, 0 },
                { 1, 1, 1, 0 },
                { 0, 0, 0, 0 }
            }
        };
    };

    class I : public Interfaceable<I>
    {
    public:
        I() = default;
        virtual ~I() = default;

        Matrix shape
        {
            {
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 }
            }
        };
    };

    class S : public Interfaceable<S>
    {
    public:
        S() = default;
        virtual ~S() = default;

        Matrix shape
        {
            {
                { 0, 0, 0, 0 },
                { 0, 1, 1, 0 },
                { 0, 1, 0, 0 },
                { 0, 1, 0, 0 }
            }
        };
    };
};

class NonCopyable
{
public:
    NonCopyable() = default;
    virtual ~NonCopyable() = default;

    NonCopyable(const NonCopyable &) = delete;
    NonCopyable(const NonCopyable &&) = delete;
    NonCopyable& operator = (const NonCopyable&) = delete;
};

class Tetris : private NonCopyable
{
public:
    using Ptr = std::unique_ptr<Shape>;

    Tetris();

    void moveBlock(std::size_t, std::size_t);
    bool isCollide(std::size_t, std::size_t);
    void spawnBlock();
    bool applyRotate();
    bool isFull();

    Point2D getPosition()
    {
        return position;
    }

private:
    void initField();
    void makeBlocks();
    void checkLine();
    void makeSolid();

    Matrix mStage;

    Point2D position;

    Shape *shape;

    void draw(std::ostream& stream) const;

    friend std::ostream& operator<<(std::ostream& stream, const Tetris& self)
    {
        self.draw(stream);
        return stream;
    }

    int blockType = 0;
    int mScore = 0;
    Matrix mBoard;

    std::minstd_rand rndEngine;

    std::vector<Ptr> shapes;

    std::size_t columnSize() const
    {
        return range(Column_Edges::from, Column_Edges::to).size();
    }

    std::size_t rowSize() const
    {
        return range(Row_Edges::from, Row_Edges::to).size();
    }

};

Tetris::Tetris()
{
    mBoard.resize(columnSize(), std::vector<int>(rowSize(), 0));
    mStage.resize(columnSize(), std::vector<int>(rowSize(), 0));

    shapes.emplace_back(std::move(std::make_unique<shapes::T>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::M>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::N>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::I>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::O>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::L>()->clone()));
    shapes.emplace_back(std::move(std::make_unique<shapes::S>()->clone()));

    initField();
}

void Tetris::initField()
{
    for (const auto& i : range(Column_Edges::from, Column_Edges::to - 1))
    {
        for (const auto& j : range(Row_Edges::from, Row_Edges::to - 1))
        {
            if ((j == 0) || (j == rowSize() - 2) || (i == columnSize() - 2))
            {
                mBoard[i][j] = mStage[i][j] = Field::wall;
            }
            else
            {
                mBoard[i][j] = mStage[i][j] = Field::empty;
            }
        }
    }

    makeBlocks();
}

void Tetris::makeBlocks()
{
    position.x = shape->size();
    position.y = 0;

    int shapeCounts = 7;

    blockType = rndEngine() % shapeCounts;

    shape = shapes[blockType].get();

    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            mBoard[i][j + shape->size()] += shapes[blockType]->getDot(i, j);
        }
    }
}

bool Tetris::isFull()
{
    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            if (mBoard[i][j + shape->size()] > 1)
            {
                return true;
            }
        }
    }

    return false;
}
void Tetris::moveBlock(std::size_t x2, std::size_t y2)
{

    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            mBoard[position.y + i][position.x + j] -= shapes[blockType]->getDot(i, j);;
        }
    }

    position.x = x2;
    position.y = y2;

    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            mBoard[position.y + i][position.x + j] += shapes[blockType]->getDot(i, j);
        }
    }
}

void Tetris::checkLine()
{
    std::copy(mBoard.begin(), mBoard.end(), mStage.begin());

    for (const auto& i : range(Column_Edges::from + 1, Column_Edges::to - 2))
    {
        bool isCompeteLine = true;

        for (const auto& j : range(Row_Edges::from + 1, Row_Edges::to - 1))
        {
            if (mStage[i][j] == 0)
            {
                isCompeteLine = false;
            }
        }

        if (isCompeteLine)
        {
            mScore += 10;

            for (const auto& k : range(Block_Edges::from, Block_Edges::to))
            {
                std::copy(mStage[i - 1 - k].begin(), mStage[i - 1 - k].end(), mStage[i - k].begin());
            }
        }
    }

    std::copy(mStage.begin(), mStage.end(), mBoard.begin());
}

bool Tetris::isCollide(std::size_t x, std::size_t y)
{
    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            if (shapes[blockType]->getDot(i, j) && mStage[y + i][x + j] != 0)
            {
                return true;
            }
        }
    }
    return false;
}

void Tetris::makeSolid()
{
    if (isCollide(position.x, position.y + 1))
    {
        for (const auto& i : range(Block_Edges::from, Block_Edges::to))
        {
            for (const auto& j : range(Block_Edges::from, Block_Edges::to))
            {
                if (shapes[blockType]->getDot(i, j) != 0)
                {
                    mBoard[position.y + i][position.x + j] = static_cast<int>(Field::wall);
                }
            }
        }
    }
}

bool Tetris::applyRotate()
{
    Matrix temp(shape->size(), std::vector<int>(shape->size(), 0));

    std::copy(shapes[blockType]->shape().begin(), shapes[blockType]->shape().end(), temp.begin());

    shape->rotate();

    if (isCollide(position.x, position.y))
    {
        std::copy(temp.begin(), temp.end(), shapes[blockType]->shape().begin());

        return true;
    }

    for (const auto& i : range(Block_Edges::from, Block_Edges::to))
    {
        for (const auto& j : range(Block_Edges::from, Block_Edges::to))
        {
            mBoard[position.y + i][position.x + j] -= temp[i][j];
            mBoard[position.y + i][position.x + j] += shapes[blockType]->getDot(i, j);
        }
    }

    return false;
}

void Tetris::spawnBlock()
{
    if (!isCollide(position.x, position.y + 1))
    {
        moveBlock(position.x, position.y + 1);
    }
    else
    {
        makeSolid();
        checkLine();
        makeBlocks();
    }
}

void Tetris::draw(std::ostream& stream) const
{
    for (auto i : mBoard)
    {
        for (auto j : i)
        {
            switch (j)
            {
            case Field::empty:
                stream << ' ';
                break;
            case Field::wall:
                rlutil::setColor(rlutil::CYAN);
                stream << '@';
                break;
            default:
                if (blockType == 0)
                {
                    rlutil::setColor(rlutil::GREY);
                }
                else
                {
                    rlutil::setColor(blockType);
                }
                stream << '#';
                break;
            }
        }

        stream << '\n';
    }

    rlutil::setColor(rlutil::GREY);

    stream << "Score : " << mScore
        << "\n\narrow keys left: ["
        << static_cast<char>(27) << "]\t down:["
        << static_cast<char>(25) << "]\t right:["
        << static_cast<char>(26) << "]\t Rotation:["
        << static_cast<char>(24) << "]";
}

class Game : private NonCopyable
{
public:
    int menu();
    void gameLoop();
private:
    void introScreen();
    void userInput();
    void display();
    void gameOverScreen();

    Tetris tetris;
};

void Game::gameOverScreen()
{
    gotoxy(10, 10);
    rlutil::setColor(rlutil::RED);

    std::cout << "\n"
        " #####     #    #     # ####### ####### #     # ####### ######\n"
        "#     #   # #   ##   ## #       #     # #     # #       #     #\n"
        "#        #   #  # # # # #       #     # #     # #       #     #\n"
        "#  #### #     # #  #  # #####   #     # #     # #####   ######\n"
        "#     # ####### #     # #       #     #  #   #  #       #   #\n"
        "#     # #     # #     # #       #     #   # #   #       #    #\n"
        " #####  #     # #     # ####### #######    #    ####### #     #\n"
        "\n\nPress enter to exit\n";

    std::cin.ignore();
    std::cin.get();
}

void Game::gameLoop()
{
    auto start = std::chrono::high_resolution_clock::now();

    while (!tetris.isFull())
    {
        auto end = std::chrono::high_resolution_clock::now();

        double timeTakenInSeconds = (end - start).count()
            * (static_cast<double>(std::chrono::high_resolution_clock::period::num)
            / std::chrono::high_resolution_clock::period::den);

        if (kbhit())
        {
            userInput();
        }

        if (timeTakenInSeconds > 0.3)
        {
            tetris.spawnBlock();
            display();
            start = std::chrono::high_resolution_clock::now();
        }
    }

    rlutil::cls();

    gameOverScreen();
}

int Game::menu()
{
    introScreen();

    int selectNum = 0;

    std::cin >> selectNum;

    switch (selectNum)
    {
    case Menu::play:
    case Menu::exit:
        break;
    default:
        selectNum = 0;
        break;
    }

    return selectNum;
}

void Game::introScreen()
{
    rlutil::cls();
    std::cout << "#==============================================================================#\n"
        "####### ####### ####### ######    ###    #####\n"
        "   #    #          #    #     #    #    #     #\n"
        "   #    #          #    #     #    #    #\n"
        "   #    #####      #    ######     #     #####\n"
        "   #    #          #    #   #      #          #\n"
        "   #    #          #    #    #     #    #     #\n"
        "   #    #######    #    #     #   ###    #####\t\tmade for fun \n"
        "\n\n\n\n"

        "\t<Menu>\n"
        "\t1: Start Game\n\t2: Quit\n\n"
        "#==============================================================================#\n"
        "Choose >> ";
}

void Game::display()
{
    rlutil::cls();

    std::cout << tetris;
}

void Game::userInput()
{
    const int k = rlutil::getkey();
    switch (k)
    {
    case rlutil::KEY_RIGHT:
        if (!tetris.isCollide(tetris.getPosition().x + 1, tetris.getPosition().y))
        {
            tetris.moveBlock(tetris.getPosition().x + 1, tetris.getPosition().y);
        }
        break;
    case rlutil::KEY_LEFT:
        if (!tetris.isCollide(tetris.getPosition().x - 1, tetris.getPosition().y))
        {
            tetris.moveBlock(tetris.getPosition().x - 1, tetris.getPosition().y);
        }
        break;
    case rlutil::KEY_DOWN:
        if (!tetris.isCollide(tetris.getPosition().x, tetris.getPosition().y + 1))
        {
            tetris.moveBlock(tetris.getPosition().x, tetris.getPosition().y + 1);
        }
        break;
    case rlutil::KEY_UP:
        tetris.applyRotate();
        break;
    }
}

int main()
{
    Game game;

    switch (game.menu())
    {
    case Menu::play:
        game.gameLoop();
        break;
    case Menu::exit:
        return 0;
    default:
        std::cerr << "Choose 1~2" << std::endl;
        return -1;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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