Search code examples
c++multidimensional-arraytic-tac-toe

Stupid behavior of multi dimensional array


The problem is simple. I made a 3X3 tictactoe game with 3X3 arrays. But the problem is:

array[0][3] = array[1][0]

Which is strange because first of all, the array I made didn't have a fourth column. So array[0][3] doesn't even exist! And to make matters complicated, it takes the value of [1][0]

I'm having problems when I input co ordinates of my move as: 0 2

void displayBoard(int tictac[3][3])
{
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            cout << tictac[i][j] << " ";
        } cout << "\n" ;
    } cout << "\n";
}



int Horizontal(int x, int y, int tictac[3][3])
{

    if(tictac[x][y+1]==0)
    {
        tictac[x][y+1]=2;
        return 1;
    }

    if(tictac[x][y-1]==0)
    {
        tictac[x][y-1]=2;
        return 1;
    }

    if(tictac[x][y-2]==0)
    {
        tictac[x][y-2]=2;
        return 1;
    }
    if(tictac[x][y+2]==0)
    {
        tictac[x][y+2]=2;
        return 1;
    }

    return 0;
}

int Vertical(int x, int y, int tictac[3][3])
{

    if(tictac[x+1][y]==0)
    {
        tictac[x+1][y]=2;
        return 1;
    }
    if(tictac[x-1][y]==0)
    {
        tictac[x-1][y]=2;
        return 1;
    }
    if(tictac[x-2][y]==0)
    {
        tictac[x-2][y]=2;
        return 1;
    }
    if(tictac[x+2][y]==0)
    {
        tictac[x+2][y]=2;
        return 1;
    }

    return 0;
}

void AI(int X,int Y,int tictac[3][3])
{
    int done = 0;
    cout << "\n-------------------------\nComputer plays: \n";

    done = Horizontal(X,Y,tictac);
    if(done == 0)
    {
    done = Vertical(X,Y,tictac);
    }
}


int main()
{
    int tictac[3][3] = {{0,0,0},{0,0,0}, {0,0,0} };
    int X, Y;
    for(int r=1; r<100; r++)
    {
    cout << "\n-------------------------\nPlayer play a move: \n";
    cin >> X;
    cin >> Y;

    if(tictac[X][Y]==0)
    {
    tictac[X][Y] = 1;
    displayBoard(tictac);
    AI(X,Y,tictac);
    displayBoard(tictac);
    }
    else
    {
        cout << "Space occupied. Try different cell." << endl;
    }


    }





}

