Tic Tac Toe with AI :) + 1 more question

Started by
22 comments, last by superpig 16 years, 4 months ago
I've finally finished it!Here it is:
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
using namespace std;

const char PLAYER_SYMBOL = 'X';
const char PC_SYMBOL = 'O';
const char DEFAULT_SYMBOL = '-';

class TicTacToe
{
private:
	vector <int> freeNodes; // the free nodes
	vector <int> usedNodes; // the used nodes
	char board [3] [3]; // set the board

public:
	void setBoard () // set the board to empty
	{
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				board  [j] = '-';
			}
		}
	}

	void setFreeNodes ()
	{
		for (int i = 1; i <= 9; i++)
		{
			freeNodes.push_back (i);
		}
	}

	void printBoard () // print the board on the screen
	{
		cout << "\n";
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				cout << board  [j] << " ";
			} 
			cout << "\n";
		}
	}
	int getNode ()
	{	
		int node;
		cout << "\n" << "Enter a number from 1-9: ";
		cin >> node;
		return node;
	}
	bool checkInput (int node)
	{
		vector <int>::iterator it;

		for (it = freeNodes.begin(); it < freeNodes.end(); it++)
		{
			if (node == *it)
			{
				return true;
			}
		}
		cout << "\n" << "Invalid input.";
		return false;
	}
	void applyInputToBoard (int node, bool playsP1, bool playsPC)
	{
		int i, j;
		if (node % 3 != 0)
		{
			i = node / 3;
			j = node % 3 - 1;
		}
		else
		{
			i = node / 3 - 1;
			j = 2;
		}

		if (playsP1 == true)
			board  [j] = PLAYER_SYMBOL; // ('X')
		else
			board  [j] = PC_SYMBOL; // ('O')
	}
	void undoInputToBoard (int node) // undo the pc's move (see pcNode)
	{
		int i, j;
		if (node % 3 != 0)
		{
			i = node / 3;
			j = node % 3 - 1;
		}
		else
		{
			i = node / 3 - 1;
			j = 2;
		}
		
		// undo the move
		board  [j] = DEFAULT_SYMBOL; // ('-')
	}
	void addRemoveNodes (int node)
	{
		usedNodes.push_back (node); // add the node in usedNodes
		sort (usedNodes.begin(), usedNodes.end() );

		//remove the node from freeNodes
		int position = 0;
		vector <int>::const_iterator it;
		for (it = freeNodes.begin(); it < freeNodes.end(); it++)
		{
			if (*it == node)
			{
				break;
			}

			position ++; // position is the position of the number (node) in the vector
		}
		
		/*when (*it = node), position isn't increased, because it breaks the loop. But that doesn't matter because when  erase, I have to delete -1
		 because it starts from the the first element. position is already -1 so I don't have to subtract anything.
		eg it should have been "freeNodes.erase (freeNodes.begin() + position - 1);", but position is -1 from the 
		node's position, so, it's ok.*/
		
		freeNodes.erase (freeNodes.begin() + position);
		sort (freeNodes.begin(), freeNodes.end() );
	}
	int checkWin ()
	{
		enum Won
		{
			none = 0,
			winsP1 = 1,
			winsPC = 2,
			tie = 3
		};

		/* This is how the board is.
		 (i, j)		 (i, j+1)	 (i, j+2) 
		(i+1, j)	(i+1, j+1)	(i+1, j+2)
		(i+2, j)	(i+2, j+1)	(i+2, j+2)
		*/

		//horizontal
		for (int i = 0; i < 3; i++)
		{
			int j = 0;
				
			if (board [j] == board [j+1] && board [j] == board [j+2])
			{
				if (board [j] == PLAYER_SYMBOL)
					return winsP1;
				else if (board [j] == PC_SYMBOL)
					return winsPC;
			}
		}

		//vertical
		for (int j = 0; j < 3; j++)
		{
			int i = 0;

			if (board [j] == board [i+1][j] && board [j] == board [i+2][j])
			{
				if (board [j] == PLAYER_SYMBOL)
					return winsP1;
				else if (board [j] == PC_SYMBOL)
					return winsPC;
			}
		}

		//diagonal
		int i = 0, j = 0;
		// 1 - 5 - 9
		if (board [j] == board [i+1][j+1] && board [j] == board [i+2][j+2])
		{
			if (board [j] == PLAYER_SYMBOL)
				return winsP1;
			else if (board [j] == PC_SYMBOL)
				return winsPC;
		}
		// 3 - 5 - 7
		if (board [j+2] == board [i+1][j+1] && board [j+2] == board [i+2][j])
		{
			if (board [j+2] == PLAYER_SYMBOL)
				return winsP1;
			else if (board [j+2] == PC_SYMBOL)
				return winsPC;
		}

		if (freeNodes.empty() == true)
			return tie;
		else
			return none;
	}
	int pcNode ()
	{
		vector <int>::iterator it = freeNodes.begin();

		// for every free node, check IF CAN WIN
		for (it = freeNodes.begin(); it < freeNodes.end(); it++)
		{
			bool winsP1, winsPC;

			applyInputToBoard (*it, false, true);

			int win = checkWin(); // check if this node is a winning node
			if (win == 1)
				winsP1 = true;
			else if (win == 2)
				winsPC = true;

			if (winsPC == true) // if this is a winning move for the pc, return the value
				return *it;
			else // if not, undo the move :)
			{
				undoInputToBoard (*it);
			}
		}
		/* -------------------------------------------------------------------------------------------------------------*/
		srand (static_cast <unsigned int> (time(NULL)));	
		// for every free node, check IF THE PLAYER CAN WIN (if he can, block him)
		int block;
		if (usedNodes.size() <= 4) //the pc will block until x nodes are used, then will have 60% to block			
			block = 1; //it will always block when <= x nodes have been used
		else if (usedNodes.size() <= 6)
			block = 1 + rand() % 80; // when the nodes used are 5 or 6, the pc has more chance to block than after
		else
			block = 1 + rand() % 100;

		int blockChance;
		if (board [1][1] == PLAYER_SYMBOL)
			blockChance = 70; // if the player has the middle node, the PC has more chance to block him (plays smarter)
		else
			blockChance = 60;

		//->
		if (block <= blockChance)
		{
			for (it = freeNodes.begin(); it < freeNodes.end(); it++)
			{
				bool winsP1, winsPC;

				applyInputToBoard (*it, true, false);

				int win = checkWin(); // check if this node is a winning node
				if (win == 1)
					winsP1 = true;
				else if (win == 2)
					winsPC = true;

				if (winsP1 == true) // if this is a winning move for the player, block him
					return *it;
				else // if not, undo the move :)
				{
					undoInputToBoard (*it);
				}
			}
		}
		// -----------------------------------------------------------------------------------------------------------
		//if the middle node is free, apply node
		int chance = 1 + rand() % 100;
		if (usedNodes.size() == 1)
		{
			if (chance <= 60)
			{
				for (it = freeNodes.begin(); it < freeNodes.end(); it++)
				{
					if (*it == 5)
						return *it;
				}
			}
		}
		else // if usedNodes > 1
		{
			for (it = freeNodes.begin(); it < freeNodes.end(); it++)
			{
				if (*it == 5)
					return *it;
			}
		}
		
		// if the player chooses the middle node, then the pc chooses a node in a corner
		random_shuffle (freeNodes.begin(), freeNodes.end() );
		if (board [1][1] == PLAYER_SYMBOL) 
		{
			for (it = freeNodes.begin(); it < freeNodes.end(); it++)
			{
				if ( (*it == 1) || (*it == 3) || (*it == 7) || (*it == 9) )
					return *it;
			}
		}

		//-----------------------------------------------------------------------------------------------------------
		//else choose a random node
		int node;
		int randomNode = 1 + rand() % 9;
		
		it = freeNodes.begin();

		while (it < freeNodes.end() )
		{
			if (randomNode == *it) // if node is free
			{
				int i = randomNode;
				return randomNode;
			}
			
			it++;

			if (it == freeNodes.end() ) // if the node is not in freeNodes (if it's not free), repeat again
			{
				it = freeNodes.begin();
				randomNode = 1 + rand () % 9;
			}
		}

		node = randomNode;
		return node;
	}
	void print()
	{
		cout << "\n" << "Used nodes: ";
		vector <int>::const_iterator it;
		for (it = usedNodes.begin(); it < usedNodes.end(); it++)
		{
			cout << *it << " ";
		}

		cout << "\n" << "Free nodes: ";
		for (it = freeNodes.begin(); it < freeNodes.end(); it++)
		{
			cout << *it << " ";
		}

		cout << "\n";
	}
};

