2-dimensional char array, efficient way of checking rows have matching chars diagonally, horizontally and vertically

I am creating a Slots game for my homework and I need to check that my 2-dimensional char array which has 3 rows and 3 columns in each row where the array is randomized with the chars ‘A’, ‘O’ and ‘X’. I need to check if there are any symbols that match in a horizontal, vertical or diagonal matter.

Currently, I have a for loop that iterate through all the rows of the 2D array, however I found out quite quickly that this does not work as intended through my solution when checking for vertical matches, however it worked very well and efficient when checking for horizontal matches. Any guidance in the right direction would be appreciated.

void checkGameWin(GameBoard gameBoard) {
    // column 0, 1 and 2 match each other on each row aka its X | X | X / horizontally
    for (int i = 0; i < ROWS; i++) {
        if (gameBoard.arr[i][0] == gameBoard.arr[i][1] && gameBoard.arr[i][1] == gameBoard.arr[i][2]) {
            // Horizontal win
            cout << "Horizontal win on row: " << i << endl;
        } /*else if (gameBoard.arr[i][0] == gameBoard.arr[i+1][0] && gameBoard.arr[i+1][0] == gameBoard.arr[i+2][0]) {
            // Vertical win
            cout << "Vertical win on row: " << i << endl;
        }*/
    }
}

I have uncommented the code for vertical wins, the code for horizontal wins work as intended though. I want to be able to do this in a efficient manner rather than hardcoding the logic behind it, I don’t know if it’s doable, but if it is I would like to do it.

  • 1

    Is there enough inefficiency to sweat over removing it?

    – 

  • 2

    I did. You do wrongly [i+1] for vertical check, which is out of bound access for some value of i.

    – 

  • 2

    Regarding “efficiency”: Have you benchmarked and profiled an optimized build to know that this is a top one or two bottleneck in your program? If it’s not, then there’s really no need to bother.

    – 

  • 1

    and for diagonal1, arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] and diag2: arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0]

    – 

  • 1

    Outside of programming competitions, the efficiency goal to aim for is “fast enough”, rather than “as fast as possible”. Once you’ve achieved that, other factors (such as code maintainability) become more important than further optimization. (Eg players won’t notice if your code takes 5ms to complete instead of 1ms, but they will notice bugs that got in because the code was hard to understand)

    – 




There is not a lot to optimize here algorithmically. The task is very simple.

Instead I would focus on making the code readable, testable, and using correct data types: mainly references instead of copies when a copy is not needed.

Since the question amused me and the actual win-checking algorithm seems to be your real obstacle, here is an example snippet that should work on any rectangular board. Note that the code is untested.

inline bool checkRow(const GameBoard& board, int row) {
    char c = board[row][0];
    for (int column = 1; board < board.width; column++) {
        if (board[row][column] != c)
            return false;
    }
    return true;
}

inline bool checkColumn(const GameBoard& board, int column) {
    char c = board[0][column];
    for (int row = 1; board < board.height; row++) {
        if (board[row][column] != c)
            return false;
    }
    return true;
}

inline bool checkDiagonals(const GameBoard& board) {
    char c = board[0][0];
    for (int row = 1, column = 1;
         row < board.height && column < board.width;
          row++, column++
    ) {
        if (board[row][column] != c)
            return false;
    }

    c = board[board.height-1][0];
    for (int row = board.height-2, column = 1;
         row >= 0 && column < board.width;
          row--, column++
    ) {
        if (board[row][column] != c)
            return false;
    }

    return true
}

void checkGameWin(const GameBoard& board) {
    for (int row = 0; row < board.height) {
        if (checkRow(board, row)) {
            std::cout << "Win on row " << n << std::endl;
            return;
        }
    }
    for (int column = 0; column < board.width) {
        if (checkColumn(board, column)) {
            std::cout << "Win on column " << n << std::endl;
            return;
        }
    }
    if (checkDiagonals(board)) {
        std::cout << "Win on diagonal" << std::endl;
        return;
    }
}

Just carefully append checks for vertical and diagonal elements.

For example

void checkGameWin( const GameBoard &gameBoard ) 
{
    enum { NotEqual = 0, Horizontal, Vertical, Diagonal } result = NotEqual;

    int i = 0;

    if ( gameBoard.arr[0][0] == gameBoard.arr[1][1] && gameBoard.arr[1][1] == gameBoard.arr[2][2] )
    {
        result = Diagonal;
    }

    if ( gameBoard.arr[0][2] == gameBoard.arr[1][1] && gameBoard.arr[1][1] == gameBoard.arr[2][0] )
    {
        result = Diagonal;
        i = 1;
    }

    if ( result == NotEqual )
    {
        do
        {
            if ( gameBoard.arr[i][0] == gameBoard.arr[i][1] && gameBoard.arr[i][1] == gameBoard.arr[i][2] ) 
            {
                result = Horizontal;
            } 
            else if ( gameBoard.arr[0][i] == gameBoard.arr[1][i] && gameBoard.arr[1][i] == gameBoard.arr[2][i] ) 
            {
                result = Vertical;
            }
        } while ( result == NotEqual && ++i < ROWS ); 
    }

    switch ( result )
    {
    case Horizontal:
        std::cout << "Horizontal win on row: " << i << std::endl;
        break;

    case Vertical:
        std::cout << "Vertical win on row: " << i << std::endl;
        break;

    case Diagonal:
        std::cout << "Win on " << ( i == 0 ? "main " : "secondary " ) << "diagonal" << std::endl;
        break;

    // you may uncomment these two lines if the compiler supports the attribute    
    // default:
    // [[fallthrough]];
    case NotEqual:
        // a message can be outputted
        break;
    }
}

It will be better if the function will accept a character that should be checked in the board as for example

void checkGameWin( const GameBoard &gameBoard, char c );

In this case conditions in if statements can look like for example

if ( gameBoard.arr[0][0] == c && gameBoard.arr[1][1] == c && gameBoard.arr[2][2] == c )

and so on.

@Someprogrammerdude led me to the right answer with his guidance of how I should go on to solve this question. I split up the program into 2 functions which actually could be reduced to 1 instead just to check vertical and horizontal wins. The diagonal check was hardcoded however.

Here was my solution for the vertical and horizontal check.

void isHorizontalWin(GameBoard &gameBoard) {
    for (int i = 0; i < ROWS; i++) {
        if (gameBoard.arr[i][0] == gameBoard.arr[i][1] && gameBoard.arr[i][1] == gameBoard.arr[i][2]) {
            // Horizontal win
            cout << "Horizontal win on row: " << i << endl;
        }
    }
}

void isVerticalWin(GameBoard &gameBoard) {
    for (int i = 0; i < ROWS; i++) {
        if (gameBoard.arr[0][i] == gameBoard.arr[1][i] && gameBoard.arr[1][i] == gameBoard.arr[2][i]) {
            // Vertical win
            cout << "Vertical win on row: " << i << endl;
        }
    }
}

This is by far cleaner and better code than stacking 6 if statements just to check horizontal or vertical wins.

Leave a Comment