2
\$\begingroup\$

Previous question:

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

I've been told to use <random>. However, it seems a bit more complicated than it looks to generate random numbers. I have implemented it but I'm not quite sure whether I implemented it correctly or not.

What's the best practice for using <random> in this code particularly?

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

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<int DIM>
class TicTacToe
{
public:
    TicTacToe();
    TicTacToe(const TicTacToe &t) = delete;
    TicTacToe(const TicTacToe &&t) = delete;
    TicTacToe& operator = (const TicTacToe&) = delete;

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

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

// utility functor to compute matching condition
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 == 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<int DIM>
TicTacToe<DIM>::TicTacToe()
{
    mGrid.fill(Player::none);
}

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

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

    --remaining;

    mGrid[position] = player;
    return false;
}

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

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

    int j = 0;

    // checking condition loop
    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<int DIM>
void TicTacToe<DIM>::draw() const
{
    //Creating a onscreen grid
    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";
}

class Game
{
public:
    typedef std::default_random_engine Dre;
    Game() = default;
    void run(Dre);

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

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

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

    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(Dre e1)
{
    char row = 0;
    char column = 0;
    std::uniform_int_distribution<> uniform_dist(0, mDim - 1);
    auto getRandom = std::bind(uniform_dist, e1);

    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()
{
    std::random_device rd;
    std::default_random_engine e(rd());
    Game game;
    game.run(e);
}
\$\endgroup\$
2
  • \$\begingroup\$ This is a nice talk about rand and the new <random> library: channel9.msdn.com/Events/GoingNative/2013/… \$\endgroup\$
    – glampert
    Nov 29, 2014 at 14:00
  • 3
    \$\begingroup\$ Instead of foo == x ? true : false you can just use foo == x. \$\endgroup\$
    – ferada
    Nov 30, 2014 at 0:50

1 Answer 1

1
\$\begingroup\$

I believe this approach is better. I have created a struct called Random to initialize related random structs and data types:

// utility class for Random Number Generation
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;
};

Complete code:

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

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\$

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.