Last active
August 29, 2015 14:12
-
-
Save MORTAL2000/0c9baaf4e1fcc5252939 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <algorithm> | |
#include <random> | |
#include <memory> | |
#include <chrono> | |
#include "utility.h" | |
using Matrix = std::vector<std::vector<int>>; | |
struct Point2D | |
{ | |
int x, y; | |
}; | |
namespace | |
{ | |
enum Field | |
{ | |
EMPTY = 0, | |
WALL = 9, | |
}; | |
enum KeyType | |
{ | |
KEY_UP = 72, | |
KEY_RIGHT = 77, | |
KEY_LEFT = 75, | |
KEY_DOWN = 80 | |
}; | |
enum Menu | |
{ | |
PLAY = 1, | |
EXIT = 0 | |
}; | |
}; | |
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; | |
std::size_t size() const | |
{ | |
return il.size(); | |
} | |
static const std::initializer_list<std::size_t> il; | |
}; | |
const std::initializer_list<std::size_t> Shape::il = | |
{ | |
0, 1, 2, 3 | |
}; | |
template <typename Derived> | |
struct Clonable : 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 (auto i : il) | |
{ | |
for (auto j : il) | |
{ | |
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; | |
} | |
}; | |
namespace shapes | |
{ | |
class O : public Clonable < 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 Clonable < 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 Clonable < 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 Clonable < 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 Clonable < 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 Clonable < 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 Clonable < 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; | |
private: | |
NonCopyable(const NonCopyable &) = delete; | |
NonCopyable(const NonCopyable &&) = delete; | |
NonCopyable& operator = (const NonCopyable&) = delete; | |
}; | |
struct Drawable | |
{ | |
virtual void draw(std::ostream& stream) const = 0; | |
}; | |
class Random : private NonCopyable | |
{ | |
public: | |
Random(int min, int max) | |
: mUniformDistribution(min, max) | |
{} | |
int operator()() | |
{ | |
return mUniformDistribution(mEngine); | |
} | |
private: | |
std::default_random_engine mEngine{ std::random_device()() }; | |
std::uniform_int_distribution<int> mUniformDistribution; | |
}; | |
class Tetris : public Drawable, 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(); | |
Matrix mStage; | |
Matrix mBlock; | |
Point2D position; | |
Ptr shape; | |
virtual void draw(std::ostream& stream) const; | |
friend std::ostream& operator<<(std::ostream& stream, const Tetris& self) | |
{ | |
self.draw(stream); | |
return stream; | |
} | |
int mScore = 0; | |
Matrix mBoard; | |
const int shapeCounts = 7; | |
Random getRandom{ 0, shapeCounts - 1 }; | |
std::vector<Ptr> shapes; | |
static const std::initializer_list<std::size_t> ilBoard; | |
static const std::initializer_list<std::size_t> ilBoardRow; | |
}; | |
const std::initializer_list<std::size_t> Tetris::ilBoard = | |
{ | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 | |
}; | |
const std::initializer_list<std::size_t> Tetris::ilBoardRow = | |
{ | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 | |
}; | |
Tetris::Tetris() | |
{ | |
mBoard.resize(ilBoard.size(), std::vector<int>(ilBoardRow.size(), 0)); | |
mStage.resize(ilBoard.size(), std::vector<int>(ilBoardRow.size(), 0)); | |
mBlock.resize(shape->size(), std::vector<int>(shape->size(), 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 (auto i = ilBoard.begin(); i != ilBoard.end() - 1; ++i) | |
{ | |
for (auto j = ilBoardRow.begin(); j != ilBoardRow.end() - 1; ++j) | |
{ | |
if ((*j == 0) || (*j == ilBoardRow.size() - 2) || (*i == ilBoard.size() - 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 blockType = getRandom(); | |
shape.reset(nullptr); | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
if (shapes[blockType]) // avoid nasty access violate | |
{ | |
mBlock[i][j] = shapes[blockType]->getDot(i, j); | |
} | |
} | |
} | |
shape = std::move(shapes[blockType]); | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
mBoard[i][j + shape->size()] += mBlock[i][j]; | |
} | |
} | |
} | |
bool Tetris::isFull() | |
{ | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
if (mBoard[i][j + shape->size()] > 1) | |
{ | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
void Tetris::moveBlock(std::size_t x2, std::size_t y2) | |
{ | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
mBoard[position.y + i][position.x + j] -= mBlock[i][j]; | |
} | |
} | |
position.x = x2; | |
position.y = y2; | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
mBoard[position.y + i][position.x + j] += mBlock[i][j]; | |
} | |
} | |
} | |
void Tetris::checkLine() | |
{ | |
std::copy(mBoard.begin(), mBoard.end(), mStage.begin()); | |
for (auto i = ilBoard.begin() + 1; i != ilBoard.end() - 2; ++i) | |
{ | |
bool isCompeteLine = true; | |
for (auto j = ilBoardRow.begin() + 1; j != ilBoardRow.end() - 1; ++j) | |
{ | |
if (mStage[*i][*j] == 0) | |
{ | |
isCompeteLine = false; | |
} | |
} | |
if (isCompeteLine) | |
{ | |
mScore += 10; | |
for (auto k : shape->il) | |
{ | |
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 (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
if (mBlock[i][j] && mStage[y + i][x + j] != 0) | |
{ | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
bool Tetris::applyRotate() | |
{ | |
Matrix temp(shape->size(), std::vector<int>(shape->size(), 0)); | |
std::copy(mBlock.begin(), mBlock.end(), temp.begin()); | |
if (shape) | |
mBlock = shape->rotate(); | |
if (isCollide(position.x, position.y)) | |
{ | |
std::copy(temp.begin(), temp.end(), mBlock.begin()); | |
return true; | |
} | |
for (auto i : shape->il) | |
{ | |
for (auto j : shape->il) | |
{ | |
mBoard[position.y + i][position.x + j] -= temp[i][j]; | |
mBoard[position.y + i][position.x + j] += mBlock[i][j]; | |
} | |
} | |
return false; | |
} | |
void Tetris::spawnBlock() | |
{ | |
if (!isCollide(position.x, position.y + 1)) | |
{ | |
moveBlock(position.x, position.y + 1); | |
} | |
else | |
{ | |
checkLine(); | |
makeBlocks(); | |
} | |
} | |
void Tetris::draw(std::ostream& stream) const | |
{ | |
for (auto i : ilBoard) | |
{ | |
for (auto j : ilBoardRow) | |
{ | |
switch (mBoard[i][j]) | |
{ | |
case EMPTY: | |
stream << ' '; | |
break; | |
case WALL: | |
stream << '@'; | |
break; | |
default: | |
stream << '#'; | |
break; | |
} | |
} | |
stream << '\n'; | |
} | |
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() | |
{ | |
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(); | |
} | |
} | |
clearScreen(); | |
gameOverScreen(); | |
} | |
int Game::menu() | |
{ | |
introScreen(); | |
int select_num = 0; | |
std::cin >> select_num; | |
switch (select_num) | |
{ | |
case PLAY: | |
case EXIT: | |
break; | |
default: | |
select_num = 0; | |
break; | |
} | |
return select_num; | |
} | |
void Game::introScreen() | |
{ | |
clearScreen(); | |
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() | |
{ | |
clearScreen(); | |
std::cout << tetris; | |
} | |
void Game::userInput() | |
{ | |
switch (_getch()) | |
{ | |
case KEY_RIGHT: | |
if (!tetris.isCollide(tetris.getPosition().x + 1, tetris.getPosition().y)) | |
{ | |
tetris.moveBlock(tetris.getPosition().x + 1, tetris.getPosition().y); | |
} | |
break; | |
case KEY_LEFT: | |
if (!tetris.isCollide(tetris.getPosition().x - 1, tetris.getPosition().y)) | |
{ | |
tetris.moveBlock(tetris.getPosition().x - 1, tetris.getPosition().y); | |
} | |
break; | |
case KEY_DOWN: | |
if (!tetris.isCollide(tetris.getPosition().x, tetris.getPosition().y + 1)) | |
{ | |
tetris.moveBlock(tetris.getPosition().x, tetris.getPosition().y + 1); | |
} | |
break; | |
case KEY_UP: | |
tetris.applyRotate(); | |
} | |
} | |
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment