Thread: Tic Tac Toe game

  1. #1
    Registered User
    Join Date
    Apr 2013
    Posts
    34

    Tic Tac Toe game

    Hello, so im writing a tic tac toe game with AI. There is no possible human win . Everything works fine ( it should work fine) , but i want to ask you what could i improve in this game? Maybe make code shorter or somehow improve it. Tell me what you think

    Code:
    #include <iostream>#include <cstdlib>
    #include <ctime>
    
    
    using namespace std;
    
    
    void initBoard();
    void printBoard(bool firstTime);
    bool placeChar(int place, char playerChar);
    int computerMove();
    int checkWin(char playerChar);
    
    
    char board[3][3];
    
    
    int main()
    {
        int tie = 0; // every time a 'X' or 'O' is placed , tie increases so if it is 9 and there is no win
        // it says that game is draw.
        int turn;
        char playerChar;
        char playAgain;
        int whosMove=1;
        bool endGame = false;
        int humanWins=0;
        int computerWins=0;
    
    
        initBoard();
        printBoard(true);
    
    
        while( endGame == false )
        {
            if(whosMove == 1)
            {
                playerChar = 'X';
                cout << "Enter your move: " << endl;
                cin >> turn;
                whosMove = 0;
                if( placeChar(turn, playerChar) == false ) // if it failed to put a 'X' to board, human's move still
                    //remains the same
                    whosMove = 1;
                else
                    tie++;
    
    
                // checks for win
                if( checkWin(playerChar) == 1 )
                {
                    cout << "You won by horizontal" << endl;
                    endGame = true;
                    humanWins++;
                }
                else if( checkWin(playerChar) == 2 )
                {
                    cout << "You won by vertical" << endl;
                    endGame = true;
                    humanWins++;
                }
                else if( checkWin(playerChar) == 3 )
                {
                    cout << "You won by diagonal from left to right" << endl;
                    endGame = true;
                    humanWins++;
                }
                else if( checkWin(playerChar) == 4 )
                {
                    cout << "You won by diagonal from right to left" << endl;
                    endGame = true;
                    humanWins++;
                }
            }
            else
            {
                int cTurn;
                cTurn = computerMove();
                playerChar = 'O';
                whosMove = 1;
                if( placeChar(cTurn, playerChar) == false )
                    whosMove = 0;
                else
                    tie++;
    
    
                // checks for win
                if( checkWin(playerChar) == 1 )
                {
                    cout << "Computer won by horizontal" << endl;
                    endGame = true;
                    computerWins++;
                }
                else if( checkWin(playerChar) == 2 )
                {
                    cout << "Computer won by vertical" << endl;
                    endGame = true;
                    computerWins++;
                }
                else if( checkWin(playerChar) == 3 )
                {
                    cout << "Computer won by diagonal from left to right" << endl;
                    endGame = true;
                    computerWins++;
                }
                else if( checkWin(playerChar) == 4 )
                {
                    cout << "Computer won by diagonal from right to left" << endl;
                    endGame = true;
                    computerWins++;
                }
            }
    
    
            if( endGame == false)
                system("cls");
    
    
            printBoard(false);
            cout << endl;
            if( tie == 9 )
            {
                cout << "Draw game." << endl;
            }
            if( endGame == true || tie == 9 ) // asks if you want to play again
            {
                cout << "Do you want to play again? y/n: " ;
                cin >> playAgain;
                if( playAgain == 'y' )
                {
                    endGame = false;
                    tie = 0;
                    initBoard();
                    printBoard(false);
                }
                else
                {
                    endGame = true;
                    system("cls");
                    cout << "Computer wins: " << computerWins << endl;
                    cout << "Your wins: " << humanWins << endl;
                }
            }
        }
    }
    
    
    void initBoard() // function to initialize board
    {
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<3; j++)
            {
                board[i][j] = ' ';
            }
        }
    }
    
    
    void printBoard(bool firstTime) // prints the board
    {
        if( firstTime == true ) // Just for visual to get know what numbers represent
        {
            board[0][0] = '1';
            board[0][1] = '2';
            board[0][2] = '3';
            board[1][0] = '4';
            board[1][1] = '5';
            board[1][2] = '6';
            board[2][0] = '7';
            board[2][1] = '8';
            board[2][2] = '9';
    
    
            for(int i=0; i<3; i++)
            {
                for(int j=0; j<3; j++)
    
    
                    cout << board[i][j] << " | ";
    
    
                cout << endl;
                cout << "- - - - - -"  << endl;
            }
    
    
            initBoard();
        }
        else
        {
            for(int i=0; i<3; i++)
            {
                for(int j=0; j<3; j++)
    
    
                    cout << board[i][j] << " | ";
    
    
                cout << endl;
                cout << "- - - - - -"  << endl;
            }
        }
    }
    
    
    bool placeChar(int place, char playerChar) // function to place char to board
    {
        // it is boolean because if there is already taken place in board it returs false, so in main function
        // you can determine whose move it is: computer or human.
        if( place < 4 )
        {
            for(int i=0; i<4; i++)
            {
                if( i == place )
                {
                    if( board[0][i-1] == ' ' )
                    {
                        board[0][i-1] = playerChar;
                        return true;
                    }
                    else
                        return false;
                }
            }
        }
        else if( place < 7 && place > 3 )
        {
            for(int i=4; i<8; i++)
            {
                if( i == place)
                {
                    if( board[1][i-4] == ' ')
                    {
                        board[1][i-4] = playerChar;
                        return true;
                    }
                    else
                        return false;
    
    
                }
            }
        }
        else if( place < 10 && place > 6)
        {
            for(int i=7; i<11; i++)
            {
                if( i == place)
                {
                    if( board[2][i-7] == ' ')
                    {
                        board[2][i-7] = playerChar;
                        return true;
                    }
                    else
                        return false;
                }
            }
        }
    }
    
    
    int computerMove() // function to get computer move
    {
        //how can i improve this one to reduce number of integers?
        int horizontal=0;
        int horizontal1=0;
        int vertical=0;
        int vertical1=0;
        int diagonal=0;
        int diagonal1=0;
        int diagonal2=0;
        int diagonal3=0;
    
    
        int horizontalA=0;
        int horizontal1A=0;
        int verticalA=0;
        int vertical1A=0;
        int diagonalA=0;
        int diagonal1A=0;
        int diagonal2A=0;
        int diagonal3A=0;
    
    
        bool attack = false; // if attack is possible, then attack becomes true so defence move wont work
        // because if it does sometimes computer will lose game
    
    
        int j=2;
        int compMove=0;
    
    
        srand(time(NULL));
    
    
        if( board[1][1] == ' ' )
        {
            compMove = 5;
        }
    
    
        // every for loop checks if there is horizontal, vertical or diagonal possible human win and if it is,
        // these algorithms blocks it. If there is possible win move, it takes priority by changing boolean
        // value "attack" to true.
    
    
        // checks for horizontal from left to right
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<2; j++)
            {
                if( board[i][j] == 'O' && board[i][2] == ' ' )
                {
                    horizontalA++;
                    if( horizontalA == 2 )
                    {
                      if( i == 0 )
                        compMove = 3;
                        else if( i == 1 )
                            compMove = 6;
                        else if( i == 2 )
                            compMove = 9;
    
    
                        cout << "COMPUTER ATTACK HORIZONTAL FROM LEFT TO RIGHT, SQUARE: " << compMove << endl;
                        attack = true;
                        break;
                    }
    
    
                }
                else if( board[i][j] == 'X' && board[i][2] == ' ' && attack == false )
                {
                    horizontal++;
                    if( horizontal == 2 )
                    {
                        if( i == 0 )
                            compMove = 3;
                        else if( i == 1 )
                            compMove = 6;
                        else if( i == 2 )
                            compMove = 9;
    
    
                        cout << "COMPUTER DEF HORIZONTAL FROM LEFT TO RIGHT, SQUARE: " << compMove << endl;
                        break;
                    }
                }
            }
            horizontal = 0;
            horizontalA = 0;
            if( compMove != 0 )
                break;
        }
    
    
        // checks for horizontal from right to left
        for(int i=0; i<3; i++)
        {
            for(int j=2; j>0; j--)
            {
                if( board[i][j] == 'O' && board[i][0] == ' ' )
                {
                    horizontal1A++;
                    if( horizontal1A == 2 )
                    {
                        if( i == 0 )
                            compMove = 1;
                        else if( i == 1 )
                            compMove = 4;
                        else if( i == 2 )
                            compMove = 7;
    
    
                        cout << "COMPUTER ATTACK HORIZONTAL FROM RIGHT TO LEFT, SQUARE: " << compMove << endl;
                        attack = true;
                        break;
                    }
    
    
                }
                else if( board[i][j] == 'X' && board[i][0] == ' ' && attack == false )
                {
                    horizontal1++;
                    if( horizontal1 == 2 )
                    {
                        if( i == 0 )
                            compMove = 1;
                        else if( i == 1 )
                            compMove = 4;
                        else if( i == 2 )
                            compMove = 7;
    
    
                        cout << "COMPUTER DEF HORIZONTAL FROM RIGHT TO LEFT, SQUARE: " << compMove << endl;
    
    
                        //break;
                    }
                }
            }
            horizontal1 = 0;
            horizontal1A = 0;
            if( compMove != 0)
                break;
        }
    
    
        // checks for vertical from top to bottom
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<2; j++)
            {
                if( board[j][i] == 'O' && board[2][i] == ' ' )
                {
                    verticalA++;
                    if( verticalA == 2 )
                    {
                        if( i == 0 )
                            compMove = 7;
                        else if( i == 1 )
                            compMove = 8;
                        else if( i == 2 )
                            compMove = 9;
    
    
                        cout << "COMPUTER ATTACK VERTICAL FROM TOP TO BOTTOM, SQUARE: " << compMove << endl;
                        attack = true;
                        break;
                    }
    
    
                }
                else if( board[j][i] == 'X' && board[2][i] == ' ' && attack == false )
                {
                    vertical++;
                    if( vertical == 2 )
                    {
                        if( i == 0 )
                            compMove = 7;
                        else if( i == 1 )
                            compMove = 8;
                        else if( i == 2 )
                            compMove = 9;
    
    
                        cout << "COMPUTER DEF VERTICAL FROM TOP TO BOTTOM, SQUARE: " << compMove << endl;
                        break;
                    }
                }
            }
            vertical = 0;
            verticalA = 0;
            if( compMove != 0 )
                break;
        }
    
    
        // checks for vertical from bottom to top
        for(int i=0; i<3; i++)
        {
            for(int j=2; j>0; j--)
            {
                if( board[j][i] == 'O' && board[0][i] == ' ' )
                {
                    vertical1A++;
                    if( vertical1A == 2 )
                    {
                        if( i == 0 )
                            compMove = 1;
                        else if( i == 1 )
                            compMove = 2;
                        else if( i == 2 )
                            compMove = 3;
    
    
                        cout << "COMPUTER ATTACK VERTICAL1 FROM BOTTOM TO TOP, SQUARE: " << compMove << endl;
                        attack = true;
                        break;
                    }
                }
                else if( board[j][i] == 'X' && board[0][i] == ' ' && attack == false )
                {
                    vertical1++;
                    if( vertical1 == 2 )
                    {
                        if( i == 0 )
                            compMove = 1;
                        else if( i == 1 )
                            compMove = 2;
                        else if( i == 2 )
                            compMove = 3;
    
    
                        cout << "COMPUTER DEF VERTICAL FROM BOTTOM TO TOP, SQUARE: " << compMove << endl;
                        break;
                    }
                }
            }
            vertical1 = 0;
            vertical1A = 0;
            if( compMove != 0 )
                break;
        }
    
    
        // checks for diagonal from top-left to bottom-right
        for(int i=0; i<2; i++)
        {
            if( board[i][i] == 'O' && board[2][2] == ' ' )
            {
                diagonalA++;
                if( diagonalA == 2 )
                {
                    compMove = 9;
                    cout << "COMPUTER DIAGONAL ATTACK FROM TOP-LEFT TO BOTTOM-RIGHT, SQUARE: " << compMove << endl;
                    attack = true;
                }
            }
            else if( board[i][i] == 'X' && board[2][2] == ' ' && attack == false )
            {
                diagonal++;
                if( diagonal == 2 )
                {
                    compMove = 9;
                    cout << "COMPUTER DIAGONAL DEF FROM TOP-LEFT TO BOTTOM-RIGHT, SQUARE: " << compMove << endl;
                }
            }
        }
    
    
        // checks for diagonal from bottom-right to top-left
        for(int i=2; i>0; i--)
        {
            if( board[i][i] == 'O' && board[0][0] == ' ' )
            {
                diagonal1A++;
                if( diagonal1A == 2 )
                {
                    compMove = 1;
                    cout << "COMPUTER ATTACK DIAGONAL FROM BOTTOM-RIGHT TO TOP-LEFT, SQUARE: " << compMove << endl;
                    attack = true;
                }
            }
            else if( board[i][i] == 'X' && board[0][0] == ' ' && attack == false )
            {
                diagonal1++;
                if( diagonal1 == 2 )
                {
                    compMove = 1;
                    cout << "COMPUTER DEF DIAGONAL FROM BOTTOM-RIGHT TO TOP-LEFT, SQUARE: " << compMove << endl;
                }
            }
        }
    
    
        // checks for diagonal from top-right to bottom-left
        for(int i=0; i<2; i++)
        {
            if( board[i][j] == 'O' && board[2][0] == ' ' )
            {
                diagonal2A++;
                if( diagonal2A == 2 )
                {
                    compMove = 7;
                    cout << "COMPUTER ATTACK DIAGONAL FROM TOP-RIGHT TO BOTTOM-LEFT, SQUARE: " << compMove << endl;
                    attack = true;
                }
            }
            else if( board[i][j] == 'X' && board[2][0] == ' ' && attack == false )
            {
                diagonal2++;
                if( diagonal2 == 2 )
                {
                    compMove = 7;
                    cout << "COMPUTER DEF DIAGONAL FROM TOP-RIGHT TO BOTTOM-LEFT, SQUARE: " << compMove << endl;
                }
            }
            j--;
        }
    
    
        j = 0; // resets j value so it can be used again
    
    
        // checks for diagonal from bottom-left to top-right
        for(int i=2; i>0; i--)
        {
            if( board[i][j] == 'O' && board[0][2] == ' ' )
            {
                diagonal3A++;
                if( diagonal3A == 2 )
                {
                    compMove = 3;
                    cout << "COMPUTER ATTACK DIAGONAL FROM BOTTOM-LEFT TO TOP-RIGHT, SQUARE: " << compMove << endl;
                    attack = true;
                }
            }
            else if( board[i][j] == 'X' && board[0][2] == ' ' && attack == false )
            {
                diagonal3++;
                if( diagonal3 == 2 )
                {
                    compMove = 3;
                    cout << "COMPUTER DEF DIAGONAL FROM BOTTOM-LEFT TO TOP-RIGHT, SQUARE: " << compMove << endl;
                }
            }
            j++;
        }
    
    
        // checks if horizontal middle square should be placed by computer
        for(int i=0; i<3; i++)
        {
            if( board[i][0] != ' ' && board[i][2] != ' ' )
            {
                if( board[i][0] == 'O' && board[i][2] == 'O' && board[i][1] == ' ' )
                {
                    if( i == 0 )
                        compMove = 2;
                    else if( i == 1 )
                        compMove = 5;
                    else if( i == 2 )
                        compMove = 8;
    
    
                    cout << "COMPUTER HORIZONTAL MIDDLE ATTACK CHECK, SQUARE: " << compMove << endl;
                    attack = true;
                }
                else if( board[i][0] == 'X' && board[i][2] == 'X' && board[i][1] == ' ' && attack == false )
                {
                    if( i == 0 )
                        compMove = 2;
                    else if( i == 1 )
                        compMove = 5;
                    else if( i == 2 )
                        compMove = 8;
    
    
                    cout << "COMPUTER HORIZONTAL MIDDLE DEF CHECK, SQUARE: " << compMove << endl;
                }
            }
        }
    
    
        // checks if vertical middle square should be placed by computer
        for(int i=0; i<3; i++)
        {
            if( board[0][i] != ' ' && board[2][i] != ' ' )
            {
                if( board[0][i] == 'O' && board[2][i] == 'O' && board[1][i] == ' ' )
                {
                    if( i == 0 )
                        compMove = 4;
                    else if( i == 1 )
                        compMove = 5;
                    else if( i == 2 )
                        compMove = 6;
    
    
                    cout << "COMPUTER MIDDLE ATTACK CHECK, SQUARE: " << compMove << endl;
                    attack = true;
                }
                else if( board[0][i] == 'X' && board[2][i] == 'X' && board[1][i] == ' ' && attack == false )
                {
                    if( i == 0 )
                        compMove = 4;
                    else if( i == 1 )
                        compMove = 5;
                    else if( i == 2 )
                        compMove = 6;
    
    
                    cout << "COMPUTER MIDDLE DEF CHECK, SQUARE: " << compMove << endl;
                }
            }
        }
    
    
        // Places O's in corners if its empty and human has no possible win or there is no possible computer win
        // No possible win for human and computer determines int "compMove". If it is 0, it means that none of above
        // algorithms was used.
        if( board[1][1] == 'X' || board[1][1] == 'O' )
        {
            if( board[0][0] == ' ' && compMove == 0 )
            {
                compMove = 1;
                cout << "Top left is free." << endl;
            }
            else if( board[0][2] == ' ' && compMove == 0 )
            {
                compMove = 3;
                cout << "Top right is free." << endl;
            }
        }
    
    
        if( compMove != 0 )
            return compMove;
        else // this should be improved, because sometimes it takes so long to generate proper number, for example
            // when there is only 1 emply place left and there is no possible human and computer wins
            return rand() % (10 - 1) + 1;
    }
    
    
    int checkWin(char playerChar) // function to check for a win
    {
        int horizontal=0;
        int vertical=0;
        int diagonal1=0;
        int diagonal2=0;
        int j=2;
    
    
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<3; j++)
            {
                if( board[i][j] == playerChar )
                {
                    horizontal++;
                    if( horizontal == 3 )
                    {
                        return 1;
                    }
                }
            }
            horizontal = 0;
        }
    
    
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<3; j++)
            {
                if( board[j][i] == playerChar )
                {
                    vertical++;
                    if( vertical == 3 )
                    {
                        return 2;
                    }
                }
            }
            vertical = 0;
        }
    
    
        for(int i=0; i<3; i++)
        {
            if( board[i][i] == playerChar )
            {
                diagonal1++;
                if( diagonal1 == 3 )
                {
                    return 3;
                }
            }
        }
    
    
        for(int i=0; i<3; i++)
        {
            if( board[i][j] == playerChar )
            {
                diagonal2++;
                if( diagonal2 == 3 )
                {
                    return 4;
                }
            }
            j--;
        }
    }
    Last edited by Aeoskype; 12-26-2013 at 04:01 PM.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    The board variable should not be global. You might want to encapsulate it in a class instead, upon which your initBoard function would become a constructor. You can then overload operator<< to print the board (and get rid of the firstTime parameter: have another function print the board legend instead).

    If you're concerned about your random move generator when there's only one move left, then a simple solution is to list the remaining numbers, then select one at random. Oh, and you should only call srand once, e.g., near the top of the main function.

    Instead of toggling with your attack flag, it might be simpler (or at least clearer) to search for a win on the current move, then if no such win exists, search for a win for the opponent in order to block it (i.e., a 2 ply search). By the way, your claim that "There is no possible human win ." is not true in your implementation because you are effectively only searching 2 ply ahead. Hence, it is possible to give your AI enough rope to hang itself, so to speak, and the PRNG might then choose a losing move.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Laserlight had some good points, but:

    • No global variables. Declare them inside the correct functions and pass them around appropriately. Alternatively, as laserlight suggested, use some classes, since you're using C++. Make these functions member functions where appropriate. Some potentially useful classes: Board, Player (maybe an abstract class to support deriving various human and computer/AI player classes).
    • Don't use magic numbers. You should define constants/enums for things like board size, win types (e.g. 1 horizontal, 2 vertical)
    • whosMove is redundant, if you just use playerChar to represent player 1 ('X') and player 2/computer ('O').
    • You don't check user input in any way whatsoever.
    • As a consequence of the previous, and because you don't properly check place in placeChar: if placeChar is called with the first param being 0, then it will access board out of bounds, resulting in undefined behavior.
    • The code in main for player 1 and 2/computer is nearly identical. Combine common code outside the player 1/2 if-else. See below for my suggestion at an overall structure
    • printBoard is poorly designed. It should do one thing and do it well, and be appropriately named. printBoard should just print the current board. Consider initializing a board to "demo state" to show the numbers, printing that (by passing it to printBoard, or by calling the printBoard member function), then clearing the board to empty state.
    • placeChar could be improved. It seems that the 3 sections are really jus the same code, but using a different index for board. Consider how to use the / (integer division) and % (modulus) operators to simplify it.
    • Also, as mentioned before, fix the if checks for place in placeChar, to disallow negative or zero values.
    • Somewhat of a personal opinion, but printing messages for the user in all caps IS LIKE SHOUTING AT THEM. Most people find all caps annoying.
    • I haven't looked really closely at computerMove(), but it seems like there is a lot of opportunity for combining common sections of code.
    • Also, TTT has a small, symmetrical board. For example, starting moves are either corner, side or middle. It doesn't matter which corner or side, they all represent the same state. This greatly reduces the size of the set of potential moves to consider for attack/defense. I think you could further reduce/condense your computerMove function.
    • This is not a bad start for a first crack at AI. However, there are more advanced techniques. For example, look at minimax trees. That may be a bit overkill considering TTT is fully solved and (as I mentioned), the board is small and very symmetrical. That makes the brute-force AI approach feasible. Good to practice the minimax approach on an easy problem like this though.
    • You could also do some fun testing. Create several different AI functions, and have your program play itself. That is, have both player 1 and 2 be computers (one calling computerMove1() and the other computerMove2(), for example). See which AI methods are better.


    Sample main
    Code:
    do
        print demo board
        clear board
        do
            if currPlayer is 'X'
                get move from player
            else
                get move from computer
            checkWin(currPlayer)
            if currPlayer won
                print win message
                increment win counter for X/O
                set gameOver to true
            else if board is full  // you could also count how many moves so far this game, and check if that is 9
                print tie game
                gameOver to true
            else
                currPlayer = currPlayer == 'X' ? 'O' : 'X';  // change curr player from 'X' to 'O'
        while !gameOver
        print who won
        ask "play again?"
    while playAgain
    Note, that pseudo code might not very good C++/object-oriented design, but should give you a decent idea of the structure I had in mind.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. C++ SDL: Undefined reference to Game::Game()?
    By DecoratorFawn82 in forum C++ Programming
    Replies: 5
    Last Post: 09-24-2013, 12:31 PM
  2. Should I use a game engine in making a RPG game?
    By m3rk in forum Game Programming
    Replies: 6
    Last Post: 01-26-2009, 04:58 AM
  3. Guessing game: how to quit the game?
    By hzr in forum C Programming
    Replies: 5
    Last Post: 12-18-2008, 10:53 AM
  4. craps game & dice game..
    By cgurl05 in forum C Programming
    Replies: 3
    Last Post: 03-25-2006, 07:58 PM
  5. Game Designer vs Game Programmer
    By the dead tree in forum Game Programming
    Replies: 8
    Last Post: 04-28-2005, 09:17 PM