Hari's Corner

Humour, comics, tech, law, software, reviews, essays, articles and HOWTOs intermingled with random philosophy now and then

A simple Tic Tac Toe program in C++ (without Minimax)

Filed under: Tutorials and HOWTOs by Hari
Posted on Sun, Oct 23, 2011 at 18:45 IST (last updated: Mon, Oct 24, 2011 @ 16:03 IST)

So, I was bored and wanted to re-visit a programming language I haven't touched in years, C++. I am a bit rusty with my C++, but eventually I managed to implement a simple, command-line Tic Tac Toe (or Noughts and Crosses for us Commonwealth folk).

I deliberately avoided cooking up yet another Minimax TicTacToe (and I must admit I'm not very strong in recursion techniques). I just implemented some common-sense rules and came up with this code:
// Simple command line TicTacToe game
// (C) 2011 V.Harishankar
// Released under the GNU GPL v3 
#include <iostream>
#include <cstdlib>
#include <ctime>
class TicTacToeGame
{
protected:
	enum Turn { PLAYER = 0, AI = 1 };
	// Represents the current turn
	Turn current_turn;
	// Character (O or X for player)
	char player_char, ai_char;
	// Represents the board
	char board[3][3];
	// Clear the board
	void initialize_board ();
	// Display the board
	void print_board ();
	// Chekc if all slots are filled
	bool all_slots_filled (); 
	// Find a winning position for given player (O or X) and fill the 
	// row and column parameter
	void find_winning_position (char noughtorcross, int &row, int &col);
	// Check if the specified player has won (O or X)
	bool check_if_won (char ch);
	// Human player move
	void player_move ();
	// Computer player move
	void ai_move ();
	// Game loop
	void play_game ();
public:
	// Constructor
	TicTacToeGame ()
	{
		// Note that these values are flipped when the game is initialized
		// for the first time
		current_turn = PLAYER;
		player_char = 'X';
		ai_char = 'O'; 
		main_menu ();
	}
	// Main game menu loop
	void main_menu ();
};
void TicTacToeGame::player_move ()
{
	int row, col;
	// Get the row and column from human player
	while (1)
	{
		std::cout << "Enter row for " << player_char << " [1..3]: ";
		std::cin >> row;
		std::cout << "Enter col for " << player_char << " [1..3]: ";
		std::cin >> col;
		row -= 1;
		col -= 1;
		// Row and column must be valid
		if ((row >= 3 || row < 0) || (col >= 3 || col < 0))
			std::cout << "Invalid row/column.\n";
		else 
		{
			// if it is a blank this is a valid move
			if (board[row][col] == ' ')
				break;
			else
				std::cout << "Cell is occupied.\n";
		}
	}
	// Set the player move
	board[row][col] = player_char;
}
void TicTacToeGame::find_winning_position (char noughtorcross, int &row, int &col)
{
	// check if specified character (nought or cross) can win along any row
	int i, j, chcount, emptycol, emptyrow;
	
	// Loop through rows
	for (i = 0; i < 3; i ++)
	{
		// Chcount -number of either O or X along the row 
		chcount = 0;
		emptycol = -1;
		
		// Loop through columns 
		for (j = 0; j < 3; j ++)
		{
			// If the specified player is occupying the cell, add to chcount
			if (board[i][j] == noughtorcross)
				chcount ++;
			// If specified cell is blank mark the column as empty
			else if (board[i][j] == ' ')
				emptycol = j;
		}
		// If there are two of the specified player in the row and there is an
		// empty cell, then the empty cell is the winning position
		if (chcount == 2 && emptycol != -1)
		{
			row = i;
			col = emptycol;
			return;
		}		
	}
	// The same logic as above applies for columns 
	// check if specified character can win along any column
	for (j = 0; j < 3; j ++)
	{
		chcount = 0;
		emptyrow = -1;
		for (i = 0; i < 3; i ++)
		{
			if (board[i][j] == noughtorcross)
				chcount ++;
			else if (board[i][j] == ' ')
				emptyrow = i;
		}
		if (chcount == 2 && emptyrow != -1)
		{
			row = emptyrow;
			col = j;
			return;
		}		
	}
	// Check if specified character can win along diagonal top-left to bottom-right
	chcount = 0;
	emptyrow = -1;
	emptycol = -1;
	// Loop through the diagonal
	for (i = 0; i < 3; i ++)
	{
		if (board[i][i] == noughtorcross)
			chcount ++;
		else if (board[i][i] == ' ')
			emptyrow = emptycol = i;
	}
	if (chcount == 2 && emptyrow != -1 && emptycol != -1)
	{
		row = emptyrow;
		col = emptycol;
		return;
	}
	// Check if specified character can win along diagonal top-right to bottom-left
	chcount = 0;
	emptyrow = -1;
	emptycol = -1;
	// Loop through the diagonal
	for (i = 0; i < 3; i ++)
	{
		if (board[i][2-i] == noughtorcross)
			chcount ++;
		else if (board[i][2-i] == ' ')
		{
			emptyrow = i;
			emptycol = 2 - i;
		}
	}
	if (chcount == 2 && emptyrow != -1 && emptycol != -1)
	{
		row = emptyrow;
		col = emptycol;
		return;
	}
}
void TicTacToeGame::ai_move ()
{
	// Check if AI can win in this move
	int row=-1, col=-1;
	// See if AI can win in this move
	find_winning_position (ai_char, row, col);
	// if AI can win, play the winning move
	if (row != -1 && col != -1)
	{
		board[row][col] = ai_char;
		return;
	}
	row = col = -1;
	// see if player can win next move and block
	// if possible
	find_winning_position (player_char, row, col);
	if (row != -1 && col != -1)
	{
		board[row][col] = ai_char;
		return;
	}
	// check if the centre can be played
	if (board[1][1] == ' ')
	{
		board[1][1] = ai_char;
		return;
	}
	// Corner positions
	int corners[4][2] = {{0, 0}, {0, 2}, {2, 0}, {2, 2}};
	// If any one or more corners are blank
	if (board[0][0] == ' ' || board[0][2] == ' ' || board[2][0] == ' ' || board[2][2] == ' ')
	{
		// randomly select corner
		while (1)
		{
			int corner = rand () % 4;
			row = corners[corner][0];
			col = corners[corner][1];
			if (board[row][col] == ' ')
			{
				board[row][col] = ai_char;
				return;
			}
		}
	}
	// Edge positions
	int edges[4][2] = {{0, 1}, {1, 0}, {1, 2}, {2, 1}};
	// if any one or more edges are blank
	if (board[0][1] == ' ' || board[1][0] == ' ' || board[1][2] == ' ' || board[2][1] == ' ')
	{
		// randomly select edge 
		while (1)
		{
			int edge = rand () % 4;
			row = edges[edge][0];
			col = edges[edge][1];
			if (board[row][col] == ' ')
			{
				board[row][col] = ai_char;
				return;
			}
		}		
	}
}
bool TicTacToeGame::check_if_won (char ch)
{
	bool win = false;
	// Check across 
	int i;
	for (i = 0; i < 3; i ++)
		if (board[i][0] == ch && board[i][1] == ch && board[i][2] == ch)
		{
			win = true;
			break;
		}
	// Check down
	for (i = 0; i < 3; i ++)
		if (board[0][i] == ch && board[1][i] == ch && board[2][i] == ch)
		{
			win = true;
			break;
		}
	// check diagonals
	if (board[0][0] == ch && board[1][1] == ch && board[2][2] == ch)
		win = true;
	if (board[0][2] == ch && board[1][1] == ch && board[2][0] == ch)
		win = true;
	return win;
}
bool TicTacToeGame::all_slots_filled ()
{
	bool filled = true;
	for (int i=0; i < 3; i++)
	{
		for (int j=0; j < 3; j++)
			if (board[i][j] == ' ')
				filled = false;
		if (filled == false)
			break;
	}
	
	return filled;
}
void TicTacToeGame::print_board ()
{
	std::cout << "\n";
	for (int i=0; i < 3; i ++)
	{
		for (int j=0; j < 3; j++)
		{
			std::cout << board[i][j] << "\t";
			if (j < 2)
				std::cout << "|\t";
		}
		if (i < 2)
			std::cout << "\n---------------------------------\n";
	}
	std::cout << "\n";
}
void TicTacToeGame::play_game ()
{
	// Initialize the board first
	initialize_board ();
	// If player char was X then make it O
	if (player_char == 'X')
	{
		player_char = 'O';
		ai_char = 'X';
		current_turn = AI;
	}
	else
	{
		ai_char = 'O';
		player_char = 'X';
		current_turn = PLAYER;
	}
	// So long as gameover is false, keep playing
	bool gameover = false;
	while (! gameover)
	{
		print_board ();
		// if turn is that of the player
		if (current_turn == PLAYER)
		{
			player_move ();
			current_turn = AI;
		}
		else
		{
			std::cout << "\nComputer played: \n";
			ai_move ();
			current_turn = PLAYER;
		}
		// check for winner nought
		if (check_if_won ('O'))
		{
			print_board ();
			std::cout << "\nNoughts won! Game over...\n";
			break;
		}
		else if (check_if_won ('X'))
		{
			print_board ();
			std::cout << "\nCrosses won! Game over...\n";
			break;
		}
		else if (all_slots_filled ())
		{
			print_board ();
			std::cout << "\nGame drawn.\n";
			break;
		}
	}
}
void TicTacToeGame::initialize_board ()
{
	for (int i = 0; i < 3; i++)
		for (int j=0; j < 3; j++)
			board[i][j] = ' ';
}
void TicTacToeGame::main_menu ()
{
	while (1)
	{
		std::cout << "\n -------------------------";
		std::cout << "\n Welcome to TicTacToe game";
		std::cout << "\n -------------------------";
		std::cout << "\n 1. Play";
		std::cout << "\n 2. Exit";
		char ch;
		std::cout << "\n Your choice: ";
		std::cin >> ch;
		switch (ch)
		{
			case '1': play_game (); break; 
			case '2': return;
		}
	}
}
int main ()
{
	// initialize randomizer
	srand (time (NULL));
	TicTacToeGame game;
	return 0;
}
Yes, it's not the prettiest code and it's probably more verbose than you would expect, but the logic is relatively simple to follow as it uses game-specific rules with a little randomization. A lot of Tic Tac Toe tutorials prefer the more sophisticated and general Minimax algorithm (which is really useful for more complex decision algorithms based on a zero-sum formula), but I found few examples with just a normal "AI".

Note that the above logic is not perfect. The AI is relatively dumb in that it doesn't start off with a goal of winning, trying instead to pick up desirable positions (centre, corners, then the other squares), but will win or block the human player from winning if it sees the chance. In actuality, this approximation doesn't affect the game all that much because of the limited scope of Tic Tac Toe board and the duration of gameplay. So far, I've not been able to beat it yet, but I am not sure whether it is an unbeatable strategy.

You can take the above code, save it in a file called tictactoe.cpp, compile it with your favourite C++ compiler and run the resultant executable from the command line to test the program.

Your feedback is much appreciated.

4 comment(s)

  1. Good job, Hari.
    What's your take on Santhosh Pandit?

    Comment by Vinu Varghese (visitor) on Mon, Oct 24, 2011 @ 10:36 IST #
  2. Vinu, I am afraid I don't know who he is. is he a film maker (from what I saw in google)? Never heard of him before.

    P.S. from what I saw of a google video, I think he is Kerala's Sam Anderson, right?

    Comment by Hari (blog owner) on Mon, Oct 24, 2011 @ 14:46 IST #
  3. thanks brother ?i clear my concept

    Comment by M imran (visitor) on Wed, Jan 22, 2014 @ 14:09 IST #
  4. ;-) ;-) ;-) ;-) ;-) wellllll doneeee ....u made my project easy................ :-) :-) :-) :-)

    Comment by shikha (visitor) on Tue, Dec 1, 2015 @ 19:46 IST #

Comments closed

The blog owner has closed further commenting on this entry.