int main()
{
	cout << "Tic Tac Toe" << "\t" << "[by Minas Mina]"
		 << "\n" << "You: X" << "\t" << "PC: O" << "\n";

	TicTacToe game; // create an object for the class

	game.setBoard(); // set the board
	game.setFreeNodes();

	bool playsP1 = true, playsPC = false;
	bool winsP1 = false, winsPC = false, tie = false;

	while ( (winsP1 == false) && (winsPC == false) && (tie == false) )
	{
		if (playsP1 == true)
		{
			int node;
			bool acceptInput = false;
			
			while (acceptInput == false)
			{	
				node = game.getNode();
				acceptInput = game.checkInput (node); // check if the node is free	
			}
			game.addRemoveNodes (node);
			game.applyInputToBoard (node, playsP1, playsPC); //update the board	
			
			int win = game.checkWin();
			if (win == 1)
				winsP1 = true;
			else if (win == 3)
				tie = true;

			game.printBoard();
			playsP1 = false;
			playsPC = true;
			//game.print(); // // prints the vectors
		}

		if (winsP1 == true || tie == true)
			break;
		cout << "\n--------------------------------------------------------------------------\n";
		
		if (playsPC == true)
		{	
			int node = game.pcNode();
			cout << "\n" << "PC plays " << node << "\n";
			game.addRemoveNodes (node);
			game.applyInputToBoard (node, playsP1, playsPC); //update the board

			int win = game.checkWin();

			if (win == 1)
				winsP1 = true;
			else if (win == 2)
				winsPC = true;
			else if (win == 3)
				tie = true;

			game.printBoard();
			playsP1 = true;
			playsPC = false;
			//game.print(); // prints the vectors
			cout << "\n--------------------------------------------------------------------------\n";
		}
	}

	if (winsP1 == true)
		cout << "\n" << "->You win!!";
	else if (winsPC == true)
		cout << "\n" << "->You lose..";
	else
		cout << "\n" << "->Tie..";

	cout << "\n";
	system ("PAUSE");
	return 0;
}

