Sign in to follow this  
nooblet

[C++] Tic-Tac-Toe

Recommended Posts

nooblet    167
Hello everybody,


I've recently started a Tic-Tac-Toe project, and have came across an issue. Now, just a disclaimer before I post the source code: [b]I'm probably not doing this the best way possible.[/b] The code, to me, looks incredibly ugly and needs some optimization, but this is why I'm in the For Beginners section :-). Now, to move on past that... the issue I'm experiencing is weird. When I make my move, it will flash "Invalid move!" on the screen prompt; however, this doesn't display for more than half a second before a clearing of the screen takes place. This brings me to believe that my if-statement in the [i]checkMove()[/i] function is misbehaving of some sort. Below is the source code for the project, and if any of you have any further comments/concerns about the code, please let me know so I can learn.

NOTE: I've also attached the source code in a zip archive to this thread.

[source lang="cpp"]

/* Program name: TicTacToe
* Program author: Cameron Smith
* Last updated: 6/2/2011
* Description: This is just a Tic-Tac-Toe clone created solely for educational purposes. It will follow the standard Tic-Tac-Toe rules :P.
*/

#include <iostream>
#include <string>

void drawGameBoard(char* gameBoard[3][3]); // function prototype
void checkMove(char* gameBoard[3][3], int playerInput, int playerTurn); // another function prototype
bool checkForWin(char* gameBoard[3][3]);

int main()
{
// Introduction
std::cout << "Welcome to Tic-Tac-Toe" << std::endl;
std::cout << "Press ENTER to continue..." << std::endl;

// Check for ENTER, then clear screen after pressing the key.
std::cin.get();
system("cls");

char* gameBoard[3][3]; // The gameboard AMAGASH
int playerInput = 0; // Used in the game loop to validate movement.
int playerTurn = 1; // Used to determine to place an X or an O.

gameBoard[0][0] = "1"; // top-left
gameBoard[0][1] = "2"; // top-middle
gameBoard[0][2] = "3"; // top-right

gameBoard[1][0] = "4"; // middle-left
gameBoard[1][1] = "5"; // middle-middle
gameBoard[1][2] = "6"; // middle-right

gameBoard[2][0] = "7"; // bottom-left
gameBoard[2][1] = "8"; // bottom-middle
gameBoard[2][2] = "9"; // bottom-right

bool gameOver = false;

while(gameOver != true) // The game loop :-). YAY!
{
drawGameBoard(gameBoard);

std::cout << std::endl;
std::cout << "Player " << playerTurn << ": Which spot would you like to take? (1-9): ";
std::cin >> playerInput;

checkMove(gameBoard, playerInput, playerTurn);

if(playerTurn == 1) {
checkMove(gameBoard, playerInput, playerTurn);
playerTurn = 2;
}
else if(playerTurn == 2) {
checkMove(gameBoard, playerInput, playerTurn);
playerTurn = 1;
}

if(checkForWin(gameBoard)) {
gameOver = true;
}
}

return 0;
}

// Function: drawGameBoard()
// Description: Outputs the game board to the player!
void drawGameBoard(char* gameBoard[3][3])
{
for(int i = 0; i < 3; ++i)
{
for(int j = 0; j < 3; ++j)
{
if(j == 0) {
std::cout << "| " << gameBoard[i][j] << " | ";
} else {
std::cout << gameBoard[i][j] << " | ";
}
}

std::cout << std::endl;
}
}

