Revision c3e6dff5-a81f-4aab-aeb5-ab65da8d6b51 - Code Review Stack Exchange

Previous question:

http://codereview.stackexchange.com/questions/74677/text-based-tetris-game-follow-up-final

Summary of improvements:

 - Implementation of a `Drawable` class
 - Separate functionality, input moves to `Game` class


How can I improve this code further?

**Tetris.cpp**

 #include <iostream>
 #include <vector>
 #include <algorithm>
 #include <random>
 
 #include "utility.h"
 
 using Matrix = std::vector<std::vector<int>>;
 
 const std::initializer_list<size_t> ilBlock = 
 {
 	0, 1, 2, 3 
 };
 
 const std::initializer_list<size_t> 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<size_t> ilBoardRow =
 {
 	0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
 };
 
 static const std::vector<Matrix> blockList =
 {
 	{
 		{ 0, 1, 0, 0 },
 		{ 0, 1, 0, 0 },
 		{ 0, 1, 0, 0 },
 		{ 0, 1, 0, 0 }
 	},
 	{
 		{ 0, 0, 0, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 1, 0, 0 },
 		{ 0, 1, 0, 0 }
 	},
 	{
 		{ 0, 0, 1, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 1, 0, 0 },
 		{ 0, 0, 0, 0 }
 	},
 	{
 		{ 0, 1, 0, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 0, 1, 0 },
 		{ 0, 0, 0, 0 }
 	},
 	{
 		{ 0, 0, 0, 0 },
 		{ 0, 1, 0, 0 },
 		{ 1, 1, 1, 0 },
 		{ 0, 0, 0, 0 }
 	},
 	{
 		{ 0, 0, 0, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 0, 0, 0 }
 	},
 	{
 		{ 0, 0, 0, 0 },
 		{ 0, 1, 1, 0 },
 		{ 0, 0, 1, 0 },
 		{ 0, 0, 1, 0 }
 	}
 };
 
 class NonCopyable
 {
 public:
 	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:
 	Tetris();
 
 	void moveBlock(int, int);
 	bool isCollide(int, int);
 	void spawnBlock();
 	bool rotateBlock();
 	bool isFull();
 	COORD getPosition()
 	{
 		return position;
 	}
 
 private:
 	void initField();
 	void makeBlocks();
 	void checkLine();
 
 	Random getRandom{ 0, static_cast<int>(blockList.size() - 1) };
 	Matrix mStage;
 	Matrix mBlock;
 
 	COORD position;
 
 	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;
 };
 
 Tetris::Tetris()
 {
 	mBlock.resize(ilBlock.size(), std::vector<int>(ilBlock.size(), 0));
 	mBoard.resize(ilBoard.size(), std::vector<int>(ilBoardRow.size(), 0));
 	mStage.resize(ilBoard.size(), std::vector<int>(ilBoardRow.size(), 0));
 
 	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] = 9;
 			}
 			else
 			{
 				mBoard[*i][*j] = mStage[*i][*j] = 0;
 			}
 		}
 	}
 
 	makeBlocks();
 }
 
 void Tetris::makeBlocks()
 {
 	position.X = ilBlock.size();
 	position.Y = 0;
 
 	int blockType = getRandom();
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			mBlock[i][j] = blockList[blockType][i][j];
 		}
 	}
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			mBoard[i][j + ilBlock.size()] += mBlock[i][j];
 		}
 	}
 }
 
 bool Tetris::isFull()
 {
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{		
 			if (mBoard[i][j + ilBlock.size()] > 1)
 			{
 				return true;
 			}
 		}
 	}
 	return false;
 }
 
 void Tetris::moveBlock(int x2, int y2)
 {
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			mBoard[position.Y + i][position.X + j] -= mBlock[i][j];
 		}
 	}
 
 	position.X = x2;
 	position.Y = y2;
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			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 : ilBlock)
 			{
 				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(int x, int y)
 {
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			if (mBlock[i][j] && mStage[y + i][x + j] != 0)
 			{
 				return true;
 			}
 		}
 	}
 	return false;
 }
 
 bool Tetris::rotateBlock()
 {
 	Matrix temp(ilBlock.size(), std::vector<int>(ilBlock.size(), 0));
 
 	std::copy(mBlock.begin(), mBlock.end(), temp.begin());
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			if (i < j)
 			{
 				std::swap(mBlock[i][j], mBlock[j][i]);
 			}
 		}
 
 		std::reverse(mBlock[i].begin(), mBlock[i].end());
 	}
 
 	if (isCollide(position.X, position.Y))
 	{
 		std::copy(temp.begin(), temp.end(), mBlock.begin());
 
 		return true;
 	}
 
 	for (auto i : ilBlock)
 	{
 		for (auto j : ilBlock)
 		{
 			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 0:
 				stream << ' ';
 				break;
 			case 9:
 				stream << '@';
 				break;
 			default:
 				stream << '#';
 				break;
 			}
 		}
 
 		stream << '\n';
 	}
 
 	stream << "Score : " << mScore << "\n\n\tA: left\tS: down\tD: right \t Rotation[Space]";
 }
 
 
 class Game : private NonCopyable
 {
 public:
 	int menu();
 	void gameLoop();
 private:
 	void introScreen();
 	void userInput();
 	void display();
 	void gameOverScreen();
 
 	const size_t GAMESPEED = 20000;
 	Tetris tetris;
 };
 
 void Game::gameOverScreen()
 {
 	std::cout << "\n"
 				 " ##### # # # ####### ####### # # ####### ######\n"
 				 "# # # # ## ## # # # # # # # #\n"
 				 "# # # # # # # # # # # # # # #\n"
 				 "# #### # # # # # ##### # # # # ##### ######\n"
 				 "# # ####### # # # # # # # # # #\n"
 				 "# # # # # # # # # # # # # #\n"
 				 " ##### # # # # ####### ####### # ####### # #\n"
 				 "\n\nPress any key and enter\n";
 
 
 	std::cin.ignore();
 	std::cin.get();
 }
 
 void Game::gameLoop()
 {
 	size_t time = 0;
 
 	while (!tetris.isFull())
 	{
 		if (_kbhit())
 		{
 			userInput();
 		}
 
 		if (time < GAMESPEED)
 		{
 			time++;
 		}
 		else
 		{
 			tetris.spawnBlock();
 			time = 0;
 			display();
 		}
 	}
 
 	clearScreen();
 
 	gameOverScreen();
 
 }
 
 int Game::menu()
 {
 	introScreen();
 
 	int select_num = 0;
 
 	std::cin >> select_num;
 
 	switch (select_num)
 	{
 		case 1:
 		case 2:
 			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 77:
 		if (!tetris.isCollide(tetris.getPosition().X + 1, tetris.getPosition().Y))
 		{
 			tetris.moveBlock(tetris.getPosition().X + 1, tetris.getPosition().Y);
 		}
 		break;
 	case 75:
 		if (!tetris.isCollide(tetris.getPosition().X - 1, tetris.getPosition().Y))
 		{
 			tetris.moveBlock(tetris.getPosition().X - 1, tetris.getPosition().Y);
 		}
 		break;
 	case 80:
 		if (!tetris.isCollide(tetris.getPosition().X, tetris.getPosition().Y + 1))
 		{
 			tetris.moveBlock(tetris.getPosition().X, tetris.getPosition().Y + 1);
 		}
 		break;
 	case 72:
 		tetris.rotateBlock();
 	}
 }
 
 int main()
 {
 	Game game;
 	switch (game.menu())
 	{
 		case 1:
 			game.gameLoop();
 			break;
 		case 2:
 			return 0;
 		default:
 			std::cerr << "Choose 1~2" << std::endl;
 			return -1;
 	}
 }