The AI can be found in the pcNode function. Is it good? [Edited by - sheep19 on November 25, 2007 3:07:57 PM]
Advertisement
hey, i'm getting a runtime error.

It says that winPC is being used without being initialized.
You really shouldn't have your functions fleshed out inside your class like that. Best to declare the methods in the class and implement them separately in separate files.

Let's call your header TicTacToe.h. You can then #include this in TicTacToe.cpp and actually define your functions within that instead of inside your header file. It keeps things a lot neater and it's much better practice because you can then include the header file in other files without running into multiple declaration linker errors.

As follows:

// TicTacToe.h#include <iostream>#include <vector>#include <algorithm>#include <ctime>using namespace std;const char PLAYER_SYMBOL = 'X';const char PC_SYMBOL = 'O';const char DEFAULT_SYMBOL = '-';class TicTacToe{private:	vector <int> freeNodes; // the free nodes	vector <int> usedNodes; // the used nodes	char board [3] [3]; // set the boardpublic:void setBoard();void setFreeNodes();void PrintBoard();int getNode();bool checkInput(int node);void applyInputToBoard(int node, bool playsP1, bool playsPC);// and so on....


// TicTacToe.cpp#include "TicTacToe.h"void TicTacToe::setBoard(){for (int i = 0; i < 3; i++)		{			for (int j = 0; j < 3; j++)			{				board  [j] = '-';			}		}}void TicTacToe::setFreeNodes(){for (int i = 1; i <= 9; i++)		{			freeNodes.push_back (i);		}}// and so on...


