Search code examples
c++minimax

C++ Tic tac toe minimax


I am trying to create a c++ unbeatable tic tac toe AI. after watching several videos on the topic i thought I had it all figured out. An error pops up on the screen saying "Expression: vector subscript out of range". I believe the error is coming from the availableMoves() function. however I do not know why. The game itself works fine. any help would be appreciated.

#include <iostream>
#include <vector>
#include <ctime>


bool in(std::vector<int> v, int element)
{
for (int i = 0; i < v.size(); i++)
{
    if (element == v[i])
    {
        return true;
    }
}

return false;
}

class Board
{
private:
char board[3][3] = { {'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'} };
public:

void displayBoard()
{

    std::cout << "___________________" << std::endl;
    for (int i = 0; i < 3; i++)
    {
        std::cout << "|  ";
        for (int j = 0; j < 3; j++)
        {
            std::cout << board[i][j] << "  |  ";
        }
        std::cout << std::endl;
    }
    std::cout << "___________________" << std::endl;
}

std::vector<int> availableMoves()
{
    std::vector<int> moves;

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] != 'X' && board[i][j] != 'O')
            {
                moves.push_back(i * 3 + j);
            }
        }
    }


    return moves;
}

void move(int choice, char mark)
{
    int y = choice / 3;
    int x = choice - y * 3;
    board[y][x] = mark;
}

void revert(int choice)
{
    int y = choice / 3;
    int x = choice - y * 3;
    board[y][x] = (char)choice + 48;
}

int checkWin()
{

    for (int i = 0; i < 3; i++)
    {
        if (board[i][0] == board[i][1] && board[i][1] == board[i][2])
        {
            if (board[i][0] == 'X')
            {
                return 1;
            }
            else if (board[i][0] == 'O')
            {
                return -1;
            }
        }
    }


    for (int i = 0; i < 3; i++)
    {
        if (board[0][i] == board[1][i] && board[1][i] == board[2][i])
        {
            if (board[0][i] == 'X')
            {
                return 1;
            }
            else if (board[0][i] == 'O')
            {
                return -1;
            }
        }
    }


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


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


    return 0;
}


int evaluate()
{


    return (checkWin() * -1) * (availableMoves().size() + 1);
}


Board& operator=(Board& b)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            board[i][j] = b.board[i][j];
        }
    }
    

    return (*this);
}

};


class TicTacToe
{
private:
Board board;
int turn;
int searches = 0;
public:

TicTacToe()
{
    std::srand(time(0));
    turn = std::rand() % 2;
}

int minimax(int depth, Board curBoard, bool is_max)
{
    searches++;
    if (depth == 0 || curBoard.checkWin() != 0)
    {
        return board.evaluate();
    }

    if (is_max)
    {
        int max_eval = -2147483647;
        
        for (int i = 0; i < curBoard.availableMoves().size(); i++)
        {

            curBoard.move(curBoard.availableMoves()[i], 'O');
            depth -= 1;
            int eval = minimax(depth, curBoard, false);
            curBoard.revert(curBoard.availableMoves()[i]);
            if (eval > max_eval)
            {
                max_eval = eval;
            }
        }
        return max_eval;

    }


    if (!is_max)
    {
        int min_eval = 2147483647;
        for (int i = 0; i < curBoard.availableMoves().size(); i++)
        {
            curBoard.move(curBoard.availableMoves()[i], 'X');
            depth -= 1;
            int eval = minimax(depth, curBoard, true);
            curBoard.revert(curBoard.availableMoves()[i]);
            if (eval < min_eval)
            {
                min_eval = eval;
            }
        }
        return min_eval;

    }
}


void game()
{

    while (board.checkWin() == 0 && board.availableMoves().size() != 0)
    {
        
        board.displayBoard();
        if (turn % 2 == 0)
        {
            std::cout << std::endl;
            int choice;
            std::cout << "Enter Your Move: ";
            std::cin >> choice;
            choice -= 1;
            while (!in(board.availableMoves(), choice))
            {
                std::cout << "Enter A Valid Move: ";
                std::cin >> choice;
            }
            board.move(choice, 'X');
            std::cout << std::endl;
            turn++;
        }
        


        board.displayBoard();

        if (board.checkWin() != 0)
        {
            break;
        }

        if (turn % 2 == 1)
        {

            int ai = minimax(9 - (turn % 2), board, true);
            std::cout << searches;
            std::cin.get();
            turn++;
        }
        
        

    }


    if (board.checkWin() == 1)
    {
        std::cout << "You Won" << std::endl;
    }
    else if (board.checkWin() == -1)
    {
        std::cout << "You Lost" << std::endl;
    }
    else
    {
        std::cout << "Tie" << std::endl;
    }



    std::cout << "Would You Like To Play Again Y/N: ";
    char playAgain;
    std::cin >> playAgain;
    if (playAgain == 'Y')
    {
        Board newBoard;
        board = newBoard;
        game();
    }

}

};


int main()
{

TicTacToe ticTacToe;
ticTacToe.game();



}

Solution

  • Do you know how to debug? If not, you should definitely learn this, it's pretty helpful. But here's some things I found out.

    The problem is not in availableMoves(), but in minimax(), more precisely in line 215, where the program calls curBoard. revert(curBoard. availableMoves()[i]).

    void revert(int choice)
    {
        int y = choice / 3;
        int x = choice - y * 3;
        board[y][x] = (char)choice + 48;
    }
    
    for (int i = 0; i < curBoard.availableMoves().size(); i++)
            {
                curBoard.move(curBoard.availableMoves()[i], 'X');
                depth -= 1;
                int eval = minimax(depth, curBoard, true);
                curBoard.revert(curBoard.availableMoves()[i]);
                if (eval < min_eval)
                {
                    min_eval = eval;
                }
            }
    

    The error happens in the function revert, but I am not sure why. Maybe availableMoves also returns something wrong. Variable i is permanently 0 in the for-loop. So it is possible that there is something wrong at position 0 of the vector moves, which revert cannot handle. Try debugging yourself, maybe you'll find the problem.