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

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

Code Review用户
提问于 2014-12-20 15:46:55
回答 2查看 37.6K关注 0票数 11

我怎样才能改进这个游戏?

代码语言:javascript
复制
#include <iomanip>
#include <iostream>
#include <vector>
#include <random>
#include <conio.h>


struct Random
{
    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;
};

std::vector<std::vector<int>> stage(22, std::vector<int>(13, 0));
std::vector<std::vector<int>> block = 
{ 
    { 0, 0, 0, 0 },
    { 0, 0, 0, 0 },
    { 0, 0, 0, 0 },
    { 0, 0, 0, 0 } 
};

std::vector<std::vector<int>> field(22, std::vector<int>(13, 0));
// coordinate
int y = 0; 
int x = 4;
bool gameover = false;
size_t GAMESPEED = 20000; 

Random getRandom{ 0, 6 };

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 } 
    } 
};

int menu(); 
int gameOver(); 
void title(); 
void gameLoop(); 
void display(); 
bool makeBlocks(); 
void initGame(); 
void moveBlock(int, int); 
void collidable();
bool isCollide(int, int); 
void userInput(); 
bool rotateBolck(); 
void spwanBlock(); 

int main()
{
    switch (menu())
    {
    case 1:
        gameLoop();
        break;
    case 2:
        return 0;
    case 0:
        std::cerr << "Choose 1~2" << std::endl;
        return -1;
    }
    return 0;
}

int gameOver()
{
    using namespace std;

    char a;
    cout << " #####     #    #     # ####### ####### #     # ####### ######\n" ;
    cout << "#     #   # #   ##   ## #       #     # #     # #       #     #\n";
    cout << "#        #   #  # # # # #       #     # #     # #       #     #\n";
    cout << "#  #### #     # #  #  # #####   #     # #     # #####   ######\n";
    cout << "#     # ####### #     # #       #     #  #   #  #       #   #\n";
    cout << "#     # #     # #     # #       #     #   # #   #       #    #\n";
    cout << " #####  #     # #     # ####### #######    #    ####### #     #\n";
    cout << "\n\nPress any key and enter\n";
    cin >> a;
    return 0;
}

void gameLoop()
{
    size_t time = 0;
    initGame();

    while (!gameover) 
    { 
        if (kbhit()) 
        {
            userInput();
        }

        if (time < GAMESPEED)
        {
            time++;
        }
        else 
        {
            spwanBlock();
            time = 0;
        }
    }

}

int menu()
{
    title();

    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 title()
{
    using namespace std;

    system("cls");

    cout << "#==============================================================================#\n";

    cout << "####### ####### ####### ######    ###    #####\n";
    cout << "   #    #          #    #     #    #    #     #\n";
    cout << "   #    #          #    #     #    #    #\n";
    cout << "   #    #####      #    ######     #     #####\n";
    cout << "   #    #          #    #   #      #          #\n";
    cout << "   #    #          #    #    #     #    #     #\n";
    cout << "   #    #######    #    #     #   ###    #####\t\tmade for fun \n";
    cout << "\n\n\n\n";

    cout << "\t<Menu>\n";
    cout << "\t1: Start Game\n\t2: Quit\n\n";

    cout << "#==============================================================================#\n";
    cout << "Choose >> ";
}

void display()
{
    system("cls");

    for (size_t i = 0; i < 21; i++) 
    {
        for (size_t j = 0; j < 12; j++) 
        {
            switch (field[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;
    }

    std::cout << "\n\tA: left\tS: down\tD: right \t Rotation[Space]";

    if (gameover)
    {
        system("cls");
        gameOver();
    }
}

void initGame()
{
    for (size_t i = 0; i <= 20; i++)
    {
        for (size_t j = 0; j <= 11; j++)
        {
            if ((j == 0) || (j == 11) || (i == 20)) 
            {
                field[i][j] = stage[i][j] = 9;
            }
            else
            {
                field[i][j] = stage[i][j] = 0;
            }
        }
    }

    makeBlocks();

    display();
}

bool makeBlocks()
{
    x = 4;
    y = 0;

    int blockType = getRandom();

    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            block[i][j] = 0;
            block[i][j] = block_list[blockType][i][j];
        }
    }

    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            field[i][j + 4] = stage[i][j + 4] + block[i][j];

            if (field[i][j + 4] > 1)
            {
                gameover = true;
                return true;
            }
        }
    }
    return false;
}

void moveBlock(int x2, int y2)
{

    //Remove block
    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            field[y + i][x + j] -= block[i][j];
        }
    }
    //Update coordinates
    x = x2;
    y = y2;

    // assign a block with the updated value
    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            field[y + i][x + j] += block[i][j];
        }
    }

    display();
}

void collidable()
{
    for (size_t i = 0; i<21; i++)
    {
        for (size_t j = 0; j<12; j++)
        {
            stage[i][j] = field[i][j];
        }
    }
}

bool isCollide(int x2, int y2)
{
    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            if (block[i][j] && stage[y2 + i][x2 + j] != 0)
            {
                return true;
            }
        }
    }
    return false;
}

void 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 ' ':
        rotateBolck();
    }
}

bool rotateBolck()
{
    std::vector<std::vector<int>> tmp(4, std::vector<int>(4, 0));

    for (size_t i = 0; i < 4; i++)
    { //Save temporarily block
        for (size_t j = 0; j < 4; j++)
        {
            tmp[i][j] = block[i][j];
        }
    }

    for (size_t i = 0; i < 4; i++)
    { //Rotate
        for (size_t j = 0; j < 4; j++)
        {
            block[i][j] = tmp[3 - j][i];
        }
    }

    if (isCollide(x, y))
    { // And stop if it overlaps not be rotated
        for (size_t i = 0; i < 4; i++)
        {
            for (size_t j = 0; j < 4; j++)
            {
                block[i][j] = tmp[i][j];
            }
        }
        return true;
    }

    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            field[y + i][x + j] -= tmp[i][j];
            field[y + i][x + j] += block[i][j];
        }
    }

    display();

    return false;
}