I don't have time to look over your code properly right now, but I feel there are a lot of parts which could be refactored and slimmed down in some way. You could also slip in a couple of reuseable functions here and there as well to avoid repeating yourself and repeating code unnecessarily.

In the meantime, please bear in mind my advice above regarding organisation of things. It's the preferred way of doing things and that's the way you'll see in basically every book you read about C++.



where do I put int main() ? In the header or in the source file?
Work fine. It know how to block. Nice!
DinGY
Yesterday is history.Tomorrow is a mystery. Today is a gift"
Quote:Original post by ICUP
hey, i'm getting a runtime error.

It says that winPC is being used without being initialized.


That's strange, because it works fine for me. Which compiler are you using? (I'm using VC++ 2005 EE)
Quote:Original post by sheep19
Quote:Original post by ICUP
hey, i'm getting a runtime error.

It says that winPC is being used without being initialized.


That's strange, because it works fine for me. Which compiler are you using? (I'm using VC++ 2005 EE)


It's most likely debug build, or the run-time checks turned on.

You have a bug:
bool winsP1, winsPC;			applyInputToBoard (*it, false, true);			int win = checkWin(); // check if this node is a winning node			if (win == 1)				winsP1 = true;			else if (win == 2)				winsPC = true;			if (winsPC == true) // if this is a winning move for the pc, return the value				return *it;


What happens if win is not 1 or 2? winsPC after the test is in undefined state.

While you may consider this a minor issue, you have just made a very common mistake in C++ - using uninitialized variable, source of many problems.

Every time you declare a variable, initialize it to proper value, or restructure the if statement above to always set the proper value.
Quote:Original post by Antheus

You have a bug:*** Source Snippet Removed ***

What happens if win is not 1 or 2? winsPC after the test is in undefined state.

While you may consider this a minor issue, you have just made a very common mistake in C++ - using uninitialized variable, source of many problems.

Every time you declare a variable, initialize it to proper value, or restructure the if statement above to always set the proper value.


Actually it's not a bug. If win is not 1 or 2, then it won't return *it. It checks if it's true, if it's uninitialized then it's not true. (If I had put
else if (winsPC == false) then it would have been a problem.
The thing is it should have given a warning. (I tried it in a simple program and it did).
I'm going to give them a value of 0 though, because it's not good practice not to.
Thank you, I haven't really noticed it.
Quote:It checks if it's true, if it's uninitialized then it's not true


Whoa, careful. This is C++, not Java or C#. The value will be anything, either true or false.

bool winsP1, winsPC;applyInputToBoard (*it, false, true);int win = checkWin(); // check if this node is a winning nodeif (win == 1)  winsP1 = true;else if (win == 2)  winsPC = true;if (winsPC == true) // for win == 3, winsPC can be anything. True or false.


Unitialized winsPC can have value of true. Or false. Depending on the garbage which happens to be located on the stack. Or perhaps not if it's stored in registers. Point is - you do not know what at that point winsPC is.

This is undefined behavior, and as such the (winsPC == true) is non-deterministic.

Quote:Actually it's not a bug.


This is a very serious bug, which can lead to very frustrating random failures that are very difficult to debug.
To avoid uninitialized variables:
int win = checkWin(); // check if this node is a winning node				winsP1 = (win == 1);				winsPC = (win == 2);			if (winsPC) // no need to test == true, since true == true				return *it;


You write:
int chance = 1 + rand() % 100;		if (usedNodes.size() == 1)		{			if (chance <= 60)			{				for (it = freeNodes.begin(); it < freeNodes.end(); it++)				{					if (*it == 5)						return *it;				}			}		}


Better (no loop, does not initialize unused variable):
		if (usedNodes.size() == 1)		{int chance = 1 + rand() % 100;			if (chance <= 60 && board[1][1] == DEFAULT_SYMBOL) {						return 5;			}		}

This topic is closed to new replies.

Advertisement