-1

I am trying to make a reverse 4x3 tic tac toe game in c++ using a depth first search and minimax. I keep getting a segmentation fault whenever the tree is trying to get the minimax value and I'm not sure why. I've looked at my logic a bunch of times and I just can't seem to see the problem. I don't think it is creating the tree correctly somehow.

My idea for depth first was that it would look at the parent node, if it didn't have all its children it would create the next child node, copy the parent board to the child board, then for each child in childNum array it would skip as many blank spaces as the number child it is, then put an X or an O accordingly on the board. Then if it was not an end state it would send that childnode to getMove() Once it got to an end state, it would store that value in the parent node and send the parent node to getMove() and work its way back up the tree.

Can anyone see what is causing this segmentation fault or any other errors I have in my code? Here it is:

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>
#include <ctype.h>
#include <iostream>
using namespace std;
struct node_t{
    node_t* parent;
    int minimax;
    int childrenVals[12];
    bool isMax;
    char board[4][3];
    int level;
    bool xmove;
    node_t* child;
};
void getMove(node_t *root);
void printBoard(char board[4][3]);
int checkStatus(char board[4][3]);
int getChildNum( int minimax[12]);
int status = -2;
char myBoard[4][3];

int main(){
    //create root node
    node_t n;
    node_t* root;
    root = &n;
    root->board[0][0] = '1';
    root->board[0][1] = '2';
    root->board[0][2] = '3';
    root->board[1][0] = '4';
    root->board[1][1] = '5';
    root->board[1][2] = '6';
    root->board[2][0] = '7';
    root->board[2][1] = '8';
    root->board[2][2] = '9';
    root->board[3][0] = '/';
    root->board[3][1] = '*';
    root->board[3][2] = '-';
    root->parent = NULL;
    root->child = NULL;
    root->isMax = true;
    root->xmove = true;
    root-> level = 0;
    for(int i = 0; i < 12; i++){
        root->childrenVals[i] = -2;
    }
    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 3; j++){
                myBoard[i][j] = root->board[i][j];
        }
    }

    cout<<"Welcome to Reverse Tic Tac Toe! Here is the board:"<<endl;
    printBoard(root->board);
    cout<<"To make a move, you will type in the number or symbol "<<endl;
    cout<<"of the box you would like to place your piece in. "<<endl;
    cout<<endl;
    cout<<"Would you like to go first or second? (type 1 or 2):"<<endl;
    char order;
    char Omove;
    bool isValid = false;
    bool gameOver = false;
    int gameValue;

    //Get order
    cin >> order;
    while(order !='1' && order != '2'){
        if(order != '1' && order != '2'){
            cout<<"Please type a 1 if you would like to go first and a 2 if you
would like to go second:";
            cin >> order;
        }
        else{
            break;
        }
    }
    if(order == '2'){
        root->xmove = true;
        getMove(root);
        //root->board = myBoard;
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 3; j++){
                    root->board[i][j] = myBoard[i][j];
            }
        }
        root->level++;
        printBoard(root->board);
    }
    while(gameOver == false){
        cout<< "Your move:";
        while(isValid == false){
            cin >> Omove;
            for(int i = 0; i < 4; i++){
                for(int j = 0; j < 3; j++){
                    if(root->board[i][j] == Omove && Omove != 'X' && Omove != 'O
'){
                        root->board[i][j] = 'O';
                        isValid = true;
                    }
                }
            }
            if(isValid == false){
                cout<<"Invalid move, please look at the board and try again:";
            }
        }
        root->level++;
        printBoard(root->board);
        gameValue = checkStatus(root->board);
        if(gameValue != -2){
            gameOver = true;
        }
        else{
            getMove(root);
            for(int i = 0; i < 4; i++){
                for(int j = 0; j < 3; j++){
                    root->board[i][j] = myBoard[i][j];
                }
            }
            root->level++;
            root->isMax = true;
            printBoard(root->board);
            if(gameValue != -2){
                gameOver = true;
            }
        }
    }
    if(gameOver == 1){
        cout<<"You lose!"<<endl;
    }
    else if(gameOver == -1){
        cout<<"You win!"<<endl;
    }
    else if(gameOver == 0){
        cout<<"It's a tie!"<<endl;
    }
    return 0;
}


