Search code examples
c++artificial-intelligencetic-tac-toeminimax

C++ Exception Minimax algorithm for Tictactoe


I'm trying to make a simple AI for playing tictactoe, but it is not working; I get an 'std::out_of_range' when the board fills up (brute forcing till the first final nodes of the first branch), and I quite didn't find a solution.

If I play X at 0,0 it crashes on the return of the best move return jogadas.at(melhorMov); // return best move after the 10th iteration.

(full code for Windows MingW here if you wish )

Image of the error

(the shown games above it's the bruteforcing steps, not the actual game)

AI algorithm:

  1. Moving/launching AI

    void IA::movimenta(){ //lauches the AI move action
        std::cout << "**Pensando**" << std::endl; //little message
        pensamentos = 0;                          //counter of iterations
        cmp = campo.getcampo();                   //gets the pointer** to the tictactoe field
        AiMove jogada = MelhorJogada(AIjogador);  //gets the best move for the AIplayer
        campo.joga(AIjogador,jogada.x,jogada.y);  //puts the best move on the field
    }
    
  2. Minimax Code

    #include "ia.h"
    #include <vector>
    #include <iostream>
    
    using namespace std;
    
    IA::IA()
    {
    
    }
    
    
    void IA::inicia(field &campo, field::jogador IAplayer){
        this->campo = campo;
    
        this->AIjogador = IAplayer;
        if(IAplayer == field::x){
            this->HUjogador = field::o;
        }else{
            this->HUjogador = field::x;
        }
    
    }
    
    
    void printacamp(field::campo **campo){
        cout << "  0 1 2" << endl;
        for (int var = 0; var < 3; ++var) {
            cout << var;
            cout << "|";
            for (int var2 = 0; var2 < 3; ++var2) {
    
                switch (campo[var][var2]) {
                case field::NADA:
                    cout << " ";
                    break;
                case field::x_c:
                    cout << "X";
                    break;
                case field::o_c:
                    cout << "O";
                    break;
                default:
                    break;
                }
    
                cout << "|";
            }
            cout << endl;
        }
    }
    
    void IA::movimenta(){ //lauches the AI move action
        std::cout << "**Pensando**" << std::endl; //little message
        pensamentos = 0;                          //couter of iterations
        cmp = campo.getcampo();                   //gets the pointer** to the tictactoe field
        AiMove jogada = MelhorJogada(AIjogador);  //gets the best move for the AIplayer
        campo.joga(AIjogador,jogada.x,jogada.y);  //puts the best move on the field
    }
    
    AiMove IA::MelhorJogada(field::jogador jogador){ //bruteforce through the possible moves and searches for the best one
        field::jogador vic = campo.checaJ(); // checks if someone wins the game (final node)
        if(vic == AIjogador){                //if AIplayer wins
            return AiMove(15);             //add 15 points
        } else if(vic == jogador){           //if human wins
            return AiMove(-15);            //add -15 points
        } else if(vic == field::NINGJ){      //if its a tie (full board no winners)
            return AiMove(0);               //score 0
        }                                  //if none of those, the game is still in progress
        std::vector<AiMove> jogadas;        //vector for moves, something tells me that it should be static but it gives alot of bugs and nosense moves (or no moves at all)
        AiMove jogada;                      //move for this iteration
        pensamentos++;                      //add counter
        cout << pensamentos << endl;        //print counter
        for (int y = 0; y < 3; ++y) {       //for each of the board
            for (int x = 0; x < 3; ++x) {
                if(cmp[x][y] == field::NADA){  // if its an empty space
                    jogada.x = x;              //gets the x,y of the empty space
                    jogada.y = y;
                    campo.jogaJ(jogador,jogada.x,jogada.y); //makes a move
    
                    printacamp(cmp);           //prints it on screen
    
                    if(jogador == AIjogador){  //if its max(AI) next is human
                        jogada.pontos = MelhorJogada(HUjogador).pontos;
                    }else{                     //if its min(Human) next is AI
                        jogada.pontos = MelhorJogada(AIjogador).pontos;
                    }
                    jogadas.push_back(jogada); //store the score of the final node
                    campo.jogaJ(field::NADAJ,jogada.x,jogada.y); // delete the move made for testings
                }
            }
        }
        int melhorMov = 0;                  // bestMove is 0
        if(jogador == AIjogador){           // if AIturn(max)
            int melhorPon = -1000000;       // best pontuation = -∞
            for (int var = 0; var < jogadas.size(); ++var) { //best move/points of the subbranches
                if(jogadas.at(var).pontos > melhorPon){
                    melhorMov = var;
                    melhorPon = jogadas.at(var).pontos;
                }
            }
        } else {                            //if human(min)
            int melhorPon = 1000000;        //best pontution = +∞
            for (int var = 0; var < jogadas.size(); ++var) { //best move/points of the subbranches
                if(jogadas.at(var).pontos < melhorPon){
                    melhorMov = var;
                    melhorPon = jogadas.at(var).pontos;
                }
            }
        }
        return jogadas.at(melhorMov); // return best move
    
    }
    
  3. AI.h

    #pragma once
    #ifndef IA_H
    #define IA_H
    #include "field.h"
    #include <vector>
    struct AiMove {
        AiMove() {};
        AiMove(int Pontos) : pontos(Pontos) {}
        int x;
        int y;
        int pontos;
    };
    
    
    class IA
    {
    
    public:
            IA();
            void inicia(field &camp, field::jogador IAplayer);
            void movimenta();
    private:
            field campo;
            AiMove MelhorJogada(field::jogador HUjogador);
            field::campo **cmp;
            field::jogador HUjogador;
            field::jogador AIjogador;
            int pensamentos = 0;
    
    };
    #endif // IA_H
    

Solution

  • Your vector is empty (as it should be, there are no remaining legal moves).

    Calling at(0) on an empty vector throws an exception, because there is no element 0.