Solution

  • You need to add bounds checking. For instance when the user inputs the move coordinates you need to ensure they are within the range of 0 to 2. The example below validates the input to ensure only numbers are entered, that both X and Y coordinates are entered on a single line and that the coordinates are within range. It uses std::stringstream to parse the coordinates instead of having to deal with checking and clearing the fail bits on std::cin

    #include <string> // at top of your .cpp file
    #include <sstream>
    
    // in main()
    
    // Get en entire input line so we can skip extra characters
    // after the cell coordinates
    string inputLine;
    std::getline(cin, inputLine);
    
    stringstream inputStream(inputLine);
    
    if(!(inputStream >> X) || !(inputStream >> Y))
    {
        cout << "Please enter the cell coordinates in the form of # #" << endl;
        continue;
    }
    
    bool invalidCoordinates = false;
    
    if(X < 0 || X > 2)
    {
        cout << "invalid X location" << endl;
        invalidCoordinates = true;
    }
    if(Y < 0 || Y > 2)
    {
        cout << "invalid Y location" << endl;
        invalidCoordinates = true;
    }
    
    // check for invalid input
    if(invalidCoordinates) continue;
    

    You also need to do the same thing in your Vertical and Horizontal functions when checking if a valid move is possible. For instance if x is 2 and y is 2 the following lines from Vertical will access data outside the bounds of the array.

    if(tictac[x+1][y]==0)
    {
        tictac[x+1][y]=2;
        return 1;
    }
    

    This is because you are actually accessing the forth element with x+1. This element technically doesn't exist but with a multi-dimensional array you end up accessing tictac[0][y+1] instead.

    You can get around the bounds checking in Vertical and Horizontal by adding some padding around the edges and fill them with a value that indicates they are unusable. In your case increase the size by 3 in each direction.

    int tictac[9][9] = {
        {3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3},
        {3,3,3,0,0,0,3,3,3},
        {3,3,3,0,0,0,3,3,3},
        {3,3,3,0,0,0,3,3,3},
        {3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3},
    };
    

    You will need to make adjustments to X and Y appropriately so they point to the correct location.

    X += 3;  // Adjust for padding
    Y += 3;  // Adjust for padding
    if(tictac[X][Y]==0)
    {
        tictac[X][Y] = 1;
        displayBoard(tictac);
        AI(X,Y,tictac);
        displayBoard(tictac);
    }
    

    You may need to make adjustments in other parts of your code but the above example should get your started.

    There is also a problem in your displayBoard function. When it prints out the elements of the array i and j are reversed so the board appears rotated 90 degrees. Change the following line

    cout << tictac[i][j] << " ";
    

    to

    cout << tictac[j][i] << " ";
    

    Another problem is that you are using \n at the end of each line you output without using std::flush to ensure the line is sent to the console. You can either put << flush; after those lines or remove the \n and put << endl; at the end of the line.

    cout << "\n-------------------------\nComputer plays: \n" << flush;
    

    or

    cout << "\n-------------------------\nComputer plays: " << endl;
    

    The code below is a complete update of the original code included in your question. It incorporates the above suggestions and makes a couple of other changes. I've also added an endgame check to determine if there are any moves left.

    #include <iostream>
    #include <string>
    #include <sstream>
    
    using namespace std;
    
    static const int BoardSize = 3;
    static const int BoardPadding = BoardSize;
    static const int ArraySize = BoardSize + (BoardPadding * 2);
    
    void displayBoard(int tictac[ArraySize][ArraySize])
    {
        for(int y = 0; y < BoardSize; y++)
        {
            for(int x = 0; x < BoardSize; x++)
            {
                cout << tictac[BoardPadding + x][BoardPadding + y] << " ";
            }
            cout << endl ;
        }
        cout << endl;
    }
    
    
    int Horizontal(int x, int y, int tictac[ArraySize][ArraySize])
    {
        if(tictac[x][y+1]==0)
        {
            tictac[x][y+1]=2;
            return 1;
        }
    
        if(tictac[x][y-1]==0)
        {
            tictac[x][y-1]=2;
            return 1;
        }
    
        if(tictac[x][y-2]==0)
        {
            tictac[x][y-2]=2;
            return 1;
        }
        if(tictac[x][y+2]==0)
        {
            tictac[x][y+2]=2;
            return 1;
        }
    
        return 0;
    }
    
    
    int Vertical(int x, int y, int tictac[ArraySize][ArraySize])
    {
        if(tictac[x+1][y]==0)
        {
            tictac[x+1][y]=2;
            return 1;
        }
        if(tictac[x-1][y]==0)
        {
            tictac[x-1][y]=2;
            return 1;
        }
        if(tictac[x-2][y]==0)
        {
            tictac[x-2][y]=2;
            return 1;
        }
        if(tictac[x+2][y]==0)
        {
            tictac[x+2][y]=2;
            return 1;
        }
    
        return 0;
    }
    
    
    void AI(int X,int Y,int tictac[ArraySize][ArraySize])
    {
        int done = 0;
        cout << "\n-------------------------\nComputer plays: " << endl;
    
        done = Horizontal(X,Y,tictac);
        if(done == 0)
        {
            done = Vertical(X,Y,tictac);
        }
    }
    
    
    // Check if all moves have been made
    bool isEndGame(int tictac[ArraySize][ArraySize])
    {
        int count = 0;
    
        for(int y = 0; y < BoardSize; y++)
        {
            for(int x = 0; x < BoardSize; x++)
            {
                count += tictac[BoardPadding + x][BoardPadding + y] ? 1 : 0;
            }
        }
    
        return count == (BoardSize * BoardSize);
    }
    
    
    int main()
    {
        int tictac[ArraySize][ArraySize] = {
            {3,3,3,3,3,3,3,3,3},
            {3,3,3,3,3,3,3,3,3},
            {3,3,3,3,3,3,3,3,3},
            {3,3,3,0,0,0,3,3,3},
            {3,3,3,0,0,0,3,3,3},
            {3,3,3,0,0,0,3,3,3},
            {3,3,3,3,3,3,3,3,3},
            {3,3,3,3,3,3,3,3,3},
            {3,3,3,3,3,3,3,3,3},
        };
        int X, Y;
    
        while(isEndGame(tictac) == false)
        {
            cout << "\n-------------------------\nPlayer play a move: " << flush;
    
            // Get en entire input line so we can skip extra characters
            // after the cell coordinates
            string inputLine;
            std::getline(cin, inputLine);
    
            stringstream inputStream(inputLine);
    
            if(!(inputStream >> X) || !(inputStream >> Y))
            {
                cout << "Please enter the cell coordinates in the form of # #" << endl;
                continue;
            }
    
            bool invalidCoordinates = false;
    
            if(X < 0 || X >= BoardSize)
            {
                cout << "invalid X location" << endl;
                invalidCoordinates = true;
            }
            if(Y < 0 || Y >= BoardSize)
            {
                cout << "invalid Y location" << endl;
                invalidCoordinates = true;
            }
    
            // check for invalid input
            if(invalidCoordinates) continue;
    
            // adjust the coordinates and do our thing
            X += BoardPadding;
            Y += BoardPadding; 
            if(tictac[X][Y]==0)
            {
                tictac[X][Y] = 1;
                displayBoard(tictac);
                AI(X,Y,tictac);
                displayBoard(tictac);
            }
            else
            {
                cout << "Space occupied. Try different cell." << endl;
            }
        }
    
        cout << "game finished...check for winner" << endl;
    }
    

    Note: It's a bad idea to use using namespace std;. It pulls everything from the std namespace into the current scope (in this case the global namespace) and can cause conflicts. It's best to use fully qualified names such as std::cout instead to avoid this.