#include "ai_player.h"
#include "board.h"
#include "global_data.h"
#include "player.h"
#include <cassert>
#include <vector>
GameModes get_game_mode();
void handle_move(Player* player, Board& board);
void check_game_state(const Board& board, const Player* player_one, const Player* player_two, bool& is_game_running, bool& can_play);
void play(bool& is_game_running, Player* player_one, Player* player_two, Board& board);
int main()
{
enum Players
{
player_one,
player_two,
computer_one,
computer_two,
};
std::vector<Player*> list_of_players(4);
bool is_player_vs_player{ false };
GameModes game_mode{ get_game_mode() };
if (game_mode == GameModes::player_vs_player)
{
is_player_vs_player = true;
list_of_players[player_one] = new Player{ "Player 1", BoardState::x };
list_of_players[player_two] = new Player{ "Player 2", BoardState::o };
}
bool is_player_vs_computer{ false };
if (game_mode == GameModes::player_vs_computer)
{
is_player_vs_computer = true;
list_of_players[player_one] = new Player{ "Player 1", BoardState::x };
list_of_players[computer_one] = new AIPlayer{ "Computer", BoardState::o };
}
bool is_computer_vs_computer{ false };
if (game_mode == GameModes::computer_vs_computer)
{
is_computer_vs_computer = true;
list_of_players[computer_one] = new AIPlayer{ "Computer 1", BoardState::x };
list_of_players[computer_two] = new AIPlayer{ "Computer 2", BoardState::o };
}
Board board{};
board.display();
bool is_game_running{ true };
while (is_game_running)
{
if (is_player_vs_player)
play(is_game_running, list_of_players[player_one], list_of_players[player_two], board);
if (is_player_vs_computer)
play(is_game_running, list_of_players[player_one], list_of_players[computer_one], board);
if (is_computer_vs_computer)
play(is_game_running, list_of_players[computer_one], list_of_players[computer_two], board);
}
for (std::size_t index{ 0 }; index < list_of_players.size(); ++index)
delete list_of_players[index];
return 0;
}
GameModes get_game_mode()
{
while (true)
{
print_text("TIC TAC TOE\n");
print_text("0. Player vs Player");
print_text("1. Player vs Computer");
print_text("2. Computer vs Computer");
print_text("\nSelect a game mode: ", false);
int input{};
std::cin >> input;
if (std::cin.fail())
{
input_failed();
continue;
}
if (input >= player_vs_player && input <= computer_vs_computer)
{
ignore_line();
return list_of_game_modes[input];
}
print_text("Invalid.");
}
}
void handle_move(Player* player, Board& board)
{
assert(player != nullptr && "A player deosn't exist");
board.update(player->get_board_position(board), player->place_marker());
}
void check_game_state(const Board& board, const Player* player_one, const Player* player_two, bool& is_game_running, bool& can_play)
{
assert(player_one != nullptr && player_two != nullptr && "The player's don't exist");
// Draw
if (board.is_board_filled() && !board.is_game_won(player_one->place_marker()) && !board.is_game_won(player_two->place_marker()))
{
print_text("The game is a draw.");
can_play = false;
is_game_running = false;
}
// Player 1 won
if (board.is_game_won(player_one->place_marker()))
{
player_one->display_winner();
can_play = false;
is_game_running = false;
}
// Player 2 won
if (board.is_game_won(player_two->place_marker()))
{
player_two->display_winner();
can_play = false;
is_game_running = false;
}
}
void play(bool& is_game_running, Player* player_one, Player* player_two, Board& board)
{
assert(player_one != nullptr && player_two != nullptr && "The player's don't exist");
bool can_player_one_play{ true };
bool can_player_two_play{ true };
if (can_player_one_play)
{
handle_move(player_one, board);
check_game_state(board, player_one, player_two, is_game_running, can_player_two_play);
board.display();
}
if (can_player_two_play)
{
handle_move(player_two, board);
check_game_state(board, player_one, player_two, is_game_running, can_player_one_play);
board.display();
}
}
#pragma once
#include <array>
#include <iostream>
#include <string_view>
enum class BoardState
{
empty,
x,
o,
};
enum class GameModes
{
player_vs_player,
player_vs_computer,
computer_vs_computer
};
inline constexpr std::array list_of_game_modes{ GameModes::player_vs_player, GameModes::player_vs_computer, GameModes::computer_vs_computer };
inline constexpr int player_vs_player{ 0 };
inline constexpr int player_vs_computer{ 1 };
inline constexpr int computer_vs_computer{ 2 };
inline constexpr int min_board_position{ 0 };
inline constexpr int max_board_position{ 8 };
inline constexpr std::size_t board_size{ 9 };
inline void ignore_line()
{
std::cin.ignore(100, '\n');
}
inline void input_failed()
{
std::cin.clear();
ignore_line();
}
inline void print_text(std::string_view prompt, bool print_newline = true)
{
if (print_newline)
{
std::cout << prompt << "\n";
}
else
{
std::cout << prompt;
}
}
#pragma once
#include "global_data.h"
#include <array>
#include <string>
class Board
{
public:
Board();
void display() const;
bool is_board_position_empty(std::size_t& board_position) const;
bool is_board_filled() const;
bool is_game_won(BoardState marker) const;
void update(std::size_t board_position, BoardState marker);
private:
std::string board_state_to_string(BoardState state) const;
private:
std::array<BoardState, board_size> data_{ BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty, BoardState::empty };
};
#include "board.h"
#include "global_data.h"
#include <cassert>
#include <iostream>
#include <string>
Board::Board()
{
assert(data_.size() == board_size && "board size must equal 9");
}
void Board::display() const
{
for (std::size_t index{ 0 }; index < data_.size(); ++index)
{
if (index % 3 == 0)
std::cout << "\n";
std::cout << "[ " << board_state_to_string(data_[index]) << " ]" << " ";
}
std::cout << "\n\n";
}
bool Board::is_board_position_empty(std::size_t& board_position) const
{
assert(board_position >= min_board_position && board_position <= max_board_position && "out of bounds board position");
if (data_[board_position] == BoardState::empty)
return true;
return false;
}
bool Board::is_board_filled() const
{
for (std::size_t index{ 0 }; index < data_.size(); ++index)
{
if (data_[index] == BoardState::empty)
return false;
}
return true;
}
bool Board::is_game_won(BoardState marker) const
{
enum BoardPositions
{
zero,
one,
two,
three,
four,
five,
six,
seven,
eight,
nine,
};
// [0] [1] [2]
// [ ] [ ] [ ]
// [ ] [ ] [ ]
if (data_[zero] == marker && data_[one] == marker && data_[two] == marker)
return true;
// [ ] [ ] [ ]
// [3] [4] [5]
// [ ] [ ] [ ]
if (data_[three] == marker && data_[four] == marker && data_[five] == marker)
return true;
// [ ] [ ] [ ]
// [ ] [ ] [ ]
// [6] [7] [8]
if (data_[six] == marker && data_[seven] == marker && data_[eight] == marker)
return true;
// [0] [ ] [ ]
// [3] [ ] [ ]
// [6] [ ] [ ]
if (data_[zero] == marker && data_[three] == marker && data_[six] == marker)
return true;
// [ ] [1] [ ]
// [ ] [4] [ ]
// [ ] [7] [ ]
if (data_[one] == marker && data_[four] == marker && data_[seven] == marker)
return true;
// [ ] [ ] [2]
// [ ] [ ] [5]
// [ ] [ ] [8]
if (data_[two] == marker && data_[five] == marker && data_[eight] == marker)
return true;
// [0] [ ] [ ]
// [ ] [4] [ ]
// [ ] [ ] [8]
if (data_[zero] == marker && data_[four] == marker && data_[eight] == marker)
return true;
// [ ] [ ] [2]
// [ ] [4] [ ]
// [6] [ ] [ ]
if (data_[two] == marker && data_[four] == marker && data_[six] == marker)
return true;
return false;
}
void Board::update(std::size_t board_position, BoardState marker)
{
assert(board_position >= min_board_position && board_position <= max_board_position && "out of bounds board position");
data_[board_position] = marker;
}
std::string Board::board_state_to_string(BoardState state) const
{
switch (state)
{
case BoardState::empty:
return " ";
case BoardState::x:
return "X";
case BoardState::o:
return "O";
default:
return "ERROR";
}
}
#pragma once
#include "player.h"
#include "board.h"
#include "global_data.h"
#include <string>
#include <string_view>
class Player
{
public:
Player(std::string_view name, BoardState marker = BoardState::x);
virtual std::size_t get_board_position(Board& board);
virtual BoardState place_marker() const;
virtual void display_winner() const;
protected:
std::string name_{ "???" };
BoardState marker_{ BoardState::empty };
};
#include "player.h"
#include "board.h"
#include "global_data.h"
#include <cassert>
#include <iostream>
#include <string_view>
Player::Player(std::string_view name, BoardState marker)
: name_{ name }, marker_{ marker }
{
assert(marker_ != BoardState::empty && "marker_ must either be x or o");
}
std::size_t Player::get_board_position(Board& board)
{
while (true)
{
std::cout << name_ << ": ";
std::size_t board_position{};
std::cin >> board_position;
if (std::cin.fail())
{
input_failed();
continue;
}
if (!board.is_board_position_empty(board_position))
{
print_text("This position is already taken.");
continue;
}
if (board_position >= min_board_position && board_position <= max_board_position)
{
ignore_line();
return board_position;
}
print_text("Valid positions are between 0 and 8.");
}
}
BoardState Player::place_marker() const
{
return marker_;
}
void Player::display_winner() const
{
print_text(name_, false); print_text(" has won.", false);
}
#pragma once
#include "board.h"
#include "player.h"
#include "global_data.h"
#include <string_view>
class AIPlayer : public Player
{
public:
AIPlayer(std::string_view name = "Computer", BoardState marker = BoardState::o);
std::size_t get_board_position(Board& board) override;
private:
int mini_max(Board& board, bool is_maximizer);
};
#include "ai_player.h"
#include "board.h"
#include "player.h"
#include "global_data.h"
#include <algorithm>
#include <random>
#include <string_view>
AIPlayer::AIPlayer(std::string_view name, BoardState marker)
: Player{ name, marker }
{
}
std::size_t AIPlayer::get_board_position(Board& board)
{
int best_score{ -1000 };
std::size_t best_move{};
for (std::size_t index{ 0 }; index < board_size; ++index)
{
if (board.is_board_position_empty(index))
{
board.update(index, marker_);
int score = mini_max(board, false);
board.update(index, BoardState::empty);
if (score > best_score)
{
best_score = score;
best_move = index;
}
}
}
return best_move;
}
int AIPlayer::mini_max(Board& board, bool is_maximizer)
{
BoardState opponent = (marker_ == BoardState::x)
? BoardState::o
: BoardState::x;
// First check game state
if (board.is_game_won(marker_))
return 1;
if (board.is_game_won(opponent))
return -1;
if (board.is_board_filled())
return 0;
if (is_maximizer)
{
int best_score = -1000;
for (std::size_t index = 0; index < board_size; ++index)
{
if (board.is_board_position_empty(index))
{
board.update(index, marker_);
int score = mini_max(board, false);
board.update(index, BoardState::empty);
best_score = std::max(best_score, score);
}
}
return best_score;
}
else
{
int best_score = 1000;
for (std::size_t index = 0; index < board_size; ++index)
{
if (board.is_board_position_empty(index))
{
board.update(index, opponent);
int score = mini_max(board, true);
board.update(index, BoardState::empty);
best_score = std::min(best_score, score);
}
}
return best_score;
}
}