Back to Code List
c++

Tic Tac Toe

This is an implementation of a tic tac toe console game. It has ability to choose between two players (human, ai defensive, ai random, ai perfect). The perfect player is pretty good at winning or draws, but I wouldn't say it is always perfect. It can definitely be improved.

   main.cpp

#include <iostream>
#include <ctime>

#include "Board.h"
#include "HumanPlayer.h"
#include "RandomPlayer.h"
#include "DefensiveRandomPlayer.h"
#include "PerfectPlayer.h"

using namespace std;

Player* getPlayer(Board::Player player)
{
  char choice;
  cout << "Will player ";
  
  if(player == Board::PLAYER_X)
  {
    cout << "X";
  }
  else 
  {
    cout << "O";
  }

  cout << " be:" << endl;
  cout << "\t(H)uman" << endl;
  cout << "\t(R)andom" << endl;
  cout << "\t(D)efensive" << endl;
  cout << "\t(P)erfect" << endl;
  
  cin >> choice;
  
  switch (choice) {
    case 'H':
    case 'h':
      return new HumanPlayer(player);
    case 'R':
    case 'r':
      return new RandomPlayer(player);
    case 'D':
    case 'd':
      return new DefensiveRandomPlayer(player);
    default:
      return new PerfectPlayer(player);
  }
}

int main()
{
  srand(time(0));

  Board board;
  Player* players[2];
  
  players[0] = getPlayer(Board::PLAYER_X);
  players[1] = getPlayer(Board::PLAYER_O);
  
  int current_player = 0;
  
  while(board.movesRemain())
  {
    board = players[current_player]->move(board);
    
    current_player++;
    current_player%=2;
  }

  board.display();
  switch(board.winner())
  {
    case Board::PLAYER_X:
      cout << "X's Win!" << endl;
      break;
      
    case Board::PLAYER_O:
      cout << "O's Win!" << endl;
      break;
      
    case Board::EMPTY:
      cout << "Cat's Game" << endl;
      break;
  }
  
  delete players[0];
  delete players[1];
}
    

   Board.cpp

#include "Board.h"
#include <cstdio>

Board::Board()
{
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
      board[i][j] = EMPTY;
}

void Board::display()
{
  printf("\n    0   1   2  \n");
  printf("   ___________\n");
  printf("  |   |   |   |\n");
  printf("0 | %c | %c | %c |\n",board[0][0],board[0][1],board[0][2]);
  printf("  |   |   |   |\n");
  printf("  |---|---|---|\n");
  printf("  |   |   |   |\n");
  printf("1 | %c | %c | %c |\n",board[1][0],board[1][1],board[1][2]);
  printf("  |   |   |   |\n");
  printf("  |---|---|---|\n");
  printf("  |   |   |   |\n");
  printf("2 | %c | %c | %c |\n",board[2][0],board[2][1],board[2][2]);
  printf("  |___|___|___|\n\n");
             
}

Board Board::move(int row,int column,Player player)
{
  Board next_move(*this);
  
  if(row < 0) return next_move;
  if(row > 2) return next_move;
  
  
  if(column < 0) return next_move;
  if(column > 2) return next_move;
  
  if(board[row][column] != EMPTY) return next_move;
  
  next_move.board[row][column] = player;
  return next_move;
}

Board::Player Board::operator()(int row,int column)
{
  return board[row][column];
}

Board::Player Board::winner()
{
  //check rows
  for(int i=0;i<3;i++)
      if(board[i][0]!=EMPTY)
        if((board[i][0] == board[i][1])&&(board[i][1]==board[i][2]))
          return board[i][0];
  
  //check columns
  for(int i=0;i<3;i++)
    if(board[0][i]!=EMPTY)
      if((board[0][i] == board[1][i])&&(board[1][i]==board[2][i]))
        return board[0][i];
  //check diagonals
  if(board[0][0]!= EMPTY)
    if((board[0][0] == board[1][1])&&(board[1][1]==board[2][2]))
      return board[0][0];
  
  if(board[0][2]!= EMPTY)
    if((board[0][2] == board[1][1])&&(board[1][1]==board[2][0]))
      return board[0][2];
  //otherwise no winner
  
  return EMPTY;
}

bool Board::operator==(const Board& other)
{
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
      if(board[i][j] != other.board[i][j])
        return false;
  return true;
}

int Board::getId()
{
  int id = 0;
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    {
      int positionValue = 0;
      switch (board[i][j]) 
      {
        case PLAYER_X:
          positionValue = 0x01;
          break;
        case PLAYER_O:
          positionValue = 0x02;
          break;
        case EMPTY:
        default:
          positionValue = 0x00;
          break;
      }
      id += positionValue << (i*3+j);
    }
    return id;
}

