首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AL *N Tic Tac Tac Toe游戏

AL *N Tic Tac Tac Toe游戏
EN

Code Review用户
提问于 2015-07-10 14:58:52
回答 1查看 941关注 0票数 3

下面是一个简单的Tic Tac脚趾游戏。我想知道如何进一步改进这段代码。

代码语言:javascript
复制
#include <iostream>
#include <cctype>
#include <array>
#include <random>

enum struct Player : char
{
    none    = '-',
    first   = 'X',
    second  = 'O'
};

std::ostream& operator<<(std::ostream& os, Player const& p)
{
    return os << std::underlying_type<Player>::type(p);
}

enum struct Type : int
{
    row,
    column,
    diagonal
};

enum struct Diagonals : int
{
    leftTopRightBottom,
    rightTopleftBottom
};

template<std::size_t DIM>
class TicTacToe 
{
public:
    TicTacToe();

    bool isFull() const;
    void draw() const;
    bool isWinner(Player player) const;
    bool applyMove(Player player, std::size_t row, std::size_t column);

private:
    std::size_t mRemain = DIM * DIM;
    std::array<Player, DIM * DIM> mGrid;
};

template<int DIM>
struct Match 
{
    Match(Type t, int i) 
        : mCategory(t)
        , mNumber(i)
    {}

    bool operator() (int number) const
    {
        switch (mCategory)
        {
        case Type::row:
            return (std::abs(number / DIM) == mNumber);

        case Type::column:
            return (number % DIM == mNumber);

        case Type::diagonal:
            if (mNumber == static_cast<int>(Diagonals::leftTopRightBottom))
            {
                return ((std::abs(number / DIM) - number % DIM) == mNumber);
            }

            if (mNumber == static_cast<int>(Diagonals::rightTopleftBottom))
            {
                return ((std::abs(number / DIM) + number % DIM) == DIM - mNumber);
            }

        default:
            return false;
        }
    }

    Type mCategory;
    int mNumber;
};

template<std::size_t DIM>
TicTacToe<DIM>::TicTacToe()
{
    mGrid.fill(Player::none);
}

template<std::size_t DIM>
bool TicTacToe<DIM>::applyMove(Player player, std::size_t row, std::size_t column)
{
    std::size_t position = row + DIM * column;

    if ((position > mGrid.size()) || (mGrid[position] != Player::none))
    {
        return true;
    }

    --mRemain;

    mGrid[position] = player;

    return false;
}

template<std::size_t DIM>
bool TicTacToe<DIM>::isFull() const
{
    return (mRemain == 0);
}

template<std::size_t DIM>
bool TicTacToe<DIM>::isWinner(Player player) const
{
    std::array<bool, 2 * (DIM + 1)> win;

    win.fill(true);

    int j = 0;

    for (auto i : mGrid)
    {
        int x = j++;

        for (auto k = 0; k < DIM; ++k)
        {
            if (Match<DIM>(Type::column, k)(x))
            {
                win[k] &= i == player;
            }

            if (Match<DIM>(Type::row, k)(x))
            {
                win[DIM + k] &= i == player;
            }

            if (Match<DIM>(Type::diagonal, k)(x))
            {
                win[2 * DIM + k] &= i == player;
            }
        }
    }

    for (auto i : win)
    {
        if (i)
        {
            return true;
        }
    }

    return false;
}

template<std::size_t DIM>
void TicTacToe<DIM>::draw() const
{
    std::cout << ' ';
    for (auto i = 1; i <= DIM; ++i)
    {
        std::cout << "  " << i;
    }

    int j = 0;
    char A = 'A';

    for (auto i : mGrid)
    {
        if (j == 0)
        {
            std::cout << "\n " << A++;
            j = DIM;
        }
        --j;

        std::cout << ' ' << i << ' ';
    }

    std::cout << "\n\n";
}

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

class Game 
{
public:
    void run();

private:
    void showResult() const;
    void turn();

