-1

Pardon me if this question already exists, I've searched a lot but I haven't gotten the answer to the question I want to ask. So, basically, I'm trying to implement a Tic-Tac-Toe AI that uses the Minimax algorithm to make moves.

However, one thing I don't get is, that when Minimax is used on an empty board, the value returned is always 0 (which makes sense because the game always ends in a draw if both players play optimally).

So Minimax always chooses the first tile as the best move when AI is X (since all moves return 0 as value). Same happens for the second move and it always chooses the second tile instead. How can I fix this problem to make my AI pick the move with the higher probability of winning? Here is the evaluation and Minimax function I use (with Alpha-Beta pruning):

int evaluate(char board[3][3], char AI)
{
for (int row = 0; row<3; row++)
{
    if (board[row][0] != '_' && board[row][0] == board[row][1] && board[row][1] == board[row][2])
    {
        if (board[row][0]==AI)
        {
            return +10;
        }
        else
        {
            return -10;
        }
    }
}

for (int col = 0; col<3; col++)
{
    if (board[0][col] != '_' && board[0][col] == board[1][col] && board[1][col] == board[2][col])
    {
        if (board[0][col]==AI)
        {
            return +10;
        }

        else
        {
            return -10;
        }
    }
}

if (board[1][1] != '_' && ((board[0][0]==board[1][1] && board[1][1]==board[2][2]) || (board[0][2]==board[1][1] && board[1][1]==board[2][0])))
{
    if (board[1][1]==AI)
    {
        return +10;
    }
    else
    {
        return -10;
    }
}

return 0;
}

int Minimax(char board[3][3], bool AITurn, char AI, char Player, int depth, int alpha, int beta)
{
bool breakout = false;
int score = evaluate(board, AI);

if(score == 10)
{
    return score - depth;
}
else if(score == -10)
{
    return score + depth;
}
else if(NoTilesEmpty(board))
{
    return 0;
}

if(AITurn == true)
{
    int bestvalue = -1024;
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j<3; j++)
        {
            if(board[i][j] == '_')
            {
                board[i][j] = AI;
                bestvalue = max(bestvalue, Minimax(board, false, AI, Player, depth+1, alpha, beta));
                alpha = max(bestvalue, alpha);
                board[i][j] = '_';
                if(beta <= alpha)
                {
                    breakout = true;
                    break;
                }
            }
        }
        if(breakout == true)
        {
            break;
        }
    }
    return bestvalue;
}

else if(AITurn == false)
{
    int bestvalue = +1024;
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j<3; j++)
        {
            if(board[i][j] == '_')
            {
                board[i][j] = Player;
                bestvalue = min(bestvalue, Minimax(board, true, AI, Player, depth+1, alpha, beta));
                beta = min(bestvalue, beta);
                board[i][j] = '_';
                if(beta <= alpha)
                {
                    breakout = true;
                    break;
                }
            }
        }
        if(breakout == true)
        {
            break;
        }
    }
    return bestvalue;
}
}
Mehrdad Pedramfar
  • 10,941
  • 4
  • 38
  • 59
Famiu
  • 93
  • 7

2 Answers2

0

Minimax assumes optimal play, so maximizing "probability of winning" is not a meaningful notion: Since the other player can force a draw but cannot force a win, they will always force a draw. If you want to play optimally against a player who is not perfectly rational (which, of course, is one of the only two ways to win*), you'll need to assume some probability distribution over the opponent's moves and use something like ExpectMinimax, where with some probability the opponent's move is overridden by a random mistake. Alternatively, you can deliberately restrict the ply of the minimax search, using a heuristic for the opponent's play beyond a certain depth (but still searching the game tree for your own moves.)

 * The other one is not to play.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
0

Organize your code into smaller routines so that it looks tidier and easier to debug. Apart from the recursive minimax function, an all-possible-valid-move generation function and a robust evaluation sub-routine are essential ( which seems lacking here).

For example, at the beginning of the game, the evaluation algorithm should return a non-zero score, every position should have a relative scoring index ( eg middle position may have slightly higher weightage than the corners).

Your minimax boundary condition - return if there is no empty cell positions ; is flawed as it will evaluate even when a winning/losing move occurred in the preceding ply. Such conditions will aggravate in more complex AI games.

If you are new to minimax, you can find plenty of ready to compile sample codes on CodeReview

seccpur
  • 4,996
  • 2
  • 13
  • 21