上一个问题:
我没有Linux终端来确定我是否正确地实现了Linux版本,但希望它没有问题。
改进摘要:
在处理代码之前,我想知道是否有什么需要处理的。
#include <iomanip>
#include <iostream>
#include <vector>
#include <random>
#ifdef __linux__
/*****************************************************************************
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";
}
void clearScreen(bool moveToStart = true) {
std::cout << "\x1B[2J";
if (moveToStart)
gotoxy(1, 1);
}
inline
void print(std::string& str, COORD& coord)
{
gotoxy(coord.X, coord.Y);
std::cout << str << std::flush;;
}
inline
void print(TCHAR* str, COORD& coord){
gotoxy(coord.X, coord.Y);
std::cout << str << std::flush;;
}
inline
void print(TCHAR& 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>
void clearScreen()
{
HANDLE hStdOut;
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD count;
DWORD cellCount;
COORD homeCoords = { 0, 0 };
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut == INVALID_HANDLE_VALUE) return;
if (!GetConsoleScreenBufferInfo(hStdOut, &csbi)) return;
cellCount = csbi.dwSize.X *csbi.dwSize.Y;
if (!FillConsoleOutputCharacter(
hStdOut,
(TCHAR) ' ',
cellCount,
homeCoords,
&count
)) return;
if (!FillConsoleOutputAttribute(
hStdOut,
csbi.wAttributes,
cellCount,
homeCoords,
&count
)) return;
/* Move the cursor home */
SetConsoleCursorPosition(hStdOut, homeCoords);
}
void gotoxy(int x, int y)
{
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
static CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD getXY()
{
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
COORD position;
position.X = csbi.dwCursorPosition.X;
position.Y = csbi.dwCursorPosition.Y;
return position;
}
static DWORD cCharsWritten = 0;
inline
void print(std::string& str, COORD& coord)
{
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), str.c_str(), str.length(), coord, &cCharsWritten);
}
inline
void print(TCHAR* str, COORD& coord)
{
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), str, _tcslen(str), coord, &cCharsWritten);
}
inline
void print(TCHAR& c, COORD& coord)
{
FillConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), c, 1, coord, &cCharsWritten);
}
#else
#error "OS not supported!"
#endif
static std::vector<std::vector<std::vector<int>>> block_list =
{
{
{ 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 }
}
};
struct NonCopyable
{
NonCopyable() = default;
NonCopyable(const NonCopyable &) = delete;
NonCopyable(const NonCopyable &&) = delete;
NonCopyable& operator = (const NonCopyable&) = delete;
};
struct Random : public NonCopyable
{
Random(int min, int max)
: mUniformDistribution(min, max)
{}
int operator()()
{
return mUniformDistribution(mEngine);
}
std::default_random_engine mEngine{ std::random_device()() };
std::uniform_int_distribution<int> mUniformDistribution;
};
struct Block : public NonCopyable
{
static const int ROTATIONS_IN_CIRCLE = 4;
int rotation_count = 0;
COORD coord;
std::vector<std::vector<int>> block;
Block()
{
block.resize(4, std::vector<int>(4, 0));
coord.X = 0;
coord.Y = 0;
}
void rotate()
{
++rotation_count;
while (rotation_count > ROTATIONS_IN_CIRCLE)
{
rotation_count -= ROTATIONS_IN_CIRCLE;
}
}
int& getDim(int row, int column)
{
switch (rotation_count % ROTATIONS_IN_CIRCLE)
{
default:
return block[row][column];
case 1:
return block[block.size() - column - 1][row];
case 2:
return block[block.size() - row - 1][block.size() - column - 1];
case 3:
return block[column][block.size() - row - 1];
}
}
size_t size()
{
return block.size();
}
};
struct Board : public NonCopyable
{
Board()
{
field.resize(22, std::vector<int>(13, 0));
coord.X = 0;
coord.Y = 0;
}
COORD coord;
std::vector<std::vector<int>> field;
int& getDim(int row, int column)
{
return field[row][column];
}
size_t size() const
{
return field.size();
}
size_t rowSize() const
{
return field[0].size();
}
};
struct Collidable : public NonCopyable
{
Collidable()
{
stage.resize(22, std::vector<int>(13, 0));
coord.X = 0;
coord.Y = 0;
}
COORD coord;
int& getDim(int row, int column)
{
return stage[row][column];
}
size_t size() const
{
return stage.size();
}
size_t rowSize() const
{
return stage[0].size();
}
std::vector<std::vector<int>> stage;
};
class Tetris : public NonCopyable
{
public:
Tetris()
: board(), block(), stage()
{};
bool makeBlocks();
void initField();
void moveBlock(int, int);
void collidable();
bool isCollide(int, int);
void userInput();
bool rotateBlock();
void spawnBlock();
virtual void display(){};
virtual void GameOverScreen() {};
protected:
int y = 0;
int x = 4;
bool gameOver = false;
Board board;
Block block;
Collidable stage;
Random getRandom{ 0, 6 };
};
void Tetris::initField()
{
for (size_t i = 0; i <= board.size() - 2; ++i)
{
for (size_t j = 0; j <= board.rowSize() - 2; ++j)
{
if ((j == 0) || (j == 11) || (i == 20))
{
board.getDim(i, j) = stage.getDim(i, j) = 9;
}
else
{
board.getDim(i, j) = stage.getDim(i, j) = 0;
}
}
}
makeBlocks();
display();
}
bool Tetris::makeBlocks()
{
x = 4;
y = 0;
int blockType = getRandom();
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
block.getDim(i, j) = 0;
block.getDim(i, j) = block_list[blockType][i][j];
}
}
for (size_t i = 0; i < block.size(); i++)
{
for (size_t j = 0; j < block.size(); j++)
{
board.getDim(i, j + block.size()) = stage.getDim(i, j + block.size()) + block.getDim(i, j);
if (board.getDim(i, j + block.size()) > 1)
{
gameOver = true;
return true;
}
}
}
return false;
}
void Tetris::moveBlock(int x2, int y2)
{
//Remove block
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
board.getDim(y + i, x + j) -= block.getDim(i, j);
}
}
//Update coordinates
x = x2;
y = y2;
// assign a block with the updated value
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
board.getDim(y + i, x + j) += block.getDim(i, j);
}
}
display();
}
void Tetris::collidable()
{
for (size_t i = 0; i < stage.size(); ++i)
{
for (size_t j = 0; j < stage.rowSize(); ++j)
{
stage.getDim(i, j) = board.getDim(i, j);
}
}
}
bool Tetris::isCollide(int x2, int y2)
{
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
if (block.getDim(i, j) && stage.getDim(y2 + i, x2 + j) != 0)
{
return true;
}
}
}
return false;
}
void Tetris::userInput()
{
char key;
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()
{
std::vector<std::vector<int>> tmp(block.size(), std::vector<int>(block.size(), 0));
for (size_t i = 0; i < tmp.size(); ++i)
{
for (size_t j = 0; j < tmp.size(); ++j)
{
tmp[i][j] = block.getDim(i, j);
}
}
for (size_t i = 0; i < block.size(); ++i)
{ //Rotate
for (size_t j = 0; j < block.size(); ++j)
{
block.getDim(i, j) = tmp[block.size() - 1 - j][i];
}
}
if (isCollide(x, y))
{ // And stop if it overlaps not be rotated
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
block.getDim(i, j) = tmp[i][j];
}
}
return true;
}
for (size_t i = 0; i < block.size(); ++i)
{
for (size_t j = 0; j < block.size(); ++j)
{
board.getDim(y + i, x + j) -= tmp[i][j];
board.getDim(y + i, x + j) += block.getDim(i, j);
}
}
display();
return false;
}
void Tetris::spawnBlock()
{
if (!isCollide(x, y + 1))
{
moveBlock(x, y + 1);
}
else
{
collidable();
makeBlocks();
display();
}
}
class Game : public Tetris
{
public:
Game() = default;
int menu();
virtual void gameOverScreen();
void gameLoop();
virtual void display();
void introScreen();
private:
size_t GAMESPEED = 20000;
};
void Game::gameOverScreen()
{
COORD coord = { 0, 0 };
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++;
print(" ##### # # # # ####### ####### # ####### # #", coord);
coord.Y += 2;
print("Press any key and enter", coord);
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++;
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 += 4;
coord.Y++;
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 (size_t i = 0; i < board.size(); ++i)
{
for (size_t j = 0; j < board.rowSize(); ++j)
{
switch (board.getDim(i, j))
{
case 0:
std::cout << " " << std::flush;
break;
case 9:
std::cout << "@" << std::flush;
break;
default:
std::cout << "#" << std::flush;
break;
}
}
std::cout << std::endl;
}
COORD coord = { 0, board.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;
case 0:
COORD coord = { 20, 20 };
print("Choose 1~2", coord);
return -1;
}
return 0;
}发布于 2014-12-21 05:50:33
我只是简单地评论一下我在浏览时看到的一些事情:
struct Block : public NonCopyable通过将其定义为struct,可以使成员成为public (除非重写)。通常情况下,默认情况下,成员最好是private。如果您要将一个成员标记为public,那么您应该对该成员有一个特定的原因。这促使您编写不依赖于成员为public的代码。您似乎并没有公开使用您的public成员,所以最好将其设置为class。注意:这并不是说从来没有理由使用struct或public成员,只是我在这里没有看到这样的原因。
block.resize(4, std::vector<int>(4, 0));
field.resize(22, std::vector<int>(13, 0));
stage.resize(22, std::vector<int>(13, 0));
int x = 4;
Random getRandom{ 0, 6 };
for (size_t i = 0; i <= board.size() - 2; ++i)
for (size_t j = 0; j <= board.rowSize() - 2; ++j)
if ((j == 0) || (j == 11) || (i == 20))
board.getDim(i, j) = stage.getDim(i, j) = 9;
x = 4;
COORD coord = { 20, 20 };代码中似乎仍然有很多神奇的数字。其中一些是重复的。
Random getRandom{ 0, 6 };可能就像
Random getRandom{ 0, blocklist.size() };我不明白以下触发因素是什么时候发生的:
if (board.getDim(i, j + block.size()) > 1)
{
gameOver = true;它可能在代码的某个地方,但我现在不想追踪它。评论一下我们为什么要和1做比较是有帮助的。
发布于 2014-12-21 07:17:31
好的,莱纳斯很好。
但是:
#ifdef __linux__
..... Several pages of stuff.
#else
#error "OS not supported!"
#endif似乎是个隐藏东西的好方法。
把一切都摆在前面。
#ifndef __linux__
#error "OS not supported!"
#endif
..... Several pages of stuff.好的。我们是优秀的程序员,并且知道静态存储持续时间对象是零初始化的。但不是每个人都有。
static char init; // Default initialization of static storage duration
// is by zero initialization so this is guaranteed to be zero.
if (init) // So we know that the first time this is executed.
return; // It will fall through and run the following code.但是,对那些经验较少的人来说,把这一点说清楚会更好。
static char init = 0;
if (init) {
return;
}即便如此,这仍然是一种非常C的做事方式。构造师会更好。
前缀下划线从来不是个好主意。
static int _kbhit(void)这实际上是一个预留的名字。不要使用前缀下划线。即使你知道规则(但你不知道),但并不是每个人都知道。所以即使你知道规则也不要这么做。见在C++标识符中使用下划线的规则是什么?。在本例中:Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace
平台相关代码:
bool gotoxy(unsigned short x = 1, unsigned short y = 1) {
if ((x == 0) || (y == 0))
return false;
std::cout << "\x1B[" << y << ";" << x << "H";
}有一些库可以隐藏终端依赖项,这些库可以更通用地实现这一点。查一下诅咒。
好的。刚找到了。
#elif _WIN32所以你有两个实现。在这种情况下,就更容易了。去做。
// Interface declaration here.
... STUFF
#ifdef __linux__
#include "<linux File.h>"
#elif _WIN33
#include "<Windowx File.h>"
#else
#error "OS not supported!"
#endif静态三维只读列表:
static std::vector<std::vector<std::vector<int>>> block_list = { /* STUFF */ };为什么不是std::array呢?或者只是一个静态的const C数组?
https://codereview.stackexchange.com/questions/74357
复制相似问题