首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于文本的俄罗斯方块游戏跟踪

基于文本的俄罗斯方块游戏跟踪
EN

Code Review用户
提问于 2014-12-21 02:25:03
回答 2查看 577关注 0票数 6

上一个问题:

基于文本的俄罗斯方块游戏

我没有Linux终端来确定我是否正确地实现了Linux版本,但希望它没有问题。

改进摘要:

  • 更多的OOP
  • 改进了类的名称和它们的函数,我希望它们现在还好
  • 更可移植的、独立的平台专用代码
  • 消除了“幻数”

在处理代码之前,我想知道是否有什么需要处理的。

代码语言:javascript
复制
#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;
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2014-12-21 05:50:33

我只是简单地评论一下我在浏览时看到的一些事情:

代码语言:javascript
复制
struct Block : public NonCopyable

通过将其定义为struct,可以使成员成为public (除非重写)。通常情况下,默认情况下,成员最好是private。如果您要将一个成员标记为public,那么您应该对该成员有一个特定的原因。这促使您编写不依赖于成员为public的代码。您似乎并没有公开使用您的public成员,所以最好将其设置为class。注意:这并不是说从来没有理由使用structpublic成员,只是我在这里没有看到这样的原因。

代码语言:javascript
复制
    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 };

代码中似乎仍然有很多神奇的数字。其中一些是重复的。

代码语言:javascript
复制
Random getRandom{ 0, 6 };

可能就像

代码语言:javascript
复制
Random getRandom{ 0, blocklist.size() };

我不明白以下触发因素是什么时候发生的:

代码语言:javascript
复制
        if (board.getDim(i, j + block.size()) > 1)
        {
            gameOver = true;

它可能在代码的某个地方,但我现在不想追踪它。评论一下我们为什么要和1做比较是有帮助的。

票数 3
EN

Code Review用户

发布于 2014-12-21 07:17:31

好的,莱纳斯很好。

但是:

代码语言:javascript
复制
#ifdef __linux__ 
..... Several pages of stuff.

#else
#error "OS not supported!"
#endif

似乎是个隐藏东西的好方法。

把一切都摆在前面。

代码语言:javascript
复制
#ifndef __linux__ 
#error "OS not supported!"
#endif

..... Several pages of stuff.

好的。我们是优秀的程序员,并且知道静态存储持续时间对象是零初始化的。但不是每个人都有。

代码语言:javascript
复制
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.

但是,对那些经验较少的人来说,把这一点说清楚会更好。

代码语言:javascript
复制
static char init = 0;

if (init) {
    return;
}

即便如此,这仍然是一种非常C的做事方式。构造师会更好。

前缀下划线从来不是个好主意。

代码语言:javascript
复制
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

平台相关代码:

代码语言:javascript
复制
bool gotoxy(unsigned short x = 1, unsigned short y = 1) {
    if ((x == 0) || (y == 0))
        return false;
    std::cout << "\x1B[" << y << ";" << x << "H";
}

有一些库可以隐藏终端依赖项,这些库可以更通用地实现这一点。查一下诅咒

好的。刚找到了。

代码语言:javascript
复制
#elif _WIN32

所以你有两个实现。在这种情况下,就更容易了。去做。

代码语言:javascript
复制
// Interface declaration here.
... STUFF

#ifdef __linux__ 
#include "<linux File.h>"
#elif _WIN33
#include "<Windowx File.h>"
#else
#error "OS not supported!"
#endif

静态三维只读列表:

代码语言:javascript
复制
static std::vector<std::vector<std::vector<int>>> block_list = { /* STUFF */ };

为什么不是std::array呢?或者只是一个静态的const C数组?

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/74357

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档