Skip to main content
2 of 5
deleted 115 characters in body; edited title
MORTAL
  • 3.3k
  • 5
  • 25
  • 40

Tic-Tac-Toe in C++

Is there any way to improve this code.

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

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

enum struct Lines : int
{
    first   = 0,
    second  = 1,
    third   = 2
};

class TicTacToe
{
public:
    TicTacToe(); 
    bool isFull() const;
    void draw() const;
    void turn(Player player);
    bool check(Player player) const;

private:
    bool applyMove(Player player, int position);

    static const std::size_t mDim = 3;
    std::array<Player, mDim * mDim> mGrid;
};

// utility functor to compute matching condition
template<int dim>
struct Match 
{
    Match(Type t, Lines i) : mCategory(t), mNumber(i){}
    bool operator() (int number) const
    {
        switch (mCategory)
        {
        case Type::row:
            return (std::abs(number / dim) == static_cast<int>(mNumber));
        case Type::column:
            return (number % dim == static_cast<int>(mNumber));
        case Type::diagonal:
            if (mNumber == Lines::first)
                return ((std::abs(number / dim) - number % dim) == static_cast<int>(mNumber));
            else
                return ((std::abs(number / dim) + number % dim) == static_cast<int>(mNumber));
        }
        
        return false; 
    }
    Type mCategory;
    Lines mNumber;
};

TicTacToe::TicTacToe() 
{ 
    mGrid.fill(Player::none);
}

bool TicTacToe::applyMove(Player player, int position)
{
    if (mGrid[position] != Player::none)
        return false;
    mGrid[position] = player;
    return true;
}

bool TicTacToe::isFull() const
{
    return 0 == std::count_if(mGrid.begin(), mGrid.end(),
        [](Player i)
    {
        return i == Player::none;
    });
}

bool TicTacToe::check(Player player) const
{
    // check for row or column wins
    std::array<bool, 8> win;
    win.fill(true);

    int j = 0;

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

        // columns
        if (Match<mDim>(Type::column, Lines::first)(x))
            win[0] &= i == player;;
        if (Match<mDim>(Type::column, Lines::second)(x))
            win[1] &= i == player;
        if (Match<mDim>(Type::column, Lines::third)(x)) 
            win[2] &= i == player;

        // rows
        if (Match<mDim>(Type::row, Lines::first)(x))
            win[3] &= i == player;
        if (Match<mDim>(Type::row, Lines::second)(x))
            win[4] &= i == player;
        if (Match<mDim>(Type::row, Lines::third)(x))
            win[5] &= i == player;

        // diagonals
        if (Match<mDim>(Type::diagonal, Lines::first)(x))
            win[6] &= i == player;
        if (Match<mDim>(Type::diagonal, Lines::third)(x))
            win[7] &= i == player;
    });

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

    return false;
}

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

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

    for (auto i : mGrid)
    {
        if (Match<mDim>(Type::column, Lines::first)(j++))
            std::cout << "\n " << A++;

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

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

void TicTacToe::turn(Player player)
{
    char row = 0;
    char column = 0;
    std::size_t position = 0;
    bool applied = false;

    std::cout << "\n" << player << ": Please play. \n";

    while (!applied)
    {
        std::cout << "Row(1,2,3,...): ";
        std::cin >> row;
        std::cout << player << ": Column(A,B,C,...): ";
        std::cin >> column;

        position = mDim * (std::toupper(column) - 'A') + (row - '1');

        if (position < mGrid.size())
        {
            applied = applyMove(player, position);

            if (!applied)
                std::cout << "Already Used. Try Again. \n";
        }
        else
        {
            std::cout << "Invalid position.  Try again.\n";
        }
    }

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

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

private:
    TicTacToe mTicTacToe;
    std::array<Player, 2> mPlayers{ { Player::first, Player::second } };
    int mPlayer = 1;

    void resultScreen(bool winner);
    std::function<void()>       display = std::bind(&TicTacToe::draw, &mTicTacToe);
    std::function<void(Player)> turn    = std::bind(&TicTacToe::turn, &mTicTacToe, std::placeholders::_1);
    std::function<bool(Player)> win     = std::bind(&TicTacToe::check, &mTicTacToe, std::placeholders::_1);
    std::function<bool()>       full    = std::bind(&TicTacToe::isFull, &mTicTacToe);
};

void Game::run()
{
    while (!win(mPlayers[mPlayer]) && !full())
    {
        mPlayer ^= 1;
        display();
        turn(mPlayers[mPlayer]);
    }

    resultScreen(win(mPlayers[mPlayer]));
}

void Game::resultScreen(bool winner)
{
    display();

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

int main()
{
    Game game;
    game.run();
}
MORTAL
  • 3.3k
  • 5
  • 25
  • 40