void getMove(node_t *node){
     status = checkStatus(node->board);

     //if node is not an end state
    if(status == -2){
        int childNum = 0;
        for(int i = 0; i < 12 - node->level; i++){
            if(node->childrenVals[i] == 0 || node->childrenVals[i] == 1 || node-
>childrenVals[i] == -1){
                childNum++;
            }
        }
        //if node does not have all its children
        if(childNum != 12 - node->level){
            if(childNum > 0){
                //delete current child pointer
                delete node->child;
            }
            //create child Node
            node_t* child = new node_t;
            child->parent = node;
            node->child = child;
            child->level = node->level + 1;
            for(int i = 0; i < 4; i++){
                for(int j = 0; j < 3; j++){
                    child->board[i][j] = node->board[i][j];
                }
            }
            for(int i = 0; i < 12; i++){
                child->childrenVals[i]= -2;
            }
            if(node->isMax == true){
                child->isMax = false;
            }
            else{
                child->isMax = true;
            }
            //xmove means the children of that node move for X
            if(node->xmove == true){
                child->xmove = false;
            }
            else{
                child->xmove = true;
            }
            int openSpace = 0;
            bool shouldbreak = false;
            for(int i=0; i < 4; i++){
                for(int j=0; j< 3; j++){
                    if(child->board[i][j] != 'X' && child->board[i][j] != 'O'){
                        if(openSpace == childNum){
                            if(node->xmove){
                                child->board[i][j] = 'X';
                                shouldbreak = true;
                                break;
                            }
                            else{
                                child->board[i][j] = 'O';
                                shouldbreak = true;
                                break;
                            }
                        }
                        else{
                            openSpace++;
                        }
                    }
                    if(shouldbreak == true){
                        break;
                    }
                }
                if(openSpace == childNum){
                    break;
                }
            }
    //  cout<<"Level: "<<node->level<<endl;
    //  printBoard(child->board);
    //  cout<<endl;
            getMove(child);
        }
        //if node does have all its children
        else{
        //cout<<"Level: "<<node->level<<endl;
    //  printBoard(node->board);
            //n is used for the case that it is back to the root node
            int n;
            if(node->isMax == true){
                for(int i = 0; i < 12 - node->level; i++){
                    if(node->childrenVals[i] > node->minimax){
                        node->minimax = node->childrenVals[i];
                        n = i;
                    }
                }
            }
            else{
                for(int i = 0; i < 12 - node->level; i++){
                    if(node->childrenVals[i] < node->minimax){
                        node->minimax = node->childrenVals[i];
                        n = i;
                    }
                }
            }
            //if not at root node
            if(node->parent != NULL){
                int next = getChildNum(node->parent->childrenVals);
                node->parent->childrenVals[next] = node->minimax;
                node_t* temp = node->parent;
                node->parent = NULL;
                getMove(temp);
            }
            //if at root node
            else{
            //get move from root by recreating nth child
            int openSpace = 0;
            for(int i=0; i < 4; i++){
                for(int j=0; j< 3; j++){
                    if(node->board[i][j] == 'X' || node->board[i][j] == 'O'){
                    //go to next space
                    }
                    else{
                        if(openSpace == n){
                            node->board[i][j] = 'X';
                            break;
                        }
                        else{
                            openSpace++;
                        }
                    }
                    //may have to do a conditional break here
                }
            }
            //set myBoard = this board
            for(int i = 0; i < 4; i++){
                for(int j = 0; j < 3; j++){
                    myBoard[i][j] = node->board[i][j];
                }
            }
            return;
            }
        }
    }
    //if node is an end state
    else{
        if(node->parent != NULL){
            int next = getChildNum(node->parent->childrenVals);
            node->parent->childrenVals[next] = status;
            getMove(node->parent);
        }
    }
}