// Function: checkMove()
// Description: Validates a movement, and modifies the gameBoard accordingly.
void checkMove(char* gameBoard[3][3], int playerInput, int playerTurn)
{
if(playerTurn == 1) {
if(playerInput == 1 && gameBoard[0][0] != "X" && gameBoard[0][0] != "O") {
gameBoard[0][0] = "X";
}
else if(playerInput == 2 && gameBoard[0][1] != "X" && gameBoard[0][1] != "O") {
gameBoard[0][1] = "X";
}
else if(playerInput == 3 && gameBoard[0][2] != "X" && gameBoard[0][2] != "O") {
gameBoard[0][2] = "X";
}
else if(playerInput == 4 && gameBoard[1][0] != "X" && gameBoard[1][0] != "O") {
gameBoard[1][0] = "X";
}
else if(playerInput == 5 && gameBoard[1][1] != "X" && gameBoard[1][1] != "O") {
gameBoard[1][1] = "X";
}
else if(playerInput == 6 && gameBoard[1][2] != "X" && gameBoard[1][2] != "O") {
gameBoard[1][2] = "X";
}
else if(playerInput == 7 && gameBoard[2][0] != "X" && gameBoard[2][0] != "O") {
gameBoard[2][0] = "X";
}
else if(playerInput == 8 && gameBoard[2][1] != "X" && gameBoard[2][1] != "O") {
gameBoard[2][1] = "X";
}
else if(playerInput == 9 && gameBoard[2][2] != "X" && gameBoard[2][2] != "O") {
gameBoard[2][2] = "X";
}
else
{
std::cout << "Invalid move!" << std::endl;
std::cin.get();
}
}
else if(playerTurn == 2)
{
if(playerInput == 1 && gameBoard[0][0] != "X" && gameBoard[0][0] != "O") {
gameBoard[0][0] = "O";
}
else if(playerInput == 2 && gameBoard[0][1] != "X" && gameBoard[0][1] != "O") {
gameBoard[0][1] = "O";
}
else if(playerInput == 3 && gameBoard[0][2] != "X" && gameBoard[0][2] != "O") {
gameBoard[0][2] = "O";
}
else if(playerInput == 4 && gameBoard[1][0] != "X" && gameBoard[1][0] != "O") {
gameBoard[1][0] = "O";
}
else if(playerInput == 5 && gameBoard[1][1] != "X" && gameBoard[1][1] != "O") {
gameBoard[1][1] = "O";
}
else if(playerInput == 6 && gameBoard[1][2] != "X" && gameBoard[1][2] != "O") {
gameBoard[1][2] = "O";
}
else if(playerInput == 7 && gameBoard[2][0] != "X" && gameBoard[2][0] != "O") {
gameBoard[2][0] = "O";
}
else if(playerInput == 8 && gameBoard[2][1] != "X" && gameBoard[2][1] != "O") {
gameBoard[2][1] = "O";
}
else if(playerInput == 9 && gameBoard[2][2] != "X" && gameBoard[2][2] != "O") {
gameBoard[2][2] = "O";
}
else
{
std::cout << "Invalid move!" << std::endl;
std::cin.get();
}
}

system("CLS"); // clear screen
}

