I've made a major update to my Noughts & Crosses program, this ISN'T a duplicate! I know it's not good to use system("pause") and system("cls"), however, I just wanted to make the console screen more readable so resorted to these methods to achieve this. I would like feedback on how well I've used std::shared_ptr, the std::algorithm library, and any suggestions on how I can improve. I have also returned a std::shared_ptr null-ptr. A lot of what I've done has been intuitive and I'm unsure if what I've done is the best way to go about doing certain things. If you run this program, please, just press enter where needed, it's two robots playing against each other. If you'd like to manually play it, please change the arguments passed into the game.play function to a human.
#include <iostream>
#include <vector>
#include <map>
#include <utility>
#include <algorithm>
#include <random>
#include <ctime>
#include <cstdlib>
#include <ranges>
class Player
{
private:
std::string m_type;
unsigned char m_name;
int m_winTally;
int m_drawed;
public:
std::string GetType()const { return m_type; };
Player(unsigned char name, std::string&& type = "Player") :m_name(name), m_type(type), m_winTally(0), m_drawed(0) {}
virtual unsigned char GetName()const { return m_name; }
virtual bool ClaimSquare(std::map<int, unsigned char>& board, int move) = 0;
virtual int NextMove(std::map<int, unsigned char>& board) = 0;
virtual ~Player() = default;
void AddWinToTally() { m_winTally++; }
void Drawed() { m_drawed++; }
int GetGameWins() const { return m_winTally; }
int GetDraws() const { return m_drawed; }
};
class Human : public Player
{
public:
Human(unsigned char name) :Player(name, "Human") {}
virtual int NextMove(std::map<int, unsigned char>& board) override {
int move;
std::cout << "Enter a number on the board (e.g. 1): ";
std::cin >> move;
return move;
}
virtual bool ClaimSquare(std::map<int, unsigned char>& board, int move)
{
auto validSquare = std::find_if(board.begin(), board.end(), [&](auto pair) {
return pair.first == move;
});
if (validSquare != board.end())
{
if (validSquare->second == '-')
{
validSquare->second = Player::GetName();
return true;
}
else
{
std::cout << "This square has already been claimed. Choose a different square!" << std::endl;
return false;
}
}
return false;
}
virtual ~Human() = default;
};
class Robot : public Player
{
public:
Robot(unsigned char name) :Player(name, "Robot") {}
bool CheckAvailability(std::map<int, unsigned char>& board, int number, std::vector<int>& keys) {
for (auto& cell : board) {
if (cell.first == number) {
if (cell.second == '-') {
return true;
}
}
}
std::remove_if(keys.begin(), keys.end(), [&](auto& key) {
return key == number;
});
return false;
}
virtual int NextMove(std::map<int, unsigned char>& board) override
{
std::vector<int>number = { 1,2,3,4,5,6,7,8,9 };
int randNum = 0;
std::srand(std::time(0));
do
{
randNum = rand() % 9 + 1;
}
while (CheckAvailability(board, randNum, number) == false);
return randNum;
}
virtual bool ClaimSquare(std::map<int, unsigned char>& board, int move)
{
auto validSquare = std::find_if(board.begin(), board.end(), [&](auto pair) {
return pair.first == move;
});
if (validSquare != board.end())
{
if (validSquare->second == '-')
{
validSquare->second = Player::GetName();
return true;
}
else
{
std::cout << "This square has already been claimed. Choose a different square!" << std::endl;
return false;
}
}
return false;
}
virtual ~Robot() = default;
};
class NoughtsAndCrosses
{
private:
//std::vector<Player*> m_p;
std::map<int, unsigned char>board;
void DisplayBoard()
{
for (auto const& cell : board)
{
if (cell.first % 3 == 1) {
std::cout << "\n\n";
}
if (cell.second != '-') {
std::cout << cell.second << " ";
}
else
{
std::cout << cell.first << " ";
}
}
std::cout << "\n\n";
}
auto CheckForAWinner(std::map<int, unsigned char>& board, std::shared_ptr<Player>& player)
{
if (board.at(1) == player->GetName() && board.at(2) == player->GetName() && board.at(3) == player->GetName()) {
return true;
}
else if (board.at(4) == player->GetName() && board.at(5) == player->GetName() && board.at(6) == player->GetName()) {
return true;
}
else if (board.at(7) == player->GetName() && board.at(8) == player->GetName() && board.at(9) == player->GetName()) {
return true;
}
else if (board.at(1) == player->GetName() && board.at(4) == player->GetName() && board.at(7) == player->GetName()) {
return true;
}
else if (board.at(2) == player->GetName() && board.at(5) == player->GetName() && board.at(8) == player->GetName()) {
return true;
}
else if (board.at(3) == player->GetName() && board.at(6) == player->GetName() && board.at(9) == player->GetName()) {
return true;
}
else if (board.at(1) == player->GetName() && board.at(5) == player->GetName() && board.at(9) == player->GetName()) {
return true;
}
else if (board.at(7) == player->GetName() && board.at(5) == player->GetName() && board.at(3) == player->GetName()) {
return true;
}
else
{
return false;
}
}
bool CheckForDraw(std::map<int, unsigned char>& board) {
return std::all_of(board.begin(), board.end(), [&](auto& pair) {return pair.second != '-'; });
}
public:
NoughtsAndCrosses()
{
board = { std::make_pair(1,'-'),std::make_pair(2,'-'),std::make_pair(3,'-'),
std::make_pair(4,'-'),std::make_pair(5,'-'),std::make_pair(6,'-'),
std::make_pair(7,'-'),std::make_pair(8,'-'),std::make_pair(9,'-') };
}
void ResetBoard() {
std::for_each(board.begin(), board.end(), [&](auto& pair) {
pair.second = '-';
});
}
auto play(std::shared_ptr<Player>& p1, std::shared_ptr<Player>& p2)
{
int currentPlayer = 1;
bool isWinner = false;
bool isDraw = false;
std::vector<std::shared_ptr<Player>>m_player = { p1, p2 };
do
{
currentPlayer = (currentPlayer + 1) % 2;
do
{
system("cls");
std::cout << m_player.at(currentPlayer)->GetType() << ": " << m_player.at(currentPlayer)->GetName() << " turn: " << std::endl;
DisplayBoard();
system("pause");
}
while (m_player.at(currentPlayer)->ClaimSquare(board, m_player.at(currentPlayer)->NextMove(board)) == false);
//std::cout << "\nPress enter to make the robot move. . .";
//std::cin.get();
//system("cls");
} while (CheckForDraw(board) == false && (isWinner = CheckForAWinner(board, m_player.at(currentPlayer))) == false);
if (isWinner == true)
{
return m_player.at(currentPlayer);
}
m_player.at(0)->Drawed();
m_player.at(1)->Drawed();
DisplayBoard();
ResetBoard();
return std::shared_ptr<Player>(nullptr);
}
};
int main() {
std::shared_ptr<Player> human1 = std::make_shared<Human>('O');
std::shared_ptr<Player> human2 = std::make_shared<Human>('X');
std::shared_ptr<Player> robot1 = std::make_shared<Robot>('O');
std::shared_ptr<Player> robot2 = std::make_shared<Robot>('X');
std::vector<std::shared_ptr<Player>>player = { robot1, robot2 };
NoughtsAndCrosses game;
int round = 3;
int roundCount = 0;
std::shared_ptr<Player>winner;
do
{
int gameCount = 1;
int totalGamesinRound = 3;
std::cout << "START GAME!\n";
system("pause");
system("cls");
std::cout << "\nROUND " << ++roundCount << ". . .\n";
do
{
std::cout << "Game " << gameCount << " of round " << roundCount << "\n";
winner = game.play(robot1, robot2);
if (winner != std::shared_ptr<Player>(nullptr))
{
std::cout << "Winner of game " << gameCount << " is type: " << winner->GetType() << ": " << winner->GetName() << "\n";
winner->AddWinToTally();
}
else
{
system("cls");
std::cout << "Game " << gameCount << " is a draw!\n";
}
gameCount++;
totalGamesinRound--;
}
while (totalGamesinRound != 0);
/* std::cout << "Game 2: Human vs Robot\n";
game.play(robot1, robot1);*/
std::cout << "Wins for " << robot1->GetType() << ": Player : " << robot1->GetName() << " - " << robot1->GetGameWins() << "\n";
std::cout << "Wins for " << robot2->GetType() << ": Player : " << robot2->GetName() << " - " << robot2->GetGameWins() << "\n";
std::cout << "Drawed: " << robot1->GetDraws() << "\n";
auto playerWithMostWins = std::max_element(player.begin(), player.end(),
[](const auto& lhs, const auto& rhs)
{
return lhs->GetGameWins() < rhs->GetGameWins();
});
std::cout << "Winner of round " << roundCount << " is " << playerWithMostWins->get()->GetName() << "\n";
round--;
}
while (round != 0);
}