int Board::maxId()
{
  Board Oboard;
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
      Oboard.board[i][j] = PLAYER_O;
  return Oboard.getId();
}

bool Board::movesRemain()
{
  if(winner()!=EMPTY) return false;
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
      if(board[i][j] == EMPTY)
        return true;
  return false;
}    

   Board.h

#ifndef _BOARD_H_
#define _BOARD_H_

class Board
{
public:
  enum Player
  {
    PLAYER_X ='X',
    PLAYER_O = 'O',
    EMPTY = ' '
  };
  Board();
  void display();
  Board move(int row,int column,Player player);
  Player winner();
  bool movesRemain();
  Player operator()(int row,int column);
  bool operator==(const Board&);
  int getId();
  static int maxId();
private:
  Player board[3][3];
};

#endif
    

   DefensiveRandomPlayer.cpp

#include "DefensiveRandomPlayer.h"
#include <cstdlib>

DefensiveRandomPlayer::DefensiveRandomPlayer(Board::Player player):
RandomPlayer(player)
{
}

Board DefensiveRandomPlayer::move(Board board)
{
  Board boardToConsider;
  Board::Player opponent;
  if (mPlayer == Board::PLAYER_X)
  {
    opponent = Board::PLAYER_O;
  }
  else 
  {
    opponent = Board::PLAYER_X;
  }

    
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    {
      if(board(i,j)==Board::EMPTY)
      { 
        boardToConsider = board.move(i,j,opponent);
        if(boardToConsider.winner()==opponent) //if a move wins for opponent, block it
        {
          return board.move(i,j,mPlayer);;
        }
      }
    }
  return RandomPlayer::move(board); //choose like RandomPlayer
}

    

   DefensiveRandomPlayer.h

#ifndef _DEFENSIVE_RANDOM_PLAYER_
#define _DEFENSIVE_RANDOM_PLAYER_

#include "RandomPlayer.h"

class DefensiveRandomPlayer:public RandomPlayer
{
public:
  DefensiveRandomPlayer(Board::Player);
  virtual Board move(Board board);

};

#endif
    

   HumanPlayer.cpp

#include "HumanPlayer.h"
#include <iostream>

using namespace std;

HumanPlayer::HumanPlayer(Board::Player player):
Player(player)
{
}

Board HumanPlayer::move(Board board)
{
  int row, col;
  bool notValid = true;
  board.display();
  while(notValid)
  {
    cout << "Enter your move - row column:";
    cin >> row >> col;
    if(board(row,col)==Board::EMPTY)
      notValid = false;
  }
  return board.move(row,col,mPlayer);
}

    

   HumanPlayer.h

#ifndef _HUMAN_PLAYER_
#define _HUMAN_PLAYER_

#include "Player.h"

class HumanPlayer:public Player
{
public:
  HumanPlayer(Board::Player);
  virtual Board move(Board board);
};

#endif
    

   PerfectPlayer.cpp

#include <random>
#include "PerfectPlayer.h"


PerfectPlayer::PerfectPlayer(Board::Player player):
Player(player)
{
}

Board PerfectPlayer::move(Board board)
{
	Board boardToConsider;
	Board::Player opponent;
	//Board boards[9];
	//int possible = 0;

	if (mPlayer == Board::PLAYER_X)
	{
		opponent = Board::PLAYER_O;
	}
	else 
	{
		opponent = Board::PLAYER_X;
	}

    
	// see if any winning moves
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
		{
			if(board(i,j)==Board::EMPTY)
			{ 
				boardToConsider = board.move(i,j,mPlayer);
				if(boardToConsider.winner()==mPlayer) //if a move wins, make it
				{
					return boardToConsider;
				}
			}
		}
	
	// see if have the need to block
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
		{
			if(board(i,j)==Board::EMPTY)
			{ 
				boardToConsider = board.move(i,j,opponent);
				if(boardToConsider.winner()==opponent) //if a move wins for opponent, block it by taking that spot
				{
					return board.move(i,j,mPlayer);
				}
			}
		}

	/* doesn't seem to be needed since it plays well without it
	// look for a winning fork
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
		{
			if (board(i,j) == Board::EMPTY)
			{
				boardToConsider = board.move(i,j,mPlayer); // look at potential move
				if (calcFork(board, mPlayer) >= 2) // if good fork move
				{
					return board.move(i,j,mPlayer);
				}
			}
		}*/

	// look for opponent's winning fork
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
		{
			if (board(i,j) == Board::EMPTY)
			{
				boardToConsider = board.move(i,j,opponent); // look at potential move
				if (calcFork(board, opponent) >= 2) // if good fork move
				{
					return board.move(i,j,mPlayer);
				}
			}
		}

	// play the next best moves
		//center
		if (board(1,1) == Board::EMPTY) return board.move(1,1,mPlayer);
		//opposite opponent corner
		if (board(0,0) == opponent && board(2,2) == Board::EMPTY) return board.move(2,2,mPlayer);
		if (board(0,2) == opponent && board(2,0) == Board::EMPTY) return board.move(2,0,mPlayer);
		if (board(2,0) == opponent && board(0,2) == Board::EMPTY) return board.move(0,2,mPlayer);
		if (board(2,2) == opponent && board(0,0) == Board::EMPTY) return board.move(0,0,mPlayer);
		//any empty corner
		for (int i=0; i<3; i+=2)
			for (int j=0; j<3; j+=2)
				if (board(i,j) == Board::EMPTY) return board.move(i,j,mPlayer);
		//any empty side
		if (board(0,1) == Board::EMPTY) return board.move(0,1,mPlayer);
		if (board(1,0) == Board::EMPTY) return board.move(1,0,mPlayer);
		if (board(1,2) == Board::EMPTY) return board.move(1,2,mPlayer);
		if (board(2,1) == Board::EMPTY) return board.move(2,1,mPlayer);

	// empty default return
	return board;
}


