Jump to content

  • Log In with Google      Sign In   
  • Create Account

tictactoe


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
18 replies to this topic

#1 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 29 September 2012 - 09:31 PM

I am working on a tic tac toe game using c++.My question is how do I get the O's to not overwrite the X's on the board.I am using a boolean array to do a simple comparison using two boolean arrays.I just need a little hint.Here is the code I am working on.
void Computer::move_player_O()
{
srand(time(NULL));
player_O = rand()%9+1;

if(array_X[player_O]==false)
{
board[player_O]='O';
}

array_O[player_O]=true;
}


Sponsor:

#2 superman3275   Crossbones+   -  Reputation: 2061

Like
0Likes
Like

Posted 29 September 2012 - 09:56 PM

Why not just use an array of chars, and have the characters set to X or O whenever you or the computer trys to put an X or an O on them, but before actually setting the character to X or O see if it's already covered by an X or an O. Also, In every game loop, check for win or lose conditions.
CODE:
char Board[9];
//ALL PIECES START AT ONE
for (index = -1; index < 8; ++index)
{
	 Board[Index] = '1'
}
//WHENEVER SOMEONE TRYS TO MAKE A MOVE
if (Board[WhateverSquareTheyreTryingToUse == 1])
{
//Set The Square on the Board to either X or 0
}
//Check for end of game conditions
Sorry I couldn't flesh it out more, but thats essentially how I programmed tic tac toe.

I'm a game programmer and computer science ninja ph34r.png!

Here's my 2D RPG-Ish Platformer Programmed in Python + Pygame, with a Custom Level Editor and Rendering System!

 

Here's my Custom IDE / Debugger Programmed in Pure Python and Designed from the Ground Up for Programming Education!

Want to ask about Python, Flask, wxPython, Pygame, C++, HTML5, CSS3, Javascript, jQuery, C++, Vimscript, SFML 1.6 / 2.0, or anything else? Recruiting for a game development team and need a passionate programmer? Just want to talk about programming? Email me here:

hobohm.business@gmail.com

or Personal-Message me on here smile.png!


#3 GameC++Expert93   Members   -  Reputation: 121

Like
0Likes
Like

Posted 29 September 2012 - 09:58 PM

I am working on a tic tac toe game using c++.My question is how do I get the O's to not overwrite the X's on the board.I am using a boolean array to do a simple comparison using two boolean arrays.I just need a little hint.Here is the code I am working on.

void Computer::move_player_O()
{
srand(time(NULL));
player_O = rand()%9+1;

if(array_X[player_O]==false)
{
board[player_O]='O';
}

array_O[player_O]=true;
}


my guess would be to only allow the computer to fill in a blank spot on your tic tac toe board by checking if the spot already contains an 0

#4 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 29 September 2012 - 10:00 PM

cool thanks for the help.

#5 Servant of the Lord   Crossbones+   -  Reputation: 21008

Like
2Likes
Like

Posted 29 September 2012 - 10:08 PM

This piece of code: "rand() % 9 + 1"generates a number between 1 and 10. In C++, arrays begin at 0, so you may not want the +1 there.
(An array with 10 elements, goes from 0 to 9)

srand(time(NULL)); is only supposed to be called once at the beginning of your program - not every function call, or the results won't be properly random. Call srand() once, and rand() as many times as you like.

If 'board[]' is a boolean array, you can't assign it a 'O'. If it's a char array, then it's okay.

If this statement: "if(array_X[player_O]==false)" is valid, then you probably want "array_O[player_O]=true;" within the if() statement also.
if(array_X[player_O]==false)
{
	 board[player_O]='O';
     array_O[player_O]=true;
}

But let's seperate out the two different things you are doing.
A) You are having a board be clicked, and checking if the place is valid, and if valid, marking the board at that spot.
B) You are also doing AI, by making it be the computer doing the move.

The two should be seperated into different functions.
void Computer::do_turn()
{
     //The computer's logic goes here.

     //Then you place the marker.
     place_marker(spot);
}

void Computer::place_marker(int spot)
{
     board[spot]='O';
     array_O[spot]=true;
}

The question is, what do you do for the computer's logic?
There are two options that come to my mind:
1) Randomly choose a location, and if it's taken, randomly choose a new location, and if that's taken, randomly choose a new location, and so on.
The problem: You can (possibly) have very bad random numbers and (potentially) loop forever accidentally choosing the same bad spots over and over.

In code, this would look like this:
void Computer::do_turn()
{
     int spot = 0;
     bool good = false;
     do
     {
		  spot = rand() % SIZE_OF_BOARD;

          //Check that we didn't previously go here.
          if(array_O[spot]==false)
          {
               //Check that the other player didn't previously go here.
               if(array_X[spot]==false)
               {
                    //If the place is actually empty, then this spot is good.
                    good = true;
               }
          }
     }
     while(!good)


	 //Place the marker where the computer decided.
	 place_marker(spot);
}

But, as mentioned, this is not good, because we could accidentally randomly loop forever if we keep on picking spots that aren't good.

2) The second option is to make sure that the only places that are available to randomly pick, are ones that we know are already empty.
Now there are two ways to do that. One is to keep track of what places haven't been used by either player. Another is to, each time the computer is ready to pick, check which places are available. We're going to try the second option.

void Computer::do_turn()
{
     //Count how many empty spaces are left.
     int numberOfEmptySpaces = 0;
     for(int i = 0; i < SIZE_OF_BOARD; i++)
     {
          if(board[i] != 'O' && board[i] != 'X')
          {
          	 numberOfEmptySpaces++;
          }
     }

     //Randomly generate a random number within the number of empty spaces.
	 int spot = rand() % numberOfEmptySpaces;

     //Keep looping until we find a spot that isn't filled.
	 while(board[spot] == 'O' || board[spot] == 'X')
	 {
          //Each time we find our spot taken, we move to the next spot.
          spot++;

          //Unless we reach the end of our array, in which case we loop back around to zero.
          if(spot >= SIZE_OF_BOARD)
          {
               spot = 0;
          }
	 }

	 //Place the marker where the computer decided.
	 place_marker(spot);
}

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#6 GameC++Expert93   Members   -  Reputation: 121

Like
0Likes
Like

Posted 29 September 2012 - 10:11 PM

This piece of code: "rand() % 9 + 1"generates a number between 1 and 10. In C++, arrays begin at 0, so you may not want the +1 there.
(An array with 10 elements, goes from 0 to 9)

srand(time(NULL)); is only supposed to be called once at the beginning of your program - not every function call, or the results won't be properly random. Call srand() once, and rand() as many times as you like.

If 'board[]' is a boolean array, you can't assign it a 'O'. If it's a char array, then it's okay.

If this statement: "if(array_X[player_O]==false)" is valid, then you probably want "array_O[player_O]=true;" within the if() statement also.

if(array_X[player_O]==false)
{
	 board[player_O]='O';
	 array_O[player_O]=true;
}

But let's seperate out the two different things you are doing.
A) You are having a board be clicked, and checking if the place is valid, and if valid, marking the board at that spot.
B) You are also doing AI, by making it be the computer doing the move.

The two should be seperated into different functions.
void Computer::do_turn()
{
	 //The computer's logic goes here.

	 //Then you place the marker.
	 place_marker(spot);
}

void Computer::place_marker(int spot)
{
	 board[spot]='O';
	 array_O[spot]=true;
}

The question is, what do you do for the computer's logic?
There are two options that come to my mind:
1) Randomly choose a location, and if it's taken, randomly choose a new location, and if that's taken, randomly choose a new location, and so on.
The problem: You can (possibly) have very bad random numbers and (potentially) loop forever accidentally choosing the same bad spots over and over.

In code, this would look like this:
void Computer::do_turn()
{
	 int spot = 0;
	 bool good = false;
	 do
	 {
		  spot = rand() % SIZE_OF_BOARD;

		  //Check that we didn't previously go here.
		  if(array_O[spot]==false)
		  {
			   //Check that the other player didn't previously go here.
			   if(array_X[spot]==false)
			   {
					//If the place is actually empty, then this spot is good.
					good = true;
			   }
		  }
	 }
	 while(!good)


	 //Place the marker where the computer decided.
	 place_marker(spot);
}

But, as mentioned, this is not good, because we could accidentally randomly loop forever if we keep on picking spots that aren't good.

