6
\$\begingroup\$

Previous question:

Tic-Tac-Toe in C++11 - follow-up 4

How to improve this code further?

#include <iostream>
#include <cctype>
#include <algorithm>
#include <functional>
#include <array>
#include <random>
#include <ctime>

// empty struct for inheriting functionality
struct Mute
{
    Mute() = default;
    Mute(const Mute &) = delete;
    Mute(const Mute &&) = delete;
    Mute& operator = (const Mute&) = delete;
};

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

std::ostream& operator<<(std::ostream& os, Player p)
{
    return os << static_cast<char>(p);
}

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

template<std::size_t DIM>
class TicTacToe : public Mute
{
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 : public Mute
{
    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 == 0)
                return ((std::abs(number / DIM) - number % DIM) == mNumber);
            if (mNumber == 1)
                return ((std::abs(number / DIM) + number % DIM) == mNumber + DIM - 2);

        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))
            {
                if (k < 2)
                    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 : public Mute
{
    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 Mute
{
public:
    void run();

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

    static const std::size_t mDim = 3;
    TicTacToe<mDim> mGame;
    std::array<Player, 2> 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();
}
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Mute is not the best name for that helper class, IMHO. You have implemented the NonCopyable pattern with it, so it should be named accordingly. \$\endgroup\$
    – glampert
    Commented Dec 2, 2014 at 14:08
  • \$\begingroup\$ @glampert ... lol, i don't know to call it, i had situation which i have many classes and structs and all of them need tobe monitor for unnecessary calling for their constructors and overloading functions. i come up with idea to create a struct that has all functionality instead of repeating exact expressions for every each of them. \$\endgroup\$
    – MORTAL
    Commented Dec 2, 2014 at 15:49
  • \$\begingroup\$ Yes, we like to joke about it, but naming stuff is a hard problem when writing code ;) \$\endgroup\$
    – glampert
    Commented Dec 2, 2014 at 16:15

1 Answer 1

3
\$\begingroup\$

I think the Match structure is kind of an overkill, especially since you never use it like an object, it only exists for the function call. Just use simple functions instead.

template<int DIM>
bool MatchRow(int mNumber, int xNumber)
{
    return std::abs(xNumber/ DIM) == mNumber;
}

template<int DIM>
bool MatchColumn(int mNumber, int xNumber)
{
    return xNumber % DIM == mNumber;
}

template<int DIM>
bool MatchDiagonal(int mNumber, int xNumber)
{
    if(mNumber == 0)
    {
        return std::abs(xNumber / DIM) - xNumber % DIM == mNumber;
    }
    else if(mNumber == 1)
    {
        return std::abs(xNumber / DIM) + xNumber % DIM == mNumber + DIM - 2;
    }
    return false;
}

This also avoids the somewhat cryptic case where mNumber was not 0 or 1 and the switch passes through the case Type::diagonal to default to return false

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

    for (auto k = 0; k < DIM; ++k)
    {
        if (MatchRow<DIM>(k, x))
        {
            win[k] &= i == player;
        }
        if (MatchColumn<DIM>(k, x))
        {
            win[DIM + k] &= i == player;
        }
        if (MatchDiagonal<DIM>(k, x))
        {   // if (k < 2) we already know k is less than 2 because MatchDiagonal returned true!!
            win[2 * DIM + k] &= i == player;
        }
    }
}
\$\endgroup\$
6
  • \$\begingroup\$ you are right in case of DIM = 3. but what if DIM = 4,k here is needed tobe less than 2 for nXn dimension. \$\endgroup\$
    – MORTAL
    Commented Dec 3, 2014 at 15:36
  • \$\begingroup\$ @MORTAL if MatchDiagonal<DIM>(k, x), or the original Match<DIM>(Type::diagonal, k)(x), returns true; k has to be equal to either 0 or 1. Thus k < 2 is redundant! \$\endgroup\$
    – flakes
    Commented Dec 3, 2014 at 15:42
  • \$\begingroup\$ you are right. it was my mistake. \$\endgroup\$
    – MORTAL
    Commented Dec 3, 2014 at 15:58
  • \$\begingroup\$ @MORTAL Test it for yourself. The size of win is 2 * (DIM + 1). The index of win at this line will be (2 * DIM) + k where k = 0, 1. Therefore you must satisfy 2 * (DIM + 1) > (2 * DIM) + k => DIM + 1 > DIM + k/2 => 1 > k/2 Plugging in possible values of k into this equation gives 1 > 1/2 and 1 > 0 which are both true! \$\endgroup\$
    – flakes
    Commented Dec 3, 2014 at 15:58
  • \$\begingroup\$ @MORTAL Glad to help!! \$\endgroup\$
    – flakes
    Commented Dec 3, 2014 at 15:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.