Skip to main content
added 211 characters in body
Source Link
MORTAL
  • 3.3k
  • 5
  • 25
  • 42
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
    };
}
namespace Field
{
    enum
    {
        empty = 0,
        wall = 9
    };
}

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

namespace Block_Edges
{
    enum
    {
        from = 0,
        to   = 4,
    };
}
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
    };
}
Source Link
MORTAL
  • 3.3k
  • 5
  • 25
  • 42

@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,
    };
}

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;
    }
}