void spwanBlock()
{
    if (!isCollide(x, y + 1))
    {
        moveBlock(x, y + 1);
    }
    else
    {
        collidable();
        makeBlocks();
        display();
    }
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2014-12-20 20:53:24

我看到了一些可以帮助您改进这段代码的东西。

不使用system("cls")

不使用system("cls")system("pause")有两个原因。首先,它不能移植到您现在可能关心或可能不关心的其他操作系统上。第二,这是一个安全漏洞,你绝对必须关心。具体来说,如果某个程序被定义并命名为clspause,那么您的程序将执行该程序而不是您想要的,而其他程序可能是任何东西。首先,将它们隔离为单独的函数cls()pause(),然后修改代码以调用这些函数而不是system。然后重写这些函数的内容,以便使用C++执行您想要的操作。例如,如果您的终端支持ANSI逃逸序列,则可以使用以下内容:

代码语言:javascript
复制
void cls()
{
    std::cout << "\x1b[2J";
}

隔离平台专用代码

在这段代码中,有几件事情是DOS/Windows的,其中包括#include <conio.h>getch()kbhit()函数,还有我已经提到的system("cls");。如果我提供了这些缺失的函数,那么您的代码在Linux上成功运行,但是如果代码中已经有一个#ifdef WINDOWS,那么您的代码就可以重新编译,而不必修改源代码。

修复拼写错误

代码有spwanBlock()而不是spawnBlock()rotateBolck()而不是rotateBlock()。这种类型的排字根本不影响编译器,但它们会困扰代码的人类读者,使其更难理解和维护。

使用更多对象

游戏更多地是用C的过程风格编写的,而不是用面向对象的C++风格编写的。游戏本身可以是一个对象,大部分过程都是该对象的函数。这将减少耦合,并使程序更容易理解。而且,每个块显然都是一个对象。它还将消除占用代码的全局变量,如xygameover

尽可能地减少可变范围,

几乎所有的全局变量都可以通过使用对象来消除,但如果仍然存在,它们应该是static,以便将它们限制在文件范围内,或者更好地使用匿名命名空间。

使用字符串连接

gameOver()title()函数都有许多重复行,其中ostream operator<<std::cout和一个固定字符串一起多次使用。这些多个电话不需要发生。您可以简单地依赖于这样一个事实,即C++自动合并单独的常量字符串。例如,下面是一个已编码的gameOver()

代码语言:javascript
复制
void gameOver()
{
    std::cout << "\n"
            " #####     #    #     # ####### ####### #     # ####### ######\n" 
            "#     #   # #   ##   ## #       #     # #     # #       #     #\n"
            "#        #   #  # # # # #       #     # #     # #       #     #\n"
            "#  #### #     # #  #  # #####   #     # #     # #####   ######\n"
            "#     # ####### #     # #       #     #  #   #  #       #   #\n"
            "#     # #     # #     # #       #     #   # #   #       #    #\n"
            " #####  #     # #     # ####### #######    #    ####### #     #\n"
            "\n\nPress any key and enter\n";
    char a;
    std::cin >> a;
}

这里要注意一些简单的事情。首先,只有一个输入和一个输出调用,因此using namespace std;似乎不值得,因此被删除了。其次,虚拟变量a是在使用之前声明的,而不是在函数开始时声明的。第三,由于返回变量既不有用也不使用,所以省略了它。

消除了“幻数”

代码,包括initGame()的实现,充满了“神奇数字”--即文本中没有明显意义的原始数字。例如:

代码语言:javascript
复制
void initGame()
{
    for (size_t i = 0; i <= 20; i++)
    {
        for (size_t j = 0; j <= 11; j++)
        {
            if ((j == 0) || (j == 11) || (i == 20)) 
            {
                field[i][j] = stage[i][j] = 9;
            }

在这段代码中,20119的含义一点也不明显。有意义地命名常量将是更好的方法来做到这一点。

在实际的

中使用const

GAMESPEED这样的变量不会被程序改变,因此应该被声明为const

使用更好的计时

当前的gameLoop()例程使用一个非常粗糙的增量循环来进行计时。这将更好地使用来自std::chrono的东西来实现,或者更好,可以使用异步编程模型而不是同步方案来重写整个过程。

增强游戏

最初的俄罗斯方块游戏会删除一行,一旦它被完全填充,但这段代码不这样做。它也保持得分,原来的游戏是彩色的。您可以添加所有这些东西,前两项通过对代码进行功能更改,最后一项通过前面提到的ANSI逃逸序列添加。这将是一个更有趣的游戏。

票数 12
EN

Code Review用户

发布于 2014-12-20 20:59:39

您的switch语句的格式在我看来很奇怪。

switch (field[i][j]) { case 0: std::cout << " " << std::flush; break; case 9: std::cout << "@" << std::flush; break; default: std::cout << "#" << std::flush; break; }

我宁愿把案子的陈述写进去,

代码语言:javascript
复制
        switch (field[i][j]) 
        {
            case 0:
                std::cout << " " << std::flush;
                break;
            case 9:
                std::cout << "@" << std::flush;
                break;
            default:
                std::cout << "#" << std::flush;
                break;
        }

这样看来,Case语句就像它们应该是Switch的子语句一样。

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

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

复制
相关文章

相似问题

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