Coding Exercise: Writing a CPU Random TicTacToe Game

0 views

TicTacToe - Coding Exercise for Beginners

Who doesn't love a litle tictactoe on the back of their notebooks during class/lectures. Using this inspiration, Last year I tried to write my own tictactoe console version when I was preparing for my interviews. In this, It'll be You versus the CPU, catch is, CPU is only random, not any AI (It's a beginners tutorial, AI one will come later)

Steps to write a simple TicTacToe Game

We'll be using C++ but a Java version is also Available at the end of the post.

  • Make a Game Board
  • Write a Function to place a piece
  • Make it a reusable function by adding pos and user name
  • Add a gameOver=false and winner flag
  • add a while loop
  • add winning conditions of rows, columns, diagonals
  • add all winning conditions to a single list
  • Loop through that list and check for winners using containAll()
  • keep a list of empty blocks as availPos
  • write a function for cpu to use only availPos so as no overwriting happens
  • Put all the functions together

Let's Start

Global Variables and Headers

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

bool gameOver = false;
string WinnerName = "";
vector<vector<string>> gameBoard;

vector<int> playerPos;
vector<int> cpuPos;
vector<int> availPos;

Total Functions needed

void printGameBoard();
void placePeice(int, string);
void init();
int indexOf(vector<int>&, int);
void wrongTurn();
void playerTurn();
void cpuTurn();
void checkWinner(vector<int>&, string);

Make a Game Board

Everyone likes to play TicTacToe on a gameboard, consisting of a grid with 9 playable places.

Let's make that

/*
  Shoud Look Like this when print
       | |
      -+-+-
       | |
      -+-+-
       | |
  */
void init()
{
  for(auto i = 1; i<10; i++)
  {
    availPos.push_back(i);
  }
  vector<string> row = {" ", "|", " ", "|", " "};
  vector<string> sep = {"-", "+", "-", "+", "-"};
  gameBoard.push_back(row);
  gameBoard.push_back(sep);
  gameBoard.push_back(row);
  gameBoard.push_back(sep);
  gameBoard.push_back(row);
}
void printGameBoard()
{
  for(auto i=0; i<5; i++)
  {
    for(auto j=0; j<5; j++)
    {
      cout<<gameBoard[i][j];
    }
    cout<<endl;
  }
}

Write a Function to place a piece

Place a piece on the board : Put X or O in the grid

void _place(int i, int j, char symbol)
{
  if(gameBoard[i][j] != "X" && gameBoard[i][j] != "O")
  {
    gameBoard[i][j] = symbol;
    return;
  }
  wrongTurn();
}

Make it a reusable function by adding pos and user name

Map the 1-9 range input to appropriate i and j values in the grid so no irregularities are there.

void placePeice(int pos, string user)
{
  char symbol = ' ';
  if(user == "Player" && pos>=1 && pos<=9)
  {
    cout<<"Player: "<<pos<<endl;
    playerPos.push_back(pos);
    symbol = 'X';
  } else if(user == "CPU" && pos>=1 && pos<=9)
  {
    cout<<"CPU: "<<pos<<endl;
    cpuPos.push_back(pos);
    symbol = 'O';
  }
  int index = indexOf(availPos, pos);
  if(index != -1)
  {
    availPos.erase(availPos.begin() + index);
  }

  switch(pos)
  {
    case 1:
      _place(0, 0, symbol);
    break;
    case 2:
      _place(0, 2, symbol);
    break;
    case 3:
      _place(0, 4, symbol);
    break;
    case 4:
      _place(2, 0, symbol);
    break;
    case 5:
      _place(2, 2, symbol);
    break;
    case 6:
      _place(2, 4, symbol);
    break;
    case 7:
      _place(4, 0, symbol);
    break;
    case 8:
      _place(4, 2, symbol);
    break;
    case 9:
      _place(4, 4, symbol);
    break;
    default:
    break;
  }
}

Handling Wrong Turns

In case someone enters a no. outside the range or enters a grid block which is already taken.

void wrongTurn()
{
  cout<<"Invalid Position, Please Try Again"<<endl;
  playerTurn();
}

Trigger User/Player Turns

void playerTurn()
{
  cout<<"Please Enter a Position [1-9]: ";
  int pos;
  cin>>pos;
  placePeice(pos, "Player");
}

Trigger CPU Turns - Random but from available/Valid places

void cpuTurn()
{
  int r = 1 + (rand() % availPos.size());
  int pos = availPos[r];
  placePeice(pos, "CPU");
}

Check for Winner and Halt the Game

We're checking the rows, columns and diagonals on each turn. Using hardcoded place values.

void checkWinner(vector<int>& v, string user)
{
  if(gameOver)
    return;
  vector<int> topRow = {1,2,3};
  vector<int> midRow = {4,5,6};
  vector<int> botRow = {7,8,9};

  vector<int> leftCol = {1,4,7};
  vector<int> midCol =  {2,5,8};
  vector<int> rightCol= {3,6,9};

  vector<int> cross1 = {1,5,9};
  vector<int> cross2 = {3,5,7};

  vector<vector<int>> winning;
  winning.push_back(topRow);
  winning.push_back(midRow);
  winning.push_back(botRow);
  winning.push_back(leftCol);
  winning.push_back(midCol);
  winning.push_back(rightCol);
  winning.push_back(cross1);
  winning.push_back(cross2);

  for(auto i = 0; i < winning.size(); i++)
  {
    int c = 0;
    vector<int> con = winning[i];
    for(auto j = 0; j < con.size(); j++)
    {
      if(std::count(v.begin(), v.end(), con[j]))
      {
        c = 1;
      } else {
        c = 0;
        break;
      }
    }
    if(c==1)
    {
      gameOver = true;
      WinnerName = user;
      return;
    }
  }
  if(playerPos.size() + cpuPos.size() == 9)
  {
    gameOver = true;
    WinnerName = "Draw";
    return;
  }
}

Finally, the main() method to drive everything and indexOf() helper function to get index from Iterator value(STL)

int main() {
  init();
  cout<<"Welcome to TicTacToe in C++"<<endl;
  printGameBoard();

  while(!gameOver)
  {
    playerTurn();
    checkWinner(playerPos, "Player");
    cpuTurn();
    checkWinner(cpuPos, "CPU");
    printGameBoard();
  }
  cout<<"Game Over!"<<endl;
  cout<<"Winner: "<<WinnerName<<endl;
}
int indexOf(vector<int>& pos, int el)
{
  auto it = find(pos.begin(), pos.end(), el);
  if(it!=pos.end())
  {
    int index = distance(pos.begin(), it);
    return index;
  }
  return -1;
}

You're final code should look like this:

Main.cpp

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

bool gameOver = false;
string WinnerName = "";
vector<vector<string>> gameBoard;

vector<int> playerPos;
vector<int> cpuPos;
vector<int> availPos;


void printGameBoard();
void placePeice(int, string);
void init();
int indexOf(vector<int>&, int);
void wrongTurn();
void playerTurn();
void cpuTurn();
void checkWinner(vector<int>&, string);


int main() {
  init();
  cout<<"Welcome to TicTacToe in C++"<<endl;
  printGameBoard();

  while(!gameOver)
  {
    playerTurn();
    checkWinner(playerPos, "Player");
    cpuTurn();
    checkWinner(cpuPos, "CPU");
    printGameBoard();
  }
  cout<<"Game Over!"<<endl;
  cout<<"Winner: "<<WinnerName<<endl;
}
void init()
{
  for(auto i = 1; i<10; i++)
  {
    availPos.push_back(i);
  }
  vector<string> row = {" ", "|", " ", "|", " "};
  vector<string> sep = {"-", "+", "-", "+", "-"};
  gameBoard.push_back(row);
  gameBoard.push_back(sep);
  gameBoard.push_back(row);
  gameBoard.push_back(sep);
  gameBoard.push_back(row);
}
int indexOf(vector<int>& pos, int el)
{
  auto it = find(pos.begin(), pos.end(), el);
  if(it!=pos.end())
  {
    int index = distance(pos.begin(), it);
    return index;
  }
  return -1;
}
void printGameBoard()
{
  for(auto i=0; i<5; i++)
  {
    for(auto j=0; j<5; j++)
    {
      cout<<gameBoard[i][j];
    }
    cout<<endl;
  }
}

void _place(int i, int j, char symbol)
{
  if(gameBoard[i][j] != "X" && gameBoard[i][j] != "O")
  {
    gameBoard[i][j] = symbol;
    return;
  }
  wrongTurn();
}

void placePeice(int pos, string user)
{
  char symbol = ' ';
  if(user == "Player" && pos>=1 && pos<=9)
  {
    cout<<"Player: "<<pos<<endl;
    playerPos.push_back(pos);
    symbol = 'X';
  } else if(user == "CPU" && pos>=1 && pos<=9)
  {
    cout<<"CPU: "<<pos<<endl;
    cpuPos.push_back(pos);
    symbol = 'O';
  }
  int index = indexOf(availPos, pos);
  if(index != -1)
  {
    availPos.erase(availPos.begin() + index);
  }

  switch(pos)
  {
    case 1:
      _place(0, 0, symbol);
    break;
    case 2:
      _place(0, 2, symbol);
    break;
    case 3:
      _place(0, 4, symbol);
    break;
    case 4:
      _place(2, 0, symbol);
    break;
    case 5:
      _place(2, 2, symbol);
    break;
    case 6:
      _place(2, 4, symbol);
    break;
    case 7:
      _place(4, 0, symbol);
    break;
    case 8:
      _place(4, 2, symbol);
    break;
    case 9:
      _place(4, 4, symbol);
    break;
    default:
    break;
  }
}

void wrongTurn()
{
  cout<<"Invalid Position, Please Try Again"<<endl;
  playerTurn();
}

void playerTurn()
{
  cout<<"Please Enter a Position [1-9]: ";
  int pos;
  cin>>pos;
  placePeice(pos, "Player");
}

void cpuTurn()
{
  int r = 1 + (rand() % availPos.size());
  int pos = availPos[r];
  placePeice(pos, "CPU");
}

void checkWinner(vector<int>& v, string user)
{
  if(gameOver)
    return;
  vector<int> topRow = {1,2,3};
  vector<int> midRow = {4,5,6};
  vector<int> botRow = {7,8,9};

  vector<int> leftCol = {1,4,7};
  vector<int> midCol =  {2,5,8};
  vector<int> rightCol= {3,6,9};

  vector<int> cross1 = {1,5,9};
  vector<int> cross2 = {3,5,7};

  vector<vector<int>> winning;
  winning.push_back(topRow);
  winning.push_back(midRow);
  winning.push_back(botRow);
  winning.push_back(leftCol);
  winning.push_back(midCol);
  winning.push_back(rightCol);
  winning.push_back(cross1);
  winning.push_back(cross2);

  for(auto i = 0; i < winning.size(); i++)
  {
    int c = 0;
    vector<int> con = winning[i];
    for(auto j = 0; j < con.size(); j++)
    {
      if(std::count(v.begin(), v.end(), con[j]))
      {
        c = 1;
      } else {
        c = 0;
        break;
      }
    }
    if(c==1)
    {
      gameOver = true;
      WinnerName = user;
      return;
    }
  }
  if(playerPos.size() + cpuPos.size() == 9)
  {
    gameOver = true;
    WinnerName = "Draw";
    return;
  }
}

How to Compile?

Use any STL Compatiable C++ compiler. I prefer gcc/g++ 10+

g++ main.cpp -o TicTacToe

Run

This is for Unix environments(Linux, AWS, MacOS), just run the exe for windows in CMD/Powershell.

./TicTacToe

References:

Buy Me A Coffee