    static const std::size_t mDim = 4;
    int mNumberOfPlayers = 2;
    TicTacToe<mDim> mGame;
    std::array<Player, mNumberOfPlayers> mPlayers{ { Player::first, Player::second } };
    int mPlayer = 1;
    Random getRandom{ 0, mDim - 1 };
};

void Game::run()
{
    while (!mGame.isWinner(mPlayers[mPlayer]) && !mGame.isFull())
    {
        mPlayer ^= 1;
        mGame.draw();
        turn();
    }

    showResult();
}

void Game::showResult() const
{
    mGame.draw();

    if (mGame.isWinner(mPlayers[mPlayer]))
    {
        std::cout << "\n" << mPlayers[mPlayer] << " is the Winner!\n";
    }
    else
    {
        std::cout << "\nTie game!\n";
    }
}

void Game::turn()
{
    char row = 0;
    char column = 0;

    for (bool pending = true; pending;)
    {
        switch (mPlayers[mPlayer])
        {
        case Player::first:
            std::cout << "\n" << mPlayers[mPlayer] << ": Please play. \n";
            std::cout << "Row(1,2,3,...): ";
            std::cin >> row;
            std::cout << mPlayers[mPlayer] << ": Column(a,b,c,...): ";
            std::cin >> column;

            column = std::toupper(column) - 'A';
            row -= '1';

            pending = column < 0 || row < 0 || mGame.applyMove(mPlayers[mPlayer], row, column);

            if (pending)
            {
                std::cout << "Invalid position.  Try again.\n";
            }
            break;
        case Player::second:
            row = getRandom();
            column = getRandom();

            pending = mGame.applyMove(mPlayers[mPlayer], row, column);
            break;
        }
    }

    std::cout << "\n\n";
}

int main()
{
    Game game;
    game.run();
}
EN

回答 1

Code Review用户

发布于 2015-07-10 15:28:48

我认为有几件事值得指出。

匹配

对于Match来说,没有理由选择一个类别或其他任何东西。您不需要TypeDiagonals枚举。给定移动时,sohuld可以确定该移动是否完成了行、列或主要对角线。

代码语言:javascript
复制
// return true if given player won with the given move,
// false otherwise
bool won(Player, size_t row, size_t column);

TicTacToe

您可能会发现,只使用二维数组来表示一个二维对象就更容易了。这条路:

代码语言:javascript
复制
bool applyMove(Player player, size_t row, size_t col)
{
    Player& square = mGrid.at(row).at(col);
    if (square == Player::none) {
        square = player;
        return true;
    }
    else {
        return false;
    }
}

此外,您还将注意到,我翻转了返回类型的applyMove。返回成功的true更有意义。此外,这将抛出std::out_of_range,而不是静默地什么也不做--这样您就可以在Game中捕获它并记录适当的消息。这就区分了无效移动和试图在坏方块上玩的行为。

对策

getRandom成为成员变量而不是成员函数是令人困惑的。我会改的。我花了一段时间才找到第一次。现在,turn()应该只服从于适当的用户:

代码语言:javascript
复制
void turn() {
    if (mPlayers[mPlayer] == Player::first) {
        userTurn();
    }
    else {
        compTurn();
    }
}

挂起的循环很奇怪。做完每一次子案例都要返回。

同样,对于compTurn部分来说,每次选择一个随机正方形是非常低效率的。如果只剩下一个开放的平方,那么您需要花费平均的N^2循环来尝试找到它。相反,首先找到空方块,然后随机选择一个:

代码语言:javascript
复制
std::vector<std::pair<size_t, size_t>> open;
for (size_t r = 0; r < mDim; ++r) {
    for (size_t c = 0; c < mDim; ++c) {
        if (mGame.isOpen(r, c)) {
            open.emplace_back(r, c);
        }
    }
}

auto choice = chooseRandom(open);
applyMove(Player::second, choice.first, choice.second);

另外,TicTacToe成员对象应该真正命名为board。毕竟这不是GameGame已经是Game了。

最后,为什么TicTacToe是类模板?如果将维度设置为变量并使用vector<vector<Player>>,则可以预先请求维度作为输入。

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

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

复制
相关文章

相似问题

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