**utility.h**

 #if defined(__linux__) || defined(__APPLE__)
 #include <sys/time.h>
 #include <termios.h> 
 #include <stdlib.h> 
 #include <unistd.h> 
 #include <stdio.h> 
 
 static struct termios g_old_kbd_mode;
 
 static void cooked(void)
 {
 tcsetattr(0, TCSANOW, &g_old_kbd_mode);
 }
 
 static void raw(void)
 {
 static char init;
 struct termios new_kbd_mode;
 
 if (init)
 {
 return;
 }
 
 tcgetattr(0, &g_old_kbd_mode);
 
 memcpy(&new_kbd_mode, &g_old_kbd_mode, sizeof(struct termios));
 
 new_kbd_mode.c_lflag &= ~(ICANON | ECHO);
 new_kbd_mode.c_cc[VTIME] = 0;
 new_kbd_mode.c_cc[VMIN] = 1;
 
 tcsetattr(0, TCSANOW, &new_kbd_mode);
 
 atexit(cooked);
 
 init = 1;
 }
 
 static int _kbhit(void)
 {
 struct timeval timeout;
 fd_set read_handles;
 int status;
 
 raw();
 
 FD_ZERO(&read_handles);
 
 FD_SET(0, &read_handles);
 
 timeout.tv_sec = timeout.tv_usec = 0;
 status = select(0 + 1, &read_handles, NULL, NULL, &timeout);
 
 if (status < 0)
 {
 printf("select() failed in kbhit()\n");
 exit(1);
 }
 
 return status;
 }
 
 static int _getch(void)
 {
 unsigned char temp;
 
 raw();
 
 if (read(0, &temp, 1) != 1)
 {
 return 0;
 }
 
 return temp;
 }
 
 bool gotoxy(unsigned short x = 1, unsigned short y = 1)
 {
 if ((x == 0) || (y == 0))
 {
 return false;
 }
 
 std::cout << "\x1B[" << y << ";" << x << "H";
 
 return true
 }
 
 void clearScreen(bool moveToStart = true)
 {
 std::cout << "\x1B[2J";
 
 if (moveToStart)
 {
 gotoxy(1, 1);
 }
 }
 
 #elif _WIN32
 
 #include <conio.h>
 #include <Windows.h>
 #include <tchar.h>
 
 namespace
 {
 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
 CONSOLE_SCREEN_BUFFER_INFO csbi;
 };
 
 void clearScreen()
 {
 static DWORD count;
 static DWORD cellCount;
 COORD homeCoords = { 0, 0 };
 
 if (!GetConsoleScreenBufferInfo(hStdOut, &csbi))
 std::cerr << "ERROR GetConsoleScreenBufferInfo - clearScreen : "
 << GetLastError() << std::endl;
 
 cellCount = csbi.dwSize.X *csbi.dwSize.Y;
 
 FillConsoleOutputCharacter(hStdOut, (TCHAR) ' ', cellCount, homeCoords, &count);
 
 FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, cellCount, homeCoords, &count);
 
 SetConsoleCursorPosition(hStdOut, homeCoords);
 }
 
 #else
 #error "OS not supported!"
 #endif

AltStyle によって変換されたページ (->オリジナル) /