Base on the previous question:
Here is a summary of improvements:
- Deleting the
Match
class for its expensive calling constructor - Deleting
enum struct Type
andenum struct Diagonals
for no longer being needed - Converting
Random
class to a template to allow eitherint
orunsigned
values as parameters
How can I improve this game?
#include <iostream>
#include <cctype>
#include <array>
#include <random>
enum struct Player : char
{
none = '-',
first = 'X',
second = 'O'
};
std::ostream& operator<<(std::ostream& os, Player const& p)
{
return os << std::underlying_type<Player>::type(p);
}
// TicTacToe Class takes care for the logic and the drawing of the game.
template<std::size_t DIM> // main reason for template is avoiding global variables
class TicTacToe
{
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<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 (std::size_t k = 0; k < DIM; ++k)
{
if (x % DIM == k)
{
win[k] &= i == player;
}
if (x / DIM == k)
{
win[DIM + k] &= i == player;
}
if ((k == 0 && (x / DIM - x % DIM == k)) // Diagonals -> LeftTop RightBottom
|| (k == 1 && (x / DIM + x % DIM == DIM - k))) // Diagonals -> RightTop leftBottom
{
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 (std::size_t 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";
}
template<typename T>
class Random
{
public:
Random(const T& min, const T& max)
: mUnifomDistribution(min, max)
{}
T operator()()
{
return mUnifomDistribution(mEngine);
}
private:
std::default_random_engine mEngine{ std::random_device()() };
template <typename U>
static auto dist() -> typename std::enable_if<std::is_integral<U>::value, std::uniform_int_distribution<U>>::type;
template <typename U>
static auto dist() -> typename std::enable_if<std::is_floating_point<U>::value, std::uniform_real_distribution<U>>::type;
decltype(dist<T>()) mUnifomDistribution;
};
// Game class represent the game loop for the tic tac toe game
// it simply takes inputs by switching users to check for whom is the winner.
class Game
{
public:
void run();
private:
void showResult() const;
void turn();
static const std::size_t mDim = 4;
static const std::size_t mNumberOfPlayers = 2;
TicTacToe<mDim> mGame;
std::array<Player, mNumberOfPlayers> mPlayers{ { Player::first, Player::second } };
int mPlayer = 1;
Random<int> 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();
}