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();
}
Mute
is not the best name for that helper class, IMHO. You have implemented theNonCopyable
pattern with it, so it should be named accordingly. \$\endgroup\$;)
\$\endgroup\$