For learning purposes i wrote a Tic Tac Toe game in object oriented manner. I have question about storing players in game class - Players(cpu and human) are derived from virtual class players and stored in vector as unique pointers to common class. Is there more elegant way to achieve this? Also, feel free to point out things i could improve or i do in wrong way.
For convenience purposes here is repo with whole project: repo
main.cpp
#include "TicTacToe.h"
#include <iostream>
int main()
{
int playersAmount;
std::cout << "0 - two CPU players" << std::endl
<< "1 - play with CPU" << std::endl
<< "2 - two players" << std::endl;
std::cin >> playersAmount;
TicTacToe::intro();
do {
TicTacToe game(playersAmount);
game.clearConsole();
game.printBoard();
while (game.isRunning()) {
game.step();
};
char input;
std::cout << "Play again?(y/n)";
std::cin >> input;
if (input != 'y') {
break;
}
} while (true);
}
TicTacToe.h
#pragma once
#include "Board.h"
#include "Players.h"
#include <array>
#include <memory>
#include <string>
#include <vector>
class TicTacToe {
public:
TicTacToe(const int numberOfHumanPlayers);
static void intro();
static std::string getInputFromConsole();
static void clearConsole();
void printBoard() const;
void step();
bool isRunning() const;
void terminate();
private:
bool m_running { true };
int m_currentPlayer { 0 };
std::vector<std::unique_ptr<Player>> m_players;
Board m_board;
void nextPlayer();
char returnPlayerSign(const int player) const;
};
TicTacToe.cpp
#include "TicTacToe.h"
#include <iostream>
#include <memory>
#include <stdlib.h>
#include <string>
#include <vector>
TicTacToe::TicTacToe(const int numberOfHumanPlayers)
{
switch (numberOfHumanPlayers) {
case 0:
m_players.push_back(std::make_unique<PlayerCPU>("CPU 1"));
m_players.push_back(std::make_unique<PlayerCPU>("CPU 2"));
break;
case 1:
m_players.push_back(std::make_unique<PlayerHuman>("Player"));
m_players.push_back(std::make_unique<PlayerCPU>("CPU"));
break;
case 2:
default:
m_players.push_back(std::make_unique<PlayerHuman>("Player 1"));
m_players.push_back(std::make_unique<PlayerHuman>("Player 2"));
}
}
void TicTacToe::step()
{
std::cout << "Player: " << m_players[m_currentPlayer]->getName() << std::endl;
int selectedField;
while (true) {
selectedField = m_players[m_currentPlayer]->provideField(m_board);
if (m_board.isMoveAllowed(selectedField)) {
break;
} else {
std::cout << "Invalid move" << std::endl;
}
}
m_board.takeFieldOnBoard(selectedField, returnPlayerSign(m_currentPlayer));
clearConsole();
printBoard();
if (m_board.isGameWon()) {
std::cout
<< m_players[m_currentPlayer]->getName() << "(" << returnPlayerSign(m_currentPlayer) << ") won!" << std::endl;
terminate();
return;
}
if (!m_board.areThereFreeFields()) {
std::cout << "Game ended" << std::endl;
terminate();
return;
}
nextPlayer();
}
void TicTacToe::printBoard() const
{
m_board.printBoard();
}
char TicTacToe::returnPlayerSign(const int player) const
{
if (player == 0) {
return 'X';
}
return 'O';
}
void TicTacToe::nextPlayer()
{
m_currentPlayer += 1;
m_currentPlayer %= 2;
}
void TicTacToe::clearConsole()
{
system("clear");
}
void TicTacToe::intro()
{
std::cout << "Tic Tac Toe game" << std::endl;
std::cout << "To make a move, enter number of field" << std::endl;
}
std::string TicTacToe::getInputFromConsole()
{
std::string input;
std::cin >> input;
return input;
}
bool TicTacToe::isRunning() const
{
return m_running;
}
void TicTacToe::terminate()
{
m_running = false;
}
Board.h
#pragma once
#include <array>
#include <vector>
class Board {
public:
Board();
bool isGameWon() const;
bool isMoveAllowed(const int field) const;
bool areThereFreeFields() const;
const std::vector<int>& returnAllowedIds() const;
void takeFieldOnBoard(const int field, const char sign);
void printBoard() const;
private:
std::array<char, 9> m_board;
std::vector<int> m_allowedFieldsIds;
bool checkCol(const int) const;
bool checkRow(const int) const;
bool checkAllCols() const;
bool checkAllRows() const;
bool checkDiagonals() const;
};
Board.cpp
#include "Board.h"
#include <algorithm>
#include <iostream>
Board::Board()
{
m_allowedFieldsIds.reserve(9);
for (int i = 0; i < 9; i++) {
m_board[i] = i + '0';
m_allowedFieldsIds.push_back(i);
};
}
const std::vector<int>& Board::returnAllowedIds() const
{
return m_allowedFieldsIds;
}
void Board::takeFieldOnBoard(const int field, const char sign)
{
m_board[field] = sign;
m_allowedFieldsIds.erase(std::remove(m_allowedFieldsIds.begin(), m_allowedFieldsIds.end(), field), m_allowedFieldsIds.end());
}
bool Board::isMoveAllowed(const int field) const
{
auto it = std::find(m_allowedFieldsIds.begin(), m_allowedFieldsIds.end(), field);
if (it != m_allowedFieldsIds.end()) {
return true;
}
return false;
}
bool Board::areThereFreeFields() const
{
return !m_allowedFieldsIds.empty();
}
bool Board::isGameWon() const
{
if (checkAllCols()) {
return true;
}
if (checkAllRows()) {
return true;
}
if (checkDiagonals()) {
return true;
}
return false;
}
bool Board::checkRow(const int row) const
{
if (m_board[row] == m_board[row + 1] && m_board[row + 1] == m_board[row + 2]) {
return true;
}
return false;
}
bool Board::checkAllRows() const
{
for (int i = 0; i < 9; i += 3) {
if (checkRow(i)) {
return true;
}
}
return false;
}
bool Board::checkCol(const int col) const
{
if (m_board[col] == m_board[col + 3] && m_board[col + 3] == m_board[col + 6]) {
return true;
}
return false;
}
bool Board::checkAllCols() const
{
for (int i = 0; i < 3; i++) {
if (checkCol(i)) {
return true;
}
}
return false;
}
bool Board::checkDiagonals() const
{
if (m_board[0] == m_board[4] && m_board[4] == m_board[8]) {
return true;
}
if (m_board[2] == m_board[4] && m_board[4] == m_board[6]) {
return true;
}
return false;
}
void Board::printBoard() const
{
for (int i = 0; i < 9; i += 3) {
std::cout << m_board[i] << '|' << m_board[i + 1] << '|' << m_board[i + 2] << std::endl;
if (i < 6) {
std::cout << "_____" << std::endl;
}
}
std::cout << std::endl;
}
Players.h
#pragma once
#include "Board.h"
#include <array>
#include <string>
class Player {
public:
Player(const std::string& name)
: m_name(name) {};
virtual int provideField(const Board& board) const = 0;
const std::string& getName() const;
private:
const std::string m_name;
};
// Human player
class PlayerHuman : public Player {
public:
PlayerHuman(const std::string& name)
: Player(name) {};
int provideField(const Board& board) const override;
private:
int askForInput() const;
};
// CPU Player
class PlayerCPU : public Player {
public:
PlayerCPU(const std::string& name)
: Player(name) {};
int provideField(const Board& board) const override;
int returnFirstAllowedField(const Board& board) const;
int returnRandomField(const Board& board) const;
};
Players.cpp
#include "Players.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <iterator>
#include <random>
#include <thread>
#include <vector>
// Player
const std::string& Player::getName() const
{
return m_name;
}
// PlayerHuman
int PlayerHuman::provideField(const Board& board) const
{
return askForInput();
}
int PlayerHuman::askForInput() const
{
int field;
std::cout << "Field #";
std::cin >> field;
return field;
}
// PlayerCPU
int PlayerCPU::provideField(const Board& board) const
{
using namespace std::chrono_literals;
std::this_thread::sleep_for(1000ms);
return returnRandomField(board);
return 0;
}
int PlayerCPU::returnFirstAllowedField(const Board& board) const
{
return board.returnAllowedIds()[0];
}
int PlayerCPU::returnRandomField(const Board& board) const
{
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_int_distribution<> distr(0, board.returnAllowedIds().size() - 1);
return board.returnAllowedIds()[distr(gen)];
}