Skip to content

Instantly share code, notes, and snippets.

@MORTAL2000
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MORTAL2000/e9adc83b3d9c59d2ba71 to your computer and use it in GitHub Desktop.
Save MORTAL2000/e9adc83b3d9c59d2ba71 to your computer and use it in GitHub Desktop.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#if defined(__linux__) || defined(__APPLE__)
/*****************************************************************************
kbhit() and getch() for Linux/UNIX
Chris Giese <geezer@execpc.com> http://my.execpc.com/~geezer
Release date: ?
This code is public domain (no copyright).
You can do whatever you want with it.
****************************************************************************/
#include <sys/time.h> /* struct timeval, select() */
/* ICANON, ECHO, TCSANOW, struct termios */
#include <termios.h> /* tcgetattr(), tcsetattr() */
#include <stdlib.h> /* atexit(), exit() */
#include <unistd.h> /* read() */
#include <stdio.h> /* printf() */
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;
}
/* put keyboard (stdin, actually) in raw, unbuffered mode */
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);
/* when we exit, go back to normal, "cooked" mode */
atexit(cooked);
init = 1;
}
static int _kbhit(void)
{
struct timeval timeout;
fd_set read_handles;
int status;
raw();
/* check stdin (fd 0) for activity */
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();
/* stdin = fd 0 */
if (read(0, &temp, 1) != 1)
{
return 0;
}
return temp;
}
struct COORD { short X; short Y; };
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);
}
}
inline
void print(const std::string& str, COORD& coord)
{
gotoxy(coord.X, coord.Y);
std::cout << str << std::flush;
}
inline
void print(const char* str, COORD& coord)
{
gotoxy(coord.X, coord.Y);
std::cout << str << std::flush;
}
inline
void print(char c, COORD& coord)
{
gotoxy(coord.X, coord.Y);
std::cout << c << std::flush;
}
#elif _WIN32
#include <conio.h> /* kbhit(), getch() */
#include <Windows.h>
#include <tchar.h>
static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
static CONSOLE_SCREEN_BUFFER_INFO csbi;
void clearScreen()
{
DWORD count;
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);
}
void gotoxy(int x, int y)
{
COORD coord{ x, y };
SetConsoleCursorPosition(hStdOut, coord);
}
static DWORD cCharsWritten;
inline
void print(const std::string& str, COORD& coord)
{
WriteConsoleOutputCharacter(hStdOut, str.c_str(), str.length(), coord, &cCharsWritten);
}
inline
void print(const TCHAR* str, COORD& coord)
{
WriteConsoleOutputCharacter(hStdOut, str, _tcslen(str), coord, &cCharsWritten);
}
inline
void print(TCHAR c, COORD& coord)
{
FillConsoleOutputCharacter(hStdOut, c, 1, coord, &cCharsWritten);
}
#else
#error "OS not supported!"
#endif
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;
};
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 : private NonCopyable
{
public:
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));
};
private:
bool makeBlocks();
void moveBlock(int, int);
bool isCollide(int, int);
bool rotateBlock();
void isFull();
void checkLine();
virtual void display(){};
virtual void GameOverScreen() {};
Random getRandom{ 0, static_cast<int>(blockList.size() - 1) };
Matrix mStage;
Matrix mBlock;
int y = 0;
int x = ilBlock.size();
protected:
void initField();
void spawnBlock();
void userInput();
bool gameOver = false;
Matrix mBoard;
};
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();
display();
}
bool Tetris::makeBlocks()
{
x = ilBlock.size();
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()] = mStage[i][j + ilBlock.size()] + mBlock[i][j];
}
}
return false;
}
void Tetris::isFull()
{
for (auto i : ilBlock)
{
for (auto j : ilBlock)
{
if (mBoard[i][j + ilBlock.size()] > 1)
{
gameOver = true;
}
}
}
}
void Tetris::moveBlock(int x2, int y2)
{
//Remove block
for (auto i : ilBlock)
{
for (auto j : ilBlock)
{
mBoard[y + i][x + j] -= mBlock[i][j];
}
}
//Update coordinates
x = x2;
y = y2;
// assign a block with the updated value
for (auto i : ilBlock)
{
for (auto j : ilBlock)
{
mBoard[y + i][x + j] += mBlock[i][j];
}
}
display();
}
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)
{
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;
}
void Tetris::userInput()
{
const char key = _getch();
switch (key)
{
case 'd':
if (!isCollide(x + 1, y))
{
moveBlock(x + 1, y);
}
break;
case 'a':
if (!isCollide(x - 1, y))
{
moveBlock(x - 1, y);
}
break;
case 's':
if (!isCollide(x, y + 1))
{
moveBlock(x, y + 1);
}
break;
case ' ':
rotateBlock();
}
}
bool Tetris::rotateBlock()
{
Matrix temp(ilBlock.size(), std::vector<int>(ilBlock.size(), 0));
std::copy(mBlock.begin(), mBlock.end(), temp.begin());
// Rotate
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(x, y))
{ // And stop if it overlaps not be rotated
std::copy(temp.begin(), temp.end(), mBlock.begin());
return true;
}
for (auto i : ilBlock)
{
for (auto j : ilBlock)
{
mBoard[y + i][x + j] -= temp[i][j];
mBoard[y + i][x + j] += mBlock[i][j];
}
}
display();
return false;
}
void Tetris::spawnBlock()
{
if (!isCollide(x, y + 1))
{
moveBlock(x, y + 1);
}
else
{
checkLine();
makeBlocks();
isFull();
display();
}
}
class Game : public Tetris
{
public:
Game() = default;
int menu();
void gameLoop();
private:
void introScreen();
virtual void display();
virtual void gameOverScreen();
size_t GAMESPEED = 20000;
};
void Game::gameOverScreen()
{
COORD coord = { 0, 0 };
coord.Y += 2;
print(" ##### # # # ####### ####### # # ####### ######", coord); coord.Y++;
print("# # # # ## ## # # # # # # # #", coord);coord.Y++;
print("# # # # # # # # # # # # # # #", coord);coord.Y++;
print("# #### # # # # # ##### # # # # ##### ######", coord); coord.Y++;
print("# # ####### # # # # # # # # # #", coord); coord.Y++;
print("# # # # # # # # # # # # # #", coord); coord.Y++;
print(" ##### # # # # ####### ####### # ####### # #", coord);coord.Y += 2;
print("Press any key and enter", coord);
gotoxy((coord.X = strlen("Press any key and enter") + 1), coord.Y++);
char a;
std::cin >> a;
}
void Game::gameLoop()
{
size_t time = 0;
initField();
while (!gameOver)
{
if (_kbhit())
{
userInput();
}
if (time < GAMESPEED)
{
time++;
}
else
{
spawnBlock();
time = 0;
}
}
}
int Game::menu()
{
introScreen();
int select_num = 0;
std::cin >> select_num;
switch (select_num)
{
case 1:
case 2:
case 3:
break;
default:
select_num = 0;
break;
}
return select_num;
}
void Game::introScreen()
{
clearScreen();
COORD coord = { 0, 0 };
print("#==============================================================================#", coord); coord.Y += 2;
print("####### ####### ####### ###### ### #####", coord); coord.Y++;
print(" # # # # # # # #", coord); coord.Y++;
print(" # # # # # # #", coord); coord.Y++;
print(" # ##### # ###### # #####", coord); coord.Y++;
print(" # # # # # # #", coord); coord.Y++;
print(" # # # # # # # #", coord); coord.Y++;
print(" # ####### # # # ### ##### made for fun ", coord); coord.Y += 5;
print(" <Menu>", coord); coord.Y++;
print(" 1: Start Game", coord); coord.Y++;
print(" 2: Quit", coord); coord.Y += 2;
print("#==============================================================================#", coord); coord.Y++;
print("Choose >> ", coord);
coord.X = strlen("Choose >> ");
gotoxy(coord.X, coord.Y);
}
void Game::display()
{
clearScreen();
for (auto i : ilBoard)
{
for (auto j : ilBoardRow)
{
switch (mBoard[i][j])
{
case 0:
std::cout << ' ';
break;
case 9:
std::cout << '@';
break;
default:
std::cout << '#';
break;
}
}
std::cout << std::endl;
}
COORD coord = { 0, static_cast<short>(ilBoard.size()) };
print(" A: left S: down D: right Rotation[Space]", coord);
if (gameOver)
{
clearScreen();
gameOverScreen();
}
}
int main()
{
Game game;
switch (game.menu())
{
case 1:
game.gameLoop();
break;
case 2:
return 0;
default:
COORD coord = { 0, 0 };
print("Choose 1~2", coord);
return -1;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment