tictactoe

Started by
17 comments, last by phil67rpg 11 years, 6 months ago
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 !

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 !

Advertisement
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;
};
'Optimize' is the wrong word. 'Simplify' makes more sense here.

A few thoughts:

  1. 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.
  2. Really, you shouldn't use classes here at all. Regular functions would work better.
  3. 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.
  4. 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?
so what your saying is that I should use functions instead classes?
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.
ok cool, well I am going to rewrite my code.
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.
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:
[size=2](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 == 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. angry.png 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. sleep.png
I will try to follow your advice.

This topic is closed to new replies.

Advertisement