3
\$\begingroup\$

Previous question:

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

Here, AI is being implemented.

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

template <typename T>
T getRandom(T min, T max)
{
    return (rand() % (max + 1 - min) + min);
}

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 = 0,
    column = 1,
    diagonal = 2
};

template<int mesh>
class TicTacToe
{
public:
    TicTacToe();
    TicTacToe(const TicTacToe &t) = delete;
    TicTacToe(const TicTacToe &&t) = delete;

    bool isFull() const;
    void draw() const;
    bool isWinner(Player player) const;
    bool applyMove(Player player, unsigned row, unsigned column);

private:
    unsigned remaining = mesh * mesh;
    std::array<Player, mesh * mesh> mGrid;
};

// utility functor to compute matching condition
template<int mesh>
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 / mesh) == mNumber);
        case Type::column:
            return (number % mesh == mNumber);
        case Type::diagonal:
            if (mNumber == 0)
                return ((std::abs(number / mesh) - number % mesh) == mNumber);
            if (mNumber == 1)
                return ((std::abs(number / mesh) + number % mesh) == mNumber + mesh - 2);
        }

        return false;
    }
    Type mCategory;
    int mNumber;
};

template<int mesh>
TicTacToe<mesh>::TicTacToe()
{
    mGrid.fill(Player::none);
}

template<int mesh>
bool TicTacToe<mesh>::applyMove(Player player, unsigned row, unsigned column)
{
    unsigned position = row + mesh * column;
    if ((position > mGrid.size()) || (mGrid[position] != Player::none))
        return true;
    --remaining;
    mGrid[position] = player;
    return false;
}

template<int mesh>
bool TicTacToe<mesh>::isFull() const
{
    return (remaining == 0) ? true : false;
}

template<int mesh>
bool TicTacToe<mesh>::isWinner(Player player) const
{
    // check for row or column wins
    std::array<bool, 2 * (mesh + 1)> win;
    win.fill(true);

    int j = 0;

    // checking condition loop
    std::for_each(mGrid.begin(), mGrid.end(),
        [&](Player i)
    {
        int x = j++;

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

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

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

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

    return false;
}

template<int mesh>
void TicTacToe<mesh>::draw() const
{
    //Creating a onscreen grid
    std::cout << ' ';
    for (auto i = 1; i <= mesh; ++i)
        std::cout << "  " << i;

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

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

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

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

class Game
{
public:
    Game() = default;
    void run();

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

    static const unsigned mMesh = 3;
    TicTacToe<mMesh> mGame;
    std::array<Player, 2> mPlayers{ { Player::first, Player::second } };
    int mPlayer = 1;
};

void Game::run()
{
    std::cout   << "Please choose Game mod: \n"
                << " 1 - Player vs Player\n"
                << " 2 - player vs Computer\n\n> ";
    int choice;
    std::cin >> choice;

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

    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(int n)
{
    char row = 0;
    char column = 0;
    if (n == 1)
    {
        std::cout << "\n" << mPlayers[mPlayer] << ": Please play. \n";

        for (bool pending = true; pending;)
        {
            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";
        }
    }
    else
    {
        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(0, int(mMesh - 1));
                column = getRandom(0, int(mMesh - 1));
                pending = mGame.applyMove(mPlayers[mPlayer], row, column);
                break;
            }

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

int main()
{
    srand(static_cast<unsigned int>(time(0)));
    Game game;
    game.run();
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

I'll make a few suggestions on coding style and improvements:

  • For getRandom(), you could be using the new <random> library. rand() should be accurate enough for your purposes, but you can try the new library to also get more familiar with it. One small detail with rand() though. It is declared in the <cstdlib> header, so you should include it if you're using the function. Also, the proper way to reference it would be from the std namespace: std::rand(). Same goes for std::srand().

  • It would look neater to align the assignments in the enum constants:

    enum struct Player : char
    {
        none   = '-',
        first  = 'X',
        second = 'O'
    };
    
  • Initializing enum constants with a 0,1,2 integer sequence is pointless, since the enum does exactly that if you don't initialize its constants:

    enum struct Type : int
    {
        row,
        column,
        diagonal
    };
    
  • template<int mesh>: Template parameter names are usually written using ALL_CAPS. Most people also prefer them to be single letter names, like the famous T for a type. I would replace mesh with MESH to differentiate it from a variable name. YMMV, however, I don't find "mesh" to be the most adequate name here. This parameter is one of the board's dimensions, so it could be SIZE or DIM.

  • You've made TicTacToe non-copyable and non-movable. It would also make sense deleting the assignment operator: TicTacToe& operator = (const TicTacToe&) = delete;

  • The for_each in TicTacToe::isWinner could be replaced by a range based for loop, like you have used in several other places. That would make the code more uniform.


Overall, I think it is quite good and modern C++. You are on the right track.

\$\endgroup\$

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.