2) The second option is to make sure that the only places that are available to randomly pick, are ones that we know are already empty.
Now there are two ways to do that. One is to keep track of what places haven't been used by either player. Another is to, each time the computer is ready to pick, check which places are available. We're going to try the second option.

void Computer::do_turn()
{
	 //Count how many empty spaces are left.
	 int numberOfEmptySpaces = 0;
	 for(int i = 0; i < SIZE_OF_BOARD; i++)
	 {
		  if(board[i] != 'O' && board[i] != 'X')
		  {
			   numberOfEmptySpaces++;
		  }
	 }

	 //Randomly generate a random number within the number of empty spaces.
	 int spot = rand() % numberOfEmptySpaces;

	 //Keep looping until we find a spot that isn't filled.
	 while(board[spot] == 'O' || board[spot] == 'X')
	 {
		  //Each time we find our spot taken, we move to the next spot.
		  spot++;

		  //Unless we reach the end of our array, in which case we loop back around to zero.
		  if(spot >= SIZE_OF_BOARD)
		  {
			   spot = 0;
		  }
	 }

	 //Place the marker where the computer decided.
	 place_marker(spot);
}


I think he may be mixed up thinking about a random number generator where u may not want the number to start with 0 and may not exactly have the workings of an array in mind

#7 Trienco   Crossbones+   -  Reputation: 2224

Like
2Likes
Like

Posted 30 September 2012 - 01:24 AM

This piece of code: "rand() % 9 + 1"generates a number between 1 and 10. In C++, arrays begin at 0, so you may not want the +1 there.
(An array with 10 elements, goes from 0 to 9)


Nitpick: the number will be between 1 and 9.

About the implementation of the second option. You actually fooled me, as my first impression was you determine a number between 0 and emptySpaces, then make as many steps from the start (skipping filled spots). In that case you should never have to wrap around. But with your approach of starting at the selected spot and then skipping ahead until the first free cell, is the first part even necessary (as in: does it produce better randomized results)?

My impression would be that it might make it less random. If the first 4 spots are filled, you would ways end up with the 5th spot: 5 spots are empty, so spot = rand() % 5 = 0-4. In each case you get spot = 4.

So I think you should either always create numbers 0-8 and skip empty spots as above, or if you do create a number using only the free spots, it would probably be something like this:

int steps = rand() % numberOfEmptySpaces;
int spot = -1;

while (steps >= 0)
{
	//If free, decrease step count
	if (board[spot] != 'O' && board[spot] != 'X')
	{
		 --steps;
	}
	++spot;
}

place_marker(spot);

I believe that should create an equal probability for each spot.

Alternatively (and somewhat more cumbersome) you could add an extra layer of indirection by keeping a list of empty spots and randomly picking an index from that list.

   //Declared somewhere and initialized with the board
   vector<int> freeSpots; //Fill with 0 - sizeOfBoard


   const int idx = rand() % freeSpots.size();
   place_marker(freeSpots[idx]);

   freeSpots.erase(freeSpots.begin() + idx);

Since erasing from anywhere but the end of a vector requires moving stuff in memory, a common method is to not remove a value, but swap it with the end and reducing the size of the vector.

swap(freeSpots[idx], freeSpots[freeSpots.size()-1]);
freeSpots.pop_back();

By not removing the last element and instead keeping track of the size yourself, you can keep reusing the same vector and just need to reset the size

vector<int> freeSpots(SIZE_OF_BOARD); //Initialize once when starting up


//When placing a marker
swap(freeSpots[idx], freeSpots[--numberOfFreeSpots]);

//When resetting the game
numberOfFreeSpots = SIZE_OF_BOARD;

Since all random numbers should be equally likely, it doesn't matter in which order the numbers are stored in freeSpots.

Personally I like this approach for simulating a deck of cards, as you don't need to do any initial "shuffling".

Edited by Trienco, 30 September 2012 - 01:34 AM.

f@dzhttp://festini.device-zero.de

#8 Servant of the Lord   Crossbones+   -  Reputation: 21008

Like
0Likes
Like

Posted 30 September 2012 - 10:18 AM

I think he may be mixed up thinking about a random number generator where u may not want the number to start with 0 and may not exactly have the workings of an array in mind

He's using C++. He's a beginner. He's using the generated value to index into two seperate arrays. Thus, it should start at 0, unless he's intentionally using position 0 for some other purpose.