int PerfectPlayer::calcFork(Board board, Board::Player player)
{
    int result = 0;

    // Vertical
    int cpuCount;
    int emptyCell;
    for (int x = 0; x < 3; x++)
    {
        cpuCount = 0;
        emptyCell = -1;
        for (int y = 0; y < 3; y++)
        {
            if (board(x,y) == player)
                cpuCount++; // count number of computer plays in a column
			else if (board(x,y) == Board::EMPTY)
                emptyCell = 0; // see if there is an empty cell available
        }
        if (cpuCount == 2 && emptyCell != -1) result++; // if there are two potential computer cells and an empty (potential win), this is a good fork
    }

    // Horizontal
    for (int y = 0; y < 3; y++)
    {
        cpuCount = 0;
        emptyCell = -1;
        for (int x = 0; x < 3; x++)
        {
            if (board(x,y) == player)
                cpuCount++;
			else if (board(x,y) == Board::EMPTY)
                emptyCell = 0;
        }
        if (cpuCount == 2 && emptyCell != -1) result++;
    }

    // Top-Left To Lower-Right Diagonal
    cpuCount = 0;
    emptyCell = -1;
    for (int i = 0; i < 3; i++)
    {
        if (board(i,i) == player)
            cpuCount++;
        else if (board(i,i) == Board::EMPTY)
            emptyCell = 0;
    }
    if (cpuCount == 2 && emptyCell != -1) result++;

    // Top-Right To Lower-Left Diagonal
    cpuCount = 0;
    emptyCell = -1;
    for (int i = 0; i < 3; i++)
    {
        if (board(2-i,i) == player)
            cpuCount++;
        else if (board(2-i,i) == Board::EMPTY)
            emptyCell = 0;
    }
    if (cpuCount == 2 && emptyCell != -1) result++;

    return result; // number of good forks with a potential move
}    

   PerfectPlayer.h

#ifndef _PERFECT_PLAYER_
#define _PERFECT_PLAYER_

#include "Player.h"

class PerfectPlayer:public Player
{
public:
  PerfectPlayer(Board::Player);
  virtual Board move(Board board);
  int calcFork(Board,Board::Player);
};

#endif
    

   Player.cpp

#include "Player.h"

Player::Player(Board::Player player):
mPlayer(player)
{
}
    

   Player.h

#ifndef _PLAYER_H_
#define _PLAYER_H_

#include "Board.h"

class Player
{
public:
  Player(Board::Player);
  virtual Board move(Board board)=0;
protected:
  Board::Player mPlayer;
};

#endif
    

   RandomPlayer.cpp

#include "RandomPlayer.h"
#include <cstdlib>

RandomPlayer::RandomPlayer(Board::Player player):
Player(player)
{
}

Board RandomPlayer::move(Board board)
{
  Board boards[9];
  int possible = 0;
  
  for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    {
      if(board(i,j)==Board::EMPTY)
      { 
        boards[possible] = board.move(i,j,mPlayer);
        if(boards[possible].winner()==mPlayer) //if a move wins, make it
        {
          return boards[possible];
        }
        possible++;
      }
    }
  int choice = rand()%possible;
  return boards[choice]; //choose a random move
}

    

   RandomPlayer.h

#ifndef _RANDOM_PLAYER_
#define _RANDOM_PLAYER_

#include "Player.h"

class RandomPlayer:public Player
{
public:
  RandomPlayer(Board::Player);
  virtual Board move(Board board);
};

#endif