// Function: checkForWin()
// Description: Checks if the player has won or lost.
bool checkForWin(char* gameBoard[3][3])
{
// TOP HORIZONTAL WIN
if(gameBoard[0][0] == "X" && gameBoard[0][1] == "X" && gameBoard[0][2] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][0] == "O" && gameBoard[0][1] == "O" && gameBoard[0][2] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// MIDDLE HORIZONTAL WIN
if(gameBoard[1][0] == "X" && gameBoard[1][1] == "X" && gameBoard[1][2] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[1][0] == "O" && gameBoard[1][1] == "O" && gameBoard[1][2] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// BOTTOM HORIZONTAL WIN
if(gameBoard[2][0] == "X" && gameBoard[2][1] == "X" && gameBoard[2][2] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[2][0] == "O" && gameBoard[2][1] == "O" && gameBoard[2][2] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// LEFT VERTICLE WIN
if(gameBoard[0][0] == "X" && gameBoard[1][0] == "X" && gameBoard[2][0] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][0] == "O" && gameBoard[1][0] == "O" && gameBoard[2][0] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// MIDDLE VERTICLE WIN
if(gameBoard[0][1] == "X" && gameBoard[1][1] == "X" && gameBoard[2][1] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][1] == "O" && gameBoard[1][1] == "O" && gameBoard[2][1] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// RIGHT VERTICLE WIN
if(gameBoard[0][2] == "X" && gameBoard[1][2] == "X" && gameBoard[2][2] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][2] == "O" && gameBoard[1][2] == "O" && gameBoard[2][2] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// DIAGONAL #1 WIN
if(gameBoard[0][0] == "X" && gameBoard[1][1] == "X" && gameBoard[2][2] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][0] == "O" && gameBoard[1][1] == "O" && gameBoard[2][2] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

// DIAGONAL #2 WIN
if(gameBoard[0][2] == "X" && gameBoard[1][1] == "X" && gameBoard[2][0] == "X") {
std::cout << "Player 1 has won!" << std::endl;
std::cin.get();
return true;
}
else if(gameBoard[0][2] == "O" && gameBoard[1][1] == "O" && gameBoard[2][0] == "O") {
std::cout << "Player 2 has won!" << std::endl;
std::cin.get();
return true;
}

if(gameBoard[0][0] != "1" && gameBoard[0][1] != "2" && gameBoard[0][2] != "3" &&
gameBoard[1][0] != "4" && gameBoard[1][1] != "5" && gameBoard[1][2] != "6" &&
gameBoard[2][0] != "7" && gameBoard[2][1] != "8" && gameBoard[2][2] != "9")
{
std::cout << "Cat has won the game!" << std::endl;
std::cin.get();
return true;
}


return false;
}
[/source]

Share this post


Link to post
Share on other sites
>When I make my move, it will flash "Invalid move!" on the screen prompt;

I'm pretty sure I can guess this one. You call checkMove twice for each move. The first time places the marker and the second time complains because the marker is already there. Keep a close eye on the state of the board between calls to checkMove, that will show the problem. Either use the debugger or just insert some extra calls to drawGameBoard

>however, this doesn't display for more than half a second before a clearing of the screen takes place.

This one I'm not so sure about. Is the std::cin >> playerInput; in the loop leaving a newline behind in the input string to immediately satisfy std::cin.get(); in checkMove?

I would step through the code with a debugger. See if it correctly pauses on the cin.get(), and capture and check that return value.

Share this post


Link to post
Share on other sites
rip-off    10979
You're getting a single character. The single character is probably the newline left from the previous input. One option is to switch to a totally line based input system - use getline(std::istream &, std::string &) everywhere.

You are doing pointer comparisons on char pointers in your code, rather than comparing the contents of the string. Consider using an enumeration for the GameBoard representation instead of raw character pointers.

Consider the following:
[code]
void checkMove(char* gameBoard[3][3], int playerInput, int playerTurn)
{
int row = (playerInput - 1) / 3;
int column = (playerInput - 1) % 3;

bool validIndex = isOnBoard(row, column);

if(validIndex && isEmpty(gameBoard[row][column]))
{
if(playerTurn == 1)
{
gameBoard[row][column] = "X";
}
else
{
gameBoard[row][column] = "0";
}
}
else
{
std::cout << "Invalid move: ";
if(validIndex)
{
std::cout << "No such board position!";
}
else
{
std::cout << "This space is occupied!";
}
std::cout << std::endl;
std::cin.get();
}


system("CLS"); // clear screen
}
[/code]
See if you can cleanup the other repeated code by replacing long sequences of almost identical code with some clever indexing and loops.

Share this post


Link to post
Share on other sites
nooblet    167
[font="arial, verdana, tahoma, sans-serif"][size="2"][quote name='EqualityAssignment' timestamp='1307020841' post='4818643']
>When I make my move, it will flash "Invalid move!" on the screen prompt;

I'm pretty sure I can guess this one. You call checkMove twice for each move. The first time places the marker and the second time complains because the marker is already there. Keep a close eye on the state of the board between calls to checkMove, that will show the problem. Either use the debugger or just insert some extra calls to drawGameBoard

>however, this doesn't display for more than half a second before a clearing of the screen takes place.

This one I'm not so sure about. Is the std::cin >> playerInput; in the loop leaving a newline behind in the input string to immediately satisfy std::cin.get(); in checkMove?

I would step through the code with a debugger. See if it correctly pauses on the cin.get(), and capture and check that return value.
[/quote][/size][/font]
[font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"][size="2"]That fixed it. I didn't even realize I had two of those being called :P. Thanks! I'll have to debug to find out why it's clearing the screen without getting the std::cin.get() now.
[/size][/font]
[quote name='rip-off' timestamp='1307021358' post='4818648']
You're getting a single character. The single character is probably the newline left from the previous input. One option is to switch to a totally line based input system - use getline(std::istream &, std::string &) everywhere.

You are doing pointer comparisons on char pointers in your code, rather than comparing the contents of the string. Consider using an enumeration for the GameBoard representation instead of raw character pointers.

Consider the following:
[code]
-snip-
[/code]
See if you can cleanup the other repeated code by replacing long sequences of almost identical code with some clever indexing and loops.
[/quote]
Thank you for the suggestions! Unfortunately, I'm a bit lost, but I'll do some research to see if I can figure out what you're talking about :-). Thanks a lot!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this