Chances are, he copied the srand(time(null); rand() % range + 1; from someplace online (which is fine) and didn't full understand it (which is fine). The highly likely chance that it was mistaken is why I commented on it; if it was a mistake, now he knows why. If it wasn't a mistaken, then my comment won't cause him any inconvience.


This piece of code: "rand() % 9 + 1"generates a number between 1 and 10.

Nitpick: the number will be between 1 and 9.

/facepalm
You are absolutely correct. Posted Image

About the implementation of the second option. You actually fooled me, as my first impression was you determine a number between 0 and emptySpaces, then make as many steps from the start (skipping filled spots). In that case you should never have to wrap around.

That would definitely be a better idea!

But with your approach of starting at the selected spot and then skipping ahead until the first free cell, is the first part even necessary (as in: does it produce better randomized results)?


No, I switched what I was originally going to do halfway through, because it'd require the OP learning too much too fast.
I was originally going to create an array of indices that represent empty spots (like you also suggest), and then just do the random indicing into that:

std::vector<int> validIndices;
for(int i = 0; i < SIZE_OF_BOARD; i++)
{
	if(board[i] != 'O' && board[i] != 'X')
	{
		  validIndices.push_back(i);
	}
}

int choice = rand() % validIndices.size();

this->place_marker(validIndices[choice]);

But that would require std::vector or other confusions.
I was trying to avoid excessively teaching new things to solve the logic puzzle within the knowledge the OP already had - but I blew it! Posted Image
I avoided using std::count() in my previous solution (counting with a for() loop instead) for the same reason.

Your non-vector no-loop suggestion is much better than my non-vector solution (which as you point out, apparenly has problems with accurate probabillity). Though I think either of us would actually change things at a higher level than just that one function, but that wouldn't help the OP much.

Edited by Servant of the Lord, 30 September 2012 - 10:22 AM.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#9 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 01 October 2012 - 03:35 PM

cool thanks for all the help, any other ideas?

#10 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 04 October 2012 - 05:57 PM

thanks servant for all the help,any other help?here is the code I am working on.

void Computer::move_player_O()
{
player_O = rand()%8+1;
board[player_O]='O';
}
void Computer::check_player_O()
{
if(array_O[player_O]==false)
{
  board[player_O]='O';
}
}


#11 superman3275   Crossbones+   -  Reputation: 2061

Like
0Likes
Like

Posted 04 October 2012 - 07:34 PM

Well, you're not making sure the square isn't covered before placing the piece. And what is the point of the Computer class?

I'm a game programmer and computer science ninja ph34r.png!

Here's my 2D RPG-Ish Platformer Programmed in Python + Pygame, with a Custom Level Editor and Rendering System!

 

Here's my Custom IDE / Debugger Programmed in Pure Python and Designed from the Ground Up for Programming Education!

Want to ask about Python, Flask, wxPython, Pygame, C++, HTML5, CSS3, Javascript, jQuery, C++, Vimscript, SFML 1.6 / 2.0, or anything else? Recruiting for a game development team and need a passionate programmer? Just want to talk about programming? Email me here:

hobohm.business@gmail.com

or Personal-Message me on here smile.png!


#12 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 05 October 2012 - 04:46 PM

is there anyway to optimize the following classes for a tic tac toe game.I think I am overcoding this program.
class Board
{
public:
void draw_board();
void draw_board_X();
void draw_board_O();
};
class Player
{
public:
void declare_winner_X();
void move_player_X();
private:
int player_X;
};
class Computer
{
public:
void declare_winner_O();
void move_player_O();
void check_player_O();
private:
int player_O;
};


#13 Servant of the Lord   Crossbones+   -  Reputation: 21008

Like
0Likes
Like

Posted 06 October 2012 - 12:06 AM

'Optimize' is the wrong word. 'Simplify' makes more sense here.

A few thoughts:
  • Class member-variables only should be used when a variable needs to store data for longer than one function call. So 'player_X' and 'player_O' both should not be member-variables.
  • Really, you shouldn't use classes here at all. Regular functions would work better.
  • You could use classes, if you wanted to abstract a 'Player', and have 'HumanPlayer' and 'ComputerPlayer' subclasses that implement the logic, but that'd be going overboard in tic-tac-toe, and might be beyond your current skill level anyway.
  • A class should (most of the time) only be used when you need more than one copy of that class. Since you only have one Board, only have one Player, and only have one Computer, they shouldn't be classes. Note: This is not always true, but it'll help you think about classes better if you (when starting out) think of it that way.
Thought challenge:
What features/abilities/purposes do Player and Computer have in common? They both operate on the board in the same way, they both place their pieces, and their pieces both need to be blocked if that place is already filled.
How can the same code that is copy+pasted between Player and Computer not need to be copy+pasted, but be separated out into single functions that are instead called from both Player and Computer, to remove duplicate code? Could those functions be added to the Board class instead?

In what ways are Player and Computer the same?
In what ways are Player and Computer different?
Could Player and Computer be instead a single class, and you have two variables both using that single class - one for player O and one for player X?
How can you separate out of the class the only things that make Player and Computer different, and put them outside of the class instead of inside it?
It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#14 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 06 October 2012 - 11:57 AM

so what your saying is that I should use functions instead classes?

#15 rip-off   Moderators   -  Reputation: 8726

Like
2Likes
Like

Posted 06 October 2012 - 12:17 PM

Yes, mostly. It might make sense to have a Board class to make the functions easier to write.

A simple way to start programming such a game is to start with main(), and write the steps the game should take. Introduce a variable only where you need to remember something.

So something like this:
int main()
{
    Player currentPlayer = PLAYER_NOBODY;
    if(randomProbability() >= 0.5) {
        currentPlayer = PLAYER_HUMAN;
    } else {
        currentPlayer = PLAYER_COMPUTER;
    }


    Board board = makeBlankBoard();
    while(!gameOver(board)) {
         if(currentPlayer != PLAYER_COMPUTER) {
             drawBoard(board);
         }

         Move move;
         do {
             move = determineMove(currentPlayer, board);
         } while(!isLegalMove(board, move, currentPlayer);

         makeMove(board, move);

         player = nextPlayer(currentPlayer);
    }

    Player winner = determineWinner(board);
    if(winner != PLAYER_NOBODY) {
        printWinner(winner);
    } else {
        printDraw();
    }
}
Note we haven't decided the specifics yet. We don't know how the player chooses a move, nor what constitutes a legal move. We don't know how to draw the board or when the game is over. In particular, the main loop above works just as easily for chess as Tic Tac Toe, or for any other standard two player board game.

#16 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 06 October 2012 - 01:45 PM

ok cool, well I am going to rewrite my code.

#17 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 07 October 2012 - 10:01 PM

I have rewritten my code using functions instead of classes as I was advised to.I am still having a problem getting the computer not to overwrite an X with an O. Here is my code:
void check_player_O()
{
if(player_O==player_X)
{
  player_O=rand()%8+1;
  board[player_O]='O';
}
}
void move_player_O()
{
player_O=rand()%8+1;
}

I suspect I am getting close to solving this problem, I will continue to work on this problem.

#18 Servant of the Lord   Crossbones+   -  Reputation: 21008

Like
2Likes
Like

Posted 07 October 2012 - 11:35 PM

Try to pass in any variables you need as arguments, rather than having them as global variables.

Here's some psuedocode for how you might structure your program:
(psuedocode means "Fake code that explains a subject, without actually being valid computer code that can be compiled")
class Board
{
public:
    const char EmptySpace = ' ';
   
    Board()
    {
	    //The board's constructor creates a 3 by 3 board, and sets it all to empty.
	    this->boardData.assign((3 * 3), EmptySpace);
    }
   
    //This function only returns true if this position on the board is empty.
    bool IsEmpty(int position) const
    {
	    //Make sure that 'position' is within range.
	    if(position < 0 || position >= boardData.size())
		    return false;
	   
	    //Check if the board is empty.
	    return boardData[position] = EmptySpace;
    }
   
    //Marks the spot 'position' on the board, using 'piece' to mark it.
    void MarkSpot(int position, char piece)
    {
	    //Make sure that 'position' is within range.
	    if(position < 0 || position >= boardData.size())
		    return;
	   
	    //Mark the board at that position.
	    boardData[position] = piece;
    }
   
    //This function checks the board for three in a row of 'piece',
    //and returns true if there are three in a row.
    bool ThreeInARow(char piece) const
    {
	    //Check all the different possible winning states.
	    //The board is laid out like this:
	    //
	    //   [0] [1] [2]
	    //   [3] [4] [5]
	    //   [6] [7] [8]
	    //
	   
	    bool threeInARow = false;
	   
	    //Top row:
	    if(boardData[0] == piece && boardData[1] == piece && boardData[2] == piece)
	    {   threeInARow = true; }
	   
	    //Middle row:
	    if(boardData[3] == piece && boardData[4] == piece && boardData[5] == piece)
	    {   threeInARow = true; }
	   
	    //Bottom row:
	    if(boardData[6] == piece && boardData[7] == piece && boardData[8] == piece)
	    {   threeInARow = true; }
	   
	    //Left column:
	    if(boardData[0] == piece && boardData[3] == piece && boardData[6] == piece)
	    {   threeInARow = true; }
	   
	    //Center column:
	    if(boardData[1] == piece && boardData[4] == piece && boardData[7] == piece)
	    {   threeInARow = true; }
	   
	    //Right column:
	    if(boardData[2] == piece && boardData[5] == piece && boardData[8] == piece)
	    {   threeInARow = true; }
	   
	    //Diagonal from top-left:
	    if(boardData[0] == piece && boardData[4] == piece && boardData[8] == piece)
	    {   threeInARow = true; }
	   
	    //Diagonal from top-right:
	    if(boardData[2] == piece && boardData[4] == piece && boardData[6] == piece)
	    {   threeInARow = true; }
	   
	    return threeInARow;
    }
   
    //Returns the number of empty spaces left on the board.
    int CountEmptySpaces() const
    {
	    //Loop over the entire board, and count the number of empty spaces.
	    for(int i = 0; i < boardData.size(); i++)
	    {
		    if(boardData[i] == EmptySpace)
		    {
			    //...?
		    }
	    }
	   
	    return ???
    }
   
private:
    //The board data.
    std::vector<char> boardData;
};
int GetPlayerDecision(const Board &board)
{
    int playerPosition = 0;
   
    //Keep looping until the player makes a valid move.
    bool validMove = false;
    while(!validMove)
    {
	    print "Where do you want to move to?"
	   
	    playerPosition = ??? //Get the player's response.
	   
	    //Check if the board is empty at that spot.
	    if(board.IsEmpty(playerPosition))
	    {
			 //Finish looping once we have a valid move.
			 validMove = true;
	    }
    }
    return playerPosition;
}
int CalculateComputerMove(const Board &board)
{
    //Credit to Trienco for suggesting this better method of choosing a random spot for the computer to go.
   
    int computerPosition = -1;
   
    //Choose a random empty space to go on.
    int steps = (rand() % board.CountEmptySpaces());
   
    //Move through the board a specific number of steps.
    while(steps >= 0)
    {
		    //...but only count empty spaces as a step.
		    if(board.IsEmpty(computerPosition))
		    {
					 steps--;
		    }
		   
		    computerPosition++;
    }
   
    return computerPosition;
}
int main()
{
    //Intro message.
    print "Welcome to TicTacToe!"
   
    //Initialize the random number generator.
    srand(time(NULL));
		   
    //Keep track of the score.
    int computerWins = 0;
    int playerWins = 0;
   
    //As long as the user wants to play, keep looping around.
    //Each loop of this outer loop is one match.
    bool playingTheGame = true;
    while(playingTheGame)
    {
	    //Create the board, and set it all to empty.
	    //(Our board is set to empty automaticly in the constructor. Yay for RAII!)
	    Board board;
	   
	    //Keep track of who won the match.
	    bool playerWon = false;
	    const char PlayerPiece = 'O';
	    const char ComputerPiece = 'X';
	   
	    //Keep looping, switching between the player's turn and the computer's, until the match is over.
	    while(true) //This is an infinite loop.
	    {
		    //----------------------------
		    //Do the player's turn.
		    //----------------------------
		   
		    //Get the player's decision. The player needs the board to make his decision,
		    //so we can make sure it's an empty spot he chose, so we pass in the board
		    //as a const reference to the function.
		    int playerMove = GetPlayerDecision(board);
		   
		    //Mark the spot the player chose.
		    board.MarkSpot(playerMove, PlayerPiece);
		   
		    //Check if the player won.
		    if(board.ThreeInARow(PlayerPiece))
		    {
			    //If the player did win, mark him as the victor...
			    playerWon = true;
			   
			    //...increment his score...
			    playerWins++;
			   
			    //...and exit the infinite loop.
			    break;
		    }
										
		    //----------------------------
		    //Do the computer's turn.
		    //----------------------------
		   
		    print "The computer is thinking..."
		   
		    //Have the computer think. The computer needs access to the board to do his thinking,
		    //So we pass it as a const reference to the computer-thinking function.
		    int computerMove = CalculateComputerMove(board);
		   
		    //Mark the spot the computer chose.
		    board.MarkSpot(computerMove, ComputerPiece);
										
		    //Check if the computer won.
		    if(board.ThreeInARow(ComputerPiece))
		    {
			    //If the computer did win, mark the player as the loser...
			    playerWon = false;
			   
			    //...increment the computer's score...
			    computerWins++;
			   
			    //...and exit the infinite loop.
			    break;
		    }
	    }
	   
	    //Print who won.
	    if(playerWon)
	    {
		    print "Congratulations! You won!"
	    }
	    else
	    {
		    print "The computer won this battle, but the war is not over!"
	    }
	   
	    //Print the score.
	    print "You've won " + playerWins + " matches, and the computer has won " + computerWins + " matches.";
	   
	    //Ask if the user wants to play again.
	    print "Do you want to play again? Y/N";
	   
	    //If he doesn't want to play again, set the flag to exit the game.
	    if(...doesnt want to play...)
	    {
		    playingTheGame = false;
	    }
    }
    //Exit message:
    print "Thanks for playing! Goodbye!"
    return 0;
}

A couple things of note:
  • I didn't use any global variables. Global variables encourage sloppy code. Sometimes, you can't avoid them, and sometimes they are the cleanest solution, but 99% of the time, avoid using them because they encourage sloppiness.
  • I declared every variable as local variables, without using dynamic memory. Memory, by default, should go on the stack.
  • Every variable is initialized the second it is declared. Never leave variables uninitialized - it's sloppy, and can create confusion and unpredictable bugs in your code.
  • I declared every variable as close to the first point of use as possible, except when and where I felt it made the code look cleaner. Sloppy programmers sometimes declare all a function's variables at the top of the function... that's not good. Posted Image It slows down the reading of code when you later encounter it and have to figure out what it does.
  • I'm not afraid to use whitespace to make the code look cleaner. Whitespace often helps me read it easier, since it's not so compact. On the other hand, I'm not afraid to remove whitespace to make the code look cleaner either (Notice the Board::ThreeInARow() function, where I put the boolean and the if() curly brackets on the same line, so I can visually see more of the code at one time, since I know that all those if() statements contain the same contents).
  • I separated the logic shared by the computer and the player, and put it in it's own function(s). (Board::MarkSpot(), Board::ThreeInARow(), for example). Duplicate code is bad because if you change the code in one place, you might forget to change it in the others causing problems.
  • Only the logic that is different between the computer and the player is separated out.
  • I also didn't mix the player and computer logic up together in the same area of the main() function. I had their logic clearly separated.
  • I commented the code. Commenting code is a very very very important habit to get into, and the sooner the better.
  • The game doesn't exit until the player wants to exit - the player can play multiple matches in a row if he wants.
  • A single function shouldn't go too many "scopes" deep (In this case, meaning a nested pair of { } inside another pair of { }). I almost went three while-loops deep, and another if() on top of that. That's not good. Code like that should really be separated into separate functions. So I separated the entire "player's movement choice" into it's own function, and the entire "computer's movement choice" into it's own function. They are separate and self-contained pieces of logic, so they should go in separate and self-contained functions.

Hopefully that helps give a better illustration of what we're talking about. If you have any questions, just ask. I did some funky const stuff there which might be new to you, and I don't know if you are familiar with references yet (the '&' symbol). Also, I ended up using std::vector to hold the board anyway, despite earlier trying to avoid it. Meh, it's the best thing to use there. Posted Image
It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#19 phil67rpg   Members   -  Reputation: 767

Like
0Likes
Like

Posted 08 October 2012 - 02:53 PM

I will try to follow your advice.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS