首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >2048游戏机游戏实现

2048游戏机游戏实现
EN

Code Review用户
提问于 2018-02-15 21:31:14
回答 1查看 3.7K关注 0票数 6

所以我被要求使用C++做一个快速的2048游戏,但是只使用数组、结构、函数、指针之类的东西,而不使用向量、lambda等等。限制在这里,因为这只是一个学习练习(或家庭作业,我应该说)。

如果需要,我可以注释掉以下代码。这就是我到目前为止想出来的:

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

using namespace std;

enum colors 
{
    BLACK, BLUE, GREEN, CYAN, RED, PURPLE, YELLOW, GREY,
    LIGHTGREY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED,
    LIGHTPURPLE, LIGHTYELLOW, WHITE
};

void setConsoleColor(int textColor, int bgColor) 
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (textColor + (bgColor * 16)));
}

int randN(int start, int end)
{
    return rand() % (end - start) + start;
}

int randN(int n1, int n2, int percent)
{
    if ((rand() % 100) < percent)
        return n1;
    else
        return n2;
}

enum Direction
{
    Left = 75,
    Right = 77,
    Up = 72,
    Down = 80
};

struct Cell
{
    int x;
    int y;
    int value;

    Cell() 
    { 
        x = -1;
        y = -1;
        value = -1;
    }

    Cell(int x, int y, int value)
    {
        this->x = x;
        this->y = y;
        this->value = value;
    }

    void setValue(int value)
    {
        this->value = value;
    }

    void display()
    {
        cout << setw(4) << value;
    }

    bool isEmpty()
    {
        return value == 0;
    }

    bool isEqualTo(Cell cell)
    {
        return value == cell.value;
    }
};

struct Game
{
    Cell *cells;
    Cell *prevCells;
    int size;
    int score = 0;
    int highscore;
    int moves = 0;

    bool isMoved()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (!cells[i].isEqualTo(prevCells[i]))
            {
                moves += 1;
                return true;
            }
        }
        return false;
    }

    void Doubled(int points)
    {
        score += points;
    }

    bool isCellsFull()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].isEmpty())
                return false;
        }
        return true;
    }

    Cell &findCell(int x, int y)
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].x == x && cells[i].y == y)
                return cells[i];
        }
        return Cell();
    }

    Cell &randEmptyCell()
    {
        if (isCellsFull())
            return Cell();
        int i;
        do
        {
            i = randN(0, size * size);
        } while (!cells[i].isEmpty());
        return cells[i];
    }

    void create(int size)
    {
        this->size = size;
        cells = new Cell[size * size];
        prevCells = new Cell[size * size];

        for (int x = 0, c = 0; x < size; x++)
        {
            for (int y = 0; y < size; y++, c++)
            {
                cells[c] = Cell(x, y, 0);
            }
        }
    }

    void display()
    {
        for (int i = 0, c = 0; i < size + 1; i++)
        {
            for (int j = 0; j < size; j++)
                cout << " ----";
            cout << endl;
            if (i == size)
                break;
            for (int j = 0; j < size + 1; j++, c++)
            {
                cout << "|";
                if (j == size)
                    break;
                if (cells[c].isEmpty())
                    cout << setw(4) << " ";
                else
                {
                    setConsoleColor(LIGHTGREEN, BLUE);
                    cells[c].display();
                    setConsoleColor(GREY, BLACK);
                }
            }
            cout << endl;
        }
        cout << "Score: " << score << "  Highscore: " << highscore << endl;
        cout << "Moves: " << moves << endl;
    }

    void handleKey()
    {
        copy(cells, cells + size * size, prevCells);
        _getch();
        int ch = _getch();
        switch (ch)
        {
        case Left:
            moveCells(Left);
            break;
        case Right:
            moveCells(Right);
            break;
        case Up:
            moveCells(Up);
            break;
        case Down:
            moveCells(Down);
            break;
        }
    }

    void moveCells(Direction dir)
    {
        int start = dir == Left || dir == Up ? 0 : size - 1;
        int end = start == 0 ? size : -1;

        for (int x = 0; x < size; x++)
        {
            for (int y = start; y != end; (start < end ? y++ : y--))
            {
                Cell &c1 = dir == Left || dir == Right ? findCell(x, y) : findCell(y, x);
                if (c1.isEmpty())
                    continue;
                for (int k = y + (start == 0 ? -1 : 1); k != (end == -1 ? size : -1); (start < end ? k-- : k++))
                {
                    Cell &c2 = dir == Left || dir == Right ? findCell(x, k) : findCell(k, x);
                    Cell &c3 = dir == Left ?  findCell(x, k + 1) :
                               dir == Right ? findCell(x, k - 1) :
                               dir == Up ?    findCell(k + 1, x) :
                                              findCell(k - 1, x);
                    if (c1.isEqualTo(c2))
                    {
                        c2.setValue(c2.value * 2);
                        c1.setValue(0);
                        Doubled(c2.value);
                    }
                    else if (!c2.isEmpty() && c3.isEmpty())
                    {
                        c3.setValue(c1.value);
                        c1.setValue(0);
                    }
                    else if (k == start && c2.isEmpty())
                    {
                        c2.setValue(c1.value);
                        c1.setValue(0);
                    }
                }
            }
        }
    }

    void spawnCells(int amount)
    {
        for (int i = 0; i < amount; i++)
        {
            randEmptyCell().setValue(randN(2, 4, 85));
        }
    }

    bool isOver()
    {
        if (!isCellsFull())
            return false;
        for (int i = 0; i < size; i++)
        {
            for (int j = 0; j < size; j++)
            {
                Cell ¢er = findCell(i, j);
                Cell &right = findCell(i, j + 1);
                Cell &bottom = findCell(i + 1, j);

                if (center.isEqualTo(right) || center.isEqualTo(bottom))
                    return false;
            }
            return true;
        }
    }

    bool isWin()
    {
        for (int i = 0; i < size * size; i++)
        {
            if (cells[i].value == 2048)
                return true;
        }
        return false;
    }
};