int checkStatus(char board[4][3]){
    //returns -2 for no win loss or draw
    //returns 1 for win
    //returns -1 for loss
    //returns 0 for draw

    if( (board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X')||
        (board[0][0] == 'X' && board[0][1] == 'X' && board[0][2] == 'X')||
        (board[0][0] == 'X' && board[1][0] == 'X' && board[2][0] == 'X')||
        (board[0][1] == 'X' && board[1][1] == 'X' && board[2][1] == 'X')||
        (board[0][2] == 'X' && board[1][2] == 'X' && board[2][2] == 'X')||
        (board[0][2] == 'X' && board[1][1] == 'X' && board[2][0] == 'X')||

        (board[1][0] == 'X' && board[2][1] == 'X' && board[3][2] == 'X')||
        (board[1][0] == 'X' && board[1][1] == 'X' && board[1][2] == 'X')||
        (board[1][0] == 'X' && board[2][0] == 'X' && board[3][0] == 'X')||
        (board[1][1] == 'X' && board[2][1] == 'X' && board[3][1] == 'X')||
        (board[1][2] == 'X' && board[2][2] == 'X' && board[3][2] == 'X')||
        (board[1][2] == 'X' && board[2][1] == 'X' && board[3][0] == 'X')||
        (board[2][0] == 'X' && board[2][1] == 'X' && board[2][2] == 'X')||
        (board[3][0] == 'X' && board[3][1] == 'X' && board[3][2] == 'X')){
            return -1;
        }
    if( (board[0][0] == 'O' && board[1][1] == 'O' && board[2][2] == 'O')||
        (board[0][0] == 'O' && board[0][1] == 'O' && board[0][2] == 'O')||
        (board[0][0] == 'O' && board[1][0] == 'O' && board[2][0] == 'O')||
        (board[0][1] == 'O' && board[1][1] == 'O' && board[2][1] == 'O')||
        (board[0][2] == 'O' && board[1][2] == 'O' && board[2][2] == 'O')||
        (board[0][2] == 'O' && board[1][1] == 'O' && board[2][0] == 'O')||

        (board[1][0] == 'O' && board[2][1] == 'O' && board[3][2] == 'O')||
        (board[1][0] == 'O' && board[1][1] == 'O' && board[1][2] == 'O')||
        (board[1][0] == 'O' && board[2][0] == 'O' && board[3][0] == 'O')||
        (board[1][1] == 'O' && board[2][1] == 'O' && board[3][1] == 'O')||
        (board[1][2] == 'O' && board[2][2] == 'O' && board[3][2] == 'O')||
        (board[1][2] == 'O' && board[2][1] == 'O' && board[3][0] == 'O')||
        (board[2][0] == 'O' && board[2][1] == 'O' && board[2][2] == 'O')||
        (board[3][0] == 'O' && board[3][1] == 'O' && board[3][2] == 'O')){
            return 1;
        }
    //if it has not returned a win or a loss and the board is full return draw
        bool isFull = true;
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 3; j++){
                if(board[i][j] != 'X' && board[i][j] != 'O'){
                    isFull = false;
                }
            }
        }
        if(isFull == true){
            return 0;
        }
        else{
            return -2;
        }
}

int getChildNum( int childrenVals[12]){
    //return index to know which child needs to be created next
    for(int i = 0; i < 12; i++){
        if(childrenVals[i] == -2){
            return i;
        }
    }
    return -1;

}

void printBoard(char board[4][3]){
    for(int i = 0; i < 4; i++){
        cout<<"|"<<board[i][0]<<"|";
        cout<<"|"<<board[i][1]<<"|";
        cout<<board[i][2]<<"|"<<endl;
    }
    cout<<endl;
}
Mgetz
  • 5,108
  • 2
  • 33
  • 51
Sarah Wilson
  • 41
  • 2
  • 8
  • _"Can see what is causing this segmentation fault or any other errors I have in my code?"_ Yes of course you can, using a debugger and stepping through your code line by line. – πάντα ῥεῖ Mar 27 '15 at 17:18
  • Use C++ containers which are bounds checked on good debug compilers to find bugs. – Neil Kirk Mar 27 '15 at 17:25
  • @SarahWilson, please see my answer below, which grew to long to contain within a comment. – clearlight Mar 27 '15 at 17:47
  • You code isn't long, you can try to insert lines of printf() to check which line actually caused segmentation fault instead of setting debugger. – Wayne Mar 27 '15 at 18:04
  • Thank you all for the comments. I really do need to get better at debugging in c++. I tried using dbg and here's what I found: Program received signal SIGSEGV, Segmentation fault. 0x000122a8 in checkStatus () (gdb) backtrace #0 0x000122a8 in checkStatus () #1 0x00011a94 in getMove () Cannot access memory at address 0xff3fffe8 I guess that I have an invalid memory access in my checkStatus() function. I also added some printf()'s and found that it goes through checkStatus successfully until somewhere deep into recursion, then it gives the seg fault after the first if. Any ideas why that is? – Sarah Wilson Mar 27 '15 at 18:52

1 Answers1

0

Start debugger, such as gdb, and run your code from there. When it seg faults, it will show you offending line in your code, if you configured debugger to find your source file. For simple cases, when the binary and source are in the same directory, a debugger can usually find the source code by default.

Otherwise, put cout or printf() lines in code & print 1, 2, 3, .... respectively at key points. Run the code, then you can see which two printfs it falls between. Then add more printfs(), such as 1.1, 1.2, 1.3 ... until you find offending line, then delete the printfs.

clearlight
  • 12,255
  • 11
  • 57
  • 75
  • Thanks for your help, after trying to debug it for a while, I ended up just rewriting my entire program in a simpler way. I did learn more about debugging though, I think my code was just way more complicated than it should've been. Thanks again! – Sarah Wilson Apr 07 '15 at 13:52
  • That's the kind of extra effort and thought that makes a really good developer. You learn some of the best lessons prototyping a solution, learning what is wrong and how to improve it and writing it correctly or better. Of course no one likes to go to the extreme of rewriting everything, but sometimes it's the best answer. Programs often outgrow their original "skeletons", and it becomes increasingly awkward to add new features. That can be another incentive to re-write, so you can come up with a design that encompasses new functionality efficiently. Great to hear you made progress. – clearlight Apr 07 '15 at 14:46