void main()
{
    setlocale(LC_ALL, "rus");

    srand(time(0));

    bool playAgain = true;
    int highscore = 0;

    do
    {
        Game game;
        game.create(4);
        game.spawnCells(2);
        game.highscore = highscore;
        game.display();

        do
        {
            game.handleKey();
            if (game.isMoved())
                game.spawnCells(1);
            if (game.score > highscore)
                game.highscore = game.score;
            system("cls");
            game.display();
        } while (!game.isWin() && !game.isOver());

        if (game.isOver())
        {
            setConsoleColor(LIGHTRED, BLACK);
            cout << "You won!" << endl;
            setConsoleColor(GREY, BLACK);
        }
        else if (game.isWin())
        {
            setConsoleColor(LIGHTGREEN, BLACK);
            cout << "You lost!" << endl;
            setConsoleColor(GREY, BLACK);
        }

        cout << "Play again? y/n\n";
        char ch;
        do
        {
            cin >> ch;
            if (ch != 'y' && ch != 'n')
                cout << "Incorrect!\n";
        } while (ch != 'y' && ch != 'n');

        switch (ch)
        {
        case 'y':
            highscore = game.score;
            system("cls");
            break;
        case 'n':
            playAgain = false;
            break;
        }
    } while (playAgain);
}

你认为需要修改/简化什么?有没有增加可读性的方法?此外,我会感谢一些一般的技巧,编写控制台游戏,这样。

EN

回答 1

Code Review用户

回答已采纳

发布于 2018-02-16 05:22:55

坏码

此代码:

代码语言:javascript
复制
Cell &findCell(int x, int y)
{
    for (int i = 0; i < size * size; i++)
    {
        if (cells[i].x == x && cells[i].y == y)
            return cells[i];
    }
    return Cell();
}

...has是一个严重的问题-- return Cell();试图返回对临时对象的引用,当调用代码接收到该引用时,该对象将不再有效。猜测一下,您一定是在使用旧的编译器-- VC++和MinGW的当前版本都错误地拒绝了这一点。

不幸的是,解决这个问题可能并不简单--你几乎需要在某种程度上重新设计代码。可供选择的办法包括:

  1. 创建"not“对象(例如,具有静态生存期,可能是全局的),并在找不到搜索对象时返回对此的引用。
  2. 如果找不到对象,则抛出异常。
  3. 返回一个指针而不是一个引用,如果找不到,可以返回一个空指针。
  4. 可能返回一个Optional<T> (但您不能执行可选的引用,所以如果您真的想走这个路线,您可能需要类似于optional<reference_wrapper<Cell>>的东西)。

至少在另外一个例子中,randEmptyCell也有同样的问题。

两步初始化

您的Game类有一个create成员函数,在我看来,这个函数很像设计味。要创建一个Game,您应该just...create一个Game。创建一个Game对象,然后调用该对象上的create来真正创建一个实际的游戏,这是两个步骤的初始化,您通常希望避免这种初始化。一般来说,如果您需要做一些事情来创建一个对象,这应该在构造函数中完成。

不必要开关

在我看来,这个开关语句是这样的:

代码语言:javascript
复制
    switch (ch)
    {
    case Left:
        moveCells(Left);
        break;
    case Right:
        moveCells(Right);
        break;
    case Up:
        moveCells(Up);
        break;
    case Down:
        moveCells(Down);
        break;
    }
}

...is不必要的冗长,有点毫无意义。在大多数情况下,它等同于just:moveCells(ch);,尽管您可能需要确保ch首先是UpDownLeftRight中的一个。一种可能性(仍然使用switch语句)是:

代码语言:javascript
复制
switch (ch) {
case Up:
case Down:
case Left:
case Right:
    moveCells(ch);
}

这样可以更容易地完成这项工作。

srand/rand必须转到

虽然使用它们要做的工作要多一些,但我更喜欢使用random中的随机数生成器,而不是srand/rand。它们的质量通常要高得多,并且它们已经提供了处理在一个范围内创建随机数的代码。我花了一段时间来创建一个包装器,以使使用这些包装更加容易,并提出了以下内容:

代码语言:javascript
复制
#pragma once
#include <array>
#include <random>
#include <algorithm>

class generator {
    template <class Rand> 
    class Seed {
        class seeder {
            std::array < std::random_device::result_type, Rand::state_size > rand_data;
        public:
            seeder() {
                std::random_device rd;
                std::generate(rand_data.begin(), rand_data.end(), std::ref(rd));
            }

            typename std::array < std::random_device::result_type, Rand::state_size >::iterator begin() { return rand_data.begin(); }
            typename std::array < std::random_device::result_type, Rand::state_size >::iterator end() { return rand_data.end(); }
        } seed;

        std::seed_seq s;

    public:
        Seed() : s(seed.begin(), seed.end()) { }

        template <class I>
        auto generate(I a, I b) { return s.generate(std::forward<I>(a), std::forward<I>(b)); }
    };

    using Rand = std::mt19937_64;
    Seed<Rand> seed;
    Rand rng;
    std::uniform_int_distribution<int> uni;
public:
    generator(int high) : rng(seed), uni(0, high) {}
    generator(int low, int high) : rng(seed), uni(low, high) { }
    int operator()() { return uni(rng); }
};

我意识到这是一堆你现在可能不想处理的代码。为了保持它的简单使用,它是作为一个标题编写的,所以基本上只需执行以下操作:

代码语言:javascript
复制
#include "rand.h"

int main() { 
    generator g(100);    // create a generator for numbers from 0 to 100

    // generate and print out some numbers:
    for (int i = 0; i < 100; i++) {
        std::cout << g() << "\t";
        if (i % 10 == 0)
            std::cout << "\n";
    }
}

因此,即使它比我想要的代码更多,使用它也很容易(甚至比srand()/rand()更容易),而且可以生成更好的随机数。哦,还有一点是次要的:就目前的情况而言,这取决于C++ 17特性,它从传递给构造函数的值中推断模板参数。对于尚未实现该功能的编译器,请将generator g(100);更改为generator<int> g(100);

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

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

复制
相关文章

相似问题

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